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

class woo extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'woo',
            'name' => 'WOO X',
            'countries' => array( 'KY' ), // Cayman Islands
            'rateLimit' => 100,
            'version' => 'v1',
            'certified' => true,
            'pro' => true,
            'hostname' => 'woox.io',
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => true,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => true,
                'cancelOrder' => true,
                'cancelWithdraw' => false, // exchange have that endpoint disabled atm, but was once implemented in ccxt per old docs => https://docx.woo.io/wootrade-documents/#cancel-withdraw-request
                'closeAllPositions' => false,
                'closePosition' => false,
                'createConvertTrade' => true,
                'createDepositAddress' => false,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrder' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => true,
                'createOrder' => true,
                'createOrderWithTakeProfitAndStopLoss' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => false,
                'createStopLossOrder' => true,
                'createStopMarketOrder' => false,
                'createStopOrder' => false,
                'createTakeProfitOrder' => true,
                'createTrailingAmountOrder' => true,
                'createTrailingPercentOrder' => true,
                'createTriggerOrder' => true,
                'fetchAccounts' => true,
                'fetchBalance' => true,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => true,
                'fetchConvertCurrencies' => true,
                'fetchConvertQuote' => true,
                'fetchConvertTrade' => true,
                'fetchConvertTradeHistory' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => true,
                'fetchFundingHistory' => true,
                'fetchFundingInterval' => true,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchIndexOHLCV' => false,
                'fetchLedger' => true,
                'fetchLeverage' => true,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrder' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchOrderTrades' => true,
                'fetchPosition' => true,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsHistory' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => true,
                'fetchTicker' => false,
                'fetchTickers' => false,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => true,
                'fetchTransactions' => 'emulated',
                'fetchTransfers' => true,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'sandbox' => true,
                'setLeverage' => true,
                'setMargin' => false,
                'setPositionMode' => true,
                'transfer' => true,
                'withdraw' => true, // exchange have that endpoint disabled atm, but was once implemented in ccxt per old docs => https://docx.woo.io/wootrade-documents/#token-withdraw
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '4h' => '4h',
                '12h' => '12h',
                '1d' => '1d',
                '1w' => '1w',
                '1M' => '1mon',
                '1y' => '1y',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/150730761-1a00e5e0-d28c-480f-9e65-089ce3e6ef3b.jpg',
                'api' => array(
                    'pub' => 'https://api-pub.woox.io',
                    'public' => 'https://api.{hostname}',
                    'private' => 'https://api.{hostname}',
                ),
                'test' => array(
                    'pub' => 'https://api-pub.staging.woox.io',
                    'public' => 'https://api.staging.woox.io',
                    'private' => 'https://api.staging.woox.io',
                ),
                'www' => 'https://woox.io/',
                'doc' => array(
                    'https://docs.woox.io/',
                ),
                'fees' => array(
                    'https://support.woox.io/hc/en-001/articles/4404611795353--Trading-Fees',
                ),
                'referral' => array(
                    'url' => 'https://woox.io/register?ref=DIJT0CNL',
                    'discount' => 0.35,
                ),
            ),
            'api' => array(
                'v1' => array(
                    'pub' => array(
                        'get' => array(
                            'hist/kline' => 10,
                            'hist/trades' => 10,
                        ),
                    ),
                    'public' => array(
                        'get' => array(
                            'info' => 1,
                            'info/{symbol}' => 1,
                            'system_info' => 1,
                            'market_trades' => 1,
                            'token' => 1,
                            'token_network' => 1,
                            'funding_rates' => 1,
                            'funding_rate/{symbol}' => 1,
                            'funding_rate_history' => 1,
                            'futures' => 1,
                            'futures/{symbol}' => 1,
                            'orderbook/{symbol}' => 1,
                            'kline' => 1,
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'client/token' => 1,
                            'order/{oid}' => 1,
                            'client/order/{client_order_id}' => 1,
                            'orders' => 1,
                            'client/trade/{tid}' => 1,
                            'order/{oid}/trades' => 1,
                            'client/trades' => 1,
                            'client/hist_trades' => 1,
                            'staking/yield_history' => 1,
                            'client/holding' => 1,
                            'asset/deposit' => 10,
                            'asset/history' => 60,
                            'sub_account/all' => 60,
                            'sub_account/assets' => 60,
                            'sub_account/asset_detail' => 60,
                            'sub_account/ip_restriction' => 10,
                            'asset/main_sub_transfer_history' => 30,
                            'token_interest' => 60,
                            'token_interest/{token}' => 60,
                            'interest/history' => 60,
                            'interest/repay' => 60,
                            'funding_fee/history' => 30,
                            'positions' => 3.33, // 30 requests per 10 seconds
                            'position/{symbol}' => 3.33,
                            'client/transaction_history' => 60,
                            'client/futures_leverage' => 60,
                        ),
                        'post' => array(
                            'order' => 1, // 10 requests per 1 second per symbol
                            'order/cancel_all_after' => 1,
                            'asset/main_sub_transfer' => 30, // 20 requests per 60 seconds
                            'asset/ltv' => 30,
                            'asset/withdraw' => 30,  // implemented in ccxt, disabled on the exchange side https://docx.woo.io/wootrade-documents/#token-withdraw
                            'asset/internal_withdraw' => 30,
                            'interest/repay' => 60,
                            'client/account_mode' => 120,
                            'client/position_mode' => 5,
                            'client/leverage' => 120,
                            'client/futures_leverage' => 30,
                            'client/isolated_margin' => 30,
                        ),
                        'delete' => array(
                            'order' => 1,
                            'client/order' => 1,
                            'orders' => 1,
                            'asset/withdraw' => 120,  // implemented in ccxt, disabled on the exchange side https://docx.woo.io/wootrade-documents/#cancel-withdraw-request
                        ),
                    ),
                ),
                'v2' => array(
                    'private' => array(
                        'get' => array(
                            'client/holding' => 1,
                        ),
                    ),
                ),
                'v3' => array(
                    'public' => array(
                        'get' => array(
                            'systemInfo' => 1, // 10/1s
                            'instruments' => 1, // 10/1s
                            'token' => 1, // 10/1s
                            'tokenNetwork' => 1, // 10/1s
                            'tokenInfo' => 1, // 10/1s
                            'marketTrades' => 1, // 10/1s
                            'marketTradesHistory' => 1, // 10/1s
                            'orderbook' => 1, // 10/1s
                            'kline' => 1, // 10/1s
                            'klineHistory' => 1, // 10/1s
                            'futures' => 1, // 10/1s
                            'fundingRate' => 1, // 10/1s
                            'fundingRateHistory' => 1, // 10/1s
                            'insuranceFund' => 1, // 10/1s
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'trade/order' => 2, // 5/1s
                            'trade/orders' => 1, // 10/1s
                            'trade/algoOrder' => 1, // 10/1s
                            'trade/algoOrders' => 1, // 10/1s
                            'trade/transaction' => 1, // 10/1s
                            'trade/transactionHistory' => 5, // 2/1s
                            'trade/tradingFee' => 5, // 2/1s
                            'account/info' => 60, // 10/60s
                            'account/tokenConfig' => 1, // 10/1s
                            'account/symbolConfig' => 1, // 10/1s
                            'account/subAccounts/all' => 60, // 10/60s
                            'account/referral/summary' => 60, // 10/60s
                            'account/referral/rewardHistory' => 60, // 10/60s
                            'account/credentials' => 60, // 10/60s
                            'asset/balances' => 1, // 10/1s
                            'asset/token/history' => 60, // 10/60s
                            'asset/transfer/history' => 30, // 20/60s
                            'asset/wallet/history' => 60, // 10/60s
                            'asset/wallet/deposit' => 60, // 10/60s
                            'asset/staking/yieldHistory' => 60, // 10/60s
                            'futures/positions' => 3.33, // 30/10s
                            'futures/leverage' => 60, // 10/60s
                            'futures/defaultMarginMode' => 60, // 10/60s
                            'futures/fundingFee/history' => 30, // 20/60s
                            'spotMargin/interestRate' => 60, // 10/60s
                            'spotMargin/interestHistory' => 60, // 10/60s
                            'spotMargin/maxMargin' => 60, // 10/60s
                            'algo/order/{oid}' => 1,
                            'algo/orders' => 1,
                            'balances' => 1,
                            'positions' => 3.33,
                            'buypower' => 1,
                            'convert/exchangeInfo' => 1,
                            'convert/assetInfo' => 1,
                            'convert/rfq' => 60,
                            'convert/trade' => 1,
                            'convert/trades' => 1,
                        ),
                        'post' => array(
                            'trade/order' => 2, // 5/1s
                            'trade/algoOrder' => 5, // 2/1s
                            'trade/cancelAllAfter' => 1, // 10/1s
                            'account/tradingMode' => 120, // 5/60s
                            'account/listenKey' => 20, // 5/10s
                            'asset/transfer' => 30, // 20/60s
                            'asset/wallet/withdraw' => 60, // 10/60s
                            'spotMargin/leverage' => 120, // 5/60s
                            'spotMargin/interestRepay' => 60, // 10/60s
                            'algo/order' => 5,
                            'convert/rft' => 60,
                        ),
                        'put' => array(
                            'trade/order' => 2, // 5/1s
                            'trade/algoOrder' => 2, // 5/1s
                            'futures/leverage' => 60, // 10/60s
                            'futures/positionMode' => 120, // 5/60s
                            'order/{oid}' => 2,
                            'order/client/{client_order_id}' => 2,
                            'algo/order/{oid}' => 2,
                            'algo/order/client/{client_order_id}' => 2,
                        ),
                        'delete' => array(
                            'trade/order' => 1, // 10/1s
                            'trade/orders' => 1, // 10/1s
                            'trade/algoOrder' => 1, // 10/1s
                            'trade/algoOrders' => 1, // 10/1s
                            'trade/allOrders' => 1, // 10/1s
                            'algo/order/{order_id}' => 1,
                            'algo/orders/pending' => 1,
                            'algo/orders/pending/{symbol}' => 1,
                            'orders/pending' => 1,
                        ),
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'maker' => $this->parse_number('0.0002'),
                    'taker' => $this->parse_number('0.0005'),
                ),
            ),
            'options' => array(
                'timeDifference' => 0, // the difference between system clock and exchange clock
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
                'sandboxMode' => false,
                'createMarketBuyOrderRequiresPrice' => true,
                // these network aliases require manual mapping here
                'network-aliases-for-tokens' => array(
                    'HT' => 'ERC20',
                    'OMG' => 'ERC20',
                    'UATOM' => 'ATOM',
                    'ZRX' => 'ZRX',
                ),
                'networks' => array(
                    'TRX' => 'TRON',
                    'TRC20' => 'TRON',
                    'ERC20' => 'ETH',
                    'BEP20' => 'BSC',
                    'ARB' => 'Arbitrum',
                ),
                'networksById' => array(
                    'TRX' => 'TRC20',
                    'TRON' => 'TRC20',
                ),
                // override defaultNetworkCodePriorities for a specific currency
                'defaultNetworkCodeForCurrencies' => array(
                    // 'USDT' => 'TRC20',
                    // 'BTC' => 'BTC',
                ),
                'transfer' => array(
                    'fillResponseFromRequest' => true,
                ),
                'brokerId' => 'bc830de7-50f3-460b-9ee0-f430f83f9dad',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                            'index' => false,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo by triggerPrice
                        'takeProfitPrice' => false, // todo by triggerPrice
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => true,
                        ),
                        'hedged' => false,
                        'trailing' => true,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => true, // todo implement
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => 90,
                        'untilDays' => 10000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => true,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'trigger' => true,
                        'trailing' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'untilDays' => 100000,
                        'trigger' => true,
                        'trailing' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => 100000,
                        'trigger' => true,
                        'trailing' => true,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'forSwap' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        'hedged' => true,
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forSwap',
                    ),
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'commonCurrencies' => array(),
            'exceptions' => array(
                'exact' => array(
                    '-1000' => '\\ccxt\\OperationFailed', // array( "code" => -1000,  "message" => "An unknown error occurred while processing the request" ) ||  array("success":false,"code":"-1000","message":"An internal error has occurred. We are unable to process your request. Please try again later.")
                    '-1001' => '\\ccxt\\AuthenticationError', // array( "code" => -1001,  "message" => "The api key or secret is in wrong format" )
                    '-1002' => '\\ccxt\\AuthenticationError', // array( "code" => -1002,  "message" => "API key or secret is invalid, it may because key have insufficient permission or the key is expired/revoked." )
                    '-1003' => '\\ccxt\\RateLimitExceeded', // array( "code" => -1003,  "message" => "Rate limit exceed." )
                    '-1004' => '\\ccxt\\BadRequest', // array( "code" => -1004,  "message" => "An unknown parameter was sent." )
                    '-1005' => '\\ccxt\\BadRequest', // array( "code" => -1005,  "message" => "Some parameters are in wrong format for api." )
                    '-1006' => '\\ccxt\\BadRequest', // array( "code" => -1006,  "message" => "The data is not found in server." )
                    '-1007' => '\\ccxt\\BadRequest', // array( "code" => -1007,  "message" => "The data is already exists or your request is duplicated." )
                    '-1008' => '\\ccxt\\InvalidOrder', // array( "code" => -1008,  "message" => "The quantity of settlement is too high than you can request." )
                    '-1009' => '\\ccxt\\BadRequest', // array( "code" => -1009,  "message" => "Can not request withdrawal settlement, you need to deposit other arrears first." )
                    '-1012' => '\\ccxt\\BadRequest', // array( "code" => -1012,  "message" => "Amount is required for buy market orders when margin disabled.")  The place/cancel order request is rejected by internal module, it may because the account is in liquidation or other internal errors. Please try again in a few seconds." }
                    '-1101' => '\\ccxt\\InvalidOrder', // array( "code" => -1101,  "message" => "The risk exposure for client is too high, it may cause by sending too big order or the leverage is too low. please refer to client info to check the current exposure." )
                    '-1102' => '\\ccxt\\InvalidOrder', // array( "code" => -1102,  "message" => "The order value (price * size) is too small." )
                    '-1103' => '\\ccxt\\InvalidOrder', // array( "code" => -1103,  "message" => "The order price is not following the tick size rule for the symbol." )
                    '-1104' => '\\ccxt\\InvalidOrder', // array( "code" => -1104,  "message" => "The order quantity is not following the step size rule for the symbol." )
                    '-1105' => '\\ccxt\\InvalidOrder', // array( "code" => -1105,  "message" => "Price is X% too high or X% too low from the mid price." )
                ),
                'broad' => array(
                    'Can not place' => '\\ccxt\\ExchangeError', // array( "code" => -1011,  "message" => "Can not place/cancel orders, it may because internal network error. Please try again in a few seconds." )
                    'maintenance' => '\\ccxt\\OnMaintenance', // array("code":"-1011","message":"The system is under maintenance.","success":false)
                    'symbol must not be blank' => '\\ccxt\\BadRequest', // when sending 'cancelOrder' without symbol [-1005]
                    'The token is not supported' => '\\ccxt\\BadRequest', // when getting incorrect token's deposit address [-1005]
                    'Your order and symbol are not valid or already canceled' => '\\ccxt\\BadRequest', // actual response whensending 'cancelOrder' for already canceled id [-1006]
                    'Insufficient WOO. Please enable margin trading for leverage trading' => '\\ccxt\\BadRequest', // when selling insufficent token [-1012]
                ),
            ),
            'precisionMode' => TICK_SIZE,
        ));
    }

    public function fetch_status($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             * the latest known information on the availability of the exchange API
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/systemInfo
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-$status-structure $status structure~
             */
            $response = Async\await($this->v3PublicGetSystemInfo ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "status" => 0,
            //             "msg" => "System is functioning properly.",
            //             "estimatedEndTime" => 1749963600362
            //         ),
            //         "timestamp" => 1751442989564
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $status = $this->safe_string($data, 'status');
            if ($status === null) {
                $status = 'error';
            } elseif ($status === '0') {
                $status = 'ok';
            } else {
                $status = 'maintenance';
            }
            return array(
                'status' => $status,
                'updated' => null,
                'eta' => null,
                'url' => null,
                'info' => $response,
            );
        }) ();
    }

    public function fetch_time($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches the current integer timestamp in milliseconds from the exchange server
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/systemInfo
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int} the current integer timestamp in milliseconds from the exchange server
             */
            $response = Async\await($this->v3PublicGetSystemInfo ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "status" => 0,
            //             "msg" => "System is functioning properly.",
            //             "estimatedEndTime" => 1749963600362
            //         ),
            //         "timestamp" => 1751442989564
            //     }
            //
            return $this->safe_integer($response, 'timestamp');
        }) ();
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves $data on all markets for woo
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/instruments
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market $data
             */
            if ($this->options['adjustForTimeDifference']) {
                Async\await($this->load_time_difference());
            }
            $response = Async\await($this->v3PublicGetInstruments ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 array(
            //                     "symbol" => "SPOT_AAVE_USDT",
            //                     "status" => "TRADING",
            //                     "baseAsset" => "AAVE",
            //                     "baseAssetMultiplier" => 1,
            //                     "quoteAsset" => "USDT",
            //                     "quoteMin" => "0",
            //                     "quoteMax" => "100000",
            //                     "quoteTick" => "0.01",
            //                     "baseMin" => "0.005",
            //                     "baseMax" => "5000",
            //                     "baseTick" => "0.0001",
            //                     "minNotional" => "1",
            //                     "bidCapRatio" => "1.1",
            //                     "bidFloorRatio" => null,
            //                     "askCapRatio" => null,
            //                     "askFloorRatio" => "0.9",
            //                     "orderMode" => "NORMAL",
            //                     "impactNotional" => null,
            //                     "isAllowedRpi" => false,
            //                     "tickGranularity" => null
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1751512951338
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_markets($rows);
        }) ();
    }

    public function parse_market(array $market): array {
        $marketId = $this->safe_string($market, 'symbol');
        $parts = explode('_', $marketId);
        $first = $this->safe_string($parts, 0);
        $spot = false;
        $swap = false;
        if ($first === 'SPOT') {
            $spot = true;
            $marketType = 'spot';
        } elseif ($first === 'PERP') {
            $swap = true;
            $marketType = 'swap';
        }
        $baseId = $this->safe_string($parts, 1);
        $quoteId = $this->safe_string($parts, 2);
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $settleId = null;
        $settle = null;
        $symbol = $base . '/' . $quote;
        $contractSize = null;
        $linear = null;
        $inverse = null;
        $margin = true;
        $contract = $swap;
        if ($contract) {
            $margin = false;
            $settleId = $this->safe_string($parts, 2);
            $settle = $this->safe_currency_code($settleId);
            $symbol = $base . '/' . $quote . ':' . $settle;
            $contractSize = $this->parse_number('1');
            $linear = true;
            $inverse = false;
        }
        $active = $this->safe_string($market, 'status') === 'TRADING';
        return array(
            'id' => $marketId,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $marketType,
            'spot' => $spot,
            'margin' => $margin,
            'swap' => $swap,
            'future' => false,
            'option' => false,
            'active' => $active,
            'contract' => $contract,
            'linear' => $linear,
            'inverse' => $inverse,
            'contractSize' => $contractSize,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'baseTick'),
                'price' => $this->safe_number($market, 'quoteTick'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'baseMin'),
                    'max' => $this->safe_number($market, 'baseMax'),
                ),
                'price' => array(
                    'min' => $this->safe_number($market, 'quoteMin'),
                    'max' => $this->safe_number($market, 'quoteMax'),
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'minNotional'),
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        );
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent trades for a particular $symbol
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/marketTrades
             *
             * @param {string} $symbol unified $symbol of the $market to fetch trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch
             * @param {int} [$limit] the maximum amount of trades to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->v3PublicGetMarketTrades ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 array(
            //                     "symbol" => "SPOT_BTC_USDT",
            //                     "side" => "SELL",
            //                     "source" => 0,
            //                     "executedPrice" => "108741.01",
            //                     "executedQuantity" => "0.02477",
            //                     "executedTimestamp" => 1751513940144
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1751513988543
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_trades($rows, $market, $since, $limit);
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // public/market_trades
        //
        //     {
        //         "symbol" => "SPOT_BTC_USDT",
        //         "side" => "SELL",
        //         "source" => 0,
        //         "executedPrice" => "108741.01",
        //         "executedQuantity" => "0.02477",
        //         "executedTimestamp" => 1751513940144
        //     }
        //
        // fetchOrderTrades, fetchOrder
        //
        //     {
        //         "id" => 1734947821,
        //         "symbol" => "SPOT_LTC_USDT",
        //         "orderId" => 60780383217,
        //         "executedPrice" => 87.86,
        //         "executedQuantity" => 0.1,
        //         "fee" => 0.0001,
        //         "realizedPnl" => null,
        //         "feeAsset" => "LTC",
        //         "orderTag" => "default",
        //         "side" => "BUY",
        //         "executedTimestamp" => "1752055173.630",
        //         "isMaker" => 0
        //     }
        //
        $isFromFetchOrder = (is_array($trade) && array_key_exists('id', $trade));
        $timestampString = $this->safe_string_2($trade, 'executed_timestamp', 'executedTimestamp');
        $timestamp = null;
        if ($timestampString !== null) {
            if (mb_strpos($timestampString, '.') > -1) {
                $timestamp = $this->safe_timestamp_2($trade, 'executed_timestamp', 'executedTimestamp');
            } else {
                $timestamp = $this->safe_integer($trade, 'executedTimestamp');
            }
        }
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->safe_string_2($trade, 'executed_price', 'executedPrice');
        $amount = $this->safe_string_2($trade, 'executed_quantity', 'executedQuantity');
        $order_id = $this->safe_string_2($trade, 'order_id', 'orderId');
        $fee = $this->parse_token_and_fee_temp($trade, array( 'fee_asset', 'feeAsset' ), array( 'fee' ));
        $feeCost = $this->safe_string($fee, 'cost');
        if ($feeCost !== null) {
            $fee['cost'] = $feeCost;
        }
        $cost = Precise::string_mul($price, $amount);
        $side = $this->safe_string_lower($trade, 'side');
        $id = $this->safe_string($trade, 'id');
        $takerOrMaker = null;
        if ($isFromFetchOrder) {
            $isMaker = $this->safe_string_2($trade, 'is_maker', 'isMaker') === '1';
            $takerOrMaker = $isMaker ? 'maker' : 'taker';
        }
        return $this->safe_trade(array(
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'side' => $side,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'order' => $order_id,
            'takerOrMaker' => $takerOrMaker,
            'type' => null,
            'fee' => $fee,
            'info' => $trade,
        ), $market);
    }

    public function parse_token_and_fee_temp($item, $feeTokenKeys, $feeAmountKeys) {
        $feeCost = $this->safe_string_n($item, $feeAmountKeys);
        $fee = null;
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string_n($item, $feeTokenKeys);
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        return $fee;
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        $marketId = $this->safe_string($fee, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        return array(
            'info' => $fee,
            'symbol' => $symbol,
            'maker' => $this->parse_number(Precise::string_div($this->safe_string($fee, 'makerFee'), '100')),
            'taker' => $this->parse_number(Precise::string_div($this->safe_string($fee, 'takerFee'), '100')),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the trading fees for a $market
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_tradingFee
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->portfolioMargin] set to true if you would like to fetch trading fees in a portfolio margin account
             * @param {string} [$params->subType] "linear" or "inverse"
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->v3PrivateGetTradeTradingFee ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "symbol" => "SPOT_BTC_USDT",
            //             "takerFee" => "10",
            //             "makerFee" => "8"
            //         ),
            //         "timestamp" => 1751858977368
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_trading_fee($data, $market);
        }) ();
    }

    public function fetch_trading_fees($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch the trading fees for multiple markets
             *
             * @see https://developer.woox.io/api-reference/endpoint/account/get_account_info
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by market symbols
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v3PrivateGetAccountInfo ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "applicationId" => "251bf5c4-f3c8-4544-bb8b-80001007c3c0",
            //             "account" => "carlos_jose_lima@yahoo.com",
            //             "alias" => "carlos_jose_lima@yahoo.com",
            //             "otpauth" => true,
            //             "accountMode" => "FUTURES",
            //             "positionMode" => "ONE_WAY",
            //             "leverage" => 0,
            //             "makerFeeRate" => 0,
            //             "takerFeeRate" => 0,
            //             "marginRatio" => "10",
            //             "openMarginRatio" => "10",
            //             "initialMarginRatio" => "10",
            //             "maintenanceMarginRatio" => "0.03",
            //             "totalCollateral" => "165.55629469",
            //             "freeCollateral" => "165.55629469",
            //             "totalAccountValue" => "167.32418611",
            //             "totalTradingValue" => "167.32418611",
            //             "totalVaultValue" => "0",
            //             "totalStakingValue" => "0",
            //             "totalLaunchpadValue" => "0",
            //             "totalEarnValue" => "0",
            //             "referrerID" => null,
            //             "accountType" => "Main"
            //         ),
            //         "timestamp" => 1752062807915
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $maker = $this->safe_string($data, 'makerFeeRate');
            $taker = $this->safe_string($data, 'takerFeeRate');
            $result = array();
            for ($i = 0; $i < count($this->symbols); $i++) {
                $symbol = $this->symbols[$i];
                $result[$symbol] = array(
                    'info' => $response,
                    'symbol' => $symbol,
                    'maker' => $this->parse_number(Precise::string_div($maker, '10000')),
                    'taker' => $this->parse_number(Precise::string_div($taker, '10000')),
                    'percentage' => true,
                    'tierBased' => true,
                );
            }
            return $result;
        }) ();
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://docs.woox.io/#available-token-public
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            $result = array();
            $tokenResponsePromise = $this->v1PublicGetToken ($params);
            //
            //    {
            //      "rows" => array(
            //         array(
            //             "token" => "ETH_USDT",
            //             "fullname" => "Tether",
            //             "network" => "ETH",
            //             "decimals" => "6",
            //             "delisted" => false,
            //             "balance_token" => "USDT",
            //             "created_time" => "1710123398",
            //             "updated_time" => "1746528481",
            //             "can_collateral" => true,
            //             "can_short" => true
            //         ),
            //         array(
            //             "token" => "BSC_USDT",
            //             "fullname" => "Tether",
            //             "network" => "BSC",
            //             "decimals" => "18",
            //             "delisted" => false,
            //             "balance_token" => "USDT",
            //             "created_time" => "1710123395",
            //             "updated_time" => "1746528601",
            //             "can_collateral" => true,
            //             "can_short" => true
            //         ),
            //         array(
            //             "token" => "ALGO",
            //             "fullname" => "Algorand",
            //             "network" => "ALGO",
            //             "decimals" => "6",
            //             "delisted" => false,
            //             "balance_token" => "ALGO",
            //             "created_time" => "1710123394",
            //             "updated_time" => "1723087518",
            //             "can_collateral" => true,
            //             "can_short" => true
            //         ),
            //         ...
            //     ),
            //     "success" => true
            // }
            //
            // only make one request for currrencies...
            $tokenNetworkResponsePromise = $this->v1PublicGetTokenNetwork ($params);
            //
            // {
            //     "rows" => array(
            //         array(
            //             "protocol" => "ERC20",
            //             "network" => "ETH",
            //             "token" => "USDT",
            //             "name" => "Ethereum (ERC20)",
            //             "minimum_withdrawal" => "10.00000000",
            //             "withdrawal_fee" => "2.00000000",
            //             "allow_deposit" => "1",
            //             "allow_withdraw" => "1"
            //         ),
            //         array(
            //             "protocol" => "TRC20",
            //             "network" => "TRX",
            //             "token" => "USDT",
            //             "name" => "Tron (TRC20)",
            //             "minimum_withdrawal" => "10.00000000",
            //             "withdrawal_fee" => "4.50000000",
            //             "allow_deposit" => "1",
            //             "allow_withdraw" => "1"
            //         ),
            //         ...
            //     ),
            //     "success" => true
            // }
            //
            list($tokenResponse, $tokenNetworkResponse) = Async\await(Promise\all(array( $tokenResponsePromise, $tokenNetworkResponsePromise )));
            $tokenRows = $this->safe_list($tokenResponse, 'rows', array());
            $tokenNetworkRows = $this->safe_list($tokenNetworkResponse, 'rows', array());
            $networksById = $this->group_by($tokenNetworkRows, 'token');
            $tokensById = $this->group_by($tokenRows, 'balance_token');
            $currencyIds = is_array($tokensById) ? array_keys($tokensById) : array();
            for ($i = 0; $i < count($currencyIds); $i++) {
                $currencyId = $currencyIds[$i];
                $code = $this->safe_currency_code($currencyId);
                $tokensByNetworkId = $this->index_by($tokensById[$currencyId], 'network');
                $chainsByNetworkId = $this->index_by($networksById[$currencyId], 'network');
                $keys = is_array($chainsByNetworkId) ? array_keys($chainsByNetworkId) : array();
                $resultingNetworks = array();
                for ($j = 0; $j < count($keys); $j++) {
                    $networkId = $keys[$j];
                    $tokenEntry = $this->safe_dict($tokensByNetworkId, $networkId, array());
                    $networkEntry = $this->safe_dict($chainsByNetworkId, $networkId, array());
                    $networkCode = $this->network_id_to_code($networkId, $code);
                    $specialNetworkId = $this->safe_string($tokenEntry, 'token');
                    $resultingNetworks[$networkCode] = array(
                        'id' => $networkId,
                        'currencyNetworkId' => $specialNetworkId, // exchange uses special crrency-ids (coin . network junction)
                        'network' => $networkCode,
                        'active' => null,
                        'deposit' => $this->safe_string($networkEntry, 'allow_deposit') === '1',
                        'withdraw' => $this->safe_string($networkEntry, 'allow_withdraw') === '1',
                        'fee' => $this->safe_number($networkEntry, 'withdrawal_fee'),
                        'precision' => $this->parse_number($this->parse_precision($this->safe_string($tokenEntry, 'decimals'))),
                        'limits' => array(
                            'withdraw' => array(
                                'min' => $this->safe_number($networkEntry, 'minimum_withdrawal'),
                                'max' => null,
                            ),
                            'deposit' => array(
                                'min' => null,
                                'max' => null,
                            ),
                        ),
                        'info' => array( $networkEntry, $tokenEntry ),
                    );
                }
                $result[$code] = $this->safe_currency_structure(array(
                    'id' => $currencyId,
                    'name' => null,
                    'code' => $code,
                    'precision' => null,
                    'active' => null,
                    'fee' => null,
                    'networks' => $resultingNetworks,
                    'deposit' => null,
                    'withdraw' => null,
                    'type' => 'crypto',
                    'limits' => array(
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'info' => array( $tokensByNetworkId, $chainsByNetworkId ),
                ));
            }
            return $result;
        }) ();
    }

    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://docs.woox.io/#send-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');
            }
            return Async\await($this->create_order($symbol, 'market', 'buy', $cost, 1, $params));
        }) ();
    }

    public function create_market_sell_order_with_cost(string $symbol, float $cost, $params = array ()) {
        return Async\async(function () use ($symbol, $cost, $params) {
            /**
             * create a $market sell order by providing the $symbol and $cost
             *
             * @see https://docs.woox.io/#send-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 . ' createMarketSellOrderWithCost() supports spot orders only');
            }
            return Async\await($this->create_order($symbol, 'market', 'sell', $cost, 1, $params));
        }) ();
    }

    public function create_trailing_amount_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, ?float $trailingAmount = null, ?float $trailingTriggerPrice = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $trailingAmount, $trailingTriggerPrice, $params) {
            /**
             * create a trailing order by providing the $symbol, $type, $side, $amount, $price and $trailingAmount
             *
             * @see https://docs.woox.io/#send-algo-order
             *
             * @param {string} $symbol unified $symbol of the market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much you want to trade in units of the base currency, or number of contracts
             * @param {float} [$price] the $price for the order to be filled at, in units of the quote currency, ignored in market orders
             * @param {float} $trailingAmount the quote $amount to trail away from the current market $price
             * @param {float} $trailingTriggerPrice the $price to activate a trailing order, default uses the $price argument
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            if ($trailingAmount === null) {
                throw new ArgumentsRequired($this->id . ' createTrailingAmountOrder() requires a $trailingAmount argument');
            }
            if ($trailingTriggerPrice === null) {
                throw new ArgumentsRequired($this->id . ' createTrailingAmountOrder() requires a $trailingTriggerPrice argument');
            }
            $params['trailingAmount'] = $trailingAmount;
            $params['trailingTriggerPrice'] = $trailingTriggerPrice;
            return Async\await($this->create_order($symbol, $type, $side, $amount, $price, $params));
        }) ();
    }

    public function create_trailing_percent_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, ?float $trailingPercent = null, ?float $trailingTriggerPrice = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $trailingPercent, $trailingTriggerPrice, $params) {
            /**
             * create a trailing order by providing the $symbol, $type, $side, $amount, $price and $trailingPercent
             *
             * @see https://docs.woox.io/#send-algo-order
             *
             * @param {string} $symbol unified $symbol of the market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much you want to trade in units of the base currency, or number of contracts
             * @param {float} [$price] the $price for the order to be filled at, in units of the quote currency, ignored in market orders
             * @param {float} $trailingPercent the percent to trail away from the current market $price
             * @param {float} $trailingTriggerPrice the $price to activate a trailing order, default uses the $price argument
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            if ($trailingPercent === null) {
                throw new ArgumentsRequired($this->id . ' createTrailingPercentOrder() requires a $trailingPercent argument');
            }
            if ($trailingTriggerPrice === null) {
                throw new ArgumentsRequired($this->id . ' createTrailingPercentOrder() requires a $trailingTriggerPrice argument');
            }
            $params['trailingPercent'] = $trailingPercent;
            $params['trailingTriggerPrice'] = $trailingTriggerPrice;
            return Async\await($this->create_order($symbol, $type, $side, $amount, $price, $params));
        }) ();
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade order
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/post_order
             * @see https://developer.woox.io/api-reference/endpoint/trading/post_algo_order
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] *for swap markets only* 'cross' or 'isolated', default 'cross'
             * @param {float} [$params->triggerPrice] The $price a trigger order is triggered at
             * @param {array} [$params->takeProfit] *$takeProfit object in $params* containing the $triggerPrice at which the attached take profit order will be triggered (perpetual swap markets only)
             * @param {float} [$params->takeProfit.triggerPrice] take profit trigger $price
             * @param {array} [$params->stopLoss] *$stopLoss object in $params* containing the $triggerPrice at which the attached stop loss order will be triggered (perpetual swap markets only)
             * @param {float} [$params->stopLoss.triggerPrice] stop loss trigger $price
             * @param {float} [$params->algoType] 'STOP' or 'TRAILING_STOP' or 'OCO' or 'CLOSE_POSITION'
             * @param {float} [$params->cost] *spot $market buy only* the quote quantity that can be used alternative for the $amount
             * @param {string} [$params->trailingAmount] the quote $amount to trail away from the current $market $price
             * @param {string} [$params->trailingPercent] the percent to trail away from the current $market $price
             * @param {string} [$params->trailingTriggerPrice] the $price to trigger a trailing order, default uses the $price argument
             * @param {string} [$params->position_side] 'SHORT' or 'LONG' - if position mode is HEDGE_MODE and the trading involves futures, then is required, otherwise this parameter is not required
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            $reduceOnly = $this->safe_bool_2($params, 'reduceOnly', 'reduce_only');
            $params = $this->omit($params, array( 'reduceOnly', 'reduce_only' ));
            $orderType = strtoupper($type);
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $orderSide = strtoupper($side);
            $request = array(
                'symbol' => $market['id'],
                'side' => $orderSide,
            );
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('createOrder', $params);
            if ($marginMode !== null) {
                $request['marginMode'] = $this->encode_margin_mode($marginMode);
            }
            $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
            $stopLoss = $this->safe_value($params, 'stopLoss');
            $takeProfit = $this->safe_value($params, 'takeProfit');
            $algoType = $this->safe_string($params, 'algoType');
            $trailingTriggerPrice = $this->safe_string_2($params, 'trailingTriggerPrice', 'activatedPrice', $this->number_to_string($price));
            $trailingAmount = $this->safe_string_2($params, 'trailingAmount', 'callbackValue');
            $trailingPercent = $this->safe_string_2($params, 'trailingPercent', 'callbackRate');
            $isTrailingAmountOrder = $trailingAmount !== null;
            $isTrailingPercentOrder = $trailingPercent !== null;
            $isTrailing = $isTrailingAmountOrder || $isTrailingPercentOrder;
            $isConditional = $isTrailing || $triggerPrice !== null || $stopLoss !== null || $takeProfit !== null || ($this->safe_value($params, 'childOrders') !== null);
            $isMarket = $orderType === 'MARKET';
            $timeInForce = $this->safe_string_lower($params, 'timeInForce');
            $postOnly = $this->is_post_only($isMarket, null, $params);
            $clientOrderIdKey = $isConditional ? 'clientAlgoOrderId' : 'clientOrderId';
            $request['type'] = $orderType; // LIMIT/MARKET/IOC/FOK/POST_ONLY/ASK/BID
            if (!$isConditional) {
                if ($postOnly) {
                    $request['type'] = 'POST_ONLY';
                } elseif ($timeInForce === 'fok') {
                    $request['type'] = 'FOK';
                } elseif ($timeInForce === 'ioc') {
                    $request['type'] = 'IOC';
                }
            }
            if ($reduceOnly) {
                $request['reduceOnly'] = $reduceOnly;
            }
            if (!$isMarket && $price !== null) {
                $request['price'] = $this->price_to_precision($symbol, $price);
            }
            if ($isMarket && !$isConditional) {
                // for $market buy it requires the $amount of quote currency to spend
                $cost = $this->safe_string_n($params, array( 'cost', 'order_amount', 'orderAmount' ));
                $params = $this->omit($params, array( 'cost', 'order_amount', 'orderAmount' ));
                $isPriceProvided = $price !== null;
                if ($market['spot'] && ($isPriceProvided || ($cost !== null))) {
                    $quoteAmount = null;
                    if ($cost !== null) {
                        $quoteAmount = $this->cost_to_precision($symbol, $cost);
                    } else {
                        $amountString = $this->number_to_string($amount);
                        $priceString = $this->number_to_string($price);
                        $costRequest = Precise::string_mul($amountString, $priceString);
                        $quoteAmount = $this->cost_to_precision($symbol, $costRequest);
                    }
                    $request['amount'] = $quoteAmount;
                } else {
                    $request['quantity'] = $this->amount_to_precision($symbol, $amount);
                }
            } elseif ($algoType !== 'POSITIONAL_TP_SL') {
                $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            }
            $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
            if ($clientOrderId !== null) {
                $request[$clientOrderIdKey] = $clientOrderId;
            }
            if ($isTrailing) {
                if ($trailingTriggerPrice === null) {
                    throw new ArgumentsRequired($this->id . ' createOrder() requires a $trailingTriggerPrice parameter for trailing orders');
                }
                $request['activatedPrice'] = $this->price_to_precision($symbol, $trailingTriggerPrice);
                $request['algoType'] = 'TRAILING_STOP';
                if ($isTrailingAmountOrder) {
                    $request['callbackValue'] = $trailingAmount;
                } elseif ($isTrailingPercentOrder) {
                    $convertedTrailingPercent = Precise::string_div($trailingPercent, '100');
                    $request['callbackRate'] = $convertedTrailingPercent;
                }
            } elseif ($triggerPrice !== null) {
                if ($algoType !== 'TRAILING_STOP') {
                    $request['triggerPrice'] = $this->price_to_precision($symbol, $triggerPrice);
                    $request['algoType'] = 'STOP';
                }
            } elseif (($stopLoss !== null) || ($takeProfit !== null)) {
                $request['algoType'] = 'BRACKET';
                $outterOrder = array(
                    'symbol' => $market['id'],
                    'reduceOnly' => false,
                    'algoType' => 'POSITIONAL_TP_SL',
                    'childOrders' => array(),
                );
                $childOrders = $outterOrder['childOrders'];
                $closeSide = ($orderSide === 'BUY') ? 'SELL' : 'BUY';
                if ($stopLoss !== null) {
                    $stopLossPrice = $this->safe_string($stopLoss, 'triggerPrice', $stopLoss);
                    $stopLossOrder = array(
                        'side' => $closeSide,
                        'algoType' => 'STOP_LOSS',
                        'triggerPrice' => $this->price_to_precision($symbol, $stopLossPrice),
                        'type' => 'CLOSE_POSITION',
                        'reduceOnly' => true,
                    );
                    $childOrders[] = $stopLossOrder;
                }
                if ($takeProfit !== null) {
                    $takeProfitPrice = $this->safe_string($takeProfit, 'triggerPrice', $takeProfit);
                    $takeProfitOrder = array(
                        'side' => $closeSide,
                        'algoType' => 'TAKE_PROFIT',
                        'triggerPrice' => $this->price_to_precision($symbol, $takeProfitPrice),
                        'type' => 'CLOSE_POSITION',
                        'reduceOnly' => true,
                    );
                    $childOrders[] = $takeProfitOrder;
                }
                $request['childOrders'] = array( $outterOrder );
            }
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id', 'postOnly', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLoss', 'takeProfit', 'trailingPercent', 'trailingAmount', 'trailingTriggerPrice' ));
            $response = null;
            if ($isConditional) {
                $response = Async\await($this->v3PrivatePostTradeAlgoOrder ($this->extend($request, $params)));
                //
                // {
                //     "success" => true,
                //     "data" => {
                //       "rows" => array(
                //         array(
                //           "orderId" => "1578938",
                //           "clientOrderId" => "0",
                //           "algoType" => "STOP_LOSS",
                //           "quantity" => "0.1"
                //         }
                //       )
                //     ),
                //     "timestamp" => "1686149372216"
                // }
                //
            } else {
                $response = Async\await($this->v3PrivatePostTradeOrder ($this->extend($request, $params)));
                //
                //     {
                //         "success" => true,
                //         "data" => array(
                //             "orderId" => 60667653330,
                //             "clientOrderId" => 0,
                //             "type" => "LIMIT",
                //             "price" => 60,
                //             "quantity" => 0.1,
                //             "amount" => null,
                //             "bidAskLevel" => null
                //         ),
                //         "timestamp" => 1751871779855
                //     }
                //
            }
            $data = $this->safe_dict($response, 'data', array());
            $data = $this->safe_dict($this->safe_list($data, 'rows'), 0, $data);
            $data['timestamp'] = $this->safe_string($response, 'timestamp');
            return $this->parse_order($data, $market);
        }) ();
    }

    public function encode_margin_mode($mode) {
        $modes = array(
            'cross' => 'CROSS',
            'isolated' => 'ISOLATED',
        );
        return $this->safe_string($modes, $mode, $mode);
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $type, $side, $amount, $price, $params) {
            /**
             * edit a trade order
             *
             * @see https://docs.woox.io/#edit-order
             * @see https://docs.woox.io/#edit-order-by-client_order_id
             * @see https://docs.woox.io/#edit-algo-order
             * @see https://docs.woox.io/#edit-algo-order-by-client_order_id
             *
             * @param {string} $id order $id
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {float} [$params->triggerPrice] The $price a trigger order is triggered at
             * @param {float} [$params->stopLossPrice] $price to trigger stop-loss orders
             * @param {float} [$params->takeProfitPrice] $price to trigger take-profit orders
             * @param {string} [$params->trailingAmount] the quote $amount to trail away from the current $market $price
             * @param {string} [$params->trailingPercent] the percent to trail away from the current $market $price
             * @param {string} [$params->trailingTriggerPrice] the $price to trigger a trailing order, default uses the $price argument
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                // 'quantity' => $this->amount_to_precision($symbol, $amount),
                // 'price' => $this->price_to_precision($symbol, $price),
            );
            if ($price !== null) {
                $request['price'] = $this->price_to_precision($symbol, $price);
            }
            if ($amount !== null) {
                $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            }
            $clientOrderIdUnified = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
            $clientOrderIdExchangeSpecific = $this->safe_string($params, 'client_order_id', $clientOrderIdUnified);
            $isByClientOrder = $clientOrderIdExchangeSpecific !== null;
            $triggerPrice = $this->safe_number_n($params, array( 'triggerPrice', 'stopPrice', 'takeProfitPrice', 'stopLossPrice' ));
            if ($triggerPrice !== null) {
                $request['triggerPrice'] = $this->price_to_precision($symbol, $triggerPrice);
            }
            $trailingTriggerPrice = $this->safe_string_2($params, 'trailingTriggerPrice', 'activatedPrice', $this->number_to_string($price));
            $trailingAmount = $this->safe_string_2($params, 'trailingAmount', 'callbackValue');
            $trailingPercent = $this->safe_string_2($params, 'trailingPercent', 'callbackRate');
            $isTrailingAmountOrder = $trailingAmount !== null;
            $isTrailingPercentOrder = $trailingPercent !== null;
            $isTrailing = $isTrailingAmountOrder || $isTrailingPercentOrder;
            if ($isTrailing) {
                if ($trailingTriggerPrice !== null) {
                    $request['activatedPrice'] = $this->price_to_precision($symbol, $trailingTriggerPrice);
                }
                if ($isTrailingAmountOrder) {
                    $request['callbackValue'] = $trailingAmount;
                } elseif ($isTrailingPercentOrder) {
                    $convertedTrailingPercent = Precise::string_div($trailingPercent, '100');
                    $request['callbackRate'] = $convertedTrailingPercent;
                }
            }
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id', 'stopPrice', 'triggerPrice', 'takeProfitPrice', 'stopLossPrice', 'trailingTriggerPrice', 'trailingAmount', 'trailingPercent' ));
            $isConditional = $isTrailing || ($triggerPrice !== null) || ($this->safe_value($params, 'childOrders') !== null);
            $response = null;
            if ($isByClientOrder) {
                $request['client_order_id'] = $clientOrderIdExchangeSpecific;
                if ($isConditional) {
                    $response = Async\await($this->v3PrivatePutAlgoOrderClientClientOrderId ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->v3PrivatePutOrderClientClientOrderId ($this->extend($request, $params)));
                }
            } else {
                $request['oid'] = $id;
                if ($isConditional) {
                    $response = Async\await($this->v3PrivatePutAlgoOrderOid ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->v3PrivatePutOrderOid ($this->extend($request, $params)));
                }
            }
            //
            //     {
            //         "code" => 0,
            //         "data" => array(
            //             "status" => "string",
            //             "success" => true
            //         ),
            //         "message" => "string",
            //         "success" => true,
            //         "timestamp" => 0
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data, $market);
        }) ();
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/cancel_order
             * @see https://developer.woox.io/api-reference/endpoint/trading/cancel_algo_order
             *
             * cancels an open order
             * @param {string} $id order $id
             * @param {string} $symbol unified $symbol of the $market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a trigger/algo order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            $isTrigger = $this->safe_bool_2($params, 'trigger', 'stop', false);
            $params = $this->omit($params, array( 'trigger', 'stop' ));
            if (!$isTrigger && ($symbol === null)) {
                throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array();
            $clientOrderIdUnified = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
            $clientOrderIdExchangeSpecific = $this->safe_string($params, 'client_order_id', $clientOrderIdUnified);
            $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
            $isByClientOrder = $clientOrderIdExchangeSpecific !== null;
            $response = null;
            if ($isTrigger) {
                if ($isByClientOrder) {
                    $request['clientAlgoOrderId'] = $clientOrderIdExchangeSpecific;
                } else {
                    $request['algoOrderId'] = $id;
                }
                $response = Async\await($this->v3PrivateDeleteTradeAlgoOrder ($this->extend($request, $params)));
            } else {
                $request['symbol'] = $market['id'];
                if ($isByClientOrder) {
                    $request['clientOrderId'] = $clientOrderIdExchangeSpecific;
                } else {
                    $request['orderId'] = $id;
                }
                $response = Async\await($this->v3PrivateDeleteTradeOrder ($this->extend($request, $params)));
            }
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "status" => "CANCEL_SENT"
            //         ),
            //         "timestamp" => 1751940315838
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $data['timestamp'] = $this->safe_string($response, 'timestamp');
            if ($isByClientOrder) {
                $data['clientOrderId'] = $clientOrderIdExchangeSpecific;
            } else {
                $data['orderId'] = $id;
            }
            return $this->parse_order($data, $market);
        }) ();
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/cancel_all_order
             * @see https://developer.woox.io/api-reference/endpoint/trading/cancel_algo_orders
             *
             * cancel all open orders in a $market
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a trigger/algo order
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $trigger = $this->safe_bool_2($params, 'stop', 'trigger');
            $params = $this->omit($params, array( 'stop', 'trigger' ));
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $response = null;
            if ($trigger) {
                $response = Async\await($this->v3PrivateDeleteTradeAlgoOrders ($params));
            } else {
                $response = Async\await($this->v3PrivateDeleteTradeOrders ($this->extend($request, $params)));
            }
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "status" => "CANCEL_ALL_SENT"
            //         ),
            //         "timestamp" => 1751941988134
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return array( $this->safe_order(array( 'info' => $data )) );
        }) ();
    }

    public function cancel_all_orders_after(?int $timeout, $params = array ()) {
        return Async\async(function () use ($timeout, $params) {
            /**
             * dead man's switch, cancel all orders after the given $timeout
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/cancel_all_after
             *
             * @param {number} $timeout time in milliseconds, 0 represents cancel the timer
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} the api result
             */
            Async\await($this->load_markets());
            $request = array(
                'triggerAfter' => ($timeout > 0) ? min ($timeout, 900000) : 0,
            );
            $response = Async\await($this->v3PrivatePostTradeCancelAllAfter ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 123,
            //     "data" => {
            //         "expectedTriggerTime" => 123
            //     }
            // }
            //
            return $response;
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_order
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_algo_order
             *
             * fetches information on an order made by the user
             * @param {string} $id the order $id
             * @param {string} $symbol unified $symbol of the $market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a trigger/algo order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $trigger = $this->safe_bool_2($params, 'stop', 'trigger');
            $params = $this->omit($params, array( 'stop', 'trigger' ));
            $request = array();
            $clientOrderId = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
            $response = null;
            if ($trigger) {
                if ($clientOrderId !== null) {
                    $request['clientAlgoOrderId'] = $id;
                } else {
                    $request['algoOrderId'] = $id;
                }
                $response = Async\await($this->v3PrivateGetTradeAlgoOrder ($this->extend($request, $params)));
                //
                //     {
                //         "success" => true,
                //         "data" => array(
                //             "algoOrderId" => 10399260,
                //             "clientAlgoOrderId" => 0,
                //             "rootAlgoOrderId" => 10399260,
                //             "parentAlgoOrderId" => 0,
                //             "symbol" => "SPOT_LTC_USDT",
                //             "algoOrderTag" => "default",
                //             "algoType" => "TAKE_PROFIT",
                //             "side" => "BUY",
                //             "quantity" => 0.1,
                //             "isTriggered" => false,
                //             "triggerPrice" => 65,
                //             "triggerStatus" => "USELESS",
                //             "type" => "LIMIT",
                //             "rootAlgoStatus" => "NEW",
                //             "algoStatus" => "NEW",
                //             "triggerPriceType" => "MARKET_PRICE",
                //             "price" => 60,
                //             "triggerTime" => "0",
                //             "totalExecutedQuantity" => 0,
                //             "visibleQuantity" => 0.1,
                //             "averageExecutedPrice" => 0,
                //             "totalFee" => 0,
                //             "feeAsset" => "",
                //             "totalRebate" => 0,
                //             "rebateAsset" => "",
                //             "reduceOnly" => false,
                //             "createdTime" => "1752049747.732",
                //             "updatedTime" => "1752049747.732",
                //             "positionSide" => "BOTH"
                //         ),
                //         "timestamp" => 1752049767550
                //     }
                //
            } else {
                if ($clientOrderId !== null) {
                    $request['clientOrderId'] = $clientOrderId;
                } else {
                    $request['orderId'] = $id;
                }
                $response = Async\await($this->v3PrivateGetTradeOrder ($this->extend($request, $params)));
                //
                //     {
                //         "success" => true,
                //         "data" => array(
                //             "orderId" => 60780315704,
                //             "clientOrderId" => 0,
                //             "symbol" => "SPOT_LTC_USDT",
                //             "orderTag" => "default",
                //             "side" => "BUY",
                //             "quantity" => 0.1,
                //             "amount" => null,
                //             "type" => "LIMIT",
                //             "status" => "NEW",
                //             "price" => 60,
                //             "executed" => 0,
                //             "visible" => 0.1,
                //             "averageExecutedPrice" => 0,
                //             "totalFee" => 0,
                //             "feeAsset" => "LTC",
                //             "totalRebate" => 0,
                //             "rebateAsset" => "USDT",
                //             "reduceOnly" => false,
                //             "createdTime" => "1752049062.496",
                //             "realizedPnl" => null,
                //             "positionSide" => "BOTH",
                //             "bidAskLevel" => null
                //         ),
                //         "timestamp" => 1752049393466
                //     }
                //
            }
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data, $market);
        }) ();
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple $orders made by the user
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_orders
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_algo_orders
             *
             * @param {string} $symbol unified $market $symbol of the $market $orders were made in
             * @param {int} [$since] the earliest time in ms to fetch $orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a trigger/algo order
             * @param {boolean} [$params->isTriggered] whether the order has been triggered (false by default)
             * @param {string} [$params->side] 'buy' or 'sell'
             * @param {boolean} [$params->paginate] set to true if you want to fetch $orders with pagination
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOrders', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_incremental('fetchOrders', $symbol, $since, $limit, $params, 'page', 500));
            }
            $request = array();
            $market = null;
            $trigger = $this->safe_bool_2($params, 'stop', 'trigger');
            $params = $this->omit($params, array( 'stop', 'trigger' ));
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            $until = $this->safe_integer($params, 'until'); // unified in milliseconds
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['endTime'] = $until;
            }
            if ($limit !== null) {
                $request['size'] = min ($limit, 500);
            }
            $response = null;
            if ($trigger) {
                $response = Async\await($this->v3PrivateGetTradeAlgoOrders ($this->extend($request, $params)));
                //
                //     {
                //         "success" => true,
                //         "data" => {
                //             "rows" => array(
                //                 {
                //                     "algoOrderId" => 10399260,
                //                     "clientAlgoOrderId" => 0,
                //                     "rootAlgoOrderId" => 10399260,
                //                     "parentAlgoOrderId" => 0,
                //                     "symbol" => "SPOT_LTC_USDT",
                //                     "algoOrderTag" => "default",
                //                     "algoType" => "TAKE_PROFIT",
                //                     "side" => "BUY",
                //                     "quantity" => 0.1,
                //                     "isTriggered" => false,
                //                     "triggerPrice" => 65,
                //                     "triggerStatus" => "USELESS",
                //                     "type" => "LIMIT",
                //                     "rootAlgoStatus" => "NEW",
                //                     "algoStatus" => "NEW",
                //                     "triggerPriceType" => "MARKET_PRICE",
                //                     "price" => 60,
                //                     "triggerTime" => "0",
                //                     "totalExecutedQuantity" => 0,
                //                     "visibleQuantity" => 0.1,
                //                     "averageExecutedPrice" => 0,
                //                     "totalFee" => 0,
                //                     "feeAsset" => "",
                //                     "totalRebate" => 0,
                //                     "rebateAsset" => "",
                //                     "reduceOnly" => false,
                //                     "createdTime" => "1752049747.730",
                //                     "updatedTime" => "1752049747.730",
                //                     "positionSide" => "BOTH"
                //                 }
                //             ),
                //             "meta" => array(
                //                 "total" => 7,
                //                 "recordsPerPage" => 1,
                //                 "currentPage" => 1
                //             }
                //         ),
                //         "timestamp" => 1752053127448
                //     }
                //
            } else {
                $response = Async\await($this->v3PrivateGetTradeOrders ($this->extend($request, $params)));
                //
                //     {
                //         "success" => true,
                //         "data" => {
                //             "rows" => array(
                //                 {
                //                     "orderId" => 60780315704,
                //                     "clientOrderId" => 0,
                //                     "symbol" => "SPOT_LTC_USDT",
                //                     "orderTag" => "default",
                //                     "side" => "BUY",
                //                     "quantity" => 0.1,
                //                     "amount" => null,
                //                     "type" => "LIMIT",
                //                     "status" => "NEW",
                //                     "price" => 60,
                //                     "executed" => 0,
                //                     "visible" => 0.1,
                //                     "averageExecutedPrice" => 0,
                //                     "totalFee" => 0,
                //                     "feeAsset" => "LTC",
                //                     "totalRebate" => 0,
                //                     "rebateAsset" => "USDT",
                //                     "reduceOnly" => false,
                //                     "createdTime" => "1752049062.496",
                //                     "realizedPnl" => null,
                //                     "positionSide" => "BOTH",
                //                     "bidAskLevel" => null
                //                 }
                //             ),
                //             "meta" => array(
                //                 "total" => 11,
                //                 "recordsPerPage" => 1,
                //                 "currentPage" => 1
                //             }
                //         ),
                //         "timestamp" => 1752053061236
                //     }
                //
            }
            $data = $this->safe_value($response, 'data', array());
            $orders = $this->safe_list($data, 'rows', array());
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple orders made by the user
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_orders
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_algo_orders
             *
             * @param {string} $symbol unified market $symbol of the market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a trigger/algo order
             * @param {boolean} [$params->isTriggered] whether the order has been triggered (false by default)
             * @param {string} [$params->side] 'buy' or 'sell'
             * @param {boolean} [$params->trailing] set to true if you want to fetch trailing orders
             * @param {boolean} [$params->paginate] set to true if you want to fetch orders with pagination
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $extendedParams = $this->extend($params, array( 'status' => 'INCOMPLETE' ));
            return Async\await($this->fetch_orders($symbol, $since, $limit, $extendedParams));
        }) ();
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple orders made by the user
             *
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_orders
             * @see https://developer.woox.io/api-reference/endpoint/trading/get_algo_orders
             *
             * @param {string} $symbol unified market $symbol of the market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->trigger] whether the order is a trigger/algo order
             * @param {boolean} [$params->isTriggered] whether the order has been triggered (false by default)
             * @param {string} [$params->side] 'buy' or 'sell'
             * @param {boolean} [$params->trailing] set to true if you want to fetch trailing orders
             * @param {boolean} [$params->paginate] set to true if you want to fetch orders with pagination
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $extendedParams = $this->extend($params, array( 'status' => 'COMPLETED' ));
            return Async\await($this->fetch_orders($symbol, $since, $limit, $extendedParams));
        }) ();
    }

    public function parse_time_in_force(?string $timeInForce) {
        $timeInForces = array(
            'ioc' => 'IOC',
            'fok' => 'FOK',
            'post_only' => 'PO',
        );
        return $this->safe_string($timeInForces, $timeInForce, null);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder
        //     {
        //         "orderId" => 60667653330,
        //         "clientOrderId" => 0,
        //         "type" => "LIMIT",
        //         "price" => 60,
        //         "quantity" => 0.1,
        //         "amount" => null,
        //         "bidAskLevel" => null,
        //         "timestamp" => 1751871779855
        //     }
        //
        // createOrder - algo
        //     {
        //         "orderId" => "1578938",
        //         "clientOrderId" => "0",
        //         "algoType" => "STOP_LOSS",
        //         "quantity" => "0.1",
        //         "timestamp" => "1686149372216"
        //     }
        //
        // fetchOrder
        //     {
        //         "orderId" => 60780315704,
        //         "clientOrderId" => 0,
        //         "symbol" => "SPOT_LTC_USDT",
        //         "orderTag" => "default",
        //         "side" => "BUY",
        //         "quantity" => 0.1,
        //         "amount" => null,
        //         "type" => "LIMIT",
        //         "status" => "NEW",
        //         "price" => 60,
        //         "executed" => 0,
        //         "visible" => 0.1,
        //         "averageExecutedPrice" => 0,
        //         "totalFee" => 0,
        //         "feeAsset" => "LTC",
        //         "totalRebate" => 0,
        //         "rebateAsset" => "USDT",
        //         "reduceOnly" => false,
        //         "createdTime" => "1752049062.496",
        //         "realizedPnl" => null,
        //         "positionSide" => "BOTH",
        //         "bidAskLevel" => null
        //     }
        //
        // fetchOrder - algo
        //     {
        //         "algoOrderId" => 10399260,
        //         "clientAlgoOrderId" => 0,
        //         "rootAlgoOrderId" => 10399260,
        //         "parentAlgoOrderId" => 0,
        //         "symbol" => "SPOT_LTC_USDT",
        //         "algoOrderTag" => "default",
        //         "algoType" => "TAKE_PROFIT",
        //         "side" => "BUY",
        //         "quantity" => 0.1,
        //         "isTriggered" => false,
        //         "triggerPrice" => 65,
        //         "triggerStatus" => "USELESS",
        //         "type" => "LIMIT",
        //         "rootAlgoStatus" => "NEW",
        //         "algoStatus" => "NEW",
        //         "triggerPriceType" => "MARKET_PRICE",
        //         "price" => 60,
        //         "triggerTime" => "0",
        //         "totalExecutedQuantity" => 0,
        //         "visibleQuantity" => 0.1,
        //         "averageExecutedPrice" => 0,
        //         "totalFee" => 0,
        //         "feeAsset" => "",
        //         "totalRebate" => 0,
        //         "rebateAsset" => "",
        //         "reduceOnly" => false,
        //         "createdTime" => "1752049747.732",
        //         "updatedTime" => "1752049747.732",
        //         "positionSide" => "BOTH"
        //     }
        //
        $timestamp = $this->safe_timestamp($order, 'createdTime');
        if ($timestamp === null) {
            $timestamp = $this->safe_integer($order, 'timestamp');
        }
        $orderId = $this->safe_string_2($order, 'orderId', 'algoOrderId');
        $clientOrderId = $this->omit_zero($this->safe_string_2($order, 'clientOrderId', 'clientAlgoOrderId')); // Somehow, this always returns 0 for limit $order
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'quantity'); // This is base $amount
        $cost = $this->safe_string($order, 'amount'); // This is quote $amount
        $orderType = $this->safe_string_lower($order, 'type');
        $status = $this->safe_value_2($order, 'status', 'algoStatus');
        $side = $this->safe_string_lower($order, 'side');
        $filled = $this->omit_zero($this->safe_value_2($order, 'executed', 'totalExecutedQuantity'));
        $average = $this->omit_zero($this->safe_string($order, 'averageExecutedPrice'));
        // $remaining = Precise::string_sub($cost, $filled);
        $fee = $this->safe_number($order, 'totalFee');
        $feeCurrency = $this->safe_string($order, 'feeAsset');
        $triggerPrice = $this->safe_number($order, 'triggerPrice');
        $lastUpdateTimestamp = $this->safe_timestamp($order, 'updatedTime');
        return $this->safe_order(array(
            'id' => $orderId,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $lastUpdateTimestamp,
            'status' => $this->parse_order_status($status),
            'symbol' => $symbol,
            'type' => $orderType,
            'timeInForce' => $this->parse_time_in_force($orderType),
            'postOnly' => null, // TO_DO
            'reduceOnly' => $this->safe_bool($order, 'reduceOnly'),
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'takeProfitPrice' => null,
            'stopLossPrice' => null,
            'average' => $average,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => null, // TO_DO
            'cost' => $cost,
            'trades' => null,
            'fee' => array(
                'cost' => $fee,
                'currency' => $feeCurrency,
            ),
            'info' => $order,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        if ($status !== null) {
            $statuses = array(
                'NEW' => 'open',
                'FILLED' => 'closed',
                'CANCEL_SENT' => 'canceled',
                'CANCEL_ALL_SENT' => 'canceled',
                'CANCELLED' => 'canceled',
                'PARTIAL_FILLED' => 'open',
                'REJECTED' => 'rejected',
                'INCOMPLETE' => 'open',
                'COMPLETED' => 'closed',
            );
            return $this->safe_string($statuses, $status, $status);
        }
        return $status;
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/orderbook
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int} [$limit] the maximum amount of order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($limit !== null) {
                $request['maxLevel'] = $limit;
            }
            $response = Async\await($this->v3PublicGetOrderbook ($this->extend($request, $params)));
            //
            // }
            //     {
            //         "success" => true,
            //         "timestamp" => 1751620923344,
            //         "data" => {
            //             "asks" => array(
            //                 {
            //                     "price" => "108924.86",
            //                     "quantity" => "0.032126"
            //                 }
            //             ),
            //             "bids" => array(
            //                 {
            //                     "price" => "108924.85",
            //                     "quantity" => "1.714147"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $timestamp = $this->safe_integer($response, 'timestamp');
            return $this->parse_order_book($data, $symbol, $timestamp, 'bids', 'asks', 'price', 'quantity');
        }) ();
    }

    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) {
            /**
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/klineHistory
             *
             * fetches historical candlestick $data containing the open, high, low, and close price, and the volume of a $market
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV $data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] max=1000, max=100 when $since is defined and is less than (now - (999 * (is_array(ms) && array_key_exists($timeframe, ms))))
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch entries for
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
                'type' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
            );
            if ($limit !== null) {
                $request['limit'] = min ($limit, 1000);
            }
            if ($since !== null) {
                $request['after'] = $since;
            }
            $until = $this->safe_integer($params, 'until');
            $params = $this->omit($params, 'until');
            if ($until !== null) {
                $request['before'] = $until;
            }
            $response = Async\await($this->v3PublicGetKlineHistory ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 array(
            //                     "symbol" => "SPOT_BTC_USDT",
            //                     "open" => "108994.16",
            //                     "close" => "108994.16",
            //                     "high" => "108994.16",
            //                     "low" => "108994.16",
            //                     "volume" => "0",
            //                     "amount" => "0",
            //                     "type" => "1m",
            //                     "startTimestamp" => 1751622120000,
            //                     "endTimestamp" => 1751622180000
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1751622205410
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_ohlcvs($rows, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        return array(
            $this->safe_integer($ohlcv, 'startTimestamp'),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $this->safe_number($ohlcv, 'volume'),
        );
    }

    public function fetch_order_trades(string $id, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $since, $limit, $params) {
            /**
             * fetch all the $trades made from a single order
             *
             * @see https://docs.woox.io/#get-$trades
             *
             * @param {string} $id order $id
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch $trades for
             * @param {int} [$limit] the maximum number of $trades to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array(
                'oid' => $id,
            );
            $response = Async\await($this->v1PrivateGetOrderOidTrades ($this->extend($request, $params)));
            // {
            //     "success" => true,
            //     "rows" => array(
            //       {
            //         "id" => "99111647",
            //         "symbol" => "SPOT_WOO_USDT",
            //         "fee" => "0.0024",
            //         "side" => "BUY",
            //         "executed_timestamp" => "1641482113.084",
            //         "order_id" => "87541111",
            //         "order_tag" => "default",
            //         "executed_price" => "1",
            //         "executed_quantity" => "12",
            //         "fee_asset" => "WOO",
            //         "is_maker" => "1"
            //       }
            //     )
            // }
            $trades = $this->safe_list($response, 'rows', array());
            return $this->parse_trades($trades, $market, $since, $limit, $params);
        }) ();
    }

    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://developer.woox.io/api-reference/endpoint/trading/get_transactions
             *
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch $trades for
             * @param {int} [$limit] the maximum number of $trades structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] set to true if you want to fetch $trades with pagination
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_incremental('fetchMyTrades', $symbol, $since, $limit, $params, 'page', 500));
            }
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            $until = $this->safe_integer($params, 'until'); // unified in milliseconds
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['endTime'] = $until;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->v3PrivateGetTradeTransactionHistory ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 {
            //                     "id" => 1734947821,
            //                     "symbol" => "SPOT_LTC_USDT",
            //                     "orderId" => 60780383217,
            //                     "executedPrice" => 87.86,
            //                     "executedQuantity" => 0.1,
            //                     "fee" => 0.0001,
            //                     "realizedPnl" => null,
            //                     "feeAsset" => "LTC",
            //                     "orderTag" => "default",
            //                     "side" => "BUY",
            //                     "executedTimestamp" => "1752055173.630",
            //                     "isMaker" => 0
            //                 }
            //             ),
            //             "meta" => array(
            //                 "total" => 1,
            //                 "recordsPerPage" => 100,
            //                 "currentPage" => 1
            //             }
            //         ),
            //         "timestamp" => 1752055545121
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $trades = $this->safe_list($data, 'rows', array());
            return $this->parse_trades($trades, $market, $since, $limit, $params);
        }) ();
    }

    public function fetch_accounts($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch all the accounts associated with a profile
             *
             * @see https://developer.woox.io/api-reference/endpoint/account/get_account_info
             * @see https://developer.woox.io/api-reference/endpoint/account/sub_accounts
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=account-structure account structures~ indexed by the account type
             */
            $mainAccountPromise = $this->v3PrivateGetAccountInfo ($params);
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "applicationId" => "251bf5c4-f3c8-4544-bb8b-80001007c3c0",
            //             "account" => "carlos_jose_lima@yahoo.com",
            //             "alias" => "carlos_jose_lima@yahoo.com",
            //             "otpauth" => true,
            //             "accountMode" => "FUTURES",
            //             "positionMode" => "ONE_WAY",
            //             "leverage" => 0,
            //             "marginRatio" => "10",
            //             "openMarginRatio" => "10",
            //             "initialMarginRatio" => "10",
            //             "maintenanceMarginRatio" => "0.03",
            //             "totalCollateral" => "165.55629469",
            //             "freeCollateral" => "165.55629469",
            //             "totalAccountValue" => "167.32418611",
            //             "totalTradingValue" => "167.32418611",
            //             "totalVaultValue" => "0",
            //             "totalStakingValue" => "0",
            //             "totalLaunchpadValue" => "0",
            //             "totalEarnValue" => "0",
            //             "referrerID" => null,
            //             "accountType" => "Main"
            //         ),
            //         "timestamp" => 1752062807915
            //     }
            //
            $subAccountPromise = $this->v3PrivateGetAccountSubAccountsAll ($params);
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "rows" => array(
            //                 array(
            //                     "applicationId" => "6b43de5c-0955-4887-9862-d84e4689f9fe",
            //                     "name" => "sub_account_2",
            //                     "createdTime" => "1606897264.994"
            //                 ),
            //             )
            //         ),
            //         "timestamp" => 1721295317627
            //     }
            //
            list($mainAccountResponse, $subAccountResponse) = Async\await(Promise\all(array( $mainAccountPromise, $subAccountPromise )));
            $mainData = $this->safe_dict($mainAccountResponse, 'data', array());
            $mainRows = array( $mainData );
            $subData = $this->safe_dict($subAccountResponse, 'data', array());
            $subRows = $this->safe_list($subData, 'rows', array());
            $rows = $this->array_concat($mainRows, $subRows);
            return $this->parse_accounts($rows, $params);
        }) ();
    }

    public function parse_account($account) {
        //
        //     {
        //         "applicationId" => "251bf5c4-f3c8-4544-bb8b-80001007c3c0",
        //         "account" => "carlos_jose_lima@yahoo.com",
        //         "alias" => "carlos_jose_lima@yahoo.com",
        //         "otpauth" => true,
        //         "accountMode" => "FUTURES",
        //         "positionMode" => "ONE_WAY",
        //         "leverage" => 0,
        //         "marginRatio" => "10",
        //         "openMarginRatio" => "10",
        //         "initialMarginRatio" => "10",
        //         "maintenanceMarginRatio" => "0.03",
        //         "totalCollateral" => "165.55629469",
        //         "freeCollateral" => "165.55629469",
        //         "totalAccountValue" => "167.32418611",
        //         "totalTradingValue" => "167.32418611",
        //         "totalVaultValue" => "0",
        //         "totalStakingValue" => "0",
        //         "totalLaunchpadValue" => "0",
        //         "totalEarnValue" => "0",
        //         "referrerID" => null,
        //         "accountType" => "Main"
        //     }
        //
        //     {
        //         "applicationId" => "6b43de5c-0955-4887-9862-d84e4689f9fe",
        //         "name" => "sub_account_2",
        //         "createdTime" => "1606897264.994"
        //     }
        //
        return array(
            'info' => $account,
            'id' => $this->safe_string($account, 'applicationId'),
            'name' => $this->safe_string_n($account, array( 'name', 'account', 'alias' )),
            'code' => null,
            'type' => $this->safe_string_lower($account, 'accountType', 'subaccount'),
        );
    }

    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.woox.io/#get-current-holding-get-balance-new
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v3PrivateGetBalances ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "holding" => array(
            //                 array(
            //                     "token" => "0_token",
            //                     "holding" => 1,
            //                     "frozen" => 0,
            //                     "staked" => 0,
            //                     "unbonding" => 0,
            //                     "vault" => 0,
            //                     "interest" => 0,
            //                     "pendingShortQty" => 0,
            //                     "pendingLongQty" => 0,
            //                     "availableBalance" => 0,
            //                     "updatedTime" => 312321.121
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1673323746259
            //     }
            //
            $data = $this->safe_dict($response, 'data');
            return $this->parse_balance($data);
        }) ();
    }

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

    public function fetch_deposit_address(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the deposit address for a $currency associated with this account
             *
             * @see https://developer.woox.io/api-reference/endpoint/assets/get_wallet_deposit
             *
             * @param {string} $code unified $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
             */
            // this method is TODO because of networks unification
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $networkCode = null;
            list($networkCode, $params) = $this->handle_network_code_and_params($params);
            $request = array(
                'token' => $currency['id'],
                'network' => $this->network_code_to_id($networkCode),
            );
            $response = Async\await($this->v3PrivateGetAssetWalletDeposit ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "address" => "0x31d64B3230f8baDD91dE1710A65DF536aF8f7cDa",
            //             "extra" => ""
            //         ),
            //         "timestamp" => 1721300689532
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_deposit_address($data, $currency);
        }) ();
    }

    public function get_dedicated_network_id($currency, array $params): mixed {
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        $networkCode = $this->network_id_to_code($networkCode, $currency['code']);
        $networkEntry = $this->safe_dict($currency['networks'], $networkCode);
        if ($networkEntry === null) {
            $supportedNetworks = is_array($currency['networks']) ? array_keys($currency['networks']) : array();
            throw new BadRequest($this->id . '  can not determine a network code, please provide unified "network" param, one from the following => ' . $this->json($supportedNetworks));
        }
        $currentyNetworkId = $this->safe_string($networkEntry, 'currencyNetworkId');
        return array( $currentyNetworkId, $params );
    }

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

    public function get_asset_history_rows(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            Async\await($this->load_markets());
            $request = array();
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['token'] = $currency['id'];
            }
            $networkCode = null;
            list($networkCode, $params) = $this->handle_network_code_and_params($params);
            if ($networkCode !== null) {
                $request['network'] = $this->network_code_to_id($networkCode);
            }
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            if ($limit !== null) {
                $request['size'] = min ($limit, 1000);
            }
            $transactionType = $this->safe_string($params, 'type');
            $params = $this->omit($params, 'type');
            if ($transactionType !== null) {
                $request['type'] = $transactionType;
            }
            $response = Async\await($this->v3PrivateGetAssetWalletHistory ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 {
            //                     "createdTime" => "1734964440.523",
            //                     "updatedTime" => "1734964614.081",
            //                     "id" => "24122314340000585",
            //                     "externalId" => "241223143600621",
            //                     "applicationId" => "251bf5c4-f3c8-4544-bb8b-80001007c3c0",
            //                     "token" => "ARB_USDCNATIVE",
            //                     "targetAddress" => "0x4d6802d2736daa85e6242ef0dc0f00aa0e68f635",
            //                     "sourceAddress" => "0x63DFE4e34A3bFC00eB0220786238a7C6cEF8Ffc4",
            //                     "extra" => "",
            //                     "type" => "BALANCE",
            //                     "tokenSide" => "WITHDRAW",
            //                     "amount" => "10.00000000",
            //                     "txId" => "0x891ade0a47fd55466bb9d06702bea4edcb75ed9367d9afbc47b93a84f496d2e6",
            //                     "feeToken" => "USDC",
            //                     "feeAmount" => "2",
            //                     "status" => "COMPLETED",
            //                     "confirmingThreshold" => null,
            //                     "confirmedNumber" => null
            //                 }
            //             ),
            //             "meta" => array(
            //                 "total" => 1,
            //                 "records_per_page" => 25,
            //                 "current_page" => 1
            //             }
            //         ),
            //         "timestamp" => 1752485344719
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return array( $currency, $this->safe_list($data, 'rows', array()) );
        }) ();
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the history of changes, actions done by the user or operations that altered balance of the user
             *
             * @see https://developer.woox.io/api-reference/endpoint/assets/get_wallet_history
             *
             * @param {string} [$code] unified $currency $code, default is null
             * @param {int} [$since] timestamp in ms of the earliest ledger entry, default is null
             * @param {int} [$limit] max number of ledger entries to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger ledger structure~
             */
            $currencyRows = Async\await($this->get_asset_history_rows($code, $since, $limit, $params));
            $currency = $this->safe_value($currencyRows, 0);
            $rows = $this->safe_list($currencyRows, 1);
            return $this->parse_ledger($rows, $currency, $since, $limit, $params);
        }) ();
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        //     {
        //         "createdTime" => "1734964440.523",
        //         "updatedTime" => "1734964614.081",
        //         "id" => "24122314340000585",
        //         "externalId" => "241223143600621",
        //         "applicationId" => "251bf5c4-f3c8-4544-bb8b-80001007c3c0",
        //         "token" => "ARB_USDCNATIVE",
        //         "targetAddress" => "0x4d6802d2736daa85e6242ef0dc0f00aa0e68f635",
        //         "sourceAddress" => "0x63DFE4e34A3bFC00eB0220786238a7C6cEF8Ffc4",
        //         "extra" => "",
        //         "type" => "BALANCE",
        //         "tokenSide" => "WITHDRAW",
        //         "amount" => "10.00000000",
        //         "txId" => "0x891ade0a47fd55466bb9d06702bea4edcb75ed9367d9afbc47b93a84f496d2e6",
        //         "feeToken" => "USDC",
        //         "feeAmount" => "2",
        //         "status" => "COMPLETED",
        //         "confirmingThreshold" => null,
        //         "confirmedNumber" => null
        //     }
        //
        $networkizedCode = $this->safe_string($item, 'token');
        $code = $this->safe_currency_code($networkizedCode, $currency);
        $currency = $this->safe_currency($code, $currency);
        $amount = $this->safe_number($item, 'amount');
        $side = $this->safe_string($item, 'tokenSide');
        $direction = ($side === 'DEPOSIT') ? 'in' : 'out';
        $timestamp = $this->safe_timestamp($item, 'createdTime');
        $fee = $this->parse_token_and_fee_temp($item, array( 'feeToken' ), array( 'feeAmount' ));
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $this->safe_string($item, 'id'),
            'currency' => $code,
            'account' => $this->safe_string($item, 'account'),
            'referenceAccount' => null,
            'referenceId' => $this->safe_string($item, 'txId'),
            'status' => $this->parse_transaction_status($this->safe_string($item, 'status')),
            'amount' => $amount,
            'before' => null,
            'after' => null,
            'direction' => $direction,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'type' => $this->parse_ledger_entry_type($this->safe_string($item, 'type')),
            'fee' => $fee,
        ), $currency);
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'BALANCE' => 'transaction', // Funds moved in/out wallet
            'COLLATERAL' => 'transfer', // Funds moved between portfolios
        );
        return $this->safe_string($types, $type, $type);
    }

    public function get_currency_from_chaincode($networkizedCode, $currency) {
        if ($currency !== null) {
            return $currency;
        } else {
            $parts = explode('_', $networkizedCode);
            $partsLength = count($parts);
            $firstPart = $this->safe_string($parts, 0);
            $currencyId = $this->safe_string($parts, 1, $firstPart);
            if ($partsLength > 2) {
                $currencyId .= '_' . $this->safe_string($parts, 2);
            }
            $currency = $this->safe_currency($currencyId);
        }
        return $currency;
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all deposits made to an account
             *
             * @see https://developer.woox.io/api-reference/endpoint/assets/get_wallet_history
             *
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch deposits for
             * @param {int} [$limit] the maximum number of deposits structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            $request = array(
                'tokenSide' => 'DEPOSIT',
            );
            return Async\await($this->fetch_deposits_withdrawals($code, $since, $limit, $this->extend($request, $params)));
        }) ();
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all withdrawals made from an account
             *
             * @see https://developer.woox.io/api-reference/endpoint/assets/get_wallet_history
             *
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch withdrawals for
             * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            $request = array(
                'tokenSide' => 'WITHDRAW',
            );
            return Async\await($this->fetch_deposits_withdrawals($code, $since, $limit, $this->extend($request, $params)));
        }) ();
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch history of deposits and withdrawals
             *
             * @see https://developer.woox.io/api-reference/endpoint/assets/get_wallet_history
             *
             * @param {string} [$code] unified $currency $code for the $currency of the deposit/withdrawals, default is null
             * @param {int} [$since] timestamp in ms of the earliest deposit/withdrawal, default is null
             * @param {int} [$limit] max number of deposit/withdrawals to return, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            $request = array(
                'type' => 'BALANCE',
            );
            $currencyRows = Async\await($this->get_asset_history_rows($code, $since, $limit, $this->extend($request, $params)));
            $currency = $this->safe_value($currencyRows, 0);
            $rows = $this->safe_list($currencyRows, 1);
            return $this->parse_transactions($rows, $currency, $since, $limit, $params);
        }) ();
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        //     {
        //         "createdTime" => "1734964440.523",
        //         "updatedTime" => "1734964614.081",
        //         "id" => "24122314340000585",
        //         "externalId" => "241223143600621",
        //         "applicationId" => "251bf5c4-f3c8-4544-bb8b-80001007c3c0",
        //         "token" => "ARB_USDCNATIVE",
        //         "targetAddress" => "0x4d6802d2736daa85e6242ef0dc0f00aa0e68f635",
        //         "sourceAddress" => "0x63DFE4e34A3bFC00eB0220786238a7C6cEF8Ffc4",
        //         "extra" => "",
        //         "type" => "BALANCE",
        //         "tokenSide" => "WITHDRAW",
        //         "amount" => "10.00000000",
        //         "txId" => "0x891ade0a47fd55466bb9d06702bea4edcb75ed9367d9afbc47b93a84f496d2e6",
        //         "feeToken" => "USDC",
        //         "feeAmount" => "2",
        //         "status" => "COMPLETED",
        //         "confirmingThreshold" => null,
        //         "confirmedNumber" => null
        //     }
        //
        $networkizedCode = $this->safe_string($transaction, 'token');
        $currencyDefined = $this->get_currency_from_chaincode($networkizedCode, $currency);
        $code = $currencyDefined['code'];
        $movementDirection = $this->safe_string_lower_2($transaction, 'token_side', 'tokenSide');
        if ($movementDirection === 'withdraw') {
            $movementDirection = 'withdrawal';
        }
        $fee = $this->parse_token_and_fee_temp($transaction, array( 'fee_token', 'feeToken' ), array( 'fee_amount', 'feeAmount' ));
        $addressTo = $this->safe_string_2($transaction, 'target_address', 'targetAddress');
        $addressFrom = $this->safe_string_2($transaction, 'source_address', 'sourceAddress');
        $timestamp = $this->safe_timestamp_2($transaction, 'created_time', 'createdTime');
        return array(
            'info' => $transaction,
            'id' => $this->safe_string_n($transaction, array( 'id', 'withdraw_id', 'withdrawId' )),
            'txid' => $this->safe_string_2($transaction, 'tx_id', 'txId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => null,
            'addressFrom' => $addressFrom,
            'addressTo' => $addressTo,
            'tag' => $this->safe_string($transaction, 'extra'),
            'tagFrom' => null,
            'tagTo' => null,
            'type' => $movementDirection,
            'amount' => $this->safe_number($transaction, 'amount'),
            'currency' => $code,
            'status' => $this->parse_transaction_status($this->safe_string($transaction, 'status')),
            'updated' => $this->safe_timestamp_2($transaction, 'updated_time', 'updatedTime'),
            'comment' => null,
            'internal' => null,
            'fee' => $fee,
            'network' => null,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'NEW' => 'pending',
            'CONFIRMING' => 'pending',
            'PROCESSING' => 'pending',
            'COMPLETED' => 'ok',
            'CANCELED' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    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.woox.io/#get-$transfer-history
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount amount to $transfer
             * @param {string} $fromAccount account to $transfer from
             * @param {string} $toAccount account to $transfer to
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$transfer-structure $transfer structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'token' => $currency['id'],
                'amount' => $this->parse_to_numeric($amount),
                'from_application_id' => $fromAccount,
                'to_application_id' => $toAccount,
            );
            $response = Async\await($this->v1PrivatePostAssetMainSubTransfer ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "id" => 200
            //     }
            //
            $transfer = $this->parse_transfer($response, $currency);
            $transferOptions = $this->safe_dict($this->options, 'transfer', array());
            $fillResponseFromRequest = $this->safe_bool($transferOptions, 'fillResponseFromRequest', true);
            if ($fillResponseFromRequest) {
                $transfer['amount'] = $amount;
                $transfer['fromAccount'] = $fromAccount;
                $transfer['toAccount'] = $toAccount;
            }
            return $transfer;
        }) ();
    }

    public function fetch_transfers(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch a history of internal transfers made on an account
             *
             * @see https://developer.woox.io/api-reference/endpoint/assets/get_transfer_history
             *
             * @param {string} $code unified $currency $code of the $currency transferred
             * @param {int} [$since] the earliest time in ms to fetch transfers for
             * @param {int} [$limit] the maximum number of  transfers structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch entries for
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
             */
            $request = array();
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
            }
            if ($limit !== null) {
                $request['size'] = $limit;
            }
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            $until = $this->safe_integer($params, 'until'); // unified in milliseconds
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['endTime'] = $until;
            }
            $response = Async\await($this->v3PrivateGetAssetTransferHistory ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 {
            //                     "id" => 225,
            //                     "token" => "USDT",
            //                     "amount" => "1000000",
            //                     "status" => "COMPLETED",
            //                     "from" => array(
            //                         "applicationId" => "046b5c5c-5b44-4d27-9593-ddc32c0a08ae",
            //                         "accountName" => "Main"
            //                     ),
            //                     "to" => array(
            //                         "applicationId" => "082ae5ae-e26a-4fb1-be5b-03e5b4867663",
            //                         "accountName" => "sub001"
            //                     ),
            //                     "createdTime" => "1642660941.534",
            //                     "updatedTime" => "1642660941.950"
            //                 }
            //             ),
            //             "meta" => array(
            //                 "total" => 46,
            //                 "recordsPerPage" => 1,
            //                 "currentPage" => 1
            //             }
            //         ),
            //         "timestamp" => 1721295317627
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_transfers($rows, $currency, $since, $limit, $params);
        }) ();
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        //    fetchTransfers
        //     {
        //         "id" => 46704,
        //         "token" => "USDT",
        //         "amount" => 30000.00000000,
        //         "status" => "COMPLETED",
        //         "from_application_id" => "0f1bd3cd-dba2-4563-b8bb-0adb1bfb83a3",
        //         "to_application_id" => "c01e6940-a735-4022-9b6c-9d3971cdfdfa",
        //         "from_user" => "LeverageLow",
        //         "to_user" => "dev",
        //         "created_time" => "1709022325.427",
        //         "updated_time" => "1709022325.542"
        //     }
        //     {
        //         "id" => 225,
        //         "token" => "USDT",
        //         "amount" => "1000000",
        //         "status" => "COMPLETED",
        //         "from" => array(
        //             "applicationId" => "046b5c5c-5b44-4d27-9593-ddc32c0a08ae",
        //             "accountName" => "Main"
        //         ),
        //         "to" => array(
        //             "applicationId" => "082ae5ae-e26a-4fb1-be5b-03e5b4867663",
        //             "accountName" => "sub001"
        //         ),
        //         "createdTime" => "1642660941.534",
        //         "updatedTime" => "1642660941.950"
        //     }
        //
        //    $transfer
        //        {
        //            "success" => true,
        //            "id" => 200
        //        }
        //
        $code = $this->safe_currency_code($this->safe_string($transfer, 'token'), $currency);
        $timestamp = $this->safe_timestamp($transfer, 'createdTime');
        $success = $this->safe_bool($transfer, 'success');
        $status = null;
        if ($success !== null) {
            $status = $success ? 'ok' : 'failed';
        }
        $fromAccount = $this->safe_dict($transfer, 'from', array());
        $toAccount = $this->safe_dict($transfer, 'to', array());
        return array(
            'id' => $this->safe_string($transfer, 'id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $code,
            'amount' => $this->safe_number($transfer, 'amount'),
            'fromAccount' => $this->safe_string($fromAccount, 'applicationId'),
            'toAccount' => $this->safe_string($toAccount, 'applicationId'),
            'status' => $this->parse_transfer_status($this->safe_string($transfer, 'status', $status)),
            'info' => $transfer,
        );
    }

    public function parse_transfer_status(?string $status): ?string {
        $statuses = array(
            'NEW' => 'pending',
            'CONFIRMING' => 'pending',
            'PROCESSING' => 'pending',
            'COMPLETED' => 'ok',
            'CANCELED' => 'canceled',
        );
        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.woox.io/#token-withdraw
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount the $amount to withdraw
             * @param {string} $address the $address to withdraw to
             * @param {string} $tag
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
             */
            list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
            Async\await($this->load_markets());
            $this->check_address($address);
            $currency = $this->currency($code);
            $request = array(
                'amount' => $amount,
                'address' => $address,
            );
            if ($tag !== null) {
                $request['extra'] = $tag;
            }
            $specialNetworkId = null;
            list($specialNetworkId, $params) = $this->get_dedicated_network_id($currency, $params);
            $request['token'] = $specialNetworkId;
            $response = Async\await($this->v1PrivatePostAssetWithdraw ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "withdraw_id" => "20200119145703654"
            //     }
            //
            return $this->parse_transaction($response, $currency);
        }) ();
    }

    public function repay_margin(string $code, float $amount, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($code, $amount, $symbol, $params) {
            /**
             * repay borrowed margin and interest
             *
             * @see https://docs.woox.io/#repay-interest
             *
             * @param {string} $code unified $currency $code of the $currency to repay
             * @param {float} $amount the $amount to repay
             * @param {string} $symbol not used by woo.repayMargin ()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=margin-loan-structure margin loan structure~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
            }
            $currency = $this->currency($code);
            $request = array(
                'token' => $currency['id'], // interest token that you want to repay
                'amount' => $this->currency_to_precision($code, $amount),
            );
            $response = Async\await($this->v1PrivatePostInterestRepay ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //     }
            //
            $transaction = $this->parse_margin_loan($response, $currency);
            return $this->extend($transaction, array(
                'amount' => $amount,
                'symbol' => $symbol,
            ));
        }) ();
    }

    public function parse_margin_loan($info, ?array $currency = null) {
        //
        //     {
        //         "success" => true,
        //     }
        //
        return array(
            'id' => null,
            'currency' => $this->safe_currency_code(null, $currency),
            'amount' => null,
            'symbol' => null,
            'timestamp' => null,
            'datetime' => null,
            'info' => $info,
        );
    }

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

    public function sign($path, $section = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $version = $section[0];
        $access = $section[1];
        $pathWithParams = $this->implode_params($path, $params);
        $url = $this->implode_hostname($this->urls['api'][$access]);
        $url .= '/' . $version . '/';
        $params = $this->omit($params, $this->extract_params($path));
        $params = $this->keysort($params);
        if ($access === 'public') {
            $url .= $access . '/' . $pathWithParams;
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } elseif ($access === 'pub') {
            $url .= $pathWithParams;
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $this->check_required_credentials();
            if ($method === 'POST' && ($path === 'trade/algoOrder' || $path === 'trade/order')) {
                $isSandboxMode = $this->safe_bool($this->options, 'sandboxMode', false);
                if (!$isSandboxMode) {
                    $applicationId = 'bc830de7-50f3-460b-9ee0-f430f83f9dad';
                    $brokerId = $this->safe_string($this->options, 'brokerId', $applicationId);
                    $isTrigger = mb_strpos($path, 'algo') > -1;
                    if ($isTrigger) {
                        $params['brokerId'] = $brokerId;
                    } else {
                        $params['broker_id'] = $brokerId;
                    }
                }
                $params = $this->keysort($params);
            }
            $auth = '';
            $ts = (string) $this->nonce();
            $url .= $pathWithParams;
            $headers = array(
                'x-api-key' => $this->apiKey,
                'x-api-timestamp' => $ts,
            );
            if ($version === 'v3') {
                $auth = $ts . $method . '/' . $version . '/' . $pathWithParams;
                if ($method === 'POST' || $method === 'PUT') {
                    $body = $this->json($params);
                    $auth .= $body;
                    $headers['content-type'] = 'application/json';
                } else {
                    if ($params) {
                        $query = $this->urlencode($params);
                        $url .= '?' . $query;
                        $auth .= '?' . $query;
                    }
                }
            } else {
                $auth = $this->urlencode($params);
                if ($method === 'POST' || $method === 'PUT' || $method === 'DELETE') {
                    $body = $auth;
                } else {
                    if ($params) {
                        $url .= '?' . $auth;
                    }
                }
                $auth .= '|' . $ts;
                $headers['content-type'] = 'application/x-www-form-urlencoded';
            }
            $headers['x-api-signature'] = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return null; // fallback to default error handler
        }
        //
        //     400 Bad Request array("success":false,"code":-1012,"message":"Amount is required for buy market orders when margin disabled.")
        //                     array("code":"-1011","message":"The system is under maintenance.","success":false)
        //
        $success = $this->safe_bool($response, 'success');
        $errorCode = $this->safe_string($response, 'code');
        if (!$success) {
            $feedback = $this->id . ' ' . $this->json($response);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
        }
        return null;
    }

    public function parse_income($income, ?array $market = null) {
        //
        //     {
        //         "id" => 1286360,
        //         "symbol" => "PERP_BTC_USDT",
        //         "fundingRate" => -0.00001445,
        //         "markPrice" => "26930.60000000",
        //         "fundingFee" => "9.56021744",
        //         "fundingIntervalHours" => 8,
        //         "paymentType" => "Pay",
        //         "status" => "COMPLETED",
        //         "createdTime" => 1696060873259,
        //         "updatedTime" => 1696060873286
        //     }
        //
        $marketId = $this->safe_string($income, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $amount = $this->safe_string($income, 'fundingFee');
        $code = $this->safe_currency_code('USD');
        $id = $this->safe_string($income, 'id');
        $timestamp = $this->safe_integer($income, 'updatedTime');
        $rate = $this->safe_number($income, 'fundingRate');
        $paymentType = $this->safe_string($income, 'paymentType');
        $amount = ($paymentType === 'Pay') ? Precise::string_neg($amount) : $amount;
        return array(
            'info' => $income,
            'symbol' => $symbol,
            'code' => $code,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $id,
            'amount' => $this->parse_number($amount),
            'rate' => $rate,
        );
    }

    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) {
            /**
             * fetch the history of funding payments paid and received on this account
             *
             * @see https://developer.woox.io/api-reference/endpoint/futures/get_fundingFee_history
             *
             * @param {string} [$symbol] unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch funding history for
             * @param {int} [$limit] the maximum number of funding history structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structure~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingHistory', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_incremental('fetchFundingHistory', $symbol, $since, $limit, $params, 'page', 500));
            }
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            $until = $this->safe_integer($params, 'until'); // unified in milliseconds
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['endTime'] = $until;
            }
            if ($limit !== null) {
                $request['size'] = min ($limit, 500);
            }
            $response = Async\await($this->v3PrivateGetFuturesFundingFeeHistory ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "meta" => array(
            //                 "total" => 670,
            //                 "recordsPerPage" => 25,
            //                 "currentPage" => 1
            //             ),
            //             "rows" => array(
            //                 array(
            //                     "id" => 1286360,
            //                     "symbol" => "PERP_BTC_USDT",
            //                     "fundingRate" => -0.00001445,
            //                     "markPrice" => "26930.60000000",
            //                     "fundingFee" => "9.56021744",
            //                     "fundingIntervalHours" => 8,
            //                     "paymentType" => "Pay",
            //                     "status" => "COMPLETED",
            //                     "createdTime" => 1696060873259,
            //                     "updatedTime" => 1696060873286
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1721351502594
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_incomes($rows, $market, $since, $limit);
        }) ();
    }

    public function parse_funding_rate($fundingRate, ?array $market = null): array {
        //
        //     {
        //         "symbol" => "PERP_BTC_USDT",
        //         "estFundingRate" => "-0.00000441",
        //         "estFundingRateTimestamp" => 1751623979022,
        //         "lastFundingRate" => "-0.00004953",
        //         "lastFundingRateTimestamp" => 1751616000000,
        //         "nextFundingTime" => 1751644800000,
        //         "lastFundingIntervalHours" => 8,
        //         "estFundingIntervalHours" => 8
        //     }
        //
        $symbol = $this->safe_string($fundingRate, 'symbol');
        $market = $this->market($symbol);
        $nextFundingTimestamp = $this->safe_integer($fundingRate, 'nextFundingTime');
        $estFundingRateTimestamp = $this->safe_integer($fundingRate, 'estFundingRateTimestamp');
        $lastFundingRateTimestamp = $this->safe_integer($fundingRate, 'lastFundingRateTimestamp');
        $intervalString = $this->safe_string($fundingRate, 'estFundingIntervalHours');
        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' => $nextFundingTimestamp,
            'fundingDatetime' => $this->iso8601($nextFundingTimestamp),
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => $this->safe_number($fundingRate, 'lastFundingRate'),
            'previousFundingTimestamp' => $lastFundingRateTimestamp,
            'previousFundingDatetime' => $this->iso8601($lastFundingRateTimestamp),
            'interval' => $intervalString . 'h',
        );
    }

    public function fetch_funding_interval(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the current funding rate interval
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/fundingRate
             *
             * @param {string} $symbol unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
             */
            return Async\await($this->fetch_funding_rate($symbol, $params));
        }) ();
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the current funding rate
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/fundingRate
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->v3PublicGetFundingRate ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 array(
            //                     "symbol" => "PERP_BTC_USDT",
            //                     "estFundingRate" => "-0.00000441",
            //                     "estFundingRateTimestamp" => 1751623979022,
            //                     "lastFundingRate" => "-0.00004953",
            //                     "lastFundingRateTimestamp" => 1751616000000,
            //                     "nextFundingTime" => 1751644800000,
            //                     "lastFundingIntervalHours" => 8,
            //                     "estFundingIntervalHours" => 8
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1751624037798
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            $first = $this->safe_dict($rows, 0, array());
            return $this->parse_funding_rate($first, $market);
        }) ();
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the funding rate for multiple markets
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/fundingRate
             *
             * @param {string[]|null} $symbols list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rates-structure funding rate structures~, indexed by market $symbols
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $response = Async\await($this->v3PublicGetFundingRate ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 array(
            //                     "symbol" => "PERP_BTC_USDT",
            //                     "estFundingRate" => "-0.00000441",
            //                     "estFundingRateTimestamp" => 1751623979022,
            //                     "lastFundingRate" => "-0.00004953",
            //                     "lastFundingRateTimestamp" => 1751616000000,
            //                     "nextFundingTime" => 1751644800000,
            //                     "lastFundingIntervalHours" => 8,
            //                     "estFundingIntervalHours" => 8
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1751624037798
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_funding_rates($rows, $symbols);
        }) ();
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches historical funding rate prices
             *
             * @see https://developer.woox.io/api-reference/endpoint/public_data/fundingRateHistory
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
             * @param {int} [$since] $timestamp in ms of the earliest funding rate to fetch
             * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~ to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] $timestamp in ms of the latest funding rate
             * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'paginate');
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_incremental('fetchFundingRateHistory', $symbol, $since, $limit, $params, 'page', 25));
            }
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
            }
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $request = array(
                'symbol' => $market['id'],
            );
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            list($request, $params) = $this->handle_until_option('endTime', $request, $params);
            $response = Async\await($this->v3PublicGetFundingRateHistory ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "rows" => array(
            //                 {
            //                     "symbol" => "PERP_BTC_USDT",
            //                     "fundingRate" => "-0.00004953",
            //                     "fundingRateTimestamp" => 1751616000000,
            //                     "nextFundingTime" => 1751644800000,
            //                     "markPrice" => "108708"
            //                 }
            //             ),
            //             "meta" => array(
            //                 "total" => 11690,
            //                 "recordsPerPage" => 25,
            //                 "currentPage" => 1
            //             }
            //         ),
            //         "timestamp" => 1751632390031
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            $rates = array();
            for ($i = 0; $i < count($rows); $i++) {
                $entry = $rows[$i];
                $marketId = $this->safe_string($entry, 'symbol');
                $timestamp = $this->safe_integer($entry, 'fundingRateTimestamp');
                $rates[] = array(
                    'info' => $entry,
                    'symbol' => $this->safe_symbol($marketId),
                    'fundingRate' => $this->safe_number($entry, 'fundingRate'),
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                );
            }
            $sorted = $this->sort_by($rates, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
        }) ();
    }

    public function set_position_mode(bool $hedged, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($hedged, $symbol, $params) {
            /**
             * set $hedged to true or false for a market
             *
             * @see https://developer.woox.io/api-reference/endpoint/futures/position_mode
             *
             * @param {bool} $hedged set to true to use HEDGE_MODE, false for ONE_WAY
             * @param {string} $symbol not used by woo setPositionMode
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} $response from the exchange
             */
            $hedgeMode = null;
            if ($hedged) {
                $hedgeMode = 'HEDGE_MODE';
            } else {
                $hedgeMode = 'ONE_WAY';
            }
            $request = array(
                'positionMode' => $hedgeMode,
            );
            $response = Async\await($this->v3PrivatePutFuturesPositionMode ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "timestamp" => 1752550492845
            //     }
            //
            return $response;
        }) ();
    }

    public function fetch_leverage(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the set leverage for a $market
             *
             * @see https://developer.woox.io/api-reference/endpoint/account/get_account_info
             * @see https://developer.woox.io/api-reference/endpoint/futures/get_leverage
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] *for swap markets only* 'cross' or 'isolated'
             * @param {string} [$params->positionMode] *for swap markets only* 'ONE_WAY' or 'HEDGE_MODE'
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $response = null;
            if ($market['spot']) {
                $response = Async\await($this->v3PrivateGetAccountInfo ($params));
                //
                //     {
                //         "success" => true,
                //         "data" => array(
                //             "applicationId" => "dsa",
                //             "account" => "dsa",
                //             "alias" => "haha",
                //             "otpauth" => true,
                //             "accountMode" => "FUTURES",
                //             "positionMode" => "ONE_WAY",
                //             "leverage" => 0,
                //             "marginRatio" => "10",
                //             "openMarginRatio" => "10",
                //             "initialMarginRatio" => "10",
                //             "maintenanceMarginRatio" => "0.03",
                //             "totalCollateral" => "165.6115334",
                //             "freeCollateral" => "165.6115334",
                //             "totalAccountValue" => "167.52723093",
                //             "totalTradingValue" => "167.52723093",
                //             "totalVaultValue" => "0",
                //             "totalStakingValue" => "0",
                //             "totalLaunchpadValue" => "0",
                //             "totalEarnValue" => "0",
                //             "referrerID" => null,
                //             "accountType" => "Main"
                //         ),
                //         "timestamp" => 1752645129054
                //     }
                //
            } elseif ($market['swap']) {
                $request = array(
                    'symbol' => $market['id'],
                );
                $marginMode = null;
                list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchLeverage', $params, 'cross');
                $request['marginMode'] = $this->encode_margin_mode($marginMode);
                $response = Async\await($this->v3PrivateGetFuturesLeverage ($this->extend($request, $params)));
                //
                // HEDGE_MODE
                //     {
                //         "success" => true,
                //         "data":
                //             {
                //                 "symbol" => "PERP_ETH_USDT",
                //                 "marginMode" => "CROSS",
                //                 "positionMode" => "HEDGE_MODE",
                //                 "details" =>  array(
                //                     array(
                //                         "positionSide" => "LONG",
                //                         "leverage" => 10
                //                     ),
                //                     array(
                //                         "positionSide" => "SHORT",
                //                         "leverage" => 10
                //                     }
                //                 )
                //             ),
                //         "timestamp" => 1720886470482
                //     }
                //
                // ONE_WAY
                //     {
                //         "success" => true,
                //         "data" => {
                //             "symbol" => "PERP_ETH_USDT",
                //             "marginMode" => "ISOLATED",
                //             "positionMode" => "ONE_WAY",
                //             "details" => array(
                //                 array(
                //                     "positionSide" => "BOTH",
                //                     "leverage" => 10
                //                 }
                //             )
                //         ),
                //         "timestamp" => 1720886810317
                //     }
                //
            } else {
                throw new NotSupported($this->id . ' fetchLeverage() is not supported for ' . $market['type'] . ' markets');
            }
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_leverage($data, $market);
        }) ();
    }

    public function parse_leverage(array $leverage, ?array $market = null): array {
        $marketId = $this->safe_string($leverage, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $marginMode = $this->safe_string_lower($leverage, 'marginMode');
        $spotLeverage = $this->safe_integer($leverage, 'leverage');
        if ($spotLeverage === 0) {
            $spotLeverage = null;
        }
        $longLeverage = $spotLeverage;
        $shortLeverage = $spotLeverage;
        $details = $this->safe_list($leverage, 'details', array());
        for ($i = 0; $i < count($details); $i++) {
            $position = $this->safe_dict($details, $i, array());
            $positionLeverage = $this->safe_integer($position, 'leverage');
            $side = $this->safe_string($position, 'positionSide');
            if ($side === 'BOTH') {
                $longLeverage = $positionLeverage;
                $shortLeverage = $positionLeverage;
            } elseif ($side === 'LONG') {
                $longLeverage = $positionLeverage;
            } elseif ($side === 'SHORT') {
                $shortLeverage = $positionLeverage;
            }
        }
        return array(
            'info' => $leverage,
            'symbol' => $market['symbol'],
            'marginMode' => $marginMode,
            'longLeverage' => $longLeverage,
            'shortLeverage' => $shortLeverage,
        );
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($leverage, $symbol, $params) {
            /**
             * set the level of $leverage for a $market
             *
             * @see https://developer.woox.io/api-reference/endpoint/spot_margin/set_leverage
             * @see https://developer.woox.io/api-reference/endpoint/futures/set_leverage
             *
             * @param {float} $leverage the rate of $leverage (1, 2, 3, 4 or 5 for spot markets, 1, 2, 3, 4, 5, 10, 15, 20 for swap markets)
             * @param {string} [$symbol] unified $market $symbol (is mandatory for swap markets)
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] *for swap markets only* 'cross' or 'isolated'
             * @param {string} [$params->positionMode] *for swap markets only* 'ONE_WAY' or 'HEDGE_MODE'
             * @return {array} response from the exchange
             */
            Async\await($this->load_markets());
            $request = array(
                'leverage' => $leverage,
            );
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            if (($symbol === null) || $market['spot']) {
                return Async\await($this->v3PrivatePostSpotMarginLeverage ($this->extend($request, $params)));
            } elseif ($market['swap']) {
                $request['symbol'] = $market['id'];
                $marginMode = null;
                list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchLeverage', $params, 'cross');
                $request['marginMode'] = $this->encode_margin_mode($marginMode);
                return Async\await($this->v3PrivatePutFuturesLeverage ($this->extend($request, $params)));
            } else {
                throw new NotSupported($this->id . ' fetchLeverage() is not supported for ' . $market['type'] . ' markets');
            }
        }) ();
    }

    public function add_margin(string $symbol, float $amount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $amount, $params) {
            /**
             * add margin
             *
             * @see https://docs.woox.io/#update-isolated-margin-setting
             *
             * @param {string} $symbol unified market $symbol
             * @param {float} $amount amount of margin to add
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->position_side] 'LONG' or 'SHORT' in hedge mode, 'BOTH' in one way mode
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=add-margin-structure margin structure~
             */
            return Async\await($this->modify_margin_helper($symbol, $amount, 'ADD', $params));
        }) ();
    }

    public function reduce_margin(string $symbol, float $amount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $amount, $params) {
            /**
             * remove margin from a position
             *
             * @see https://docs.woox.io/#update-isolated-margin-setting
             *
             * @param {string} $symbol unified market $symbol
             * @param {float} $amount amount of margin to remove
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->position_side] 'LONG' or 'SHORT' in hedge mode, 'BOTH' in one way mode
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=reduce-margin-structure margin structure~
             */
            return Async\await($this->modify_margin_helper($symbol, $amount, 'REDUCE', $params));
        }) ();
    }

    public function modify_margin_helper(string $symbol, $amount, $type, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $amount, $type, $params) {
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
                'adjust_token' => 'USDT', // todo check
                'adjust_amount' => $amount,
                'action' => $type,
            );
            return Async\await($this->v1PrivatePostClientIsolatedMargin ($this->extend($request, $params)));
        }) ();
    }

    public function fetch_position(?string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch data on an open position
             *
             * @see https://developer.woox.io/api-reference/endpoint/futures/get_positions
             *
             * @param {string} $symbol unified $market $symbol of the $market the position is held in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->v3PrivateGetFuturesPositions ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "positions" => array(
            //                 array(
            //                     "symbol" => "PERP_LTC_USDT",
            //                     "holding" => "0.1",
            //                     "pendingLongQty" => "0",
            //                     "pendingShortQty" => "0",
            //                     "settlePrice" => "96.87",
            //                     "averageOpenPrice" => "96.87",
            //                     "pnl24H" => "0",
            //                     "fee24H" => "0.0048435",
            //                     "markPrice" => "96.83793449",
            //                     "estLiqPrice" => "0",
            //                     "timestamp" => 1752500555823,
            //                     "adlQuantile" => 2,
            //                     "positionSide" => "BOTH",
            //                     "marginMode" => "CROSS",
            //                     "isolatedMarginToken" => "",
            //                     "isolatedMarginAmount" => "0",
            //                     "isolatedFrozenLong" => "0",
            //                     "isolatedFrozenShort" => "0",
            //                     "leverage" => 10
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1752500579848
            //     }
            //
            $result = $this->safe_dict($response, 'data', array());
            $positions = $this->safe_list($result, 'positions', array());
            $first = $this->safe_dict($positions, 0, array());
            return $this->parse_position($first, $market);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open $positions
             *
             * @see https://developer.woox.io/api-reference/endpoint/futures/get_positions
             *
             * @param {string[]} [$symbols] list of unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v3PrivateGetFuturesPositions ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "positions" => array(
            //                 array(
            //                     "symbol" => "PERP_LTC_USDT",
            //                     "holding" => "0.1",
            //                     "pendingLongQty" => "0",
            //                     "pendingShortQty" => "0",
            //                     "settlePrice" => "96.87",
            //                     "averageOpenPrice" => "96.87",
            //                     "pnl24H" => "0",
            //                     "fee24H" => "0.0048435",
            //                     "markPrice" => "96.83793449",
            //                     "estLiqPrice" => "0",
            //                     "timestamp" => 1752500555823,
            //                     "adlQuantile" => 2,
            //                     "positionSide" => "BOTH",
            //                     "marginMode" => "CROSS",
            //                     "isolatedMarginToken" => "",
            //                     "isolatedMarginAmount" => "0",
            //                     "isolatedFrozenLong" => "0",
            //                     "isolatedFrozenShort" => "0",
            //                     "leverage" => 10
            //                 }
            //             )
            //         ),
            //         "timestamp" => 1752500579848
            //     }
            //
            $result = $this->safe_dict($response, 'data', array());
            $positions = $this->safe_list($result, 'positions', array());
            return $this->parse_positions($positions, $symbols);
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        // v1PrivateGetPositionSymbol
        //     {
        //         "symbol" => "PERP_ETH_USDT",
        //         "position_side" => "BOTH",
        //         "leverage" => 10,
        //         "margin_mode" => "CROSS",
        //         "average_open_price" => 3139.9,
        //         "isolated_margin_amount" => 0.0,
        //         "isolated_margin_token" => "",
        //         "opening_time" => "1720627963.094",
        //         "mark_price" => 3155.19169891,
        //         "pending_short_qty" => 0.0,
        //         "pending_long_qty" => 0.0,
        //         "holding" => -0.7,
        //         "pnl_24_h" => 0.0,
        //         "est_liq_price" => 9107.40055552,
        //         "settle_price" => 3151.0319904,
        //         "success" => true,
        //         "fee_24_h" => 0.0,
        //         "isolated_frozen_long" => 0.0,
        //         "isolated_frozen_short" => 0.0,
        //         "timestamp" => "1720867502.544"
        //     }
        //
        // v3PrivateGetPositions
        //     {
        //         "symbol" => "PERP_LTC_USDT",
        //         "holding" => "0.1",
        //         "pendingLongQty" => "0",
        //         "pendingShortQty" => "0",
        //         "settlePrice" => "96.87",
        //         "averageOpenPrice" => "96.87",
        //         "pnl24H" => "0",
        //         "fee24H" => "0.0048435",
        //         "markPrice" => "96.83793449",
        //         "estLiqPrice" => "0",
        //         "timestamp" => 1752500555823,
        //         "adlQuantile" => 2,
        //         "positionSide" => "BOTH",
        //         "marginMode" => "CROSS",
        //         "isolatedMarginToken" => "",
        //         "isolatedMarginAmount" => "0",
        //         "isolatedFrozenLong" => "0",
        //         "isolatedFrozenShort" => "0",
        //         "leverage" => 10
        //     }
        //
        $contract = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($contract, $market);
        $size = $this->safe_string($position, 'holding');
        $side = null;
        if (Precise::string_gt($size, '0')) {
            $side = 'long';
        } else {
            $side = 'short';
        }
        $contractSize = $this->safe_string($market, 'contractSize');
        $markPrice = $this->safe_string_2($position, 'markPrice', 'mark_price');
        $timestampString = $this->safe_string($position, 'timestamp');
        $timestamp = null;
        if ($timestampString !== null) {
            if (mb_strpos($timestampString, '.') > -1) {
                $timestamp = $this->safe_timestamp($position, 'timestamp');
            } else {
                $timestamp = $this->safe_integer($position, 'timestamp');
            }
        }
        $entryPrice = $this->safe_string_2($position, 'averageOpenPrice', 'average_open_price');
        $priceDifference = Precise::string_sub($markPrice, $entryPrice);
        $unrealisedPnl = Precise::string_mul($priceDifference, $size);
        $size = Precise::string_abs($size);
        $notional = Precise::string_mul($size, $markPrice);
        $positionSide = $this->safe_string($position, 'positionSide'); // 'SHORT' or 'LONG' for hedged, 'BOTH' for non-hedged
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $this->safe_string($market, 'symbol'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastUpdateTimestamp' => null,
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'entryPrice' => $this->parse_number($entryPrice),
            'notional' => $this->parse_number($notional),
            'leverage' => $this->safe_number($position, 'leverage'),
            'unrealizedPnl' => $this->parse_number($unrealisedPnl),
            'contracts' => $this->parse_number($size),
            'contractSize' => $this->parse_number($contractSize),
            'marginRatio' => null,
            'liquidationPrice' => $this->safe_number_2($position, 'estLiqPrice', 'est_liq_price'),
            'markPrice' => $this->parse_number($markPrice),
            'lastPrice' => null,
            'collateral' => null,
            'marginMode' => $this->safe_string_lower_2($position, 'marginMode', 'margin_mode'),
            'side' => $side,
            'percentage' => null,
            'hedged' => $positionSide !== 'BOTH',
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function fetch_convert_quote(string $fromCode, string $toCode, ?float $amount = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($fromCode, $toCode, $amount, $params) {
            /**
             * fetch a quote for converting from one currency to another
             *
             * @see https://docs.woox.io/#get-quote-rfq
             *
             * @param {string} $fromCode the currency that you want to sell and convert from
             * @param {string} $toCode the currency that you want to buy and convert into
             * @param {float} [$amount] how much you want to trade in units of the from currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'sellToken' => strtoupper($fromCode),
                'buyToken' => strtoupper($toCode),
                'sellQuantity' => $this->number_to_string($amount),
            );
            $response = Async\await($this->v3PrivateGetConvertRfq ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "quoteId" => 123123123,
            //             "counterPartyId" => "",
            //             "sellToken" => "ETH",
            //             "sellQuantity" => "0.0445",
            //             "buyToken" => "USDT",
            //             "buyQuantity" => "33.45",
            //             "buyPrice" => "6.77",
            //             "expireTimestamp" => 1659084466000,
            //             "message" => 1659084466000
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $fromCurrencyId = $this->safe_string($data, 'sellToken', $fromCode);
            $fromCurrency = $this->currency($fromCurrencyId);
            $toCurrencyId = $this->safe_string($data, 'buyToken', $toCode);
            $toCurrency = $this->currency($toCurrencyId);
            return $this->parse_conversion($data, $fromCurrency, $toCurrency);
        }) ();
    }

    public function create_convert_trade(string $id, string $fromCode, string $toCode, ?float $amount = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $fromCode, $toCode, $amount, $params) {
            /**
             * convert from one currency to another
             *
             * @see https://docs.woox.io/#send-quote-rft
             *
             * @param {string} $id the $id of the trade that you want to make
             * @param {string} $fromCode the currency that you want to sell and convert from
             * @param {string} $toCode the currency that you want to buy and convert into
             * @param {float} [$amount] how much you want to trade in units of the from currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'quoteId' => $id,
            );
            $response = Async\await($this->v3PrivatePostConvertRft ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "quoteId" => 123123123,
            //             "counterPartyId" => "",
            //             "rftAccepted" => 1 // 1 -> success; 2 -> processing; 3 -> fail
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_conversion($data);
        }) ();
    }

    public function fetch_convert_trade(string $id, ?string $code = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch the $data for a conversion trade
             *
             * @see https://docs.woox.io/#get-quote-trade
             *
             * @param {string} $id the $id of the trade that you want to fetch
             * @param {string} [$code] the unified currency $code of the conversion trade
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=conversion-structure conversion structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'quoteId' => $id,
            );
            $response = Async\await($this->v3PrivateGetConvertTrade ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "quoteId" => 12,
            //             "buyAsset" => "",
            //             "sellAsset" => "",
            //             "buyAmount" => 12.11,
            //             "sellAmount" => 12.11,
            //             "tradeStatus" => 12,
            //             "createdTime" => ""
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $fromCurrencyId = $this->safe_string($data, 'sellAsset');
            $toCurrencyId = $this->safe_string($data, 'buyAsset');
            $fromCurrency = null;
            $toCurrency = null;
            if ($fromCurrencyId !== null) {
                $fromCurrency = $this->currency($fromCurrencyId);
            }
            if ($toCurrencyId !== null) {
                $toCurrency = $this->currency($toCurrencyId);
            }
            return $this->parse_conversion($data, $fromCurrency, $toCurrency);
        }) ();
    }

    public function fetch_convert_trade_history(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the users history of conversion trades
             *
             * @see https://docs.woox.io/#get-quote-trades
             *
             * @param {string} [$code] the unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch conversions for
             * @param {int} [$limit] the maximum number of conversion structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest conversion to fetch
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=conversion-structure conversion structures~
             */
            Async\await($this->load_markets());
            $request = array();
            list($request, $params) = $this->handle_until_option('endTime', $request, $params);
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            if ($limit !== null) {
                $request['size'] = $limit;
            }
            $response = Async\await($this->v3PrivateGetConvertTrades ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "data" => {
            //             "count" => 12,
            //             "tradeVos":array(
            //                 {
            //                     "quoteId" => 12,
            //                     "buyAsset" => "",
            //                     "sellAsset" => "",
            //                     "buyAmount" => 12.11,
            //                     "sellAmount" => 12.11,
            //                     "tradeStatus" => 12,
            //                     "createdTime" => ""
            //                 }
            //                 ...
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'tradeVos', array());
            return $this->parse_conversions($rows, $code, 'sellAsset', 'buyAsset', $since, $limit);
        }) ();
    }

    public function parse_conversion(array $conversion, ?array $fromCurrency = null, ?array $toCurrency = null): array {
        //
        // fetchConvertQuote
        //
        //     {
        //         "quoteId" => 123123123,
        //         "counterPartyId" => "",
        //         "sellToken" => "ETH",
        //         "sellQuantity" => "0.0445",
        //         "buyToken" => "USDT",
        //         "buyQuantity" => "33.45",
        //         "buyPrice" => "6.77",
        //         "expireTimestamp" => 1659084466000,
        //         "message" => 1659084466000
        //     }
        //
        // createConvertTrade
        //
        //     {
        //         "quoteId" => 123123123,
        //         "counterPartyId" => "",
        //         "rftAccepted" => 1 // 1 -> success; 2 -> processing; 3 -> fail
        //     }
        //
        // fetchConvertTrade, fetchConvertTradeHistory
        //
        //     {
        //         "quoteId" => 12,
        //         "buyAsset" => "",
        //         "sellAsset" => "",
        //         "buyAmount" => 12.11,
        //         "sellAmount" => 12.11,
        //         "tradeStatus" => 12,
        //         "createdTime" => ""
        //     }
        //
        $timestamp = $this->safe_integer_2($conversion, 'expireTimestamp', 'createdTime');
        $fromCurr = $this->safe_string_2($conversion, 'sellToken', 'buyAsset');
        $fromCode = $this->safe_currency_code($fromCurr, $fromCurrency);
        $to = $this->safe_string_2($conversion, 'buyToken', 'sellAsset');
        $toCode = $this->safe_currency_code($to, $toCurrency);
        return array(
            'info' => $conversion,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $this->safe_string($conversion, 'quoteId'),
            'fromCurrency' => $fromCode,
            'fromAmount' => $this->safe_number_2($conversion, 'sellQuantity', 'sellAmount'),
            'toCurrency' => $toCode,
            'toAmount' => $this->safe_number_2($conversion, 'buyQuantity', 'buyAmount'),
            'price' => $this->safe_number($conversion, 'buyPrice'),
            'fee' => null,
        );
    }

    public function fetch_convert_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies that can be converted
             *
             * @see https://docs.woox.io/#get-quote-asset-info
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            Async\await($this->load_markets());
            $response = Async\await($this->v3PrivateGetConvertAssetInfo ($params));
            //
            //     {
            //         "success" => true,
            //         "rows" => array(
            //             array(
            //                 "token" => "BTC",
            //                 "tick" => 0.0001,
            //                 "createdTime" => "1575014248.99", // Unix epoch time in seconds
            //                 "updatedTime" => "1575014248.99"  // Unix epoch time in seconds
            //             ),
            //         )
            //     }
            //
            $result = array();
            $data = $this->safe_list($response, 'rows', array());
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $id = $this->safe_string($entry, 'token');
                $code = $this->safe_currency_code($id);
                $result[$code] = array(
                    'info' => $entry,
                    'id' => $id,
                    'code' => $code,
                    'networks' => null,
                    'type' => null,
                    'name' => null,
                    'active' => null,
                    'deposit' => null,
                    'withdraw' => null,
                    'fee' => null,
                    'precision' => $this->safe_number($entry, 'tick'),
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'created' => $this->safe_timestamp($entry, 'createdTime'),
                );
            }
            return $result;
        }) ();
    }

    public function default_network_code_for_currency($code) {
        $currencyItem = $this->currency($code);
        $networks = $currencyItem['networks'];
        $networkKeys = is_array($networks) ? array_keys($networks) : array();
        for ($i = 0; $i < count($networkKeys); $i++) {
            $network = $networkKeys[$i];
            if ($network === 'ETH') {
                return $network;
            }
        }
        // if it was not returned according to above options, then return the first $network of currency
        return $this->safe_value($networkKeys, 0);
    }

    public function set_sandbox_mode(bool $enable) {
        parent::set_sandbox_mode($enable);
        $this->options['sandboxMode'] = $enable;
    }
}
