<?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\oxfun as Exchange;
use ccxt\ExchangeError;
use ccxt\ArgumentsRequired;
use ccxt\BadRequest;
use ccxt\NotSupported;
use \React\Async;
use \React\Promise;
use \React\Promise\PromiseInterface;

class oxfun extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'oxfun',
            'name' => 'OXFUN',
            'countries' => array( 'PA' ), // Panama todo check
            'version' => 'v3',
            'rateLimit' => 120, // 100 requests per second and 25000 per 5 minutes
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createDepositAddress' => false,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => false,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'deposit' => false,
                'editOrder' => false,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchBidsAsks' => false,
                'fetchBorrowInterest' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDeposit' => false,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositWithdrawFee' => false,
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchL3OrderBook' => false,
                'fetchLedger' => false,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => true,
                'fetchMarketLeverageTiers' => 'emulated',
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrder' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => false,
                'fetchOrders' => false,
                'fetchOrderTrades' => false,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => false,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTradingLimits' => false,
                'fetchTransactionFee' => false,
                'fetchTransactionFees' => false,
                'fetchTransactions' => false,
                'fetchTransfers' => true,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => true,
                'fetchWithdrawalWhitelist' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'sandbox' => true,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'signIn' => false,
                'transfer' => true,
                'withdraw' => true,
                'ws' => true,
            ),
            'timeframes' => array(
                '1m' => '60s',
                '5m' => '300s',
                '15m' => '900s',
                '30m' => '1800s',
                '1h' => '3600s',
                '2h' => '7200s',
                '4h' => '14400s',
                '1d' => '86400s',
            ),
            'urls' => array(
                'logo' => 'https://github.com/ccxt/ccxt/assets/43336371/6a196124-c1ee-4fae-8573-962071b61a85',
                'referral' => 'https://ox.fun/register?shareAccountId=5ZUD4a7G',
                'api' => array(
                    'public' => 'https://api.ox.fun',
                    'private' => 'https://api.ox.fun',
                ),
                'test' => array(
                    'public' => 'https://stgapi.ox.fun',
                    'private' => 'https://stgapi.ox.fun',
                ),
                'www' => 'https://ox.fun/',
                'doc' => 'https://docs.ox.fun/',
                'fees' => 'https://support.ox.fun/en/articles/8819866-trading-fees',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'v3/markets' => 1,
                        'v3/assets' => 1,
                        'v3/tickers' => 1,
                        'v3/funding/estimates' => 1,
                        'v3/candles' => 1,
                        'v3/depth' => 1,
                        'v3/markets/operational' => 1,
                        'v3/exchange-trades' => 1,
                        'v3/funding/rates' => 1,
                        'v3/leverage/tiers' => 1,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'v3/account' => 1,
                        'v3/account/names' => 1,
                        'v3/wallet' => 1, // retruns only FUNDING in OX
                        'v3/transfer' => 1,
                        'v3/balances' => 1,
                        'v3/positions' => 1,
                        'v3/funding' => 1,
                        'v3/deposit-addresses' => 1,
                        'v3/deposit' => 1,
                        'v3/withdrawal-addresses' => 1,
                        'v3/withdrawal' => 1,
                        'v3/withdrawal-fees' => 1,
                        'v3/orders/status' => 1,
                        'v3/orders/working' => 1,
                        'v3/trades' => 1,
                    ),
                    'post' => array(
                        'v3/transfer' => 1,
                        'v3/withdrawal' => 1,
                        'v3/orders/place' => 1,
                    ),
                    'delete' => array(
                        'v3/orders/cancel' => 1,
                        'v3/orders/cancel-all' => 1,
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'maker' => $this->parse_number('0.00020'),
                    'taker' => $this->parse_number('0.00070'),
                    'tiers' => array(
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.00020') ),
                            array( $this->parse_number('2500000'), $this->parse_number('0.00010') ),
                            array( $this->parse_number('25000000'), $this->parse_number('0') ),
                        ),
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.00070') ),
                            array( $this->parse_number('2500000'), $this->parse_number('0.00050') ),
                            array( $this->parse_number('25000000'), $this->parse_number('0.00040') ),
                        ),
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            // exchange-specific options
            'options' => array(
                'sandboxMode' => false,
                'networks' => array(
                    'BTC' => 'Bitcoin',
                    'ERC20' => 'Ethereum',
                    'AVAX' => 'Avalanche',
                    'SOL' => 'Solana',
                    'ARB' => 'Arbitrum',
                    'MATIC' => 'Polygon',
                    'FTM' => 'Fantom',
                    'BNB' => 'BNBSmartChain',
                    'OPTIMISM' => 'Optimism',
                ),
                'networksById' => array(
                    'Bitcoin' => 'BTC',
                    'Ethereum' => 'ERC20',
                    'Avalanche' => 'AVAX',
                    'Solana' => 'SOL',
                    'Arbitrum' => 'ARB',
                    'Polygon' => 'MATIC',
                    'Fantom' => 'FTM',
                    'Base' => 'BASE',
                    'BNBSmartChain' => 'BNB',
                    'Optimism' => 'OPTIMISM',
                ),
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerDirection' => false,
                        'triggerPriceType' => null,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => array(
                            'EXPIRE_MAKER' => true,
                            'EXPIRE_TAKER' => true,
                            'EXPIRE_BOTH' => true,
                            'NONE' => true,
                        ),
                        'iceberg' => true, // todo
                    ),
                    'createOrders' => array(
                        'max' => 10, // todo
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => 100000, // todo
                        'untilDays' => 7,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => null, // todo?
                    'fetchOHLCV' => array(
                        'limit' => 500,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'default',
                    ),
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    '-0010' => '\\ccxt\\OperationFailed', // array("event":null,"success":false,"message":"Validation failed","code":"0010","data":null) - failed transfer
                    '-429' => '\\ccxt\\RateLimitExceeded', // Rate limit reached
                    '-05001' => '\\ccxt\\AuthenticationError', // Your operation authority is invalid
                    '-10001' => '\\ccxt\\ExchangeError', // General networking failure
                    '-20000' => '\\ccxt\\BadRequest', // Signature is invalid
                    '-20001' => '\\ccxt\\BadRequest', // "success":false,"code":"20001","message":"marketCode is invalid"
                    '-20002' => '\\ccxt\\BadRequest', // Unexpected error, please check if your request data complies with the specification.
                    '-20003' => '\\ccxt\\NotSupported', // Unrecognized operation
                    '-20005' => '\\ccxt\\AuthenticationError', // Already logged in
                    '-20006' => '\\ccxt\\BadRequest', // Quantity must be greater than zero
                    '-20007' => '\\ccxt\\AuthenticationError', // You are accessing server too rapidly
                    '-20008' => '\\ccxt\\BadRequest', // clientOrderId must be greater than zero if provided
                    '-20009' => '\\ccxt\\BadRequest', // JSON data format is invalid
                    '-20010' => '\\ccxt\\ArgumentsRequired', // Either clientOrderId or orderId is required
                    '-20011' => '\\ccxt\\ArgumentsRequired', // marketCode is required
                    '-20012' => '\\ccxt\\ArgumentsRequired', // side is required
                    '-20013' => '\\ccxt\\ArgumentsRequired', // orderType is required
                    '-20014' => '\\ccxt\\BadRequest', // clientOrderId is not long type
                    '-20015' => '\\ccxt\\BadSymbol', // marketCode is invalid
                    '-20016' => '\\ccxt\\BadRequest', // side is invalid
                    '-20017' => '\\ccxt\\BadRequest', // orderType is invalid
                    '-20018' => '\\ccxt\\BadRequest', // timeInForce is invalid
                    '-20019' => '\\ccxt\\BadRequest', // orderId is invalid
                    '-20020' => '\\ccxt\\BadRequest', // stopPrice or limitPrice is invalid
                    '-20021' => '\\ccxt\\BadRequest', // price is invalid
                    '-20022' => '\\ccxt\\ArgumentsRequired', // price is required for LIMIT order
                    '-20023' => '\\ccxt\\ArgumentsRequired', // timestamp is required
                    '-20024' => '\\ccxt\\ExchangeError', // timestamp exceeds the threshold
                    '-20025' => '\\ccxt\\AuthenticationError', // API key is invalid
                    '-20026' => '\\ccxt\\BadRequest', // Token is invalid or expired
                    '-20027' => '\\ccxt\\BadRequest', // The length of the message exceeds the maximum length
                    '-20028' => '\\ccxt\\BadRequest', // price or stopPrice or limitPrice must be greater than zero
                    '-20029' => '\\ccxt\\BadRequest', // stopPrice must be less than limitPrice for Buy Stop Order
                    '-20030' => '\\ccxt\\BadRequest', // limitPrice must be less than stopPrice for Sell Stop Order
                    '-20031' => '\\ccxt\\MarketClosed', // The marketCode is closed for trading temporarily
                    '-20032' => '\\ccxt\\NetworkError', // Failed to submit due to timeout in server side
                    '-20033' => '\\ccxt\\BadRequest', // triggerType is invalid
                    '-20034' => '\\ccxt\\BadRequest', // The size of tag must be less than 32
                    '-20050' => '\\ccxt\\ExchangeError', // selfTradePreventionMode is invalid
                    '-30001' => '\\ccxt\\BadRequest', // array("success":false,"code":"30001","message":"Required parameter 'marketCode' is missing")
                    '-35034' => '\\ccxt\\AuthenticationError', // array("success":false,"code":"35034","message":"Wallet API is not functioning properly, please try again or contact support.")
                    '-35046' => '\\ccxt\\AuthenticationError', // array("success":false,"code":"35046","message":"Error. Please refresh the page.")
                    '-40001' => '\\ccxt\\ExchangeError', // Alert from the server
                    '-50001' => '\\ccxt\\ExchangeError', // Unknown server error
                    '-300001' => '\\ccxt\\AccountNotEnabled', // Invalid account status xxx, please contact administration if any questions
                    '-300011' => '\\ccxt\\InvalidOrder', // Repo market orders are not allowed during the auction window
                    '-300012' => '\\ccxt\\InvalidOrder', // Repo bids above 0 and offers below 0 are not allowed during the auction window
                    '-100005' => '\\ccxt\\OrderNotFound', // Open order not found
                    '-100006' => '\\ccxt\\InvalidOrder', // Open order is not owned by the user
                    '-100008' => '\\ccxt\\BadRequest', // Quantity cannot be less than the quantity increment xxx
                    '-100015' => '\\ccxt\\NetworkError', // recvWindow xxx has expired
                    '-710001' => '\\ccxt\\ExchangeError', // System failure, exception thrown -> xxx
                    '-710002' => '\\ccxt\\BadRequest', // The price is lower than the minimum
                    '-710003' => '\\ccxt\\BadRequest', // The price is higher than the maximum
                    '-710004' => '\\ccxt\\BadRequest', // Position quantity exceeds the limit
                    '-710005' => '\\ccxt\\InsufficientFunds', // Insufficient margin
                    '-710006' => '\\ccxt\\InsufficientFunds', // Insufficient balance
                    '-710007' => '\\ccxt\\InsufficientFunds', // Insufficient position
                    '-000101' => '\\ccxt\\NetworkError', // Internal server is unavailable temporary, try again later
                    '-000201' => '\\ccxt\\NetworkError', // Trade service is busy, try again later
                ),
                'broad' => array(
                    '-20001' => '\\ccxt\\OperationFailed', // Operation failed, please contact system administrator
                    '-200050' => '\\ccxt\\RequestTimeout', // The market is not active
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves data on all $markets for bitmex
             *
             * @see https://docs.ox.fun/?json#get-v3-$markets
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market data
             */
            list($responseFromMarkets, $responseFromTickers) = Async\await(Promise\all(array( $this->publicGetV3Markets ($params), $this->publicGetV3Tickers ($params) )));
            $marketsFromMarkets = $this->safe_list($responseFromMarkets, 'data', array());
            //
            //         {
            //             success => true,
            //             data => array(
            //                 array(
            //                     marketCode => 'OX-USD-SWAP-LIN',
            //                     name => 'OX/USD Perp',
            //                     referencePair => 'OX/USDT',
            //                     base => 'OX',
            //                     counter => 'USD',
            //                     type => 'FUTURE',
            //                     tickSize => '0.00001',
            //                     minSize => '1',
            //                     listedAt => '1704766320000',
            //                     upperPriceBound => '0.02122',
            //                     lowerPriceBound => '0.01142',
            //                     markPrice => '0.01632',
            //                     indexPrice => '0.01564',
            //                     lastUpdatedAt => '1714762235569'
            //                 ),
            //                 array(
            //                     marketCode => 'BTC-USD-SWAP-LIN',
            //                     name => 'BTC/USD Perp',
            //                     referencePair => 'BTC/USDT',
            //                     base => 'BTC',
            //                     counter => 'USD',
            //                     type => 'FUTURE',
            //                     tickSize => '1',
            //                     minSize => '0.0001',
            //                     listedAt => '1704686640000',
            //                     upperPriceBound => '67983',
            //                     lowerPriceBound => '55621',
            //                     markPrice => '61802',
            //                     indexPrice => '61813',
            //                     lastUpdatedAt => '1714762234765'
            //                 ),
            //                 array(
            //                     "marketCode" => "MILK-OX",
            //                     "name" => "MILK/OX",
            //                     "referencePair" => "MILK/OX",
            //                     "base" => "MILK",
            //                     "counter" => "OX",
            //                     "type" => "SPOT",
            //                     "tickSize" => "0.0001",
            //                     "minSize" => "1",
            //                     "listedAt" => "1706608500000",
            //                     "upperPriceBound" => "1.0000",
            //                     "lowerPriceBound" => "-1.0000",
            //                     "markPrice" => "0.0269",
            //                     "indexPrice" => "0.0269",
            //                     "lastUpdatedAt" => "1714757402185"
            //                 ),
            //                 ...
            //             )
            //         }
            //
            $marketsFromTickers = $this->safe_list($responseFromTickers, 'data', array());
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "marketCode" => "DYM-USD-SWAP-LIN",
            //                 "markPrice" => "3.321",
            //                 "open24h" => "3.315",
            //                 "high24h" => "3.356",
            //                 "low24h" => "3.255",
            //                 "volume24h" => "0",
            //                 "currencyVolume24h" => "0",
            //                 "openInterest" => "1768.1",
            //                 "lastTradedPrice" => "3.543",
            //                 "lastTradedQuantity" => "1.0",
            //                 "lastUpdatedAt" => "1714853388102"
            //             ),
            //             ...
            //         )
            //     }
            //
            $markets = $this->array_concat($marketsFromMarkets, $marketsFromTickers);
            return $this->parse_markets($markets);
        }) ();
    }

    public function parse_markets($markets): array {
        $marketIds = array();
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $marketId = $this->safe_string($market, 'marketCode');
            if (!($this->in_array($marketId, $marketIds))) {
                $marketIds[] = $marketId;
                $result[] = $this->parse_market($market);
            }
        }
        return $result;
    }

    public function parse_market($market): array {
        $id = $this->safe_string($market, 'marketCode', '');
        $parts = explode('-', $id);
        $baseId = $this->safe_string($parts, 0);
        $quoteId = $this->safe_string($parts, 1);
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $type = $this->safe_string_lower($market, 'type', 'spot'); // markets from v3/tickers are spot and have no $type
        $settleId = null;
        $settle = null;
        $isFuture = ($type === 'future'); // the exchange has only perpetual futures
        if ($isFuture) {
            $type = 'swap';
            $settleId = 'OX';
            $settle = $this->safe_currency_code('OX');
            $symbol = $symbol . ':' . $settle;
        }
        $isSpot = $type === 'spot';
        return $this->safe_market_structure(array(
            'id' => $id,
            'numericId' => null,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $type,
            'spot' => $isSpot,
            'margin' => false,
            'swap' => $isFuture,
            'future' => false,
            'option' => false,
            'active' => true,
            'contract' => $isFuture,
            'linear' => $isFuture ? true : null,
            'inverse' => $isFuture ? false : null,
            'taker' => $this->fees['trading']['taker'],
            'maker' => $this->fees['trading']['maker'],
            'contractSize' => $isFuture ? 1 : null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => null, // todo find it out
                'price' => $this->safe_number($market, 'tickSize'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'minSize'),
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'created' => $this->safe_integer($market, 'listedAt'),
            'index' => null,
            'info' => $market,
        ));
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://docs.ox.fun/?json#get-v3-assets
             *
             * @param {dict} [$params] extra parameters specific to the exchange API endpoint
             * @return {dict} an associative dictionary of currencies
             */
            $response = Async\await($this->publicGetV3Assets ($params));
            //
            //     {
            //         "success" => true,
            //         "data" =>  array(
            //             array(
            //                 "asset" => "OX",
            //                 "isCollateral" => true,
            //                 "loanToValue" => "1.000000000",
            //                 "loanToValueFactor" => "0.000000000",
            //                 "networkList" =>  array(
            //                     array(
            //                         "network" => "BNBSmartChain",
            //                         "tokenId" => "0x78a0A62Fba6Fb21A83FE8a3433d44C73a4017A6f",
            //                         "transactionPrecision" => "18",
            //                         "isWithdrawalFeeChargedToUser" => true,
            //                         "canDeposit" => true,
            //                         "canWithdraw" => false,
            //                         "minDeposit" => "0.00010",
            //                         "minWithdrawal" => "0.00010"
            //                     ),
            //                     array(
            //                         "network" => "Polygon",
            //                         "tokenId" => "0x78a0A62Fba6Fb21A83FE8a3433d44C73a4017A6f",
            //                         "transactionPrecision" => "18",
            //                         "isWithdrawalFeeChargedToUser" => true,
            //                         "canDeposit" => true,
            //                         "canWithdraw" => false,
            //                         "minDeposit" => "0.00010",
            //                         "minWithdrawal" => "0.00010"
            //                     ),
            //                     ...
            //                 )
            //             ),
            //             {
            //                 "asset" => "BTC",
            //                 "isCollateral" => true,
            //                 "loanToValue" => "0.950000000",
            //                 "loanToValueFactor" => "0.000000000",
            //                 "networkList" =>  array(
            //                     array(
            //                         "network" => "Bitcoin",
            //                         "transactionPrecision" => "8",
            //                         "isWithdrawalFeeChargedToUser" => true,
            //                         "canDeposit" => true,
            //                         "canWithdraw" => true,
            //                         "minDeposit" => "0.00010",
            //                         "minWithdrawal" => "0.00010"
            //                     }
            //                 )
            //             ),
            //             {
            //                 "asset" => "USDT.ARB",
            //                 "isCollateral" => true,
            //                 "loanToValue" => "0.950000000",
            //                 "loanToValueFactor" => "0.000000000",
            //                 "networkList" => array(
            //                     array(
            //                         "network" => "Arbitrum",
            //                         "tokenId" => "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
            //                         "transactionPrecision" => "18",
            //                         "isWithdrawalFeeChargedToUser" => true,
            //                         "canDeposit" => true,
            //                         "canWithdraw" => true,
            //                         "minDeposit" => "0.00010",
            //                         "minWithdrawal" => "0.00010"
            //                     }
            //                 )
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $currency = $data[$i];
                $fullId = $this->safe_string($currency, 'asset', '');
                $parts = explode('.', $fullId);
                $id = $parts[0];
                $code = $this->safe_currency_code($id);
                if (!(is_array($result) && array_key_exists($code, $result))) {
                    $result[$code] = array(
                        'id' => $id,
                        'code' => $code,
                        'precision' => null,
                        'type' => null,
                        'name' => null,
                        'active' => null,
                        'deposit' => null,
                        'withdraw' => null,
                        'fee' => null,
                        'limits' => array(
                            'withdraw' => array(
                                'min' => null,
                                'max' => null,
                            ),
                            'deposit' => array(
                                'min' => null,
                                'max' => null,
                            ),
                        ),
                        'networks' => array(),
                        'info' => array(),
                    );
                }
                $chains = $this->safe_list($currency, 'networkList', array());
                for ($j = 0; $j < count($chains); $j++) {
                    $chain = $chains[$j];
                    $networkId = $this->safe_string($chain, 'network');
                    $networkCode = $this->network_id_to_code($networkId);
                    $result[$code]['networks'][$networkCode] = array(
                        'id' => $networkId,
                        'network' => $networkCode,
                        'margin' => null,
                        'deposit' => $this->safe_bool($chain, 'canDeposit'),
                        'withdraw' => $this->safe_bool($chain, 'canWithdraw'),
                        'active' => null,
                        'fee' => null,
                        'precision' => $this->parse_number($this->parse_precision($this->safe_string($chain, 'transactionPrecision'))),
                        'limits' => array(
                            'deposit' => array(
                                'min' => $this->safe_number($chain, 'minDeposit'),
                                'max' => null,
                            ),
                            'withdraw' => array(
                                'min' => $this->safe_number($chain, 'minWithdrawal'),
                                'max' => null,
                            ),
                        ),
                        'info' => $chain,
                    );
                }
                $infos = $this->safe_list($result[$code], 'info', array());
                $infos[] = $currency;
                $result[$code]['info'] = $infos;
            }
            // only after all entries are formed in currencies, restructure each entry
            $allKeys = is_array($result) ? array_keys($result) : array();
            for ($i = 0; $i < count($allKeys); $i++) {
                $code = $allKeys[$i];
                $result[$code] = $this->safe_currency_structure($result[$code]); // this is needed after adding network entry
            }
            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://docs.ox.fun/?json#get-v3-$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());
            $symbols = $this->market_symbols($symbols);
            $response = Async\await($this->publicGetV3Tickers ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "marketCode" => "NII-USDT",
            //                 "markPrice" => "0",
            //                 "open24h" => "0",
            //                 "high24h" => "0",
            //                 "low24h" => "0",
            //                 "volume24h" => "0",
            //                 "currencyVolume24h" => "0",
            //                 "openInterest" => "0",
            //                 "lastTradedPrice" => "0",
            //                 "lastTradedQuantity" => "0",
            //                 "lastUpdatedAt" => "1714853388621"
            //             ),
            //             array(
            //                 "marketCode" => "GEC-USDT",
            //                 "markPrice" => "0",
            //                 "open24h" => "0",
            //                 "high24h" => "0",
            //                 "low24h" => "0",
            //                 "volume24h" => "0",
            //                 "currencyVolume24h" => "0",
            //                 "openInterest" => "0",
            //                 "lastTradedPrice" => "0",
            //                 "lastTradedQuantity" => "0",
            //                 "lastUpdatedAt" => "1714853388621"
            //             ),
            //             array(
            //                 "marketCode" => "DYM-USD-SWAP-LIN",
            //                 "markPrice" => "3.321",
            //                 "open24h" => "3.315",
            //                 "high24h" => "3.356",
            //                 "low24h" => "3.255",
            //                 "volume24h" => "0",
            //                 "currencyVolume24h" => "0",
            //                 "openInterest" => "1768.1",
            //                 "lastTradedPrice" => "3.543",
            //                 "lastTradedQuantity" => "1.0",
            //                 "lastUpdatedAt" => "1714853388102"
            //             ),
            //             ...
            //         )
            //     }
            //
            $tickers = $this->safe_list($response, 'data', array());
            return $this->parse_tickers($tickers, $symbols);
        }) ();
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches a price $ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             *
             * @see https://docs.ox.fun/?json#get-v3-tickers
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the $ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'marketCode' => $market['id'],
            );
            $response = Async\await($this->publicGetV3Tickers ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "marketCode" => "BTC-USD-SWAP-LIN",
            //                 "markPrice" => "64276",
            //                 "open24h" => "63674",
            //                 "high24h" => "64607",
            //                 "low24h" => "62933",
            //                 "volume24h" => "306317655.80000",
            //                 "currencyVolume24h" => "48.06810",
            //                 "openInterest" => "72.39250",
            //                 "lastTradedPrice" => "64300.0",
            //                 "lastTradedQuantity" => "1.0",
            //                 "lastUpdatedAt" => "1714925196034"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $ticker = $this->safe_dict($data, 0, array());
            return $this->parse_ticker($ticker, $market);
        }) ();
    }

    public function parse_ticker($ticker, ?array $market = null): array {
        //
        //     {
        //         "marketCode" => "BTC-USD-SWAP-LIN",
        //         "markPrice" => "64276",
        //         "open24h" => "63674",
        //         "high24h" => "64607",
        //         "low24h" => "62933",
        //         "volume24h" => "306317655.80000",
        //         "currencyVolume24h" => "48.06810",
        //         "openInterest" => "72.39250",
        //         "lastTradedPrice" => "64300.0",
        //         "lastTradedQuantity" => "1.0",
        //         "lastUpdatedAt" => "1714925196034"
        //     }
        //
        $timestamp = $this->safe_integer($ticker, 'lastUpdatedAt');
        $marketId = $this->safe_string($ticker, 'marketCode');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $last = $this->safe_string($ticker, 'lastTradedPrice');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high24h'),
            'low' => $this->safe_string($ticker, 'low24h'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => $this->safe_string($ticker, 'open24h'),
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'currencyVolume24h'),
            'quoteVolume' => null, // the exchange returns cost in OX
            'markPrice' => $this->safe_string($ticker, 'markPrice'),
            'info' => $ticker,
        ), $market);
    }

    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://docs.ox.fun/?json#get-v3-candles
             *
             * @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 (default 24 hours ago)
             * @param {int} [$limit] the maximum amount of candles to fetch (default 200, max 500)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest candle to fetch (default now)
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $timeframe = $this->safe_string($this->timeframes, $timeframe, $timeframe);
            $request = array(
                'marketCode' => $market['id'],
                'timeframe' => $timeframe,
            );
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            } elseif ($since !== null) {
                $request['endTime'] = $this->sum($since, 7 * 24 * 60 * 60 * 1000); // for the exchange not to throw an exception if $since is younger than 7 days
            }
            $response = Async\await($this->publicGetV3Candles ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "timeframe" => "3600s",
            //         "data" => array(
            //             array(
            //                 "open" => "0.03240000",
            //                 "high" => "0.03240000",
            //                 "low" => "0.03240000",
            //                 "close" => "0.03240000",
            //                 "volume" => "0",
            //                 "currencyVolume" => "0",
            //                 "openedAt" => "1714906800000"
            //             ),
            //             array(
            //                 "open" => "0.03240000",
            //                 "high" => "0.03240000",
            //                 "low" => "0.03240000",
            //                 "close" => "0.03240000",
            //                 "volume" => "0",
            //                 "currencyVolume" => "0",
            //                 "openedAt" => "1714903200000"
            //             ),
            //             ...
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
            return $this->parse_ohlcvs($result, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "open" => "0.03240000",
        //         "high" => "0.03240000",
        //         "low" => "0.03240000",
        //         "close" => "0.03240000",
        //         "volume" => "0",
        //         "currencyVolume" => "0",
        //         "openedAt" => "1714906800000"
        //     }
        //
        return array(
            $this->safe_integer($ohlcv, 'openedAt'),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $this->safe_number($ohlcv, 'currencyVolume'),
        );
    }

    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://docs.ox.fun/?json#get-v3-depth
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int} [$limit] the maximum amount of order book entries to return (default 5, max 100)
             * @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(
                'marketCode' => $market['id'],
            );
            if ($limit !== null) {
                $request['level'] = $limit;
            }
            $response = Async\await($this->publicGetV3Depth ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "level" => "5",
            //         "data" => {
            //             "marketCode" => "BTC-USD-SWAP-LIN",
            //             "lastUpdatedAt" => "1714933499266",
            //             "asks" => array(
            //                 array( 64073.0, 8.4622 ),
            //                 array( 64092.0, 8.1912 ),
            //                 array( 64111.0, 8.0669 ),
            //                 array( 64130.0, 11.7195 ),
            //                 array( 64151.0, 10.1798 )
            //             ),
            //             "bids" => array(
            //                 array( 64022.0, 10.1292 ),
            //                 array( 64003.0, 8.1619 ),
            //                 array( 64000.0, 1.0 ),
            //                 array( 63984.0, 12.7724 ),
            //                 array( 63963.0, 11.0073 )
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $timestamp = $this->safe_integer($data, 'lastUpdatedAt');
            return $this->parse_order_book($data, $market['symbol'], $timestamp);
        }) ();
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the current funding rates for multiple markets
             *
             * @see https://docs.ox.fun/?json#get-v3-funding-estimates
             *
             * @param {string[]} $symbols unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Order[]} an array of ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $response = Async\await($this->publicGetV3FundingEstimates ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "marketCode" => "OX-USD-SWAP-LIN",
            //                 "fundingAt" => "1715515200000",
            //                 "estFundingRate" => "0.000200000"
            //             ),
            //             array(
            //                 "marketCode" => "BTC-USD-SWAP-LIN",
            //                 "fundingAt" => "1715515200000",
            //                 "estFundingRate" => "0.000003"
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_funding_rates($data, $symbols);
        }) ();
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the current funding rates for a $symbol
             *
             * @see https://docs.ox.fun/?json#get-v3-funding-estimates
             *
             * @param {string} $symbol unified market symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Order[]} an array of ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structures~
             */
            Async\await($this->load_markets());
            $request = array(
                'marketCode' => $this->market_id($symbol),
            );
            $response = Async\await($this->publicGetV3FundingEstimates ($this->extend($request, $params)));
            //
            $data = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            return $this->parse_funding_rate($first, $this->market($symbol));
        }) ();
    }

    public function parse_funding_rate($fundingRate, ?array $market = null): array {
        //
        //     {
        //         "marketCode" => "OX-USD-SWAP-LIN",
        //         "fundingAt" => "1715515200000",
        //         "estFundingRate" => "0.000200000"
        //     }
        //
        $symbol = $this->safe_string($fundingRate, 'marketCode');
        $market = $this->market($symbol);
        $estFundingRateTimestamp = $this->safe_integer($fundingRate, 'fundingAt');
        return array(
            'info' => $fundingRate,
            'symbol' => $market['symbol'],
            'markPrice' => null,
            'indexPrice' => null,
            'interestRate' => $this->parse_number('0'),
            'estimatedSettlePrice' => null,
            'timestamp' => $estFundingRateTimestamp,
            'datetime' => $this->iso8601($estFundingRateTimestamp),
            'fundingRate' => $this->safe_number($fundingRate, 'estFundingRate'),
            'fundingTimestamp' => null,
            'fundingDatetime' => null,
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => null,
        );
    }

    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 the history of funding rates
             *
             * @see https://docs.ox.fun/?json#get-v3-funding-rates
             *
             * @param {string} $symbol unified $symbol of the $market to fetch trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch (default 24 hours ago)
             * @param {int} [$limit] the maximum amount of trades to fetch (default 200, max 500)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest trade to fetch (default now)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'marketCode' => $market['id'],
            );
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            }
            $response = Async\await($this->publicGetV3FundingRates ($this->extend($request, $params)));
            //
            //     {
            //         success => true,
            //         $data => [
            //         array(
            //             marketCode => 'NEAR-USD-SWAP-LIN',
            //             fundingRate => '-0.000010000',
            //             createdAt => '1715428870755'
            //         ),
            //         array(
            //             marketCode => 'ENA-USD-SWAP-LIN',
            //             fundingRate => '0.000150000',
            //             createdAt => '1715428868616'
            //         ),
            //         ...
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_funding_rate_histories($data, $market, $since, $limit);
        }) ();
    }

    public function parse_funding_rate_history($info, ?array $market = null) {
        //
        //     {
        //         success => true,
        //         data => [
        //         array(
        //             marketCode => 'NEAR-USD-SWAP-LIN',
        //             fundingRate => '-0.000010000',
        //             createdAt => '1715428870755'
        //         ),
        //         array(
        //             marketCode => 'ENA-USD-SWAP-LIN',
        //             fundingRate => '0.000150000',
        //             createdAt => '1715428868616'
        //         ),
        //         ...
        //     }
        //
        $marketId = $this->safe_string($info, 'marketCode');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($info, 'createdAt');
        return array(
            'info' => $info,
            'symbol' => $symbol,
            'fundingRate' => $this->safe_number($info, 'fundingRate'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches the history of funding payments
             *
             * @see https://docs.ox.fun/?json#get-v3-funding
             *
             * @param {string} $symbol unified $symbol of the $market to fetch trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch (default 24 hours ago)
             * @param {int} [$limit] the maximum amount of trades to fetch (default 200, max 500)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest trade to fetch (default now)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'marketCode' => $market['id'],
            );
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            }
            $response = Async\await($this->privateGetV3Funding ($this->extend($request, $params)));
            //
            //     {
            //         success => true,
            //         data => array(
            //             array(
            //                 id => '966709913041305605',
            //                 marketCode => 'ETH-USD-SWAP-LIN',
            //                 payment => '-0.00430822',
            //                 fundingRate => '0.000014',
            //                 position => '0.001',
            //                 indexPrice => '3077.3',
            //                 createdAt => '1715086852890'
            //             ),
            //             array(
            //                 id => '966698111997509637',
            //                 marketCode => 'ETH-USD-SWAP-LIN',
            //                 payment => '-0.0067419',
            //                 fundingRate => '0.000022',
            //                 position => '0.001',
            //                 indexPrice => '3064.5',
            //                 createdAt => '1715083251516'
            //             ),
            //             ...
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
            return $this->parse_incomes($result, $market, $since, $limit);
        }) ();
    }

    public function parse_income($income, ?array $market = null) {
        //
        //     array(
        //         $id => '966709913041305605',
        //         marketCode => 'ETH-USD-SWAP-LIN',
        //         payment => '-0.00430822',
        //         fundingRate => '0.000014',
        //         position => '0.001',
        //         indexPrice => '3077.3',
        //         createdAt => '1715086852890'
        //     ),
        //
        $marketId = $this->safe_string($income, 'marketCode');
        $symbol = $this->safe_symbol($marketId, $market);
        $amount = $this->safe_number($income, 'payment');
        $code = $this->safe_currency_code('OX');
        $id = $this->safe_string($income, 'id');
        $timestamp = $this->safe_timestamp($income, 'createdAt');
        $rate = $this->safe_number($income, 'fundingRate');
        return array(
            'info' => $income,
            'symbol' => $symbol,
            'code' => $code,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $id,
            'amount' => $amount,
            'rate' => $rate,
        );
    }

    public function fetch_leverage_tiers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes, if a market has a leverage tier of 0, then the leverage tiers cannot be obtained for this market
             *
             * @see https://docs.ox.fun/?json#get-v3-leverage-tiers
             *
             * @param {string[]} [$symbols] list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=leverage-tiers-structure leverage tiers structures~, indexed by market $symbols
             */
            Async\await($this->load_markets());
            $response = Async\await($this->publicGetV3LeverageTiers ($params));
            //
            //     {
            //         success => true,
            //         $data => array(
            //            array(
            //                 marketCode => 'SOL-USD-SWAP-LIN',
            //                 tiers => array(
            //                     array(
            //                         tier => '1',
            //                         leverage => '10',
            //                         positionFloor => '0',
            //                         positionCap => '200000000',
            //                         initialMargin => '0.1',
            //                         maintenanceMargin => '0.05',
            //                         maintenanceAmount => '0'
            //                     ),
            //                     array(
            //                         tier => '2',
            //                         leverage => '5',
            //                         positionFloor => '200000000',
            //                         positionCap => '280000000',
            //                         initialMargin => '0.2',
            //                         maintenanceMargin => '0.1',
            //                         maintenanceAmount => '7000000'
            //                     ),
            //                     array(
            //                         tier => '3',
            //                         leverage => '4',
            //                         positionFloor => '280000000',
            //                         positionCap => '460000000',
            //                         initialMargin => '0.25',
            //                         maintenanceMargin => '0.125',
            //                         maintenanceAmount => '14000000'
            //                     ),
            //                     ...
            //                 )
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_leverage_tiers($data, $symbols, 'marketCode');
        }) ();
    }

    public function parse_market_leverage_tiers($info, ?array $market = null): array {
        //
        //     {
        //         marketCode => 'SOL-USD-SWAP-LIN',
        //         $tiers => array(
        //             array(
        //                 $tier => '1',
        //                 leverage => '10',
        //                 positionFloor => '0',
        //                 positionCap => '200000000',
        //                 initialMargin => '0.1',
        //                 maintenanceMargin => '0.05',
        //                 maintenanceAmount => '0'
        //             ...
        //         )
        //     ),
        //
        $marketId = $this->safe_string($info, 'marketCode');
        $market = $this->safe_market($marketId, $market);
        $listOfTiers = $this->safe_list($info, 'tiers', array());
        $tiers = array();
        for ($j = 0; $j < count($listOfTiers); $j++) {
            $tier = $listOfTiers[$j];
            $tiers[] = array(
                'tier' => $this->safe_number($tier, 'tier'),
                'symbol' => $this->safe_symbol($marketId, $market),
                'currency' => $market['settle'],
                'minNotional' => $this->safe_number($tier, 'positionFloor'),
                'maxNotional' => $this->safe_number($tier, 'positionCap'),
                'maintenanceMarginRate' => $this->safe_number($tier, 'maintenanceMargin'),
                'maxLeverage' => $this->safe_number($tier, 'leverage'),
                'info' => $tier,
            );
        }
        return $tiers;
    }

    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 the list of most recent trades for a particular $symbol
             *
             * @see https://docs.ox.fun/?json#get-v3-exchange-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 (default 24 hours ago)
             * @param {int} [$limit] the maximum amount of trades to fetch (default 200, max 500)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest trade to fetch (default now)
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'marketCode' => $market['id'],
            );
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            } elseif ($since !== null) {
                $request['endTime'] = $this->sum($since, 7 * 24 * 60 * 60 * 1000); // for the exchange not to throw an exception if $since is younger than 7 days
            }
            $response = Async\await($this->publicGetV3ExchangeTrades ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "marketCode" => "BTC-USD-SWAP-LIN",
            //                 "matchPrice" => "63900",
            //                 "matchQuantity" => "1",
            //                 "side" => "SELL",
            //                 "matchType" => "TAKER",
            //                 "matchedAt" => "1714934112352"
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $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://docs.ox.fun/?json#get-v3-trades
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch trades for
             * @param {int} [$limit] the maximum amount of trades to fetch (default 200, max 500)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest trade to fetch (default now)
             * @return {Trade[]} a list of {@link https://github.com/ccxt/ccxt/wiki/Manual#trade-structure trade structures}
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['marketCode'] = $market['id'];
            }
            if ($since !== null) { // startTime and endTime must be within 7 days of each other
                $request['startTime'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            } elseif ($since !== null) {
                $request['endTime'] = $this->sum($since, 7 * 24 * 60 * 60 * 1000); // for the exchange not to throw an exception if $since is younger than 7 days
            }
            $response = Async\await($this->privateGetV3Trades ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "orderId" => "1000104903698",
            //                 "clientOrderId" => "1715000260094",
            //                 "matchId" => "400017129522773178",
            //                 "marketCode" => "ETH-USD-SWAP-LIN",
            //                 "side" => "BUY",
            //                 "matchedQuantity" => "0.001",
            //                 "matchPrice" => "3100.2",
            //                 "total" => "310.02",
            //                 "orderMatchType" => "MAKER",
            //                 "feeAsset" => "OX",
            //                 "fee" => "0.062004",
            //                 "source" => "0",
            //                 "matchedAt" => "1715000267420"
            //             }
            //         )
            //     }
            //
            $result = $this->safe_list($response, 'data', array());
            return $this->parse_trades($result, $market, $since, $limit);
        }) ();
    }

    public function parse_trade($trade, ?array $market = null): array {
        //
        // public fetchTrades
        //
        //     {
        //         "marketCode" => "BTC-USD-SWAP-LIN",
        //         "matchPrice" => "63900",
        //         "matchQuantity" => "1",
        //         "side" => "SELL",
        //         "matchType" => "TAKER",
        //         "matchedAt" => "1714934112352"
        //     }
        //
        //
        // private fetchMyTrades
        //
        //     {
        //         "orderId" => "1000104903698",
        //         "clientOrderId" => "1715000260094",
        //         "matchId" => "400017129522773178",
        //         "marketCode" => "ETH-USD-SWAP-LIN",
        //         "side" => "BUY",
        //         "matchedQuantity" => "0.001",
        //         "matchPrice" => "3100.2",
        //         "total" => "310.02",
        //         "orderMatchType" => "MAKER",
        //         "feeAsset" => "OX",
        //         "fee" => "0.062004",
        //         "source" => "0",
        //         "matchedAt" => "1715000267420"
        //     }
        //
        $marketId = $this->safe_string($trade, 'marketCode');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($trade, 'matchedAt');
        $fee = array(
            'cost' => $this->safe_string($trade, 'fee'),
            'currency' => $this->safe_currency_code($this->safe_string($trade, 'feeAsset')),
        );
        return $this->safe_trade(array(
            'id' => $this->safe_string($trade, 'matchId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => null,
            'order' => $this->safe_string($trade, 'orderId'),
            'side' => $this->safe_string_lower($trade, 'side'),
            'takerOrMaker' => $this->safe_string_lower_2($trade, 'matchType', 'orderMatchType'),
            'price' => $this->safe_string($trade, 'matchPrice'),
            'amount' => $this->safe_string_2($trade, 'matchQuantity', 'matchedQuantity'),
            'cost' => null, // the exchange returns total cost in OX
            'fee' => $fee,
            'info' => $trade,
        ), $market);
    }

    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://docs.ox.fun/?json#get-v3-balances
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->asset] currency id, if empty the exchange returns info about all currencies
             * @param {string} [$params->subAcc] Name of sub account. If no $subAcc is given, then the $response contains only the account linked to the API-Key.
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$balance-structure $balance structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetV3Balances ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "accountId" => "106490",
            //                 "name" => "main",
            //                 "balances" => array(
            //                     array(
            //                         "asset" => "OX",
            //                         "total" => "-7.55145065000",
            //                         "available" => "-71.16445065000",
            //                         "reserved" => "0",
            //                         "lastUpdatedAt" => "1715000448946"
            //                     ),
            //                     array(
            //                         "asset" => "ETH",
            //                         "total" => "0.01",
            //                         "available" => "0.01",
            //                         "reserved" => "0",
            //                         "lastUpdatedAt" => "1714914512750"
            //                     ),
            //                     ...
            //                 )
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $balance = $data[0];
            $subAcc = $this->safe_string($params, 'subAcc');
            if ($subAcc !== null) {
                for ($i = 0; $i < count($data); $i++) {
                    $b = $data[$i];
                    $name = $this->safe_string($b, 'name');
                    if ($name === $subAcc) {
                        $balance = $b;
                        break;
                    }
                }
            }
            return $this->parse_balance($balance);
        }) ();
    }

    public function parse_balance($balance): array {
        //
        //     {
        //         "accountId" => "106490",
        //         "name" => "main",
        //         "balances" => array(
        //             array(
        //                 "asset" => "OX",
        //                 "total" => "-7.55145065000",
        //                 "available" => "-71.16445065000",
        //                 "reserved" => "0",
        //                 "lastUpdatedAt" => "1715000448946"
        //             ),
        //             array(
        //                 "asset" => "ETH",
        //                 "total" => "0.01",
        //                 "available" => "0.01",
        //                 "reserved" => "0",
        //                 "lastUpdatedAt" => "1714914512750"
        //             ),
        //             ...
        //         )
        //     }
        //
        $result = array(
            'info' => $balance,
        );
        $balances = $this->safe_list($balance, 'balances', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balanceEntry = $balances[$i];
            $currencyId = $this->safe_string($balanceEntry, 'asset');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['total'] = $this->safe_string($balanceEntry, 'total');
            $account['free'] = $this->safe_string($balanceEntry, 'available');
            $account['used'] = $this->safe_string($balanceEntry, 'reserved');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_accounts($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch subaccounts associated with a profile
             *
             * @see https://docs.ox.fun/?json#get-v3-account-names
             *
             * @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());
            // this endpoint can only be called using API keys paired with the parent account! Returns all active subaccounts.
            $response = Async\await($this->privateGetV3AccountNames ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "accountId" => "106526",
            //                 "name" => "testSubAccount"
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_accounts($data, $params);
        }) ();
    }

    public function parse_account($account) {
        //
        //     array(
        //         "accountId" => "106526",
        //         "name" => "testSubAccount"
        //     ),
        //
        return array(
            'id' => $this->safe_string($account, 'accountId'),
            'type' => null,
            'code' => null,
            'info' => $account,
        );
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $fromAccount, $toAccount, $params) {
            /**
             * transfer $currency internally between wallets on the same account
             *
             * @see https://docs.ox.fun/?json#post-v3-transfer
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount amount to transfer
             * @param {string} $fromAccount account id to transfer from
             * @param {string} $toAccount account id to transfer to
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
             */
            // transferring funds between sub-accounts is restricted to API keys linked to the parent account.
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'asset' => $currency['id'],
                'quantity' => $this->currency_to_precision($code, $amount),
                'fromAccount' => $fromAccount,
                'toAccount' => $toAccount,
            );
            $response = Async\await($this->privatePostV3Transfer ($this->extend($request, $params)));
            //
            //     {
            //         timestamp => 1715430036267,
            //         datetime => '2024-05-11T12:20:36.267Z',
            //         $currency => 'OX',
            //         $amount => 10,
            //         $fromAccount => '106464',
            //         $toAccount => '106570',
            //         info => {
            //         asset => 'OX',
            //         quantity => '10',
            //         $fromAccount => '106464',
            //         $toAccount => '106570',
            //         transferredAt => '1715430036267'
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_transfer($data, $currency);
        }) ();
    }

    public function fetch_transfers(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch a history of internal transfers made on an account
             *
             * @see https://docs.ox.fun/?json#get-v3-transfer
             *
             * @param {string} $code unified $currency $code of the $currency transferred
             * @param {int} [$since] the earliest time in ms to fetch transfers for (default 24 hours ago)
             * @param {int} [$limit] the maximum number of transfer structures to retrieve (default 50, max 200)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch transfers for (default time now)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
             */
            // API keys linked to the parent account can get all account transfers, while API keys linked to a sub-account can only see transfers where the sub-account is either the "fromAccount" or "toAccount"
            Async\await($this->load_markets());
            $request = array();
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['asset'] = $currency['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            } elseif ($since !== null) {
                $request['endTime'] = $this->sum($since, 7 * 24 * 60 * 60 * 1000); // for the exchange not to throw an exception if $since is younger than 7 days
            }
            $response = Async\await($this->privateGetV3Transfer ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "asset" => "USDT",
            //                 "quantity" => "5",
            //                 "fromAccount" => "106490",
            //                 "toAccount" => "106526",
            //                 "id" => "966706320886267905",
            //                 "status" => "COMPLETED",
            //                 "transferredAt" => "1715085756708"
            //             ),
            //             ...
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_transfers($data, $currency, $since, $limit);
        }) ();
    }

    public function parse_transfer($transfer, ?array $currency = null) {
        //
        // fetchTransfers
        //
        //     {
        //         "asset" => "USDT",
        //         "quantity" => "5",
        //         "fromAccount" => "106490",
        //         "toAccount" => "106526",
        //         "id" => "966706320886267905",
        //         "status" => "COMPLETED",
        //         "transferredAt" => "1715085756708"
        //     }
        //
        $timestamp = $this->safe_integer($transfer, 'transferredAt');
        $currencyId = $this->safe_string($transfer, 'asset');
        return array(
            'id' => $this->safe_string($transfer, 'id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'amount' => $this->safe_number($transfer, 'quantity'),
            'fromAccount' => $this->safe_string($transfer, 'fromAccount'),
            'toAccount' => $this->safe_string($transfer, 'toAccount'),
            'status' => $this->parse_transfer_status($this->safe_string($transfer, 'status')),
            'info' => $transfer,
        );
    }

    public function parse_transfer_status($status) {
        $statuses = array(
            'COMPLETED' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    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://docs.ox.fun/?json#get-v3-deposit-addresses
             *
             * @param {string} $code unified $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->network] network for fetch deposit address
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
             */
            $networkCode = $this->safe_string($params, 'network');
            $networkId = $this->network_code_to_id($networkCode, $code);
            if ($networkId === null) {
                throw new BadRequest($this->id . ' fetchDepositAddress() require network parameter');
            }
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'asset' => $currency['id'],
                'network' => $networkId,
            );
            $params = $this->omit($params, 'network');
            $response = Async\await($this->privateGetV3DepositAddresses ($this->extend($request, $params)));
            //
            //     array("success":true,"data":array("address":"0x998dEc76151FB723963Bd8AFD517687b38D33dE8"))
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_deposit_address($data, $currency);
        }) ();
    }

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

    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://docs.ox.fun/?json#get-v3-deposit
             *
             * @param {string} $code unified $currency $code of the $currency transferred
             * @param {int} [$since] the earliest time in ms to fetch transfers for (default 24 hours ago)
             * @param {int} [$limit] the maximum number of transfer structures to retrieve (default 50, max 200)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch transfers for (default time now)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['asset'] = $currency['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            }
            $response = Async\await($this->privateGetV3Deposit ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "asset":"USDC",
            //                 "network":"Ethereum",
            //                 "address" => "0x998dEc76151FB723963Bd8AFD517687b38D33dE8",
            //                 "quantity":"50",
            //                 "id":"5914",
            //                 "status" => "COMPLETED",
            //                 "txId":"0xf5e79663830a0c6f94d46638dcfbc134566c12facf1832396f81ecb55d3c75dc",
            //                 "creditedAt":"1714821645154"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            for ($i = 0; $i < count($data); $i++) {
                $data[$i]['type'] = 'deposit';
            }
            return $this->parse_transactions($data, $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://docs.ox.fun/?json#get-v3-withdrawal
             *
             * @param {string} $code unified $currency $code of the $currency transferred
             * @param {int} [$since] the earliest time in ms to fetch transfers for (default 24 hours ago)
             * @param {int} [$limit] the maximum number of transfer structures to retrieve (default 50, max 200)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch transfers for (default time now)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['asset'] = $currency['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since; // startTime and endTime must be within 7 days of each other
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['endTime'] = $until;
                $params = $this->omit($params, 'until');
            }
            $response = Async\await($this->privateGetV3Withdrawal ($this->extend($request, $params)));
            //
            //     {
            //         success => true,
            //         $data => array(
            //             {
            //                 id => '968163212989431811',
            //                 asset => 'OX',
            //                 network => 'Arbitrum',
            //                 address => '0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9',
            //                 quantity => '11.7444',
            //                 fee => '1.744400000',
            //                 status => 'COMPLETED',
            //                 txId => '0xe96b2d128b737fdbca927edf355cff42202e65b0fb960e64ffb9bd68c121f69f',
            //                 requestedAt => '1715530365450',
            //                 completedAt => '1715530527000'
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            for ($i = 0; $i < count($data); $i++) {
                $data[$i]['type'] = 'withdrawal';
            }
            return $this->parse_transactions($data, $currency, $since, $limit);
        }) ();
    }

    public function parse_transactions($transactions, ?array $currency = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        $result = array();
        for ($i = 0; $i < count($transactions); $i++) {
            $transactions[$i] = $this->extend($transactions[$i], $params);
            $transaction = $this->parse_transaction($transactions[$i], $currency);
            $result[] = $transaction;
        }
        $result = $this->sort_by($result, 'timestamp');
        $code = ($currency !== null) ? $currency['code'] : null;
        return $this->filter_by_currency_since_limit($result, $code, $since, $limit);
    }

    public function parse_transaction($transaction, ?array $currency = null): array {
        //
        //  fetchDeposits
        //     {
        //         "asset":"USDC",
        //         "network":"Ethereum",
        //         "address" => "0x998dEc76151FB723963Bd8AFD517687b38D33dE8",
        //         "quantity":"50",
        //         "id":"5914",
        //         "status" => "COMPLETED",
        //         "txId":"0xf5e79663830a0c6f94d46638dcfbc134566c12facf1832396f81ecb55d3c75dc",
        //         "creditedAt":"1714821645154"
        //     }
        //
        // fetchWithdrawals
        //     {
        //         $id => '968163212989431811',
        //         asset => 'OX',
        //         $network => 'Arbitrum',
        //         $address => '0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9',
        //         quantity => '11.7444',
        //         $fee => '1.744400000',
        //         $status => 'COMPLETED',
        //         txId => '0xe96b2d128b737fdbca927edf355cff42202e65b0fb960e64ffb9bd68c121f69f',
        //         requestedAt => '1715530365450',
        //         completedAt => '1715530527000'
        //     }
        //
        // withdraw
        //     {
        //         "id" => "968364664449302529",
        //         "asset" => "OX",
        //         "network" => "Arbitrum",
        //         "address" => "0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9",
        //         "quantity" => "10",
        //         "externalFee" => false,
        //         "fee" => "1.6728",
        //         "status" => "PENDING",
        //         "requestedAt" => "1715591843616"
        //     }
        //
        $id = $this->safe_string($transaction, 'id');
        $type = $this->safe_string($transaction, 'type');
        $transaction = $this->omit($transaction, 'type');
        $address = null;
        $addressTo = null;
        $status = null;
        if ($type === 'deposit') {
            $address = $this->safe_string($transaction, 'address');
            $status = $this->parse_deposit_status($this->safe_string($transaction, 'status'));
        } elseif ($type === 'withdrawal') {
            $addressTo = $this->safe_string($transaction, 'address');
            $status = $this->parse_withdrawal_status($this->safe_string($transaction, 'status'));
        }
        $txid = $this->safe_string($transaction, 'txId');
        $currencyId = $this->safe_string($transaction, 'asset');
        $code = $this->safe_currency_code($currencyId, $currency);
        $network = $this->safe_string($transaction, 'network');
        $networkCode = $this->network_id_to_code($network);
        $timestamp = $this->safe_integer_2($transaction, 'creditedAt', 'requestedAt');
        $amount = $this->safe_number($transaction, 'quantity');
        $feeCost = $this->safe_number($transaction, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => $networkCode,
            'address' => $address,
            'addressTo' => $addressTo,
            'addressFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'internal' => null,
            'comment' => null,
            'fee' => $fee,
        );
    }

    public function parse_deposit_status($status) {
        $statuses = array(
            'COMPLETED' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_withdrawal_status($status) {
        $statuses = array(
            'COMPLETED' => 'ok',
            'PROCESSING' => 'pending',
            'IN SWEEPING' => 'pending',
            'PENDING' => 'pending',
            'ON HOLD' => 'pending',
            'CANCELED' => 'canceled',
            'FAILED' => 'failed',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    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://docs.ox.fun/?json#post-v3-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
             * @param {string} [$params->network] network for withdraw
             * @param {bool} [$params->externalFee] if false, then the fee is taken from the quantity, also with the burn fee for asset SOLO
             *
             * EXCHANGE SPECIFIC PARAMETERS
             * @param {string} [$params->tfaType] GOOGLE, or AUTHY_SECRET, or YUBIKEY, for 2FA
             * @param {string} [$params->code] 2FA $code
             * @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->currency($code);
            $stringAmount = $this->currency_to_precision($code, $amount);
            $request = array(
                'asset' => $currency['id'],
                'address' => $address,
                'quantity' => $stringAmount,
            );
            if ($tag !== null) {
                $request['memo'] = $tag;
            }
            $networkCode = null;
            list($networkCode, $params) = $this->handle_network_code_and_params($params);
            if ($networkCode !== null) {
                $request['network'] = $this->network_code_to_id($networkCode);
            }
            $request['externalFee'] = false;
            $response = Async\await($this->privatePostV3Withdrawal ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "id" => "968364664449302529",
            //             "asset" => "OX",
            //             "network" => "Arbitrum",
            //             "address" => "0x90fc1fB49a4ED8f485dd02A2a1Cf576897f6Bfc9",
            //             "quantity" => "10",
            //             "externalFee" => false,
            //             "fee" => "1.6728",
            //             "status" => "PENDING",
            //             "requestedAt" => "1715591843616"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $data['type'] = 'withdrawal';
            return $this->parse_transaction($data, $currency);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open $positions
             *
             * @see https://docs.ox.fun/?json#get-v3-$positions
             *
             * @param {string[]|null} $symbols list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->subAcc]
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            // Calling this endpoint using an API key pair linked to the parent $account with the parameter "subAcc"
            // allows the caller to include $positions of additional sub-accounts in the $response->
            // This feature does not work when using API key pairs linked to a sub-$account
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $response = Async\await($this->privateGetV3Positions ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             array(
            //                 "accountId" => "106490",
            //                 "name" => "main",
            //                 "positions" => array(
            //                     array(
            //                         "marketCode" => "BTC-USD-SWAP-LIN",
            //                         "baseAsset" => "BTC",
            //                         "counterAsset" => "USD",
            //                         "position" => "0.00010",
            //                         "entryPrice" => "64300.0",
            //                         "markPrice" => "63278",
            //                         "positionPnl" => "-10.1900",
            //                         "estLiquidationPrice" => "0",
            //                         "lastUpdatedAt" => "1714915841448"
            //                     ),
            //                     ...
            //                 )
            //             ),
            //             {
            //                 "accountId" => "106526",
            //                 "name" => "testSubAccount",
            //                 "positions" => array(
            //                     array(
            //                         "marketCode" => "ETH-USD-SWAP-LIN",
            //                         "baseAsset" => "ETH",
            //                         "counterAsset" => "USD",
            //                         "position" => "0.001",
            //                         "entryPrice" => "3080.5",
            //                         "markPrice" => "3062.0",
            //                         "positionPnl" => "-1.8500",
            //                         "estLiquidationPrice" => "0",
            //                         "lastUpdatedAt" => "1715089678013"
            //                     ),
            //                     ...
            //                 )
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $allPositions = array();
            for ($i = 0; $i < count($data); $i++) {
                $account = $data[$i];
                $positions = $this->safe_list($account, 'positions', array());
                $allPositions = $this->array_concat($allPositions, $positions);
            }
            return $this->parse_positions($allPositions, $symbols);
        }) ();
    }

    public function parse_position($position, ?array $market = null) {
        //
        //     {
        //         "marketCode" => "ETH-USD-SWAP-LIN",
        //         "baseAsset" => "ETH",
        //         "counterAsset" => "USD",
        //         "position" => "0.001",
        //         "entryPrice" => "3080.5",
        //         "markPrice" => "3062.0",
        //         "positionPnl" => "-1.8500",
        //         "estLiquidationPrice" => "0",
        //         "lastUpdatedAt" => "1715089678013"
        //     }
        //
        $marketId = $this->safe_string($position, 'marketCode');
        $market = $this->safe_market($marketId, $market);
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $market['symbol'],
            'notional' => null,
            'marginMode' => 'cross',
            'liquidationPrice' => $this->safe_number($position, 'estLiquidationPrice'),
            'entryPrice' => $this->safe_number($position, 'entryPrice'),
            'unrealizedPnl' => $this->safe_number($position, 'positionPnl'),
            'realizedPnl' => null,
            'percentage' => null,
            'contracts' => $this->safe_number($position, 'position'),
            'contractSize' => null,
            'markPrice' => $this->safe_number($position, 'markPrice'),
            'lastPrice' => null,
            'side' => null,
            'hedged' => null,
            'timestamp' => null,
            'datetime' => null,
            'lastUpdateTimestamp' => $this->safe_integer($position, 'lastUpdatedAt'),
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'collateral' => null,
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'leverage' => null,
            'marginRatio' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade $order
             *
             * @see https://docs.ox.fun/?json#post-v3-orders-place
             *
             * @param {string} $symbol unified $symbol of the market to create an $order in
             * @param {string} $type 'market', 'limit', 'STOP_LIMIT' or 'STOP_MARKET'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->clientOrderId] a unique id for the $order
             * @param {int} [$params->timestamp] in milliseconds. If an $order reaches the matching engine and the current timestamp exceeds timestamp . $recvWindow, then the $order will be rejected.
             * @param {int} [$params->recvWindow] in milliseconds. If an $order reaches the matching engine and the current timestamp exceeds timestamp . $recvWindow, then the $order will be rejected. If timestamp is provided without $recvWindow, then a default $recvWindow of 1000ms is used.
             * @param {string} [$params->responseType] FULL or ACK
             * @param {float} [$params->cost] the quote quantity that can be used alternative for the $amount for market buy orders
             * @param {float} [$params->triggerPrice] The $price at which a trigger $order is triggered at
             * @param {float} [$params->limitPrice] Limit $price for the STOP_LIMIT $order
             * @param {bool} [$params->postOnly] if true, the $order will only be posted if it will be a maker $order
             * @param {string} [$params->timeInForce] GTC (default), IOC, FOK, PO, MAKER_ONLY or MAKER_ONLY_REPRICE (reprices $order to the best maker only $price if the specified $price were to lead to a taker trade)
             * @param {string} [$params->selfTradePrevention] NONE, EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH for more info check here array(@link https://docs.ox.fun/?json#self-trade-prevention-modes)
             * @param {string} [$params->displayQuantity] for an iceberg $order, pass both quantity and displayQuantity fields in the $order $request
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'responseType' => $this->safe_string($params, 'responseType', 'FULL'),
                'timestamp' => $this->safe_integer($params, 'timestamp', $this->milliseconds()),
            );
            $params = $this->omit($params, array( 'responseType', 'timestamp' ));
            $recvWindow = $this->safe_integer($params, 'recvWindow');
            if ($recvWindow !== null) {
                $request['recvWindow'] = $recvWindow;
                $params = $this->omit($params, 'recvWindow');
            }
            $orderRequest = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
            $request['orders'] = array( $orderRequest );
            $response = Async\await($this->privatePostV3OrdersPlace ($request));
            //
            // accepted market $order responseType FULL
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "notice" => "OrderMatched",
            //                 "accountId" => "106490",
            //                 "orderId" => "1000109901865",
            //                 "submitted" => true,
            //                 "clientOrderId" => "0",
            //                 "marketCode" => "OX-USDT",
            //                 "status" => "FILLED",
            //                 "side" => "SELL",
            //                 "isTriggered" => false,
            //                 "quantity" => "150.0",
            //                 "amount" => "0.0",
            //                 "remainQuantity" => "0.0",
            //                 "matchId" => "100017047880451399",
            //                 "matchPrice" => "0.01465",
            //                 "matchQuantity" => "150.0",
            //                 "feeInstrumentId" => "USDT",
            //                 "fees" => "0.0015382500",
            //                 "orderType" => "MARKET",
            //                 "createdAt" => "1715592472236",
            //                 "lastMatchedAt" => "1715592472200",
            //                 "displayQuantity" => "150.0"
            //             }
            //         )
            //     }
            //
            // accepted limit $order responseType FULL
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "notice" => "OrderOpened",
            //                 "accountId" => "106490",
            //                 "orderId" => "1000111482406",
            //                 "submitted" => true,
            //                 "clientOrderId" => "0",
            //                 "marketCode" => "ETH-USD-SWAP-LIN",
            //                 "status" => "OPEN",
            //                 "side" => "SELL",
            //                 "price" => "4000.0",
            //                 "isTriggered" => false,
            //                 "quantity" => "0.01",
            //                 "amount" => "0.0",
            //                 "orderType" => "LIMIT",
            //                 "timeInForce" => "GTC",
            //                 "createdAt" => "1715763507682",
            //                 "displayQuantity" => "0.01"
            //             }
            //         )
            //     }
            //
            // accepted $order responseType ACK
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "accountId" => "106490",
            //                 "orderId" => "1000109892193",
            //                 "submitted" => true,
            //                 "marketCode" => "OX-USDT",
            //                 "side" => "BUY",
            //                 "price" => "0.01961",
            //                 "isTriggered" => false,
            //                 "quantity" => "100",
            //                 "orderType" => "MARKET",
            //                 "timeInForce" => "IOC",
            //                 "createdAt" => "1715591529057",
            //                 "selfTradePreventionMode" => "NONE"
            //             }
            //         )
            //     }
            //
            //  rejected $order (balance insufficient)
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "code" => "710001",
            //                 "message" => "System failure, exception thrown -> null",
            //                 "submitted" => false,
            //                 "marketCode" => "OX-USDT",
            //                 "side" => "BUY",
            //                 "price" => "0.01961",
            //                 "amount" => "100",
            //                 "orderType" => "MARKET",
            //                 "timeInForce" => "IOC",
            //                 "createdAt" => "1715591678835",
            //                 "source" => 11,
            //                 "selfTradePreventionMode" => "NONE"
            //             }
            //         )
            //     }
            //
            // rejected $order (bad $request)
            //     {
            //         "success" => true,
            //         "data" => array(
            //             {
            //                 "code" => "20044",
            //                 "message" => "Amount is not supported for this $order $type",
            //                 "submitted" => false,
            //                 "marketCode" => "OX-USDT",
            //                 "side" => "SELL",
            //                 "amount" => "200",
            //                 "orderType" => "MARKET",
            //                 "createdAt" => "1715592079986",
            //                 "source" => 11
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $order = $this->safe_dict($data, 0, array());
            return $this->parse_order($order);
        }) ();
    }

    public function create_orders(array $orders, $params = array ()): PromiseInterface {
        return Async\async(function () use ($orders, $params) {
            /**
             * create a list of trade $orders
             *
             * @see https://docs.ox.fun/?json#post-v3-$orders-place
             *
             * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely $symbol, $type, $side, $amount, $price and $params
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->timestamp] *for all $orders* in milliseconds. If $orders reach the matching engine and the current timestamp exceeds timestamp . recvWindow, then all $orders will be rejected.
             * @param {int} [$params->recvWindow] *for all $orders* in milliseconds. If $orders reach the matching engine and the current timestamp exceeds timestamp . recvWindow, then all $orders will be rejected. If timestamp is provided without recvWindow, then a default recvWindow of 1000ms is used.
             * @param {string} [$params->responseType] *for all $orders* FULL or ACK
             * @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];
                $symbol = $this->safe_string($rawOrder, 'symbol');
                $type = $this->safe_string($rawOrder, 'type');
                $side = $this->safe_string($rawOrder, 'side');
                $amount = $this->safe_number($rawOrder, 'amount');
                $price = $this->safe_number($rawOrder, 'price');
                $orderParams = $this->safe_dict($rawOrder, 'params', array());
                $orderRequest = $this->create_order_request($symbol, $type, $side, $amount, $price, $orderParams);
                $ordersRequests[] = $orderRequest;
            }
            $request = array(
                'responseType' => 'FULL',
                'timestamp' => $this->milliseconds(),
                'orders' => $ordersRequests,
            );
            $response = Async\await($this->privatePostV3OrdersPlace ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data);
        }) ();
    }

    public function create_order_request(string $symbol, string $type, string $side, $amount, $price = null, $params = array ()) {
        /**
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market', 'limit', 'STOP_LIMIT' or 'STOP_MARKET'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->clientOrderId] a unique id for the order
         * @param {float} [$params->cost] the quote quantity that can be used alternative for the $amount for $market buy orders
         * @param {float} [$params->triggerPrice] The $price at which a trigger order is triggered at
         * @param {float} [$params->limitPrice] Limit $price for the STOP_LIMIT order
         * @param {bool} [$params->postOnly] if true, the order will only be posted if it will be a maker order
         * @param {string} [$params->timeInForce] GTC (default), IOC, FOK, PO, MAKER_ONLY or MAKER_ONLY_REPRICE (reprices order to the best maker only $price if the specified $price were to lead to a taker trade)
         * @param {string} [$params->selfTradePrevention] NONE, EXPIRE_MAKER, EXPIRE_TAKER or EXPIRE_BOTH for more info check here array(@link https://docs.ox.fun/?json#self-trade-prevention-modes)
         * @param {string} [$params->displayQuantity] for an iceberg order, pass both quantity and displayQuantity fields in the order $request
         */
        $market = $this->market($symbol);
        $request = array(
            'marketCode' => $market['id'],
            'side' => strtoupper($side),
            'source' => 1000,
        );
        $cost = $this->safe_string_2($params, 'cost', 'amount');
        if ($cost !== null) {
            $request['amount'] = $cost; // todo costToPrecision
            $params = $this->omit($params, array( 'cost', 'amount' ));
        } else {
            $request['quantity'] = $amount; // todo amountToPrecision
        }
        $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
        $orderType = strtoupper($type);
        if ($triggerPrice !== null) {
            if ($orderType === 'MARKET') {
                $orderType = 'STOP_MARKET';
            } elseif ($orderType === 'LIMIT') {
                $orderType = 'STOP_LIMIT';
            }
            $request['stopPrice'] = $triggerPrice; // todo priceToPrecision
            $params = $this->omit($params, array( 'triggerPrice', 'stopPrice' ));
        }
        $request['orderType'] = $orderType;
        if ($orderType === 'STOP_LIMIT') {
            $request['limitPrice'] = $price; // todo priceToPrecision
        } elseif ($price !== null) {
            $request['price'] = $price; // todo priceToPrecision
        }
        $postOnly = null;
        $isMarketOrder = ($orderType === 'MARKET') || ($orderType === 'STOP_MARKET');
        list($postOnly, $params) = $this->handle_post_only($isMarketOrder, false, $params);
        $timeInForce = $this->safe_string_upper($params, 'timeInForce');
        if ($postOnly && ($timeInForce !== 'MAKER_ONLY_REPRICE')) {
            $request['timeInForce'] = 'MAKER_ONLY';
        }
        $selfTradePrevention = null;
        list($selfTradePrevention, $params) = $this->handle_option_and_params($params, 'createOrder', 'selfTradePrevention');
        if ($selfTradePrevention !== null) {
            $request['selfTradePreventionMode'] = strtoupper($selfTradePrevention);
        }
        return $this->extend($request, $params);
    }

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

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://docs.ox.fun/?json#get-v3-orders-status
             *
             * fetches information on an order made by the user
             * @param {string} $id a unique $id for the order
             * @param {string} [$symbol] not used by oxfun fetchOrder
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->clientOrderId] the client order $id of the order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'orderId' => $id,
            );
            $response = Async\await($this->privateGetV3OrdersStatus ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "orderId" => "1000111762980",
            //             "clientOrderId" => "0",
            //             "marketCode" => "ETH-USD-SWAP-LIN",
            //             "status" => "OPEN",
            //             "side" => "BUY",
            //             "price" => "2700.0",
            //             "isTriggered" => false,
            //             "remainQuantity" => "0.01",
            //             "totalQuantity" => "0.01",
            //             "amount" => "0",
            //             "displayQuantity" => "0.01",
            //             "cumulativeMatchedQuantity" => "0",
            //             "orderType" => "STOP_LIMIT",
            //             "timeInForce" => "GTC",
            //             "source" => "11",
            //             "createdAt" => "1715794191277"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data);
        }) ();
    }

    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://docs.ox.fun/?json#get-v3-orders-working
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch open orders for
             * @param {int} [$limit] the maximum number of  open orders structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->orderId] a unique id for the order
             * @param {int} [$params->clientOrderId] the client order id of the order
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $response = Async\await($this->privateGetV3OrdersWorking ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    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://docs.ox.fun/?json#delete-v3-orders-cancel
             *
             * @param {string} $id $order $id
             * @param {string} $symbol unified $symbol of the $market the $order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->clientOrderId] a unique $id for the $order
             * @param {int} [$params->timestamp] in milliseconds
             * @param {int} [$params->recvWindow] in milliseconds
             * @param {string} [$params->responseType] 'FULL' or 'ACK'
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
            }
            $market = $this->market($symbol);
            $marketId = $market['id'];
            $request = array(
                'timestamp' => $this->milliseconds(),
                'responseType' => 'FULL',
            );
            $orderRequest = array(
                'marketCode' => $marketId,
                'orderId' => $id,
            );
            $clientOrderId = $this->safe_integer($params, 'clientOrderId');
            if ($clientOrderId !== null) {
                $orderRequest['clientOrderId'] = $clientOrderId;
            }
            $request['orders'] = array( $orderRequest );
            $response = Async\await($this->privateDeleteV3OrdersCancel ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data', array());
            $order = $this->safe_dict($data, 0, array());
            return $this->parse_order($order);
        }) ();
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * cancel all open orders
             *
             * @see https://docs.ox.fun/?json#delete-v3-orders-cancel-all
             *
             * @param {string} $symbol unified $market $symbol, only orders in the $market of this $symbol are cancelled when $symbol is not null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} $response from exchange
             */
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['marketCode'] = $market['id'];
            }
            //
            //     {
            //         "success" => true,
            //         "data" => array( "notice" => "Orders queued for cancelation" )
            //     }
            //
            //     {
            //         "success" => true,
            //         "data" => array( "notice" => "No working orders found" )
            //     }
            //
            $response = Async\await($this->privateDeleteV3OrdersCancelAll ($this->extend($request, $params)));
            return array( $this->safe_order(array( 'info' => $response )) );
        }) ();
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple $orders
             *
             * @see https://docs.ox.fun/?json#delete-v3-$orders-cancel
             *
             * @param {string[]} $ids $order $ids
             * @param {string} [$symbol] unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->timestamp] in milliseconds
             * @param {int} [$params->recvWindow] in milliseconds
             * @param {string} [$params->responseType] 'FULL' or 'ACK'
             * @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);
            $marketId = $market['id'];
            $request = array(
                'timestamp' => $this->milliseconds(),
                'responseType' => 'FULL',
            );
            $orders = array();
            for ($i = 0; $i < count($ids); $i++) {
                $order = array(
                    'marketCode' => $marketId,
                    'orderId' => $ids[$i],
                );
                $orders[] = $order;
            }
            $request['orders'] = $orders;
            $response = Async\await($this->privateDeleteV3OrdersCancel ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market);
        }) ();
    }

    public function parse_order($order, ?array $market = null): array {
        //
        // accepted $market $order responseType FULL
        //     {
        //         "notice" => "OrderMatched",
        //         "accountId" => "106490",
        //         "orderId" => "1000109901865",
        //         "submitted" => true,
        //         "clientOrderId" => "0",
        //         "marketCode" => "OX-USDT",
        //         "status" => "FILLED",
        //         "side" => "SELL",
        //         "isTriggered" => false,
        //         "quantity" => "150.0",
        //         "amount" => "0.0",
        //         "remainQuantity" => "0.0",
        //         "matchId" => "100017047880451399",
        //         "matchPrice" => "0.01465",
        //         "matchQuantity" => "150.0",
        //         "feeInstrumentId" => "USDT",
        //         "fees" => "0.0015382500",
        //         "orderType" => "MARKET",
        //         "createdAt" => "1715592472236",
        //         "lastMatchedAt" => "1715592472200",
        //         "displayQuantity" => "150.0"
        //     }
        //
        // accepted limit $order responseType FULL
        //     {
        //         "notice" => "OrderOpened",
        //         "accountId" => "106490",
        //         "orderId" => "1000111482406",
        //         "submitted" => true,
        //         "clientOrderId" => "0",
        //         "marketCode" => "ETH-USD-SWAP-LIN",
        //         "status" => "OPEN",
        //         "side" => "SELL",
        //         "price" => "4000.0",
        //         "isTriggered" => false,
        //         "quantity" => "0.01",
        //         "amount" => "0.0",
        //         "orderType" => "LIMIT",
        //         "timeInForce" => "GTC",
        //         "createdAt" => "1715763507682",
        //         "displayQuantity" => "0.01"
        //     }
        //
        // accepted $order responseType ACK
        //     {
        //         "accountId" => "106490",
        //         "orderId" => "1000109892193",
        //         "submitted" => true,
        //         "marketCode" => "OX-USDT",
        //         "side" => "BUY",
        //         "price" => "0.01961",
        //         "isTriggered" => false,
        //         "quantity" => "100",
        //         "orderType" => "MARKET",
        //         "timeInForce" => "IOC",
        //         "createdAt" => "1715591529057",
        //         "selfTradePreventionMode" => "NONE"
        //     }
        //
        //  rejected $order (balance insufficient)
        //     {
        //         "code" => "710001",
        //         "message" => "System failure, exception thrown -> null",
        //         "submitted" => false,
        //         "marketCode" => "OX-USDT",
        //         "side" => "BUY",
        //         "price" => "0.01961",
        //         "amount" => "100",
        //         "orderType" => "MARKET",
        //         "timeInForce" => "IOC",
        //         "createdAt" => "1715591678835",
        //         "source" => 11,
        //         "selfTradePreventionMode" => "NONE"
        //     }
        //
        // rejected $order (bad request)
        //     {
        //         "code" => "20044",
        //         "message" => "Amount is not supported for this $order type",
        //         "submitted" => false,
        //         "marketCode" => "OX-USDT",
        //         "side" => "SELL",
        //         "amount" => "200",
        //         "orderType" => "MARKET",
        //         "createdAt" => "1715592079986",
        //         "source" => 11
        //     }
        //
        // fetchOrder
        //     {
        //         "orderId" => "1000111762980",
        //         "clientOrderId" => "0",
        //         "marketCode" => "ETH-USD-SWAP-LIN",
        //         "status" => "OPEN",
        //         "side" => "BUY",
        //         "price" => "2700.0",
        //         "isTriggered" => false,
        //         "remainQuantity" => "0.01",
        //         "totalQuantity" => "0.01",
        //         "amount" => "0",
        //         "displayQuantity" => "0.01",
        //         "cumulativeMatchedQuantity" => "0",
        //         "orderType" => "STOP_LIMIT",
        //         "timeInForce" => "GTC",
        //         "source" => "11",
        //         "createdAt" => "1715794191277"
        //     }
        //
        $marketId = $this->safe_string($order, 'marketCode');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer($order, 'createdAt');
        $fee = null;
        $feeCurrency = $this->safe_string($order, 'feeInstrumentId');
        if ($feeCurrency !== null) {
            $fee = array(
                'currency' => $this->safe_currency_code($feeCurrency),
                'cost' => $this->safe_number($order, 'fees'),
            );
        }
        $status = $this->safe_string($order, 'status');
        $code = $this->safe_integer($order, 'code'); // rejected orders have $code of the error
        if ($code !== null) {
            $status = 'rejected';
        }
        $triggerPrice = $this->safe_string($order, 'stopPrice');
        return $this->safe_order(array(
            'id' => $this->safe_string($order, 'orderId'),
            'clientOrderId' => $this->safe_string($order, 'clientOrderId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $this->safe_integer($order, 'lastMatchedAt'),
            'lastUpdateTimestamp' => $this->safe_integer($order, 'lastModifiedAt'),
            'status' => $this->parse_order_status($status),
            'symbol' => $market['symbol'],
            'type' => $this->parse_order_type($this->safe_string($order, 'orderType')),
            'timeInForce' => $this->parse_order_time_in_force($this->safe_string($order, 'timeInForce')), // only for limit orders
            'side' => $this->safe_string_lower($order, 'side'),
            'price' => $this->safe_string_n($order, array( 'price', 'matchPrice', 'limitPrice' )),
            'average' => null,
            'amount' => $this->safe_string_2($order, 'totalQuantity', 'quantity'),
            'filled' => $this->safe_string_2($order, 'cumulativeMatchedQuantity', 'matchQuantity'),
            'remaining' => $this->safe_string($order, 'remainQuantity'),
            'triggerPrice' => $triggerPrice,
            'stopLossPrice' => $triggerPrice,
            'cost' => $this->omit_zero($this->safe_string($order, 'amount')),
            'trades' => null,
            'fee' => $fee,
            'info' => $order,
        ), $market);
    }

    public function parse_order_status($status) {
        $statuses = array(
            'OPEN' => 'open',
            'PARTIALLY_FILLED' => 'open',
            'PARTIAL_FILL' => 'open',
            'FILLED' => 'closed',
            'CANCELED' => 'canceled',
            'CANCELED_BY_USER' => 'canceled',
            'CANCELED_BY_MAKER_ONLY' => 'rejected',
            'CANCELED_BY_FOK' => 'rejected',
            'CANCELED_ALL_BY_IOC' => 'rejected',
            'CANCELED_PARTIAL_BY_IOC' => 'canceled',
            'CANCELED_BY_SELF_TRADE_PROTECTION' => 'rejected',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($type) {
        $types = array(
            'LIMIT' => 'limit',
            'STOP_LIMIT' => 'limit',
            'MARKET' => 'market',
            'STOP_MARKET' => 'market',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_order_time_in_force($type) {
        $types = array(
            'GTC' => 'GTC',
            'IOC' => 'IOC',
            'FOK' => 'FOK',
            'MAKER_ONLY' => 'PO',
            'MAKER_ONLY_REPRICE' => 'PO',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $baseUrl = $this->urls['api'][$api];
        $url = $baseUrl . '/' . $path;
        $queryString = '';
        if ($method === 'GET') {
            $queryString = $this->urlencode($params);
            if (strlen($queryString) !== 0) {
                $url .= '?' . $queryString;
            }
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = $this->milliseconds();
            $isoDatetime = $this->iso8601($timestamp);
            $datetimeParts = explode('.', $isoDatetime);
            $datetime = $datetimeParts[0];
            $nonce = $this->nonce();
            $urlParts = explode('//', $baseUrl);
            if (($method === 'POST') || ($method === 'DELETE')) {
                $body = $this->json($params);
                $queryString = $body;
            }
            $msgString = $datetime . '\n' . (string) $nonce . '\n' . $method . '\n' . $urlParts[1] . '\n/' . $path . '\n' . $queryString;
            $signature = $this->hmac($this->encode($msgString), $this->encode($this->secret), 'sha256', 'base64');
            $headers = array(
                'Content-Type' => 'application/json',
                'AccessKey' => $this->apiKey,
                'Timestamp' => $datetime,
                'Signature' => $signature,
                'Nonce' => (string) $nonce,
            );
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors($code, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null;
        }
        if ($code !== 200) {
            $responseCode = $this->safe_string($response, 'code', null);
            $feedback = $this->id . ' ' . $body;
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $responseCode, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
