<?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\cryptocom 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 cryptocom extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'cryptocom',
            'name' => 'Crypto.com',
            'countries' => array( 'MT' ),
            'version' => 'v2',
            'rateLimit' => 10, // 100 requests per second
            'certified' => true,
            'pro' => true,
            'has' => array(
                'CORS' => false,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => true,
                'option' => true,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'cancelOrdersForSymbols' => true,
                'closeAllPositions' => false,
                'closePosition' => true,
                'createMarketBuyOrderWithCost' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => true,
                'createStopOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => true,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchBidsAsks' => false,
                'fetchBorrowInterest' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchClosedOrders' => 'emulated',
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => true,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => false,
                'fetchDepositWithdrawFee' => 'emulated',
                'fetchDepositWithdrawFees' => true,
                'fetchFundingHistory' => false,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => false,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchLedger' => true,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchPosition' => true,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsHistory' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => true,
                'fetchStatus' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => false,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => true,
                'fetchTransactionFees' => false,
                'fetchTransactions' => false,
                'fetchTransfers' => false,
                'fetchUnderlyingAssets' => false,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'sandbox' => true,
                'setLeverage' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '4h' => '4h',
                '6h' => '6h',
                '12h' => '12h',
                '1d' => '1D',
                '1w' => '7D',
                '2w' => '14D',
                '1M' => '1M',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/147792121-38ed5e36-c229-48d6-b49a-48d05fc19ed4.jpeg',
                'test' => array(
                    'v1' => 'https://uat-api.3ona.co/exchange/v1',
                    'v2' => 'https://uat-api.3ona.co/v2',
                    'derivatives' => 'https://uat-api.3ona.co/v2',
                ),
                'api' => array(
                    'base' => 'https://api.crypto.com',
                    'v1' => 'https://api.crypto.com/exchange/v1',
                    'v2' => 'https://api.crypto.com/v2',
                    'derivatives' => 'https://deriv-api.crypto.com/v1',
                ),
                'www' => 'https://crypto.com/',
                'referral' => array(
                    'url' => 'https://crypto.com/exch/kdacthrnxt',
                    'discount' => 0.75,
                ),
                'doc' => array(
                    'https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html',
                    'https://exchange-docs.crypto.com/spot/index.html',
                    'https://exchange-docs.crypto.com/derivatives/index.html',
                ),
                'fees' => 'https://crypto.com/exchange/document/fees-limits',
            ),
            'api' => array(
                'base' => array(
                    'public' => array(
                        'get' => array(
                            'v1/public/get-announcements' => 1, // no description of rate limit
                        ),
                    ),
                ),
                'v1' => array(
                    'public' => array(
                        'get' => array(
                            'public/auth' => 10 / 3,
                            'public/get-instruments' => 10 / 3,
                            'public/get-book' => 1,
                            'public/get-candlestick' => 1,
                            'public/get-trades' => 1,
                            'public/get-tickers' => 1,
                            'public/get-valuations' => 1,
                            'public/get-expired-settlement-price' => 10 / 3,
                            'public/get-insurance' => 1,
                            'public/get-risk-parameters' => 1,
                        ),
                        'post' => array(
                            'public/staking/get-conversion-rate' => 2,
                        ),
                    ),
                    'private' => array(
                        'post' => array(
                            'private/set-cancel-on-disconnect' => 10 / 3,
                            'private/get-cancel-on-disconnect' => 10 / 3,
                            'private/user-balance' => 10 / 3,
                            'private/user-balance-history' => 10 / 3,
                            'private/get-positions' => 10 / 3,
                            'private/create-order' => 2 / 3,
                            'private/amend-order' => 4 / 3, // no description of rate limit
                            'private/create-order-list' => 10 / 3,
                            'private/cancel-order' => 2 / 3,
                            'private/cancel-order-list' => 10 / 3,
                            'private/cancel-all-orders' => 2 / 3,
                            'private/close-position' => 10 / 3,
                            'private/get-order-history' => 100,
                            'private/get-open-orders' => 10 / 3,
                            'private/get-order-detail' => 1 / 3,
                            'private/get-trades' => 100,
                            'private/change-account-leverage' => 10 / 3,
                            'private/get-transactions' => 10 / 3,
                            'private/create-subaccount-transfer' => 10 / 3,
                            'private/get-subaccount-balances' => 10 / 3,
                            'private/get-order-list' => 10 / 3,
                            'private/create-withdrawal' => 10 / 3,
                            'private/get-currency-networks' => 10 / 3,
                            'private/get-deposit-address' => 10 / 3,
                            'private/get-accounts' => 10 / 3,
                            'private/get-withdrawal-history' => 10 / 3,
                            'private/get-deposit-history' => 10 / 3,
                            'private/get-fee-rate' => 2,
                            'private/get-instrument-fee-rate' => 2,
                            'private/staking/stake' => 2,
                            'private/staking/unstake' => 2,
                            'private/staking/get-staking-position' => 2,
                            'private/staking/get-staking-instruments' => 2,
                            'private/staking/get-open-stake' => 2,
                            'private/staking/get-stake-history' => 2,
                            'private/staking/get-reward-history' => 2,
                            'private/staking/convert' => 2,
                            'private/staking/get-open-convert' => 2,
                            'private/staking/get-convert-history' => 2,
                        ),
                    ),
                ),
                'v2' => array(
                    'public' => array(
                        'get' => array(
                            'public/auth' => 1,
                            'public/get-instruments' => 1,
                            'public/get-book' => 1,
                            'public/get-candlestick' => 1,
                            'public/get-ticker' => 1,
                            'public/get-trades' => 1,
                            'public/margin/get-transfer-currencies' => 1,
                            'public/margin/get-load-currenices' => 1,
                            'public/respond-heartbeat' => 1,
                        ),
                    ),
                    'private' => array(
                        'post' => array(
                            'private/set-cancel-on-disconnect' => 10 / 3,
                            'private/get-cancel-on-disconnect' => 10 / 3,
                            'private/create-withdrawal' => 10 / 3,
                            'private/get-withdrawal-history' => 10 / 3,
                            'private/get-currency-networks' => 10 / 3,
                            'private/get-deposit-history' => 10 / 3,
                            'private/get-deposit-address' => 10 / 3,
                            'private/export/create-export-request' => 10 / 3,
                            'private/export/get-export-requests' => 10 / 3,
                            'private/export/download-export-output' => 10 / 3,
                            'private/get-account-summary' => 10 / 3,
                            'private/create-order' => 2 / 3,
                            'private/cancel-order' => 2 / 3,
                            'private/cancel-all-orders' => 2 / 3,
                            'private/create-order-list' => 10 / 3,
                            'private/get-order-history' => 10 / 3,
                            'private/get-open-orders' => 10 / 3,
                            'private/get-order-detail' => 1 / 3,
                            'private/get-trades' => 100,
                            'private/get-accounts' => 10 / 3,
                            'private/get-subaccount-balances' => 10 / 3,
                            'private/create-subaccount-transfer' => 10 / 3,
                            'private/otc/get-otc-user' => 10 / 3,
                            'private/otc/get-instruments' => 10 / 3,
                            'private/otc/request-quote' => 100,
                            'private/otc/accept-quote' => 100,
                            'private/otc/get-quote-history' => 10 / 3,
                            'private/otc/get-trade-history' => 10 / 3,
                            'private/otc/create-order' => 10 / 3,
                        ),
                    ),
                ),
                'derivatives' => array(
                    'public' => array(
                        'get' => array(
                            'public/auth' => 10 / 3,
                            'public/get-instruments' => 10 / 3,
                            'public/get-book' => 1,
                            'public/get-candlestick' => 1,
                            'public/get-trades' => 1,
                            'public/get-tickers' => 1,
                            'public/get-valuations' => 1,
                            'public/get-expired-settlement-price' => 10 / 3,
                            'public/get-insurance' => 1,
                        ),
                    ),
                    'private' => array(
                        'post' => array(
                            'private/set-cancel-on-disconnect' => 10 / 3,
                            'private/get-cancel-on-disconnect' => 10 / 3,
                            'private/user-balance' => 10 / 3,
                            'private/user-balance-history' => 10 / 3,
                            'private/get-positions' => 10 / 3,
                            'private/create-order' => 2 / 3,
                            'private/create-order-list' => 10 / 3,
                            'private/cancel-order' => 2 / 3,
                            'private/cancel-order-list' => 10 / 3,
                            'private/cancel-all-orders' => 2 / 3,
                            'private/close-position' => 10 / 3,
                            'private/convert-collateral' => 10 / 3,
                            'private/get-order-history' => 100,
                            'private/get-open-orders' => 10 / 3,
                            'private/get-order-detail' => 1 / 3,
                            'private/get-trades' => 100,
                            'private/change-account-leverage' => 10 / 3,
                            'private/get-transactions' => 10 / 3,
                            'private/create-subaccount-transfer' => 10 / 3,
                            'private/get-subaccount-balances' => 10 / 3,
                            'private/get-order-list' => 10 / 3,
                        ),
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'maker' => $this->parse_number('0.004'),
                    'taker' => $this->parse_number('0.004'),
                    'tiers' => array(
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.004') ),
                            array( $this->parse_number('25000'), $this->parse_number('0.0035') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0015') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.001') ),
                            array( $this->parse_number('250000'), $this->parse_number('0.0009') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('20000000'), $this->parse_number('0.0007') ),
                            array( $this->parse_number('100000000'), $this->parse_number('0.0006') ),
                            array( $this->parse_number('200000000'), $this->parse_number('0.0004') ),
                        ),
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.004') ),
                            array( $this->parse_number('25000'), $this->parse_number('0.0035') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0025') ),
                            array( $this->parse_number('100000'), $this->parse_number('0.0016') ),
                            array( $this->parse_number('250000'), $this->parse_number('0.00015') ),
                            array( $this->parse_number('1000000'), $this->parse_number('0.00014') ),
                            array( $this->parse_number('20000000'), $this->parse_number('0.00013') ),
                            array( $this->parse_number('100000000'), $this->parse_number('0.00012') ),
                            array( $this->parse_number('200000000'), $this->parse_number('0.0001') ),
                        ),
                    ),
                ),
            ),
            'options' => array(
                'defaultType' => 'spot',
                'accountsById' => array(
                    'funding' => 'SPOT',
                    'spot' => 'SPOT',
                    'margin' => 'MARGIN',
                    'derivatives' => 'DERIVATIVES',
                    'swap' => 'DERIVATIVES',
                    'future' => 'DERIVATIVES',
                ),
                'networks' => array(
                    'BEP20' => 'BSC',
                    'ERC20' => 'ETH',
                    'TRC20' => 'TRON',
                ),
                'broker' => 'CCXT',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        // todo => implementation fix
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                            'index' => true,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'selfTradePrevention' => true, // todo => implement
                        'trailing' => false,
                        'iceberg' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => true,
                    ),
                    'createOrders' => array(
                        'max' => 10,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => null,
                        'untilDays' => 1,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => true,
                        'limit' => 100,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => null,
                        'untilDays' => 1,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => 1,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 300,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                    'fetchCurrencies' => array(
                        'private' => true,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
                'future' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => array(
                        'extends' => 'default',
                    ),
                ),
            ),
            // https://exchange-docs.crypto.com/spot/index.html#response-and-reason-codes
            'commonCurrencies' => array(
                'USD_STABLE_COIN' => 'USDC',
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    '219' => '\\ccxt\\InvalidOrder',
                    '306' => '\\ccxt\\InsufficientFunds', // array( "id" : 1753xxx, "method" : "private/amend-order", "code" : 306, "message" : "INSUFFICIENT_AVAILABLE_BALANCE", "result" : array( "client_oid" : "1753xxx", "order_id" : "6530xxx" ) )
                    '314' => '\\ccxt\\InvalidOrder', // array( "id" : 1700xxx, "method" : "private/create-order", "code" : 314, "message" : "EXCEEDS_MAX_ORDER_SIZE", "result" : array( "client_oid" : "1700xxx", "order_id" : "6530xxx" ) )
                    '325' => '\\ccxt\\InvalidOrder', // array( "id" : 1741xxx, "method" : "private/create-order", "code" : 325, "message" : "EXCEED_DAILY_VOL_LIMIT", "result" : array( "client_oid" : "1741xxx", "order_id" : "6530xxx" ) )
                    '415' => '\\ccxt\\InvalidOrder', // array( "id" : 1741xxx, "method" : "private/create-order", "code" : 415, "message" : "BELOW_MIN_ORDER_SIZE", "result" : array( "client_oid" : "1741xxx", "order_id" : "6530xxx" ) )
                    '10001' => '\\ccxt\\ExchangeError',
                    '10002' => '\\ccxt\\PermissionDenied',
                    '10003' => '\\ccxt\\PermissionDenied',
                    '10004' => '\\ccxt\\BadRequest',
                    '10005' => '\\ccxt\\PermissionDenied',
                    '10006' => '\\ccxt\\DDoSProtection',
                    '10007' => '\\ccxt\\InvalidNonce',
                    '10008' => '\\ccxt\\BadRequest',
                    '10009' => '\\ccxt\\BadRequest',
                    '20001' => '\\ccxt\\BadRequest',
                    '20002' => '\\ccxt\\InsufficientFunds',
                    '20005' => '\\ccxt\\AccountNotEnabled', // array("id":"123xxx","method":"private/margin/xxx","code":"20005","message":"ACCOUNT_NOT_FOUND")
                    '30003' => '\\ccxt\\BadSymbol',
                    '30004' => '\\ccxt\\BadRequest',
                    '30005' => '\\ccxt\\BadRequest',
                    '30006' => '\\ccxt\\InvalidOrder',
                    '30007' => '\\ccxt\\InvalidOrder',
                    '30008' => '\\ccxt\\InvalidOrder',
                    '30009' => '\\ccxt\\InvalidOrder',
                    '30010' => '\\ccxt\\BadRequest',
                    '30013' => '\\ccxt\\InvalidOrder',
                    '30014' => '\\ccxt\\InvalidOrder',
                    '30016' => '\\ccxt\\InvalidOrder',
                    '30017' => '\\ccxt\\InvalidOrder',
                    '30023' => '\\ccxt\\InvalidOrder',
                    '30024' => '\\ccxt\\InvalidOrder',
                    '30025' => '\\ccxt\\InvalidOrder',
                    '40001' => '\\ccxt\\BadRequest',
                    '40002' => '\\ccxt\\BadRequest',
                    '40003' => '\\ccxt\\BadRequest',
                    '40004' => '\\ccxt\\BadRequest',
                    '40005' => '\\ccxt\\BadRequest',
                    '40006' => '\\ccxt\\BadRequest',
                    '40007' => '\\ccxt\\BadRequest',
                    '40101' => '\\ccxt\\AuthenticationError',
                    '40102' => '\\ccxt\\InvalidNonce', // Nonce value differs by more than 60 seconds from server
                    '40103' => '\\ccxt\\AuthenticationError', // IP address not whitelisted
                    '40104' => '\\ccxt\\AuthenticationError', // Disallowed based on user tier
                    '40107' => '\\ccxt\\BadRequest', // Session subscription limit has been exceeded
                    '40401' => '\\ccxt\\OrderNotFound',
                    '40801' => '\\ccxt\\RequestTimeout',
                    '42901' => '\\ccxt\\RateLimitExceeded',
                    '43005' => '\\ccxt\\InvalidOrder', // Rejected POST_ONLY create-order request (normally happened when exec_inst contains POST_ONLY but time_in_force is NOT GOOD_TILL_CANCEL)
                    '43003' => '\\ccxt\\InvalidOrder', // FOK order has not been filled and cancelled
                    '43004' => '\\ccxt\\InvalidOrder', // IOC order has not been filled and cancelled
                    '43012' => '\\ccxt\\BadRequest', // Canceled due to Self Trade Prevention
                    '50001' => '\\ccxt\\ExchangeError',
                    '9010001' => '\\ccxt\\OnMaintenance', // array("code":9010001,"message":"SYSTEM_MAINTENANCE","details":"Crypto.com Exchange is currently under maintenance. Please refer to https://status.crypto.com for more details.")
                ),
                'broad' => array(),
            ),
        ));
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-$currency-$networks
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            // this endpoint requires authentication
            if (!$this->check_required_credentials(false)) {
                return array();
            }
            $skipFetchCurrencies = false;
            list($skipFetchCurrencies, $params) = $this->handle_option_and_params($params, 'fetchCurrencies', 'skipFetchCurrencies', false);
            if ($skipFetchCurrencies) {
                // sub-accounts can't access this endpoint
                return array();
            }
            $response = array();
            try {
                $response = Async\await($this->v1PrivatePostPrivateGetCurrencyNetworks ($params));
            } catch (Exception $e) {
                if ($e instanceof ExchangeError) {
                    // sub-accounts can't access this endpoint
                    // array("code":"10001","msg":"SYS_ERROR")
                    return array();
                }
                throw $e;
                // do nothing
                // sub-accounts can't access this endpoint
            }
            //
            //    {
            //        "id" => "1747502328559",
            //        "method" => "private/get-$currency-$networks",
            //        "code" => "0",
            //        "result" => {
            //            "update_time" => "1747502281000",
            //            "currency_map" => {
            //                "USDT" => {
            //                    "full_name" => "Tether USD",
            //                    "default_network" => "ETH",
            //                    "network_list" => array(
            //                        array(
            //                            "network_id" => "ETH",
            //                            "withdrawal_fee" => "10.00000000",
            //                            "withdraw_enabled" => true,
            //                            "min_withdrawal_amount" => "20.0",
            //                            "deposit_enabled" => true,
            //                            "confirmation_required" => "32"
            //                        ),
            //                        array(
            //                            "network_id" => "CRONOS",
            //                            "withdrawal_fee" => "0.18000000",
            //                            "withdraw_enabled" => true,
            //                            "min_withdrawal_amount" => "0.35",
            //                            "deposit_enabled" => true,
            //                            "confirmation_required" => "15"
            //                        ),
            //                        {
            //                            "network_id" => "SOL",
            //                            "withdrawal_fee" => "5.31000000",
            //                            "withdraw_enabled" => true,
            //                            "min_withdrawal_amount" => "10.62",
            //                            "deposit_enabled" => true,
            //                            "confirmation_required" => "1"
            //                        }
            //                    )
            //                }
            //            }
            //        }
            //    }
            //
            $resultData = $this->safe_dict($response, 'result', array());
            $currencyMap = $this->safe_dict($resultData, 'currency_map', array());
            $keys = is_array($currencyMap) ? array_keys($currencyMap) : array();
            $result = array();
            for ($i = 0; $i < count($keys); $i++) {
                $key = $keys[$i];
                $currency = $currencyMap[$key];
                $id = $key;
                $code = $this->safe_currency_code($id);
                $networks = array();
                $chains = $this->safe_list($currency, 'network_list', array());
                for ($j = 0; $j < count($chains); $j++) {
                    $chain = $chains[$j];
                    $networkId = $this->safe_string($chain, 'network_id');
                    $network = $this->network_id_to_code($networkId);
                    $networks[$network] = array(
                        'info' => $chain,
                        'id' => $networkId,
                        'network' => $network,
                        'active' => null,
                        'deposit' => $this->safe_bool($chain, 'deposit_enabled', false),
                        'withdraw' => $this->safe_bool($chain, 'withdraw_enabled', false),
                        'fee' => $this->safe_number($chain, 'withdrawal_fee'),
                        'precision' => null,
                        'limits' => array(
                            'withdraw' => array(
                                'min' => $this->safe_number($chain, 'min_withdrawal_amount'),
                                'max' => null,
                            ),
                        ),
                    );
                }
                $result[$code] = $this->safe_currency_structure(array(
                    'info' => $currency,
                    'id' => $id,
                    'code' => $code,
                    'name' => $this->safe_string($currency, 'full_name'),
                    'active' => null,
                    'deposit' => null,
                    'withdraw' => null,
                    'fee' => null,
                    'precision' => null,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'type' => 'crypto', // only crypto now
                    'networks' => $networks,
                ));
            }
            return $result;
        }) ();
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-instruments
             *
             * retrieves $data on all markets for cryptocom
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing $market $data
             */
            $response = Async\await($this->v1PublicGetPublicGetInstruments ($params));
            //
            //     {
            //         "id" => 1,
            //         "method" => "public/get-instruments",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 array(
            //                     "symbol" => "BTC_USDT",
            //                     "inst_type" => "CCY_PAIR",
            //                     "display_name" => "BTC/USDT",
            //                     "base_ccy" => "BTC",
            //                     "quote_ccy" => "USDT",
            //                     "quote_decimals" => 2,
            //                     "quantity_decimals" => 5,
            //                     "price_tick_size" => "0.01",
            //                     "qty_tick_size" => "0.00001",
            //                     "max_leverage" => "50",
            //                     "tradable" => true,
            //                     "expiry_timestamp_ms" => 0,
            //                     "beta_product" => false,
            //                     "margin_buy_enabled" => false,
            //                     "margin_sell_enabled" => true
            //                 ),
            //                 array(
            //                     "symbol" => "RUNEUSD-PERP",
            //                     "inst_type" => "PERPETUAL_SWAP",
            //                     "display_name" => "RUNEUSD Perpetual",
            //                     "base_ccy" => "RUNE",
            //                     "quote_ccy" => "USD",
            //                     "quote_decimals" => 3,
            //                     "quantity_decimals" => 1,
            //                     "price_tick_size" => "0.001",
            //                     "qty_tick_size" => "0.1",
            //                     "max_leverage" => "50",
            //                     "tradable" => true,
            //                     "expiry_timestamp_ms" => 0,
            //                     "beta_product" => false,
            //                     "underlying_symbol" => "RUNEUSD-INDEX",
            //                     "contract_size" => "1",
            //                     "margin_buy_enabled" => false,
            //                     "margin_sell_enabled" => false
            //                 ),
            //                 array(
            //                     "symbol" => "ETHUSD-230825",
            //                     "inst_type" => "FUTURE",
            //                     "display_name" => "ETHUSD Futures 20230825",
            //                     "base_ccy" => "ETH",
            //                     "quote_ccy" => "USD",
            //                     "quote_decimals" => 2,
            //                     "quantity_decimals" => 4,
            //                     "price_tick_size" => "0.01",
            //                     "qty_tick_size" => "0.0001",
            //                     "max_leverage" => "100",
            //                     "tradable" => true,
            //                     "expiry_timestamp_ms" => 1692950400000,
            //                     "beta_product" => false,
            //                     "underlying_symbol" => "ETHUSD-INDEX",
            //                     "contract_size" => "1",
            //                     "margin_buy_enabled" => false,
            //                     "margin_sell_enabled" => false
            //                 ),
            //                 array(
            //                     "symbol" => "BTCUSD-230630-CW30000",
            //                     "inst_type" => "WARRANT",
            //                     "display_name" => "BTCUSD-230630-CW30000",
            //                     "base_ccy" => "BTC",
            //                     "quote_ccy" => "USD",
            //                     "quote_decimals" => 3,
            //                     "quantity_decimals" => 0,
            //                     "price_tick_size" => "0.001",
            //                     "qty_tick_size" => "10",
            //                     "max_leverage" => "50",
            //                     "tradable" => true,
            //                     "expiry_timestamp_ms" => 1688112000000,
            //                     "beta_product" => false,
            //                     "underlying_symbol" => "BTCUSD-INDEX",
            //                     "put_call" => "CALL",
            //                     "strike" => "30000",
            //                     "contract_size" => "0.0001",
            //                     "margin_buy_enabled" => false,
            //                     "margin_sell_enabled" => false
            //                 ),
            //             )
            //         }
            //     }
            //
            $resultResponse = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($resultResponse, 'data', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $market = $data[$i];
                $inst_type = $this->safe_string($market, 'inst_type');
                $spot = $inst_type === 'CCY_PAIR';
                $swap = $inst_type === 'PERPETUAL_SWAP';
                $future = $inst_type === 'FUTURE';
                $option = $inst_type === 'WARRANT';
                $baseId = $this->safe_string($market, 'base_ccy');
                $quoteId = $this->safe_string($market, 'quote_ccy');
                $settleId = $spot ? null : $quoteId;
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                $settle = $spot ? null : $this->safe_currency_code($settleId);
                $optionType = $this->safe_string_lower($market, 'put_call');
                $strike = $this->safe_string($market, 'strike');
                $marginBuyEnabled = $this->safe_bool($market, 'margin_buy_enabled');
                $marginSellEnabled = $this->safe_bool($market, 'margin_sell_enabled');
                $expiryString = $this->omit_zero($this->safe_string($market, 'expiry_timestamp_ms'));
                $expiry = ($expiryString !== null) ? intval($expiryString) : null;
                $symbol = $base . '/' . $quote;
                $type = null;
                $contract = null;
                if ($inst_type === 'CCY_PAIR') {
                    $type = 'spot';
                    $contract = false;
                } elseif ($inst_type === 'PERPETUAL_SWAP') {
                    $type = 'swap';
                    $symbol = $symbol . ':' . $quote;
                    $contract = true;
                } elseif ($inst_type === 'FUTURE') {
                    $type = 'future';
                    $symbol = $symbol . ':' . $quote . '-' . $this->yymmdd($expiry);
                    $contract = true;
                } elseif ($inst_type === 'WARRANT') {
                    $type = 'option';
                    $symbolOptionType = ($optionType === 'call') ? 'C' : 'P';
                    $symbol = $symbol . ':' . $quote . '-' . $this->yymmdd($expiry) . '-' . $strike . '-' . $symbolOptionType;
                    $contract = true;
                }
                $result[] = array(
                    'id' => $this->safe_string($market, 'symbol'),
                    'symbol' => $symbol,
                    'base' => $base,
                    'quote' => $quote,
                    'settle' => $settle,
                    'baseId' => $baseId,
                    'quoteId' => $quoteId,
                    'settleId' => $settleId,
                    'type' => $type,
                    'spot' => $spot,
                    'margin' => (($marginBuyEnabled) || ($marginSellEnabled)),
                    'swap' => $swap,
                    'future' => $future,
                    'option' => $option,
                    'active' => $this->safe_bool($market, 'tradable'),
                    'contract' => $contract,
                    'linear' => ($contract) ? true : null,
                    'inverse' => ($contract) ? false : null,
                    'contractSize' => $this->safe_number($market, 'contract_size'),
                    'expiry' => $expiry,
                    'expiryDatetime' => $this->iso8601($expiry),
                    'strike' => $this->parse_number($strike),
                    'optionType' => $optionType,
                    'precision' => array(
                        'price' => $this->parse_number($this->safe_string($market, 'price_tick_size')),
                        'amount' => $this->parse_number($this->safe_string($market, 'qty_tick_size')),
                    ),
                    'limits' => array(
                        'leverage' => array(
                            'min' => $this->parse_number('1'),
                            'max' => $this->safe_number($market, 'max_leverage'),
                        ),
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'price' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'cost' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'created' => null,
                    'info' => $market,
                );
            }
            return $result;
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-tickers
             * @see https://exchange-docs.crypto.com/derivatives/index.html#public-get-tickers
             *
             * @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());
            $market = null;
            $request = array();
            if ($symbols !== null) {
                $symbol = null;
                if (gettype($symbols) === 'array' && array_keys($symbols) === array_keys(array_keys($symbols))) {
                    $symbolsLength = count($symbols);
                    if ($symbolsLength > 1) {
                        throw new BadRequest($this->id . ' fetchTickers() $symbols argument cannot contain more than 1 symbol');
                    }
                    $symbol = $symbols[0];
                } else {
                    $symbol = $symbols;
                }
                $market = $this->market($symbol);
                $request['instrument_name'] = $market['id'];
            }
            $response = Async\await($this->v1PublicGetPublicGetTickers ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-tickers",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 array(
            //                     "i" => "AVAXUSD-PERP",
            //                     "h" => "13.209",
            //                     "l" => "12.148",
            //                     "a" => "13.209",
            //                     "v" => "1109.8",
            //                     "vv" => "14017.33",
            //                     "c" => "0.0732",
            //                     "b" => "13.210",
            //                     "k" => "13.230",
            //                     "oi" => "10888.9",
            //                     "t" => 1687402657575
            //                 ),
            //             )
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            return $this->parse_tickers($data, $symbols);
        }) ();
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-$tickers
             *
             * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
             * @param {string} $symbol unified $symbol of the market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $symbol = $this->symbol($symbol);
            $tickers = Async\await($this->fetch_tickers(array( $symbol ), $params));
            return $this->safe_value($tickers, $symbol);
        }) ();
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple $orders made by the user
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-order-history
             *
             * @param {string} $symbol unified $market $symbol of the $market the $orders were made in
             * @param {int} [$since] the earliest time in ms to fetch $orders for, max date range is one day
             * @param {int} [$limit] the maximum number of order structures to retrieve, default 100 max 100
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms for the ending date filter, default is the current time
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchOrders', $symbol, $since, $limit, $params));
            }
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instrument_name'] = $market['id'];
            }
            if ($since !== null) {
                $request['start_time'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_time'] = $until;
            }
            $response = Async\await($this->v1PrivatePostPrivateGetOrderHistory ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1686881486183,
            //         "method" => "private/get-order-history",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "account_id" => "ce075bef-1234-4321-bd6g-ff9007252e63",
            //                     "order_id" => "6142909895014042762",
            //                     "client_oid" => "4e918597-1234-4321-8201-a7577e1e1d91",
            //                     "order_type" => "MARKET",
            //                     "time_in_force" => "GOOD_TILL_CANCEL",
            //                     "side" => "SELL",
            //                     "exec_inst" => array( ),
            //                     "quantity" => "0.00024",
            //                     "order_value" => "5.7054672",
            //                     "maker_fee_rate" => "0",
            //                     "taker_fee_rate" => "0",
            //                     "avg_price" => "25023.97",
            //                     "trigger_price" => "0",
            //                     "ref_price" => "0",
            //                     "ref_price_type" => "NULL_VAL",
            //                     "cumulative_quantity" => "0.00024",
            //                     "cumulative_value" => "6.0057528",
            //                     "cumulative_fee" => "0.001501438200",
            //                     "status" => "FILLED",
            //                     "update_user_id" => "ce075bef-1234-4321-bd6g-ff9007252e63",
            //                     "order_date" => "2023-06-15",
            //                     "instrument_name" => "BTC_USD",
            //                     "fee_instrument_name" => "USD",
            //                     "create_time" => 1686805465891,
            //                     "create_time_ns" => "1686805465891812578",
            //                     "update_time" => 1686805465891
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'result', array());
            $orders = $this->safe_list($data, 'data', array());
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-$trades
             *
             * @param {string} $symbol unified $symbol of the $market to fetch $trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch, maximum date range is one day
             * @param {int} [$limit] the maximum number of $trades to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms for the ending date filter, default is the current time
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchTrades', $symbol, $since, $limit, $params));
            }
            $market = $this->market($symbol);
            $request = array(
                'instrument_name' => $market['id'],
            );
            if ($since !== null) {
                $request['start_ts'] = $since;
            }
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_ts'] = $until;
            }
            $response = Async\await($this->v1PublicGetPublicGetTrades ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-$trades",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 array(
            //                     "s" => "sell",
            //                     "p" => "26386.00",
            //                     "q" => "0.00453",
            //                     "t" => 1686944282062,
            //                     "tn" : 1704476468851524373,
            //                     "d" => "4611686018455979970",
            //                     "i" => "BTC_USD"
            //                 ),
            //             )
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $trades = $this->safe_list($result, 'data', array());
            return $this->parse_trades($trades, $market, $since, $limit);
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-candlestick
             *
             * @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 in ms for the ending date filter, default is the current time
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {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, 300));
            }
            $market = $this->market($symbol);
            $request = array(
                'instrument_name' => $market['id'],
                'timeframe' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
            );
            if ($limit !== null) {
                if ($limit > 300) {
                    $limit = 300;
                }
                $request['count'] = $limit;
            }
            $now = $this->microseconds();
            $duration = $this->parse_timeframe($timeframe);
            $until = $this->safe_integer($params, 'until', $now);
            $params = $this->omit($params, array( 'until' ));
            if ($since !== null) {
                $request['start_ts'] = $since - $duration * 1000;
                if ($limit !== null) {
                    $request['end_ts'] = $this->sum($since, $duration * $limit * 1000);
                } else {
                    $request['end_ts'] = $until;
                }
            } else {
                $request['end_ts'] = $until;
            }
            $response = Async\await($this->v1PublicGetPublicGetCandlestick ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-candlestick",
            //         "code" => 0,
            //         "result" => {
            //             "interval" => "1m",
            //             "data" => array(
            //                 array(
            //                     "o" => "26949.89",
            //                     "h" => "26957.64",
            //                     "l" => "26948.24",
            //                     "c" => "26950.00",
            //                     "v" => "0.0670",
            //                     "t" => 1687237080000
            //                 ),
            //             ),
            //             "instrument_name" => "BTC_USD"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-book
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int} [$limit] the number of order book entries to return, max 50
             * @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(
                'instrument_name' => $market['id'],
            );
            if ($limit) {
                $request['depth'] = min ($limit, 50); // max 50
            }
            $response = Async\await($this->v1PublicGetPublicGetBook ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-book",
            //         "code" => 0,
            //         "result" => {
            //             "depth" => 3,
            //             "data" => array(
            //                 {
            //                     "bids" => array( array( "30025.00", "0.00004", "1" ), array( "30020.15", "0.02498", "1" ), array( "30020.00", "0.00004", "1" ) ),
            //                     "asks" => array( array( "30025.01", "0.04090", "1" ), array( "30025.70", "0.01000", "1" ), array( "30026.94", "0.02681", "1" ) ),
            //                     "t" => 1687491287380
            //                 }
            //             ),
            //             "instrument_name" => "BTC_USD"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            $orderBook = $this->safe_value($data, 0);
            $timestamp = $this->safe_integer($orderBook, 't');
            return $this->parse_order_book($orderBook, $symbol, $timestamp);
        }) ();
    }

    public function parse_balance($response): array {
        $responseResult = $this->safe_dict($response, 'result', array());
        $data = $this->safe_list($responseResult, 'data', array());
        $positionBalances = $this->safe_value($data[0], 'position_balances', array());
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($positionBalances); $i++) {
            $balance = $positionBalances[$i];
            $currencyId = $this->safe_string($balance, 'instrument_name');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['total'] = $this->safe_string($balance, 'quantity');
            $account['used'] = $this->safe_string($balance, 'reserved_qty');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-user-balance
             *
             * @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());
            $response = Async\await($this->v1PrivatePostPrivateUserBalance ($params));
            //
            //     {
            //         "id" => 1687300499018,
            //         "method" => "private/user-balance",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "total_available_balance" => "5.84684368",
            //                     "total_margin_balance" => "5.84684368",
            //                     "total_initial_margin" => "0",
            //                     "total_maintenance_margin" => "0",
            //                     "total_position_cost" => "0",
            //                     "total_cash_balance" => "6.44412101",
            //                     "total_collateral_value" => "5.846843685",
            //                     "total_session_unrealized_pnl" => "0",
            //                     "instrument_name" => "USD",
            //                     "total_session_realized_pnl" => "0",
            //                     "position_balances" => array(
            //                         array(
            //                             "quantity" => "0.0002119875",
            //                             "reserved_qty" => "0",
            //                             "collateral_weight" => "0.9",
            //                             "collateral_amount" => "5.37549592",
            //                             "market_value" => "5.97277325",
            //                             "max_withdrawal_balance" => "0.00021198",
            //                             "instrument_name" => "BTC",
            //                             "hourly_interest_rate" => "0"
            //                         ),
            //                     ),
            //                     "total_effective_leverage" => "0",
            //                     "position_limit" => "3000000",
            //                     "used_position_limit" => "0",
            //                     "total_borrow" => "0",
            //                     "margin_score" => "0",
            //                     "is_liquidating" => false,
            //                     "has_risk" => false,
            //                     "terminatable" => true
            //                 }
            //             )
            //         }
            //     }
            //
            return $this->parse_balance($response);
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-$order-detail
             *
             * @param {string} $id $order $id
             * @param {string} $symbol unified $symbol of the $market the $order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array(
                'order_id' => $id,
            );
            $response = Async\await($this->v1PrivatePostPrivateGetOrderDetail ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1686872583882,
            //         "method" => "private/get-$order-detail",
            //         "code" => 0,
            //         "result" => {
            //             "account_id" => "ae075bef-1234-4321-bd6g-bb9007252a63",
            //             "order_id" => "6142909895025252686",
            //             "client_oid" => "CCXT_c2d2152cc32d40a3ae7fbf",
            //             "order_type" => "LIMIT",
            //             "time_in_force" => "GOOD_TILL_CANCEL",
            //             "side" => "BUY",
            //             "exec_inst" => [ ],
            //             "quantity" => "0.00020",
            //             "limit_price" => "20000.00",
            //             "order_value" => "4",
            //             "avg_price" => "0",
            //             "trigger_price" => "0",
            //             "ref_price" => "0",
            //             "cumulative_quantity" => "0",
            //             "cumulative_value" => "0",
            //             "cumulative_fee" => "0",
            //             "status" => "ACTIVE",
            //             "update_user_id" => "ae075bef-1234-4321-bd6g-bb9007252a63",
            //             "order_date" => "2023-06-15",
            //             "instrument_name" => "BTC_USD",
            //             "fee_instrument_name" => "BTC",
            //             "create_time" => 1686870220684,
            //             "create_time_ns" => "1686870220684239675",
            //             "update_time" => 1686870220684
            //         }
            //     }
            //
            $order = $this->safe_dict($response, 'result', array());
            return $this->parse_order($order, $market);
        }) ();
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $uppercaseType = strtoupper($type);
        $request = array(
            'instrument_name' => $market['id'],
            'side' => strtoupper($side),
            'quantity' => $this->amount_to_precision($symbol, $amount),
        );
        if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'STOP_LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $broker = $this->safe_string($this->options, 'broker', 'CCXT');
        $request['broker_id'] = $broker;
        $marketType = null;
        $marginMode = null;
        list($marketType, $params) = $this->handle_market_type_and_params('createOrder', $market, $params);
        list($marginMode, $params) = $this->custom_handle_margin_mode_and_params('createOrder', $params);
        if (($marketType === 'margin') || ($marginMode !== null)) {
            $request['spot_margin'] = 'MARGIN';
        } elseif ($marketType === 'spot') {
            $request['spot_margin'] = 'SPOT';
        }
        $timeInForce = $this->safe_string_upper_2($params, 'timeInForce', 'time_in_force');
        if ($timeInForce !== null) {
            if ($timeInForce === 'GTC') {
                $request['time_in_force'] = 'GOOD_TILL_CANCEL';
            } elseif ($timeInForce === 'IOC') {
                $request['time_in_force'] = 'IMMEDIATE_OR_CANCEL';
            } elseif ($timeInForce === 'FOK') {
                $request['time_in_force'] = 'FILL_OR_KILL';
            } else {
                $request['time_in_force'] = $timeInForce;
            }
        }
        $postOnly = $this->safe_bool($params, 'postOnly', false);
        if (($postOnly) || ($timeInForce === 'PO')) {
            $request['exec_inst'] = array( 'POST_ONLY' );
            $request['time_in_force'] = 'GOOD_TILL_CANCEL';
        }
        $triggerPrice = $this->safe_string_n($params, array( 'stopPrice', 'triggerPrice', 'ref_price' ));
        $stopLossPrice = $this->safe_number($params, 'stopLossPrice');
        $takeProfitPrice = $this->safe_number($params, 'takeProfitPrice');
        $isTrigger = ($triggerPrice !== null);
        $isStopLossTrigger = ($stopLossPrice !== null);
        $isTakeProfitTrigger = ($takeProfitPrice !== null);
        if ($isTrigger) {
            $request['ref_price'] = $this->price_to_precision($symbol, $triggerPrice);
            $priceString = $this->number_to_string($price);
            if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'STOP_LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
                if ($side === 'buy') {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'TAKE_PROFIT_LIMIT';
                    } else {
                        $request['type'] = 'STOP_LIMIT';
                    }
                } else {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'STOP_LIMIT';
                    } else {
                        $request['type'] = 'TAKE_PROFIT_LIMIT';
                    }
                }
            } else {
                if ($side === 'buy') {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'TAKE_PROFIT';
                    } else {
                        $request['type'] = 'STOP_LOSS';
                    }
                } else {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'STOP_LOSS';
                    } else {
                        $request['type'] = 'TAKE_PROFIT';
                    }
                }
            }
        } elseif ($isStopLossTrigger) {
            if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'STOP_LIMIT')) {
                $request['type'] = 'STOP_LIMIT';
            } else {
                $request['type'] = 'STOP_LOSS';
            }
            $request['ref_price'] = $this->price_to_precision($symbol, $stopLossPrice);
        } elseif ($isTakeProfitTrigger) {
            if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
                $request['type'] = 'TAKE_PROFIT_LIMIT';
            } else {
                $request['type'] = 'TAKE_PROFIT';
            }
            $request['ref_price'] = $this->price_to_precision($symbol, $takeProfitPrice);
        } else {
            $request['type'] = $uppercaseType;
        }
        $params = $this->omit($params, array( 'postOnly', 'clientOrderId', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice' ));
        return $this->extend($request, $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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-order
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {string} $type 'market', 'limit', 'stop_loss', 'stop_limit', 'take_profit', 'take_profit_limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much 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->timeInForce] 'GTC', 'IOC', 'FOK' or 'PO'
             * @param {string} [$params->ref_price_type] 'MARK_PRICE', 'INDEX_PRICE', 'LAST_PRICE' which trigger $price $type to use, default is MARK_PRICE
             * @param {float} [$params->triggerPrice] $price to trigger a trigger order
             * @param {float} [$params->stopLossPrice] $price to trigger a stop-loss trigger order
             * @param {float} [$params->takeProfitPrice] $price to trigger a take-profit trigger order
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
            $response = Async\await($this->v1PrivatePostPrivateCreateOrder ($request));
            //
            //     {
            //         "id" => 1686804664362,
            //         "method" => "private/create-order",
            //         "code" : 0,
            //         "result" => {
            //             "order_id" => "6540219377766741832",
            //             "client_oid" => "CCXT_d6ef7c3db6c1495aa8b757"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            return $this->parse_order($result, $market);
        }) ();
    }

    public function create_orders(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * create a list of trade $orders
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-order-list-list
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-order-list-oco
             *
             * @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
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $ordersRequests = array();
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $marketId = $this->safe_string($rawOrder, '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_advanced_order_request($marketId, $type, $side, $amount, $price, $orderParams);
                $ordersRequests[] = $orderRequest;
            }
            $contigency = $this->safe_string($params, 'contingency_type', 'LIST');
            $request = array(
                'contingency_type' => $contigency, // or OCO
                'order_list' => $ordersRequests,
            );
            $response = Async\await($this->v1PrivatePostPrivateCreateOrderList ($this->extend($request, $params)));
            //
            // {
            //     "id" => 12,
            //     "method" => "private/create-order-list",
            //     "code" => 10001,
            //     "result" => {
            //       "result_list" => array(
            //         array(
            //           "index" => 0,
            //           "code" => 0,
            //           "order_id" => "2015106383706015873",
            //           "client_oid" => "my_order_0001"
            //         ),
            //         {
            //           "index" => 1,
            //           "code" => 20007,
            //           "message" => "INVALID_REQUEST",
            //           "client_oid" => "my_order_0002"
            //         }
            //       )
            //     }
            // }
            //
            //   {
            //       "id" : 1698068111133,
            //       "method" : "private/create-order-list",
            //       "code" : 0,
            //       "result" : array( array(
            //         "code" : 0,
            //         "index" : 0,
            //         "client_oid" : "1698068111133_0",
            //         "order_id" : "6142909896519488206"
            //       ), {
            //         "code" : 306,
            //         "index" : 1,
            //         "client_oid" : "1698068111133_1",
            //         "message" : "INSUFFICIENT_AVAILABLE_BALANCE",
            //         "order_id" : "6142909896519488207"
            //       } )
            //   }
            //
            $result = $this->safe_value($response, 'result', array());
            $listId = $this->safe_string($result, 'list_id');
            if ($listId !== null) {
                $ocoOrders = array( array( 'order_id' => $listId ) );
                return $this->parse_orders($ocoOrders);
            }
            return $this->parse_orders($result);
        }) ();
    }

    public function create_advanced_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        // differs slightly from createOrderRequest
        // since the advanced order endpoint requires a different set of parameters
        // namely here we don't support ref_price or spot_margin
        // and $market-buy orders need to send notional instead of quantity
        $market = $this->market($symbol);
        $uppercaseType = strtoupper($type);
        $request = array(
            'instrument_name' => $market['id'],
            'side' => strtoupper($side),
        );
        if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'STOP_LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $broker = $this->safe_string($this->options, 'broker', 'CCXT');
        $request['broker_id'] = $broker;
        $timeInForce = $this->safe_string_upper_2($params, 'timeInForce', 'time_in_force');
        if ($timeInForce !== null) {
            if ($timeInForce === 'GTC') {
                $request['time_in_force'] = 'GOOD_TILL_CANCEL';
            } elseif ($timeInForce === 'IOC') {
                $request['time_in_force'] = 'IMMEDIATE_OR_CANCEL';
            } elseif ($timeInForce === 'FOK') {
                $request['time_in_force'] = 'FILL_OR_KILL';
            } else {
                $request['time_in_force'] = $timeInForce;
            }
        }
        $postOnly = $this->safe_bool($params, 'postOnly', false);
        if (($postOnly) || ($timeInForce === 'PO')) {
            $request['exec_inst'] = array( 'POST_ONLY' );
            $request['time_in_force'] = 'GOOD_TILL_CANCEL';
        }
        $triggerPrice = $this->safe_string_n($params, array( 'stopPrice', 'triggerPrice', 'ref_price' ));
        $stopLossPrice = $this->safe_number($params, 'stopLossPrice');
        $takeProfitPrice = $this->safe_number($params, 'takeProfitPrice');
        $isTrigger = ($triggerPrice !== null);
        $isStopLossTrigger = ($stopLossPrice !== null);
        $isTakeProfitTrigger = ($takeProfitPrice !== null);
        if ($isTrigger) {
            $priceString = $this->number_to_string($price);
            if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'STOP_LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
                if ($side === 'buy') {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'TAKE_PROFIT_LIMIT';
                    } else {
                        $request['type'] = 'STOP_LIMIT';
                    }
                } else {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'STOP_LIMIT';
                    } else {
                        $request['type'] = 'TAKE_PROFIT_LIMIT';
                    }
                }
            } else {
                if ($side === 'buy') {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'TAKE_PROFIT';
                    } else {
                        $request['type'] = 'STOP_LOSS';
                    }
                } else {
                    if (Precise::string_lt($priceString, $triggerPrice)) {
                        $request['type'] = 'STOP_LOSS';
                    } else {
                        $request['type'] = 'TAKE_PROFIT';
                    }
                }
            }
        } elseif ($isStopLossTrigger) {
            if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'STOP_LIMIT')) {
                $request['type'] = 'STOP_LIMIT';
            } else {
                $request['type'] = 'STOP_LOSS';
            }
        } elseif ($isTakeProfitTrigger) {
            if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
                $request['type'] = 'TAKE_PROFIT_LIMIT';
            } else {
                $request['type'] = 'TAKE_PROFIT';
            }
        } else {
            $request['type'] = $uppercaseType;
        }
        if (($side === 'buy') && (($uppercaseType === 'MARKET') || ($uppercaseType === 'STOP_LOSS') || ($uppercaseType === 'TAKE_PROFIT'))) {
            // use createmarketBuy logic here
            $quoteAmount = null;
            $createMarketBuyOrderRequiresPrice = true;
            list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
            $cost = $this->safe_number_2($params, 'cost', 'notional');
            $params = $this->omit($params, 'cost');
            if ($cost !== null) {
                $quoteAmount = $this->cost_to_precision($symbol, $cost);
            } elseif ($createMarketBuyOrderRequiresPrice) {
                if ($price === null) {
                    throw new InvalidOrder($this->id . ' createOrder() requires the $price argument for $market buy orders to calculate the total $cost to spend ($amount * $price), alternatively set the $createMarketBuyOrderRequiresPrice option or param to false and pass the $cost to spend (quote quantity) in the $amount argument');
                } else {
                    $amountString = $this->number_to_string($amount);
                    $priceString = $this->number_to_string($price);
                    $costRequest = Precise::string_mul($amountString, $priceString);
                    $quoteAmount = $this->cost_to_precision($symbol, $costRequest);
                }
            } else {
                $quoteAmount = $this->cost_to_precision($symbol, $amount);
            }
            $request['notional'] = $quoteAmount;
        } else {
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        $params = $this->omit($params, array( 'postOnly', 'clientOrderId', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice' ));
        return $this->extend($request, $params);
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $type, $side, $amount, $price, $params) {
            /**
             * edit a trade order
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-amend-order
             *
             * @param {string} $id order $id
             * @param {string} $symbol unified market $symbol of the order to edit
             * @param {string} [$type] not used by cryptocom editOrder
             * @param {string} [$side] not used by cryptocom editOrder
             * @param {float} $amount (mandatory) how much of the currency you want to trade in units of the base currency
             * @param {float} $price (mandatory) the $price for 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->clientOrderId] the original client order $id of the order to edit, required if $id is not provided
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $request = $this->edit_order_request($id, $symbol, $amount, $price, $params);
            $response = Async\await($this->v1PrivatePostPrivateAmendOrder ($request));
            $result = $this->safe_dict($response, 'result', array());
            return $this->parse_order($result);
        }) ();
    }

    public function edit_order_request(string $id, string $symbol, float $amount, ?float $price = null, $params = array ()) {
        $request = array();
        if ($id !== null) {
            $request['order_id'] = $id;
        } else {
            $originalClientOrderId = $this->safe_string_2($params, 'orig_client_oid', 'clientOrderId');
            if ($originalClientOrderId === null) {
                throw new ArgumentsRequired($this->id . ' editOrder() requires an $id argument or orig_client_oid parameter');
            } else {
                $request['orig_client_oid'] = $originalClientOrderId;
                $params = $this->omit($params, array( 'orig_client_oid', 'clientOrderId' ));
            }
        }
        if (($amount === null) || ($price === null)) {
            throw new ArgumentsRequired($this->id . ' editOrder() requires both $amount and $price arguments. If you do not want to change the $amount or $price, you should pass the original values');
        }
        $request['new_quantity'] = $this->amount_to_precision($symbol, $amount);
        $request['new_price'] = $this->price_to_precision($symbol, $price);
        return $this->extend($request, $params);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * cancel all open orders
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-all-orders
             *
             * @param {string} $symbol unified $market $symbol of the orders to cancel
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} Returns exchange raw messagearray(@link https://docs.ccxt.com/#/?id=order-structure)
             */
            Async\await($this->load_markets());
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instrument_name'] = $market['id'];
            }
            $response = Async\await($this->v1PrivatePostPrivateCancelAllOrders ($this->extend($request, $params)));
            return array( $this->safe_order(array( 'info' => $response )) );
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-order
             *
             * @param {string} $id the order $id of the order to cancel
             * @param {string} [$symbol] unified $symbol of the $market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array(
                'order_id' => $id,
            );
            $response = Async\await($this->v1PrivatePostPrivateCancelOrder ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1686882846638,
            //         "method" => "private/cancel-order",
            //         "code" => 0,
            //         "message" => "NO_ERROR",
            //         "result" => {
            //             "client_oid" => "CCXT_c2d2152cc32d40a3ae7fbf",
            //             "order_id" => "6142909895025252686"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            return $this->parse_order($result, $market);
        }) ();
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple orders
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-$order-list-list
             *
             * @param {string[]} $ids $order $ids
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrders() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $orderRequests = array();
            for ($i = 0; $i < count($ids); $i++) {
                $id = $ids[$i];
                $order = array(
                    'instrument_name' => $market['id'],
                    'order_id' => (string) $id,
                );
                $orderRequests[] = $order;
            }
            $request = array(
                'contingency_type' => 'LIST',
                'order_list' => $orderRequests,
            );
            $response = Async\await($this->v1PrivatePostPrivateCancelOrderList ($this->extend($request, $params)));
            $result = $this->safe_list($response, 'result', array());
            return $this->parse_orders($result, $market, null, null, $params);
        }) ();
    }

    public function cancel_orders_for_symbols(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * cancel multiple $orders for multiple symbols
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-cancel-$order-list-list
             *
             * @param {CancellationRequest[]} $orders each $order should contain the parameters required by cancelOrder namely $id and $symbol, example [array("id" => "a", "symbol" => "BTC/USDT"), array("id" => "b", "symbol" => "ETH/USDT")]
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structures~
             */
            Async\await($this->load_markets());
            $orderRequests = array();
            for ($i = 0; $i < count($orders); $i++) {
                $order = $orders[$i];
                $id = $this->safe_string($order, 'id');
                $symbol = $this->safe_string($order, 'symbol');
                $market = $this->market($symbol);
                $orderItem = array(
                    'instrument_name' => $market['id'],
                    'order_id' => (string) $id,
                );
                $orderRequests[] = $orderItem;
            }
            $request = array(
                'contingency_type' => 'LIST',
                'order_list' => $orderRequests,
            );
            $response = Async\await($this->v1PrivatePostPrivateCancelOrderList ($this->extend($request, $params)));
            $result = $this->safe_list($response, 'result', array());
            return $this->parse_orders($result, null, null, null, $params);
        }) ();
    }

    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) {
            /**
             * fetch all unfilled currently open $orders
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-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
             * @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['instrument_name'] = $market['id'];
            }
            $response = Async\await($this->v1PrivatePostPrivateGetOpenOrders ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1686806134961,
            //         "method" => "private/get-open-$orders",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "account_id" => "ce075bef-1234-4321-bd6g-ff9007252e63",
            //                     "order_id" => "6530219477767564494",
            //                     "client_oid" => "CCXT_7ce730f0388441df9bc218",
            //                     "order_type" => "LIMIT",
            //                     "time_in_force" => "GOOD_TILL_CANCEL",
            //                     "side" => "BUY",
            //                     "exec_inst" => array( ),
            //                     "quantity" => "0.00020",
            //                     "limit_price" => "20000.00",
            //                     "order_value" => "4",
            //                     "avg_price" => "0",
            //                     "trigger_price" => "0",
            //                     "ref_price" => "0",
            //                     "cumulative_quantity" => "0",
            //                     "cumulative_value" => "0",
            //                     "cumulative_fee" => "0",
            //                     "status" => "ACTIVE",
            //                     "update_user_id" => "ce075bef-1234-4321-bd6g-gg9007252e63",
            //                     "order_date" => "2023-06-15",
            //                     "instrument_name" => "BTC_USD",
            //                     "fee_instrument_name" => "BTC",
            //                     "create_time" => 1686806053992,
            //                     "create_time_ns" => "1686806053992921880",
            //                     "update_time" => 1686806053993
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'result', array());
            $orders = $this->safe_list($data, 'data', array());
            return $this->parse_orders($orders, $market, $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) {
            /**
             * fetch all $trades made by the user
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-$trades
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch $trades for, maximum date range is one day
             * @param {int} [$limit] the maximum number of trade structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms for the ending date filter, default is the current time
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_dynamic('fetchMyTrades', $symbol, $since, $limit, $params, 100));
            }
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['instrument_name'] = $market['id'];
            }
            if ($since !== null) {
                $request['start_time'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_time'] = $until;
            }
            $response = Async\await($this->v1PrivatePostPrivateGetTrades ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1686942003520,
            //         "method" => "private/get-$trades",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "account_id" => "ds075abc-1234-4321-bd6g-ff9007252r63",
            //                     "event_date" => "2023-06-16",
            //                     "journal_type" => "TRADING",
            //                     "side" => "BUY",
            //                     "instrument_name" => "BTC_USD",
            //                     "fees" => "-0.0000000525",
            //                     "trade_id" => "6142909898247428343",
            //                     "trade_match_id" => "4611686018455978480",
            //                     "create_time" => 1686941992887,
            //                     "traded_price" => "26347.16",
            //                     "traded_quantity" => "0.00021",
            //                     "fee_instrument_name" => "BTC",
            //                     "client_oid" => "d1c70a60-810e-4c92-b2a0-72b931cb31e0",
            //                     "taker_side" => "TAKER",
            //                     "order_id" => "6142909895036331486",
            //                     "create_time_ns" => "1686941992887207066"
            //                 }
            //             )
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $trades = $this->safe_list($result, 'data', array());
            return $this->parse_trades($trades, $market, $since, $limit);
        }) ();
    }

    public function parse_address($addressString) {
        $address = null;
        $tag = null;
        $rawTag = null;
        if (mb_strpos($addressString, '?') > 0) {
            list($address, $rawTag) = explode('?', $addressString);
            $splitted = explode('=', $rawTag);
            $tag = $splitted[1];
        } else {
            $address = $addressString;
        }
        return array( $address, $tag );
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-create-withdrawal
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount the $amount to withdraw
             * @param {string} $address the $address to withdraw to
             * @param {string} $tag
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
            Async\await($this->load_markets());
            $currency = $this->safe_currency($code); // for instance, USDC is not inferred from markets but it's still available
            $request = array(
                'currency' => $currency['id'],
                'amount' => $amount,
                'address' => $address,
            );
            if ($tag !== null) {
                $request['address_tag'] = $tag;
            }
            $networkCode = null;
            list($networkCode, $params) = $this->handle_network_code_and_params($params);
            $networkId = $this->network_code_to_id($networkCode);
            if ($networkId !== null) {
                $request['network_id'] = $networkId;
            }
            $response = Async\await($this->v1PrivatePostPrivateCreateWithdrawal ($this->extend($request, $params)));
            //
            //    {
            //        "id":-1,
            //        "method":"private/create-withdrawal",
            //        "code":0,
            //        "result" => {
            //            "id" => 2220,
            //            "amount" => 1,
            //            "fee" => 0.0004,
            //            "symbol" => "BTC",
            //            "address" => "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBf",
            //            "client_wid" => "my_withdrawal_002",
            //            "create_time":1607063412000
            //        }
            //     }
            //
            $result = $this->safe_dict($response, 'result');
            return $this->parse_transaction($result, $currency);
        }) ();
    }

    public function fetch_deposit_addresses_by_network(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch a dictionary of $addresses for a $currency, indexed by $network
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-deposit-$address
             *
             * @param {string} $code unified $currency $code of the $currency for the deposit $address
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$address-structure $address structures~ indexed by the $network
             */
            Async\await($this->load_markets());
            $currency = $this->safe_currency($code);
            $request = array(
                'currency' => $currency['id'],
            );
            $response = Async\await($this->v1PrivatePostPrivateGetDepositAddress ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1234555011221,
            //         "method" => "private/get-deposit-$address",
            //         "code" => 0,
            //         "result" => {
            //             "deposit_address_list" => array(
            //                 array(
            //                     "currency" => "BTC",
            //                     "create_time" => 1686730755000,
            //                     "id" => "3737377",
            //                     "address" => "3N9afggxTSmJ3H4jaMQuWyEiLBzZdAbK6d",
            //                     "status":"1",
            //                     "network" => "BTC"
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'result', array());
            $addresses = $this->safe_list($data, 'deposit_address_list', array());
            $addressesLength = count($addresses);
            if ($addressesLength === 0) {
                throw new ExchangeError($this->id . ' fetchDepositAddressesByNetwork() generating $address->..');
            }
            $result = array();
            for ($i = 0; $i < $addressesLength; $i++) {
                $value = $this->safe_dict($addresses, $i);
                $addressString = $this->safe_string($value, 'address');
                $currencyId = $this->safe_string($value, 'currency');
                $responseCode = $this->safe_currency_code($currencyId);
                list($address, $tag) = $this->parse_address($addressString);
                $this->check_address($address);
                $networkId = $this->safe_string($value, 'network');
                $network = $this->network_id_to_code($networkId, $responseCode);
                $result[$network] = array(
                    'info' => $value,
                    'currency' => $responseCode,
                    'network' => $network,
                    'address' => $address,
                    'tag' => $tag,
                );
            }
            return $result;
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-deposit-address
             *
             * @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~
             */
            $network = $this->safe_string_upper($params, 'network');
            $params = $this->omit($params, array( 'network' ));
            $depositAddresses = Async\await($this->fetch_deposit_addresses_by_network($code, $params));
            if (is_array($depositAddresses) && array_key_exists($network, $depositAddresses)) {
                return $depositAddresses[$network];
            } else {
                $keys = is_array($depositAddresses) ? array_keys($depositAddresses) : array();
                return $depositAddresses[$keys[0]];
            }
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-deposit-history
             *
             * @param {string} $code unified $currency $code
             * @param {int} [$since] the earliest time in ms to fetch deposits for
             * @param {int} [$limit] the maximum number of deposits structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms for the ending date filter, default is the current time
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            Async\await($this->load_markets());
            $currency = null;
            $request = array();
            if ($code !== null) {
                $currency = $this->safe_currency($code);
                $request['currency'] = $currency['id'];
            }
            if ($since !== null) {
                // 90 days date range
                $request['start_ts'] = $since;
            }
            if ($limit !== null) {
                $request['page_size'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_ts'] = $until;
            }
            $response = Async\await($this->v1PrivatePostPrivateGetDepositHistory ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1688701375714,
            //         "method" => "private/get-deposit-history",
            //         "code" => 0,
            //         "result" => {
            //             "deposit_list" => array(
            //                 array(
            //                     "currency" => "BTC",
            //                     "fee" => 0,
            //                     "create_time" => 1688023659000,
            //                     "id" => "6201135",
            //                     "update_time" => 1688178509000,
            //                     "amount" => 0.00114571,
            //                     "address" => "1234fggxTSmJ3H4jaMQuWyEiLBzZdAbK6d",
            //                     "status" => "1",
            //                     "txid" => "f0ae4202b76eb999c301eccdde44dc639bee42d1fdd5974105286ca3393f6065/2"
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'result', array());
            $depositList = $this->safe_list($data, 'deposit_list', array());
            return $this->parse_transactions($depositList, $currency, $since, $limit);
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-withdrawal-history
             *
             * @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
             * @param {int} [$params->until] timestamp in ms for the ending date filter, default is the current time
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            Async\await($this->load_markets());
            $currency = null;
            $request = array();
            if ($code !== null) {
                $currency = $this->safe_currency($code);
                $request['currency'] = $currency['id'];
            }
            if ($since !== null) {
                // 90 days date range
                $request['start_ts'] = $since;
            }
            if ($limit !== null) {
                $request['page_size'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_ts'] = $until;
            }
            $response = Async\await($this->v1PrivatePostPrivateGetWithdrawalHistory ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1688613879534,
            //         "method" => "private/get-withdrawal-history",
            //         "code" => 0,
            //         "result" => {
            //             "withdrawal_list" => array(
            //                 {
            //                     "currency" => "BTC",
            //                     "client_wid" => "",
            //                     "fee" => 0.0005,
            //                     "create_time" => 1688613850000,
            //                     "id" => "5275977",
            //                     "update_time" => 1688613850000,
            //                     "amount" => 0.0005,
            //                     "address" => "1234NMEWbiF8ZkwUMxmfzMxi2A1MQ44bMn",
            //                     "status" => "1",
            //                     "txid" => "",
            //                     "network_id" => "BTC"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'result', array());
            $withdrawalList = $this->safe_list($data, 'withdrawal_list', array());
            return $this->parse_transactions($withdrawalList, $currency, $since, $limit);
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // fetchTicker
        //
        //     {
        //         "i" => "BTC_USD",
        //         "h" => "30821.45",
        //         "l" => "28685.11",
        //         "a" => "30446.00",
        //         "v" => "1767.8734",
        //         "vv" => "52436726.42",
        //         "c" => "0.0583",
        //         "b" => "30442.00",
        //         "k" => "30447.66",
        //         "t" => 1687403045415
        //     }
        //
        // fetchTickers
        //
        //     {
        //         "i" => "AVAXUSD-PERP",
        //         "h" => "13.209",
        //         "l" => "12.148",
        //         "a" => "13.209",
        //         "v" => "1109.8",
        //         "vv" => "14017.33",
        //         "c" => "0.0732",
        //         "b" => "13.210",
        //         "k" => "13.230",
        //         "oi" => "10888.9",
        //         "t" => 1687402657575
        //     }
        //
        $timestamp = $this->safe_integer($ticker, 't');
        $marketId = $this->safe_string($ticker, 'i');
        $market = $this->safe_market($marketId, $market, '_');
        $quote = $this->safe_string($market, 'quote');
        $last = $this->safe_string($ticker, 'a');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_number($ticker, 'h'),
            'low' => $this->safe_number($ticker, 'l'),
            'bid' => $this->safe_number($ticker, 'b'),
            'bidVolume' => null,
            'ask' => $this->safe_number($ticker, 'k'),
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => $this->safe_string($ticker, 'c'),
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'v'),
            'quoteVolume' => ($quote === 'USD') ? $this->safe_string($ticker, 'vv') : null,
            'info' => $ticker,
        ), $market);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades
        //
        //     {
        //         "s" => "sell",
        //         "p" => "26386.00",
        //         "q" => "0.00453",
        //         "tn" => 1686944282062,
        //         "tn" => 1704476468851524373,
        //         "d" => "4611686018455979970",
        //         "i" => "BTC_USD"
        //     }
        //
        // fetchMyTrades
        //
        //     {
        //         "account_id" => "ds075abc-1234-4321-bd6g-ff9007252r63",
        //         "event_date" => "2023-06-16",
        //         "journal_type" => "TRADING",
        //         "side" => "BUY",
        //         "instrument_name" => "BTC_USD",
        //         "fees" => "-0.0000000525",
        //         "trade_id" => "6142909898247428343",
        //         "trade_match_id" => "4611686018455978480",
        //         "create_time" => 1686941992887,
        //         "traded_price" => "26347.16",
        //         "traded_quantity" => "0.00021",
        //         "fee_instrument_name" => "BTC",
        //         "client_oid" => "d1c70a60-1234-4c92-b2a0-72b931cb31e0",
        //         "taker_side" => "TAKER",
        //         "order_id" => "6142909895036331486",
        //         "create_time_ns" => "1686941992887207066"
        //     }
        //
        $timestamp = $this->safe_integer_2($trade, 't', 'create_time');
        $marketId = $this->safe_string_2($trade, 'i', 'instrument_name');
        $market = $this->safe_market($marketId, $market, '_');
        $feeCurrency = $this->safe_string($trade, 'fee_instrument_name');
        $feeCostString = $this->safe_string($trade, 'fees');
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $this->safe_string_2($trade, 'd', 'trade_id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'order' => $this->safe_string($trade, 'order_id'),
            'side' => $this->safe_string_lower_2($trade, 's', 'side'),
            'takerOrMaker' => $this->safe_string_lower($trade, 'taker_side'),
            'price' => $this->safe_number_2($trade, 'p', 'traded_price'),
            'amount' => $this->safe_number_2($trade, 'q', 'traded_quantity'),
            'cost' => null,
            'type' => null,
            'fee' => array(
                'currency' => $this->safe_currency_code($feeCurrency),
                'cost' => $this->parse_number(Precise::string_neg($feeCostString)),
            ),
        ), $market);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "o" => "26949.89",
        //         "h" => "26957.64",
        //         "l" => "26948.24",
        //         "c" => "26950.00",
        //         "v" => "0.0670",
        //         "t" => 1687237080000
        //     }
        //
        return array(
            $this->safe_integer($ohlcv, 't'),
            $this->safe_number($ohlcv, 'o'),
            $this->safe_number($ohlcv, 'h'),
            $this->safe_number($ohlcv, 'l'),
            $this->safe_number($ohlcv, 'c'),
            $this->safe_number($ohlcv, 'v'),
        );
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'ACTIVE' => 'open',
            'CANCELED' => 'canceled',
            'FILLED' => 'closed',
            'REJECTED' => 'rejected',
            'EXPIRED' => 'expired',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_time_in_force(?string $timeInForce) {
        $timeInForces = array(
            'GOOD_TILL_CANCEL' => 'GTC',
            'IMMEDIATE_OR_CANCEL' => 'IOC',
            'FILL_OR_KILL' => 'FOK',
        );
        return $this->safe_string($timeInForces, $timeInForce, $timeInForce);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder, cancelOrder
        //
        //     {
        //         "order_id" => "6540219377766741832",
        //         "client_oid" => "CCXT_d6ef7c3db6c1495aa8b757"
        //     }
        //
        // fetchOpenOrders, fetchOrder, fetchOrders
        //
        //     {
        //         "account_id" => "ce075bef-1234-4321-bd6g-ff9007252e63",
        //         "order_id" => "6530219477767564494",
        //         "client_oid" => "CCXT_7ce730f0388441df9bc218",
        //         "order_type" => "LIMIT",
        //         "time_in_force" => "GOOD_TILL_CANCEL",
        //         "side" => "BUY",
        //         "exec_inst" => [ ],
        //         "quantity" => "0.00020",
        //         "limit_price" => "20000.00",
        //         "order_value" => "4",
        //         "avg_price" => "0",
        //         "trigger_price" => "0",
        //         "ref_price" => "0",
        //         "cumulative_quantity" => "0",
        //         "cumulative_value" => "0",
        //         "cumulative_fee" => "0",
        //         "status" => "ACTIVE",
        //         "update_user_id" => "ce075bef-1234-4321-bd6g-gg9007252e63",
        //         "order_date" => "2023-06-15",
        //         "instrument_name" => "BTC_USD",
        //         "fee_instrument_name" => "BTC",
        //         "create_time" => 1686806053992,
        //         "create_time_ns" => "1686806053992921880",
        //         "update_time" => 1686806053993
        //     }
        //
        // createOrders
        //     {
        //             "code" : 306,
        //             "index" : 1,
        //             "client_oid" : "1698068111133_1",
        //             "message" : "INSUFFICIENT_AVAILABLE_BALANCE",
        //             "order_id" : "6142909896519488207"
        //     }
        //
        $code = $this->safe_integer($order, 'code');
        if (($code !== null) && ($code !== 0)) {
            return $this->safe_order(array(
                'id' => $this->safe_string($order, 'order_id'),
                'clientOrderId' => $this->safe_string($order, 'client_oid'),
                'info' => $order,
                'status' => 'rejected',
            ));
        }
        $created = $this->safe_integer($order, 'create_time');
        $marketId = $this->safe_string($order, 'instrument_name');
        $symbol = $this->safe_symbol($marketId, $market);
        $execInst = $this->safe_value($order, 'exec_inst');
        $postOnly = null;
        if ($execInst !== null) {
            $postOnly = false;
            for ($i = 0; $i < count($execInst); $i++) {
                $inst = $execInst[$i];
                if ($inst === 'POST_ONLY') {
                    $postOnly = true;
                    break;
                }
            }
        }
        $feeCurrency = $this->safe_string($order, 'fee_instrument_name');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($order, 'order_id'),
            'clientOrderId' => $this->safe_string($order, 'client_oid'),
            'timestamp' => $created,
            'datetime' => $this->iso8601($created),
            'lastTradeTimestamp' => $this->safe_integer($order, 'update_time'),
            'status' => $this->parse_order_status($this->safe_string($order, 'status')),
            'symbol' => $symbol,
            'type' => $this->safe_string_lower($order, 'order_type'),
            'timeInForce' => $this->parse_time_in_force($this->safe_string($order, 'time_in_force')),
            'postOnly' => $postOnly,
            'side' => $this->safe_string_lower($order, 'side'),
            'price' => $this->safe_number($order, 'limit_price'),
            'amount' => $this->safe_number($order, 'quantity'),
            'filled' => $this->safe_number($order, 'cumulative_quantity'),
            'remaining' => null,
            'average' => $this->safe_number($order, 'avg_price'),
            'cost' => $this->safe_number($order, 'cumulative_value'),
            'fee' => array(
                'currency' => $this->safe_currency_code($feeCurrency),
                'cost' => $this->safe_number($order, 'cumulative_fee'),
            ),
            'trades' => array(),
        ), $market);
    }

    public function parse_deposit_status($status) {
        $statuses = array(
            '0' => 'pending',
            '1' => 'ok',
            '2' => 'failed',
            '3' => 'pending',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_withdrawal_status($status) {
        $statuses = array(
            '0' => 'pending',
            '1' => 'pending',
            '2' => 'failed',
            '3' => 'pending',
            '4' => 'failed',
            '5' => 'ok',
            '6' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // fetchDeposits
        //
        //     {
        //         "currency" => "BTC",
        //         "fee" => 0,
        //         "create_time" => 1688023659000,
        //         "id" => "6201135",
        //         "update_time" => 1688178509000,
        //         "amount" => 0.00114571,
        //         "address" => "1234fggxTSmJ3H4jaMQuWyEiLBzZdAbK6d",
        //         "status" => "1",
        //         "txid" => "f0ae4202b76eb999c301eccdde44dc639bee42d1fdd5974105286ca3393f6065/2"
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "currency" => "BTC",
        //         "client_wid" => "",
        //         "fee" => 0.0005,
        //         "create_time" => 1688613850000,
        //         "id" => "5775977",
        //         "update_time" => 1688613850000,
        //         "amount" => 0.0005,
        //         "address" => "1234NMEWbiF8ZkwUMxmfzMxi2A1MQ44bMn",
        //         "status" => "1",
        //         "txid" => "",
        //         "network_id" => "BTC"
        //     }
        //
        // withdraw
        //
        //     {
        //         "id" => 2220,
        //         "amount" => 1,
        //         "fee" => 0.0004,
        //         "symbol" => "BTC",
        //         "address" => "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBf",
        //         "client_wid" => "my_withdrawal_002",
        //         "create_time":1607063412000
        //     }
        //
        $type = null;
        $rawStatus = $this->safe_string($transaction, 'status');
        $status = null;
        if (is_array($transaction) && array_key_exists('client_wid', $transaction)) {
            $type = 'withdrawal';
            $status = $this->parse_withdrawal_status($rawStatus);
        } else {
            $type = 'deposit';
            $status = $this->parse_deposit_status($rawStatus);
        }
        $addressString = $this->safe_string($transaction, 'address');
        list($address, $tag) = $this->parse_address($addressString);
        $currencyId = $this->safe_string($transaction, 'currency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $timestamp = $this->safe_integer($transaction, 'create_time');
        $feeCost = $this->safe_number($transaction, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array( 'currency' => $code, 'cost' => $feeCost );
        }
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'id'),
            'txid' => $this->safe_string($transaction, 'txid'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => null,
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $this->safe_number($transaction, 'amount'),
            'currency' => $code,
            'status' => $status,
            'updated' => $this->safe_integer($transaction, 'update_time'),
            'internal' => null,
            'comment' => $this->safe_string($transaction, 'client_wid'),
            'fee' => $fee,
        );
    }

    public function custom_handle_margin_mode_and_params($methodName, $params = array ()) {
        /**
         * @ignore
         * $marginMode specified by $params["marginMode"], $this->options["marginMode"], $this->options["defaultMarginMode"], $params["margin"] = true or $this->options["defaultType"] = 'margin'
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Array} the $marginMode in lowercase
         */
        $defaultType = $this->safe_string($this->options, 'defaultType');
        $isMargin = $this->safe_bool($params, 'margin', false);
        $params = $this->omit($params, 'margin');
        $marginMode = null;
        list($marginMode, $params) = $this->handle_margin_mode_and_params($methodName, $params);
        if ($marginMode !== null) {
            if ($marginMode !== 'cross') {
                throw new NotSupported($this->id . ' only cross margin is supported');
            }
        } else {
            if (($defaultType === 'margin') || ($isMargin === true)) {
                $marginMode = 'cross';
            }
        }
        return array( $marginMode, $params );
    }

    public function parse_deposit_withdraw_fee($fee, ?array $currency = null) {
        //
        //    {
        //        "full_name" => "Alchemix",
        //        "default_network" => "ETH",
        //        "network_list" => array(
        //          {
        //            "network_id" => "ETH",
        //            "withdrawal_fee" => "0.25000000",
        //            "withdraw_enabled" => true,
        //            "min_withdrawal_amount" => "0.5",
        //            "deposit_enabled" => true,
        //            "confirmation_required" => "0"
        //          }
        //        )
        //    }
        //
        $networkList = $this->safe_list($fee, 'network_list', array());
        $networkListLength = count($networkList);
        $result = array(
            'info' => $fee,
            'withdraw' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'deposit' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'networks' => array(),
        );
        if ($networkList !== null) {
            for ($i = 0; $i < $networkListLength; $i++) {
                $networkInfo = $networkList[$i];
                $networkId = $this->safe_string($networkInfo, 'network_id');
                $currencyCode = $this->safe_string($currency, 'code');
                $networkCode = $this->network_id_to_code($networkId, $currencyCode);
                $result['networks'][$networkCode] = array(
                    'deposit' => array( 'fee' => null, 'percentage' => null ),
                    'withdraw' => array( 'fee' => $this->safe_number($networkInfo, 'withdrawal_fee'), 'percentage' => false ),
                );
                if ($networkListLength === 1) {
                    $result['withdraw']['fee'] = $this->safe_number($networkInfo, 'withdrawal_fee');
                    $result['withdraw']['percentage'] = false;
                }
            }
        }
        return $result;
    }

    public function fetch_deposit_withdraw_fees(?array $codes = null, $params = array ()) {
        return Async\async(function () use ($codes, $params) {
            /**
             * fetch deposit and withdraw fees
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-currency-networks
             *
             * @param {string[]|null} $codes list of unified currency $codes
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v1PrivatePostPrivateGetCurrencyNetworks ($params));
            $data = $this->safe_value($response, 'result');
            $currencyMap = $this->safe_list($data, 'currency_map');
            return $this->parse_deposit_withdraw_fees($currencyMap, $codes, 'full_name');
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-transactions
             *
             * @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 for the ending date filter, default is the current time
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ledger ledger structure~
             */
            Async\await($this->load_markets());
            $request = array();
            $currency = null;
            if ($code !== null) {
                $currency = $this->safe_currency($code);
            }
            if ($since !== null) {
                $request['start_time'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_time'] = $until;
            }
            $response = Async\await($this->v1PrivatePostPrivateGetTransactions ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1686813195698,
            //         "method" => "private/get-transactions",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 array(
            //                     "account_id" => "ce075cef-1234-4321-bd6e-gf9007351e64",
            //                     "event_date" => "2023-06-15",
            //                     "journal_type" => "TRADING",
            //                     "journal_id" => "6530219460124075091",
            //                     "transaction_qty" => "6.0091224",
            //                     "transaction_cost" => "6.0091224",
            //                     "realized_pnl" => "0",
            //                     "order_id" => "6530219477766741833",
            //                     "trade_id" => "6530219495775954765",
            //                     "trade_match_id" => "4611686018455865176",
            //                     "event_timestamp_ms" => 1686804665013,
            //                     "event_timestamp_ns" => "1686804665013642422",
            //                     "client_oid" => "CCXT_d6ea7c5db6c1495aa8b758",
            //                     "taker_side" => "",
            //                     "side" => "BUY",
            //                     "instrument_name" => "USD"
            //                 ),
            //             )
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $ledger = $this->safe_list($result, 'data', array());
            return $this->parse_ledger($ledger, $currency, $since, $limit);
        }) ();
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        //     {
        //         "account_id" => "ce075cef-1234-4321-bd6e-gf9007351e64",
        //         "event_date" => "2023-06-15",
        //         "journal_type" => "TRADING",
        //         "journal_id" => "6530219460124075091",
        //         "transaction_qty" => "6.0091224",
        //         "transaction_cost" => "6.0091224",
        //         "realized_pnl" => "0",
        //         "order_id" => "6530219477766741833",
        //         "trade_id" => "6530219495775954765",
        //         "trade_match_id" => "4611686018455865176",
        //         "event_timestamp_ms" => 1686804665013,
        //         "event_timestamp_ns" => "1686804665013642422",
        //         "client_oid" => "CCXT_d6ea7c5db6c1495aa8b758",
        //         "taker_side" => "",
        //         "side" => "BUY",
        //         "instrument_name" => "USD"
        //     }
        //
        $timestamp = $this->safe_integer($item, 'event_timestamp_ms');
        $currencyId = $this->safe_string($item, 'instrument_name');
        $code = $this->safe_currency_code($currencyId, $currency);
        $currency = $this->safe_currency($currencyId, $currency);
        $amount = $this->safe_string($item, 'transaction_qty');
        $direction = null;
        if (Precise::string_lt($amount, '0')) {
            $direction = 'out';
            $amount = Precise::string_abs($amount);
        } else {
            $direction = 'in';
        }
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $this->safe_string($item, 'order_id'),
            'direction' => $direction,
            'account' => $this->safe_string($item, 'account_id'),
            'referenceId' => $this->safe_string($item, 'trade_id'),
            'referenceAccount' => $this->safe_string($item, 'trade_match_id'),
            'type' => $this->parse_ledger_entry_type($this->safe_string($item, 'journal_type')),
            'currency' => $code,
            'amount' => $this->parse_number($amount),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'before' => null,
            'after' => null,
            'status' => null,
            'fee' => array(
                'currency' => null,
                'cost' => null,
            ),
        ), $currency);
    }

    public function parse_ledger_entry_type($type) {
        $ledgerType = array(
            'TRADING' => 'trade',
            'TRADE_FEE' => 'fee',
            'WITHDRAW_FEE' => 'fee',
            'WITHDRAW' => 'withdrawal',
            'DEPOSIT' => 'deposit',
            'ROLLBACK_WITHDRAW' => 'rollback',
            'ROLLBACK_DEPOSIT' => 'rollback',
            'FUNDING' => 'fee',
            'REALIZED_PNL' => 'trade',
            'INSURANCE_FUND' => 'insurance',
            'SOCIALIZED_LOSS' => 'trade',
            'LIQUIDATION_FEE' => 'fee',
            'SESSION_RESET' => 'reset',
            'ADJUSTMENT' => 'adjustment',
            'SESSION_SETTLE' => 'settlement',
            'UNCOVERED_LOSS' => 'trade',
            'ADMIN_ADJUSTMENT' => 'adjustment',
            'DELIST' => 'delist',
            'SETTLEMENT_FEE' => 'fee',
            'AUTO_CONVERSION' => 'conversion',
            'MANUAL_CONVERSION' => 'conversion',
        );
        return $this->safe_string($ledgerType, $type, $type);
    }

    public function fetch_accounts($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch all the $accounts associated with a profile
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-$accounts
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=account-structure account structures~ indexed by the account type
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v1PrivatePostPrivateGetAccounts ($params));
            //
            //     {
            //         "id" => 1234567894321,
            //         "method" => "private/get-$accounts",
            //         "code" => 0,
            //         "result" => {
            //             "master_account" => array(
            //                 "uuid" => "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
            //                 "user_uuid" => "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
            //                 "enabled" => true,
            //                 "tradable" => true,
            //                 "name" => "YOUR_NAME",
            //                 "country_code" => "CAN",
            //                 "phone_country_code" => "CAN",
            //                 "incorp_country_code" => "",
            //                 "margin_access" => "DEFAULT",
            //                 "derivatives_access" => "DEFAULT",
            //                 "create_time" => 1656445188000,
            //                 "update_time" => 1660794567262,
            //                 "two_fa_enabled" => true,
            //                 "kyc_level" => "ADVANCED",
            //                 "suspended" => false,
            //                 "terminated" => false,
            //                 "spot_enabled" => false,
            //                 "margin_enabled" => false,
            //                 "derivatives_enabled" => false
            //             ),
            //             "sub_account_list" => array()
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $masterAccount = $this->safe_dict($result, 'master_account', array());
            $accounts = $this->safe_list($result, 'sub_account_list', array());
            $accounts[] = $masterAccount;
            return $this->parse_accounts($accounts, $params);
        }) ();
    }

    public function parse_account($account) {
        //
        //     {
        //         "uuid" => "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
        //         "user_uuid" => "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
        //         "master_account_uuid" => "a1234abc-1234-4321-q5r7-b1ab0a0b12b",
        //         "label" => "FORMER_MASTER_MARGIN",
        //         "enabled" => true,
        //         "tradable" => true,
        //         "name" => "YOUR_NAME",
        //         "country_code" => "YOUR_COUNTRY_CODE",
        //         "incorp_country_code" => "",
        //         "margin_access" => "DEFAULT",
        //         "derivatives_access" => "DEFAULT",
        //         "create_time" => 1656481992000,
        //         "update_time" => 1667272884594,
        //         "two_fa_enabled" => false,
        //         "kyc_level" => "ADVANCED",
        //         "suspended" => false,
        //         "terminated" => false,
        //         "spot_enabled" => false,
        //         "margin_enabled" => false,
        //         "derivatives_enabled" => false,
        //         "system_label" => "FORMER_MASTER_MARGIN"
        //     }
        //
        return array(
            'id' => $this->safe_string($account, 'uuid'),
            'type' => $this->safe_string($account, 'label'),
            'code' => null,
            'info' => $account,
        );
    }

    public function fetch_settlement_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches historical settlement records
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-expired-settlement-price
             *
             * @param {string} $symbol unified $market $symbol of the settlement history
             * @param {int} [$since] timestamp in ms
             * @param {int} [$limit] number of records
             * @param {array} [$params] exchange specific $params
             * @param {int} [$params->type] 'future', 'option'
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=settlement-history-structure settlement history objects~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $type = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchSettlementHistory', $market, $params);
            $this->check_required_argument('fetchSettlementHistory', $type, 'type', array( 'future', 'option', 'WARRANT', 'FUTURE' ));
            if ($type === 'option') {
                $type = 'WARRANT';
            }
            $request = array(
                'instrument_type' => strtoupper($type),
            );
            $response = Async\await($this->v1PublicGetPublicGetExpiredSettlementPrice ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-expired-settlement-price",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "i" => "BTCUSD-230526",
            //                     "x" => 1685088000000,
            //                     "v" => "26464.1",
            //                     "t" => 1685087999500
            //                 }
            //             )
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            $settlements = $this->parse_settlements($data, $market);
            $sorted = $this->sort_by($settlements, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
        }) ();
    }

    public function parse_settlement($settlement, $market) {
        //
        //     {
        //         "i" => "BTCUSD-230526",
        //         "x" => 1685088000000,
        //         "v" => "26464.1",
        //         "t" => 1685087999500
        //     }
        //
        $timestamp = $this->safe_integer($settlement, 'x');
        $marketId = $this->safe_string($settlement, 'i');
        return array(
            'info' => $settlement,
            'symbol' => $this->safe_symbol($marketId, $market),
            'price' => $this->safe_number($settlement, 'v'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
    }

    public function parse_settlements($settlements, $market) {
        //
        //     array(
        //         {
        //             "i" => "BTCUSD-230526",
        //             "x" => 1685088000000,
        //             "v" => "26464.1",
        //             "t" => 1685087999500
        //         }
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($settlements); $i++) {
            $result[] = $this->parse_settlement($settlements[$i], $market);
        }
        return $result;
    }

    public function fetch_funding_rate(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches historical funding rates
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-valuations
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
             * @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~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new BadSymbol($this->id . ' fetchFundingRate() supports swap contracts only');
            }
            $request = array(
                'instrument_name' => $market['id'],
                'valuation_type' => 'estimated_funding_rate',
                'count' => 1,
            );
            $response = Async\await($this->v1PublicGetPublicGetValuations ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-valuations",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 array(
            //                     "v" => "-0.000001884",
            //                     "t" => 1687892400000
            //                 ),
            //             ),
            //             "instrument_name" => "BTCUSD-PERP"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            $entry = $this->safe_dict($data, 0, array());
            return $this->parse_funding_rate($entry, $market);
        }) ();
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        //
        //                 array(
        //                     "v" => "-0.000001884",
        //                     "t" => 1687892400000
        //                 ),
        //
        $timestamp = $this->safe_integer($contract, 't');
        $fundingTimestamp = null;
        if ($timestamp !== null) {
            $fundingTimestamp = (int) ceil($timestamp / 3600000) * 3600000; // end of the next hour
        }
        return array(
            'info' => $contract,
            'symbol' => $this->safe_symbol(null, $market),
            'markPrice' => null,
            'indexPrice' => null,
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'fundingRate' => $this->safe_number($contract, 'v'),
            'fundingTimestamp' => $fundingTimestamp,
            'fundingDatetime' => $this->iso8601($fundingTimestamp),
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => '1h',
        );
    }

    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 $rates
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#public-get-valuations
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
             * @param {int} [$since] $timestamp in ms of the earliest funding rate to fetch
             * @param {int} [$limit] the maximum amount of [funding rate structures] to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] $timestamp in ms for the ending date filter, default is the current time
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_deterministic('fetchFundingRateHistory', $symbol, $since, $limit, '8h', $params));
            }
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new BadSymbol($this->id . ' fetchFundingRateHistory() supports swap contracts only');
            }
            $request = array(
                'instrument_name' => $market['id'],
                'valuation_type' => 'funding_hist',
            );
            if ($since !== null) {
                $request['start_ts'] = $since;
            }
            if ($limit !== null) {
                $request['count'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_ts'] = $until;
            }
            $response = Async\await($this->v1PublicGetPublicGetValuations ($this->extend($request, $params)));
            //
            //     {
            //         "id" => -1,
            //         "method" => "public/get-valuations",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 array(
            //                     "v" => "-0.000001884",
            //                     "t" => 1687892400000
            //                 ),
            //             ),
            //             "instrument_name" => "BTCUSD-PERP"
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            $marketId = $this->safe_string($result, 'instrument_name');
            $rates = array();
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $timestamp = $this->safe_integer($entry, 't');
                $rates[] = array(
                    'info' => $entry,
                    'symbol' => $this->safe_symbol($marketId, $market),
                    'fundingRate' => $this->safe_number($entry, 'v'),
                    '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 fetch_position(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch $data on a single open contract trade position
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-positions
             *
             * @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(
                'instrument_name' => $market['id'],
            );
            $response = Async\await($this->v1PrivatePostPrivateGetPositions ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1688015952050,
            //         "method" => "private/get-positions",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "account_id" => "ce075bef-b600-4277-bd6e-ff9007251e63",
            //                     "quantity" => "0.0001",
            //                     "cost" => "3.02392",
            //                     "open_pos_cost" => "3.02392",
            //                     "open_position_pnl" => "-0.0010281328",
            //                     "session_pnl" => "-0.0010281328",
            //                     "update_timestamp_ms" => 1688015919091,
            //                     "instrument_name" => "BTCUSD-PERP",
            //                     "type" => "PERPETUAL_SWAP"
            //                 }
            //             )
            //         }
            //     }
            //
            $result = $this->safe_dict($response, 'result', array());
            $data = $this->safe_list($result, 'data', array());
            return $this->parse_position($this->safe_dict($data, 0), $market);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open $positions
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-$positions
             *
             * @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 structure~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $request = array();
            $market = null;
            if ($symbols !== null) {
                $symbol = null;
                if (gettype($symbols) === 'array' && array_keys($symbols) === array_keys(array_keys($symbols))) {
                    $symbolsLength = count($symbols);
                    if ($symbolsLength > 1) {
                        throw new BadRequest($this->id . ' fetchPositions() $symbols argument cannot contain more than 1 symbol');
                    }
                    $symbol = $symbols[0];
                } else {
                    $symbol = $symbols;
                }
                $market = $this->market($symbol);
                $request['instrument_name'] = $market['id'];
            }
            $response = Async\await($this->v1PrivatePostPrivateGetPositions ($this->extend($request, $params)));
            //
            //     {
            //         "id" => 1688015952050,
            //         "method" => "private/get-$positions",
            //         "code" => 0,
            //         "result" => {
            //             "data" => array(
            //                 {
            //                     "account_id" => "ce075bef-b600-4277-bd6e-ff9007251e63",
            //                     "quantity" => "0.0001",
            //                     "cost" => "3.02392",
            //                     "open_pos_cost" => "3.02392",
            //                     "open_position_pnl" => "-0.0010281328",
            //                     "session_pnl" => "-0.0010281328",
            //                     "update_timestamp_ms" => 1688015919091,
            //                     "instrument_name" => "BTCUSD-PERP",
            //                     "type" => "PERPETUAL_SWAP"
            //                 }
            //             )
            //         }
            //     }
            //
            $responseResult = $this->safe_dict($response, 'result', array());
            $positions = $this->safe_list($responseResult, 'data', array());
            $result = array();
            for ($i = 0; $i < count($positions); $i++) {
                $entry = $positions[$i];
                $marketId = $this->safe_string($entry, 'instrument_name');
                $marketInner = $this->safe_market($marketId, null, null, 'contract');
                $result[] = $this->parse_position($entry, $marketInner);
            }
            return $this->filter_by_array_positions($result, 'symbol', null, false);
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //         "account_id" => "ce075bef-b600-4277-bd6e-ff9007251e63",
        //         "quantity" => "0.0001",
        //         "cost" => "3.02392",
        //         "open_pos_cost" => "3.02392",
        //         "open_position_pnl" => "-0.0010281328",
        //         "session_pnl" => "-0.0010281328",
        //         "update_timestamp_ms" => 1688015919091,
        //         "instrument_name" => "BTCUSD-PERP",
        //         "type" => "PERPETUAL_SWAP"
        //     }
        //
        $marketId = $this->safe_string($position, 'instrument_name');
        $market = $this->safe_market($marketId, $market, null, 'contract');
        $symbol = $this->safe_symbol($marketId, $market, null, 'contract');
        $timestamp = $this->safe_integer($position, 'update_timestamp_ms');
        $amount = $this->safe_string($position, 'quantity');
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'hedged' => null,
            'side' => Precise::string_gt($amount, '0') ? 'buy' : 'sell',
            'contracts' => Precise::string_abs($amount),
            'contractSize' => $market['contractSize'],
            'entryPrice' => null,
            'markPrice' => null,
            'notional' => null,
            'leverage' => null,
            'collateral' => $this->safe_number($position, 'open_pos_cost'),
            'initialMargin' => $this->safe_number($position, 'cost'),
            'maintenanceMargin' => null,
            'initialMarginPercentage' => null,
            'maintenanceMarginPercentage' => null,
            'unrealizedPnl' => $this->safe_number($position, 'open_position_pnl'),
            'liquidationPrice' => null,
            'marginMode' => null,
            'percentage' => null,
            'marginRatio' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function nonce() {
        return $this->milliseconds();
    }

    public function params_to_string($object, $level) {
        $maxLevel = 3;
        if ($level >= $maxLevel) {
            return (string) $object;
        }
        if (gettype($object) === 'string') {
            return $object;
        }
        $returnString = '';
        $paramsKeys = null;
        if (gettype($object) === 'array' && array_keys($object) === array_keys(array_keys($object))) {
            $paramsKeys = $object;
        } else {
            $sorted = $this->keysort($object);
            $paramsKeys = is_array($sorted) ? array_keys($sorted) : array();
        }
        for ($i = 0; $i < count($paramsKeys); $i++) {
            $key = $paramsKeys[$i];
            $returnString .= $key;
            $value = $object[$key];
            if ($value === 'null') {
                $returnString .= 'null';
            } elseif (gettype($value) === 'array' && array_keys($value) === array_keys(array_keys($value))) {
                for ($j = 0; $j < count($value); $j++) {
                    $returnString .= $this->params_to_string($value[$j], $level + 1);
                }
            } else {
                $returnString .= (string) $value;
            }
        }
        return $returnString;
    }

    public function close_position(string $symbol, ?string $side = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $side, $params) {
            /**
             * closes open positions for a $market
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-close-position
             *
             * @param {string} $symbol Unified CCXT $market $symbol
             * @param {string} [$side] not used by cryptocom.closePositions
             * @param {array} [$params] extra parameters specific to the okx api endpoint
             *
             * EXCHANGE SPECIFIC PARAMETERS
             * @param {string} [$params->type] LIMIT or MARKET
             * @param {number} [$params->price] for limit orders only
             * @return {array[]} ~@link https://docs.ccxt.com/#/?id=position-structure A list of position structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instrument_name' => $market['id'],
                'type' => 'MARKET',
            );
            $type = $this->safe_string_upper($params, 'type');
            $price = $this->safe_string($params, 'price');
            if ($type !== null) {
                $request['type'] = $type;
            }
            if ($price !== null) {
                $request['price'] = $this->price_to_precision($market['symbol'], $price);
            }
            $response = Async\await($this->v1PrivatePostPrivateClosePosition ($this->extend($request, $params)));
            //
            //    {
            //        "id" : 1700830813298,
            //        "method" : "private/close-position",
            //        "code" : 0,
            //        "result" : {
            //            "client_oid" : "179a909d-5614-655b-0d0e-9e85c9a25c85",
            //            "order_id" : "6142909897021751347"
            //        }
            //    }
            //
            $result = $this->safe_dict($response, 'result');
            return $this->parse_order($result, $market);
        }) ();
    }

    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://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-instrument-fee-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=fee-structure fee structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'instrument_name' => $market['id'],
            );
            $response = Async\await($this->v1PrivatePostPrivateGetInstrumentFeeRate ($this->extend($request, $params)));
            //
            //    {
            //        "id" => 1,
            //        "code" => 0,
            //        "method" => "private/staking/unstake",
            //        "result" => {
            //          "staking_id" => "1",
            //          "instrument_name" => "SOL.staked",
            //          "status" => "NEW",
            //          "quantity" => "1",
            //          "underlying_inst_name" => "SOL",
            //          "reason" => "NO_ERROR"
            //        }
            //    }
            //
            $data = $this->safe_dict($response, 'result', array());
            return $this->parse_trading_fee($data, $market);
        }) ();
    }

    public function fetch_trading_fees($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html#private-get-fee-rate
             *
             * fetch the trading fees for multiple markets
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by market symbols
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v1PrivatePostPrivateGetFeeRate ($params));
            //
            //   {
            //       "id" => 1,
            //       "method" => "/private/get-fee-rate",
            //       "code" => 0,
            //       "result" => {
            //         "spot_tier" => "3",
            //         "deriv_tier" => "3",
            //         "effective_spot_maker_rate_bps" => "6.5",
            //         "effective_spot_taker_rate_bps" => "6.9",
            //         "effective_deriv_maker_rate_bps" => "1.1",
            //         "effective_deriv_taker_rate_bps" => "3"
            //       }
            //   }
            //
            $result = $this->safe_dict($response, 'result', array());
            return $this->parse_trading_fees($result);
        }) ();
    }

    public function parse_trading_fees($response) {
        //
        // {
        //         "spot_tier" => "3",
        //         "deriv_tier" => "3",
        //         "effective_spot_maker_rate_bps" => "6.5",
        //         "effective_spot_taker_rate_bps" => "6.9",
        //         "effective_deriv_maker_rate_bps" => "1.1",
        //         "effective_deriv_taker_rate_bps" => "3"
        //  }
        //
        $result = array();
        $result['info'] = $response;
        for ($i = 0; $i < count($this->symbols); $i++) {
            $symbol = $this->symbols[$i];
            $market = $this->market($symbol);
            $isSwap = $market['swap'];
            $takerFeeKey = $isSwap ? 'effective_deriv_taker_rate_bps' : 'effective_spot_taker_rate_bps';
            $makerFeeKey = $isSwap ? 'effective_deriv_maker_rate_bps' : 'effective_spot_maker_rate_bps';
            $tradingFee = array(
                'info' => $response,
                'symbol' => $symbol,
                'maker' => $this->parse_number(Precise::string_div($this->safe_string($response, $makerFeeKey), '10000')),
                'taker' => $this->parse_number(Precise::string_div($this->safe_string($response, $takerFeeKey), '10000')),
                'percentage' => null,
                'tierBased' => null,
            );
            $result[$symbol] = $tradingFee;
        }
        return $result;
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        //
        // {
        //      "instrument_name" => "BTC_USD",
        //      "effective_maker_rate_bps" => "6.5",
        //      "effective_taker_rate_bps" => "6.9"
        // }
        //
        $marketId = $this->safe_string($fee, 'instrument_name');
        $symbol = $this->safe_symbol($marketId, $market);
        return array(
            'info' => $fee,
            'symbol' => $symbol,
            'maker' => $this->parse_number(Precise::string_div($this->safe_string($fee, 'effective_maker_rate_bps'), '10000')),
            'taker' => $this->parse_number(Precise::string_div($this->safe_string($fee, 'effective_taker_rate_bps'), '10000')),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $type = $this->safe_string($api, 0);
        $access = $this->safe_string($api, 1);
        $url = $this->urls['api'][$type] . '/' . $path;
        $query = $this->omit($params, $this->extract_params($path));
        if ($access === 'public') {
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } else {
            $this->check_required_credentials();
            $nonce = (string) $this->nonce();
            $requestParams = $this->extend(array(), $params);
            $paramsKeys = is_array($requestParams) ? array_keys($requestParams) : array();
            $strSortKey = $this->params_to_string($requestParams, 0);
            $payload = $path . $nonce . $this->apiKey . $strSortKey . $nonce;
            $signature = $this->hmac($this->encode($payload), $this->encode($this->secret), 'sha256');
            $paramsKeysLength = count($paramsKeys);
            $body = $this->json(array(
                'id' => $nonce,
                'method' => $path,
                'params' => $params,
                'api_key' => $this->apiKey,
                'sig' => $signature,
                'nonce' => $nonce,
            ));
            // fix issue https://github.com/ccxt/ccxt/issues/11179
            // php always encodes dictionaries
            // if an array is empty, php will put it in square brackets
            // python and js will put it in curly brackets
            // the code below checks and replaces those brackets in empty requests
            if ($paramsKeysLength === 0) {
                $paramsString = '{}';
                $arrayString = '[]';
                $body = str_replace($arrayString, $paramsString, $body);
            }
            $headers = array(
                'Content-Type' => 'application/json',
            );
        }
        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) {
        $errorCode = $this->safe_string($response, 'code');
        if ($errorCode !== '0') {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            throw new ExchangeError($this->id . ' ' . $body);
        }
        return null;
    }
}
