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

class woofipro extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'woofipro',
            'name' => 'WOOFI PRO',
            'countries' => array( 'KY' ), // Cayman Islands
            'rateLimit' => 100,
            'version' => 'v1',
            'certified' => true,
            'pro' => true,
            'dex' => true,
            'hostname' => 'dex.woo.org',
            'has' => array(
                'CORS' => null,
                'spot' => false,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'cancelWithdraw' => false,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createConvertTrade' => false,
                'createDepositAddress' => false,
                'createMarketBuyOrderWithCost' => false,
                'createMarketOrder' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrderWithTakeProfitAndStopLoss' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => false,
                'createStopLossOrder' => true,
                'createStopMarketOrder' => false,
                'createStopOrder' => false,
                'createTakeProfitOrder' => true,
                'createTrailingAmountOrder' => false,
                'createTrailingPercentOrder' => false,
                'createTriggerOrder' => true,
                'fetchAccounts' => false,
                'fetchBalance' => true,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => true,
                'fetchConvertCurrencies' => false,
                'fetchConvertQuote' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => 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,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => true,
                'fetchTicker' => false,
                'fetchTickers' => false,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => true,
                'fetchTransactions' => 'emulated',
                'fetchTransfers' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'setLeverage' => true,
                'setMargin' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true, // exchange have that endpoint disabled atm, but was once implemented in ccxt per old docs => https://kronosresearch.github.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://github.com/user-attachments/assets/9ba21b8a-a9c7-4770-b7f1-ce3bcbde68c1',
                'api' => array(
                    'public' => 'https://api-evm.orderly.org',
                    'private' => 'https://api-evm.orderly.org',
                ),
                'test' => array(
                    'public' => 'https://testnet-api-evm.orderly.org',
                    'private' => 'https://testnet-api-evm.orderly.org',
                ),
                'www' => 'https://dex.woo.org',
                'doc' => array(
                    'https://orderly.network/docs/build-on-evm/building-on-evm',
                ),
                'fees' => array(
                    'https://dex.woo.org/en/orderly',
                ),
                'referral' => array(
                    'url' => 'https://dex.woo.org/en/trade?ref=CCXT',
                    'discount' => 0.05,
                ),
            ),
            'api' => array(
                'v1' => array(
                    'public' => array(
                        'get' => array(
                            'public/volume/stats' => 1,
                            'public/broker/name' => 1,
                            'public/chain_info/{broker_id}' => 1,
                            'public/system_info' => 1,
                            'public/vault_balance' => 1,
                            'public/insurancefund' => 1,
                            'public/chain_info' => 1,
                            'faucet/usdc' => 1,
                            'public/account' => 1,
                            'get_account' => 1,
                            'registration_nonce' => 1,
                            'get_orderly_key' => 1,
                            'public/liquidation' => 1,
                            'public/liquidated_positions' => 1,
                            'public/config' => 1,
                            'public/campaign/ranking' => 10,
                            'public/campaign/stats' => 10,
                            'public/campaign/user' => 10,
                            'public/campaign/stats/details' => 10,
                            'public/campaigns' => 10,
                            'public/points/leaderboard' => 1,
                            'client/points' => 1,
                            'public/points/epoch' => 1,
                            'public/points/epoch_dates' => 1,
                            'public/referral/check_ref_code' => 1,
                            'public/referral/verify_ref_code' => 1,
                            'referral/admin_info' => 1,
                            'referral/info' => 1,
                            'referral/referee_info' => 1,
                            'referral/referee_rebate_summary' => 1,
                            'referral/referee_history' => 1,
                            'referral/referral_history' => 1,
                            'referral/rebate_summary' => 1,
                            'client/distribution_history' => 1,
                            'tv/config' => 1,
                            'tv/history' => 1,
                            'tv/symbol_info' => 1,
                            'public/funding_rate_history' => 1,
                            'public/funding_rate/{symbol}' => 0.33,
                            'public/funding_rates' => 1,
                            'public/info' => 1,
                            'public/info/{symbol}' => 1,
                            'public/market_trades' => 1,
                            'public/token' => 1,
                            'public/futures' => 1,
                            'public/futures/{symbol}' => 1,
                        ),
                        'post' => array(
                            'register_account' => 1,
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'client/key_info' => 6,
                            'client/orderly_key_ip_restriction' => 6,
                            'order/{oid}' => 1,
                            'client/order/{client_order_id}' => 1,
                            'algo/order/{oid}' => 1,
                            'algo/client/order/{client_order_id}' => 1,
                            'orders' => 1,
                            'algo/orders' => 1,
                            'trade/{tid}' => 1,
                            'trades' => 1,
                            'order/{oid}/trades' => 1,
                            'client/liquidator_liquidations' => 1,
                            'liquidations' => 1,
                            'asset/history' => 60,
                            'client/holding' => 1,
                            'withdraw_nonce' => 1,
                            'settle_nonce' => 1,
                            'pnl_settlement/history' => 1,
                            'volume/user/daily' => 60,
                            'volume/user/stats' => 60,
                            'client/statistics' => 60,
                            'client/info' => 60,
                            'client/statistics/daily' => 60,
                            'positions' => 3.33,
                            'position/{symbol}' => 3.33,
                            'funding_fee/history' => 30,
                            'notification/inbox/notifications' => 60,
                            'notification/inbox/unread' => 60,
                            'volume/broker/daily' => 60,
                            'broker/fee_rate/default' => 10,
                            'broker/user_info' => 10,
                            'orderbook/{symbol}' => 1,
                            'kline' => 1,
                        ),
                        'post' => array(
                            'orderly_key' => 1,
                            'client/set_orderly_key_ip_restriction' => 6,
                            'client/reset_orderly_key_ip_restriction' => 6,
                            'order' => 1,
                            'batch-order' => 10,
                            'algo/order' => 1,
                            'liquidation' => 1,
                            'claim_insurance_fund' => 1,
                            'withdraw_request' => 1,
                            'settle_pnl' => 1,
                            'notification/inbox/mark_read' => 60,
                            'notification/inbox/mark_read_all' => 60,
                            'client/leverage' => 120,
                            'client/maintenance_config' => 60,
                            'delegate_signer' => 10,
                            'delegate_orderly_key' => 10,
                            'delegate_settle_pnl' => 10,
                            'delegate_withdraw_request' => 10,
                            'broker/fee_rate/set' => 10,
                            'broker/fee_rate/set_default' => 10,
                            'broker/fee_rate/default' => 10,
                            'referral/create' => 10,
                            'referral/update' => 10,
                            'referral/bind' => 10,
                            'referral/edit_split' => 10,
                        ),
                        'put' => array(
                            'order' => 1,
                            'algo/order' => 1,
                        ),
                        'delete' => array(
                            'order' => 1,
                            'algo/order' => 1,
                            'client/order' => 1,
                            'algo/client/order' => 1,
                            'algo/orders' => 1,
                            'orders' => 1,
                            'batch-order' => 1,
                            'client/batch-order' => 1,
                        ),
                    ),
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'accountId' => true,
                'privateKey' => false,
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'maker' => $this->parse_number('0.0002'),
                    'taker' => $this->parse_number('0.0005'),
                ),
            ),
            'options' => array(
                'sandboxMode' => false,
                'brokerId' => 'CCXT',
                'verifyingContractAddress' => '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false, // todo by triggerPrice
                        'takeProfitPrice' => false, // todo by triggerPrice
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => true,
                        'leverage' => true, // todo implement
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => true, // todo implement
                    ),
                    'createOrders' => array(
                        'max' => 10,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'untilDays' => 100000,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => true,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'trigger' => true,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => 100000,
                        'trigger' => true,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'forDerivatives' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        // todo => implementation needs unification
                        'triggerPriceType' => null,
                        'attachedStopLossTakeProfit' => array(
                            // todo => implementation needs unification
                            'triggerPriceType' => null,
                            'price' => false,
                        ),
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forDerivatives',
                    ),
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'commonCurrencies' => array(),
            'exceptions' => array(
                'exact' => array(
                    '-1000' => '\\ccxt\\ExchangeError', // UNKNOWN The data does not exist
                    '-1001' => '\\ccxt\\AuthenticationError', // INVALID_SIGNATURE The api key or secret is in wrong format.
                    '-1002' => '\\ccxt\\AuthenticationError', // UNAUTHORIZED API key or secret is invalid, it may because key have insufficient permission or the key is expired/revoked.
                    '-1003' => '\\ccxt\\RateLimitExceeded', // TOO_MANY_REQUEST Rate limit exceed.
                    '-1004' => '\\ccxt\\BadRequest', // UNKNOWN_PARAM An unknown parameter was sent.
                    '-1005' => '\\ccxt\\BadRequest', // INVALID_PARAM Some parameters are in wrong format for api.
                    '-1006' => '\\ccxt\\InvalidOrder', // RESOURCE_NOT_FOUND The data is not found in server. For example, when client try canceling a CANCELLED order, will raise this error.
                    '-1007' => '\\ccxt\\BadRequest', // DUPLICATE_REQUEST The data is already exists or your request is duplicated.
                    '-1008' => '\\ccxt\\InvalidOrder', // QUANTITY_TOO_HIGH The quantity of settlement is too high than you can request.
                    '-1009' => '\\ccxt\\InsufficientFunds', // CAN_NOT_WITHDRAWAL Can not request withdrawal settlement, you need to deposit other arrears first.
                    '-1011' => '\\ccxt\\NetworkError', // RPC_NOT_CONNECT Can not place/cancel orders, it may because internal network error. Please try again in a few seconds.
                    '-1012' => '\\ccxt\\BadRequest', // RPC_REJECT 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\\InsufficientFunds', // RISK_TOO_HIGH 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', // MIN_NOTIONAL The order value (price * size) is too small.
                    '-1103' => '\\ccxt\\InvalidOrder', // PRICE_FILTER The order price is not following the tick size rule for the symbol.
                    '-1104' => '\\ccxt\\InvalidOrder', // SIZE_FILTER The order quantity is not following the step size rule for the symbol.
                    '-1105' => '\\ccxt\\InvalidOrder', // PERCENTAGE_FILTER Price is X% too high or X% too low from the mid price.
                    '-1201' => '\\ccxt\\BadRequest', // LIQUIDATION_REQUEST_RATIO_TOO_SMALL total notional < 10000, least req ratio should = 1
                    '-1202' => '\\ccxt\\BadRequest', // LIQUIDATION_STATUS_ERROR No need to liquidation because user margin is enough.
                    '29' => '\\ccxt\\BadRequest', // array("success":false,"code":29,"message":"Verify contract is invalid")
                    '9' => '\\ccxt\\AuthenticationError', // array("success":false,"code":9,"message":"Address and signature do not match")
                    '3' => '\\ccxt\\AuthenticationError', // array("success":false,"code":3,"message":"Signature error")
                    '2' => '\\ccxt\\BadRequest', // array("success":false,"code":2,"message":"Timestamp expired")
                    '15' => '\\ccxt\\BadRequest', // array("success":false,"code":15,"message":"BrokerId is not exist")
                ),
                'broad' => array(
                ),
            ),
            'precisionMode' => TICK_SIZE,
        ));
    }

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

    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://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-system-maintenance-$status
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-$status-structure $status structure~
             */
            $response = Async\await($this->v1PublicGetPublicSystemInfo ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "status" => 0,
            //             "msg" => "System is functioning properly."
            //         ),
            //         "timestamp" => "1709274106602"
            //     }
            //
            $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://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-system-maintenance-status
             *
             * @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->v1PublicGetPublicSystemInfo ($params));
            //
            //     {
            //         "success" => true,
            //         "data" => array(
            //             "status" => 0,
            //             "msg" => "System is functioning properly."
            //         ),
            //         "timestamp" => "1709274106602"
            //     }
            //
            return $this->safe_integer($response, 'timestamp');
        }) ();
    }

    public function parse_market(array $market): array {
        //
        //   {
        //     "symbol" => "PERP_BTC_USDC",
        //     "quote_min" => 123,
        //     "quote_max" => 100000,
        //     "quote_tick" => 0.1,
        //     "base_min" => 0.00001,
        //     "base_max" => 20,
        //     "base_tick" => 0.00001,
        //     "min_notional" => 1,
        //     "price_range" => 0.02,
        //     "price_scope" => 0.4,
        //     "std_liquidation_fee" => 0.03,
        //     "liquidator_fee" => 0.015,
        //     "claim_insurance_fund_discount" => 0.0075,
        //     "funding_period" => 8,
        //     "cap_funding" => 0.000375,
        //     "floor_funding" => -0.000375,
        //     "interest_rate" => 0.0001,
        //     "created_time" => 1684140107326,
        //     "updated_time" => 1685345968053,
        //     "base_mmr" => 0.05,
        //     "base_imr" => 0.1,
        //     "imr_factor" => 0.0002512,
        //     "liquidation_tier" => "1"
        //   }
        //
        $marketId = $this->safe_string($market, 'symbol');
        $parts = explode('_', $marketId);
        $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 = $this->safe_string($parts, 2);
        $settle = $this->safe_currency_code($settleId);
        $symbol = $base . '/' . $quote . ':' . $settle;
        return array(
            'id' => $marketId,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $marketType,
            'spot' => false,
            'margin' => false,
            'swap' => true,
            'future' => false,
            'option' => false,
            'active' => null,
            'contract' => true,
            'linear' => true,
            'inverse' => false,
            'contractSize' => $this->parse_number('1'),
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'base_tick'),
                'price' => $this->safe_number($market, 'quote_tick'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'base_min'),
                    'max' => $this->safe_number($market, 'base_max'),
                ),
                'price' => array(
                    'min' => $this->safe_number($market, 'quote_min'),
                    'max' => $this->safe_number($market, 'quote_max'),
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'min_notional'),
                    'max' => null,
                ),
            ),
            'created' => $this->safe_integer($market, 'created_time'),
            'info' => $market,
        );
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves $data on all markets for woofipro
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-available-symbols
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market $data
             */
            $response = Async\await($this->v1PublicGetPublicInfo ($params));
            //
            //   {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => array(
            //         {
            //           "symbol" => "PERP_BTC_USDC",
            //           "quote_min" => 123,
            //           "quote_max" => 100000,
            //           "quote_tick" => 0.1,
            //           "base_min" => 0.00001,
            //           "base_max" => 20,
            //           "base_tick" => 0.00001,
            //           "min_notional" => 1,
            //           "price_range" => 0.02,
            //           "price_scope" => 0.4,
            //           "std_liquidation_fee" => 0.03,
            //           "liquidator_fee" => 0.015,
            //           "claim_insurance_fund_discount" => 0.0075,
            //           "funding_period" => 8,
            //           "cap_funding" => 0.000375,
            //           "floor_funding" => -0.000375,
            //           "interest_rate" => 0.0001,
            //           "created_time" => 1684140107326,
            //           "updated_time" => 1685345968053,
            //           "base_mmr" => 0.05,
            //           "base_imr" => 0.1,
            //           "imr_factor" => 0.0002512,
            //           "liquidation_tier" => "1"
            //         }
            //       )
            //     }
            //   }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_markets($rows);
        }) ();
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available currencies on an exchange
             *
             * @see https://orderly.network/docs/build-on-omnichain/evm-api/restful-api/public/get-supported-collateral-info#get-supported-collateral-info
             * @see https://orderly.network/docs/build-on-omnichain/evm-api/restful-api/public/get-supported-chains-per-builder#get-supported-chains-per-builder
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of currencies
             */
            $result = array();
            $tokenPromise = $this->v1PublicGetPublicToken ($params);
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => [{
            //         "token" => "USDC",
            //         "decimals" => 6,
            //         "minimum_withdraw_amount" => 0.000001,
            //         "token_hash" => "0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa",
            //         "chain_details" => [array(
            //             "chain_id" => 43113,
            //             "contract_address" => "0x5d64c9cfb0197775b4b3ad9be4d3c7976e0d8dc3",
            //             "cross_chain_withdrawal_fee" => 123,
            //             "decimals" => 6,
            //             "withdraw_fee" => 2
            //             )]
            //         }
            //       ]
            //     }
            // }
            //
            $chainPromise = $this->v1PublicGetPublicChainInfo ($params);
            list($tokenResponse, $chainResponse) = Async\await(Promise\all(array( $tokenPromise, $chainPromise )));
            $tokenData = $this->safe_dict($tokenResponse, 'data', array());
            $tokenRows = $this->safe_list($tokenData, 'rows', array());
            $chainData = $this->safe_dict($chainResponse, 'data', array());
            $chainRows = $this->safe_list($chainData, 'rows', array());
            $indexedChains = $this->index_by($chainRows, 'chain_id');
            for ($i = 0; $i < count($tokenRows); $i++) {
                $token = $tokenRows[$i];
                $currencyId = $this->safe_string($token, 'token');
                $networks = $this->safe_list($token, 'chain_details');
                $code = $this->safe_currency_code($currencyId);
                $resultingNetworks = array();
                for ($j = 0; $j < count($networks); $j++) {
                    $networkEntry = $networks[$j];
                    $networkId = $this->safe_string($networkEntry, 'chain_id');
                    $networkRow = $this->safe_dict($indexedChains, $networkId);
                    $networkName = $this->safe_string($networkRow, 'name');
                    $networkCode = $this->network_id_to_code($networkName, $code);
                    $resultingNetworks[$networkCode] = array(
                        'id' => $networkId,
                        'network' => $networkCode,
                        'limits' => array(
                            'withdraw' => array(
                                'min' => null,
                                'max' => null,
                            ),
                            'deposit' => array(
                                'min' => null,
                                'max' => null,
                            ),
                        ),
                        'active' => null,
                        'deposit' => null,
                        'withdraw' => null,
                        'fee' => $this->safe_number($networkEntry, 'withdrawal_fee'),
                        'precision' => $this->parse_number($this->parse_precision($this->safe_string($networkEntry, 'decimals'))),
                        'info' => array( $networkEntry, $networkRow ),
                    );
                }
                $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,
                    'limits' => array(
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => $this->safe_number($token, 'minimum_withdraw_amount'),
                            'max' => null,
                        ),
                    ),
                    'info' => $token,
                ));
            }
            return $result;
        }) ();
    }

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

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // public/market_trades
        //
        //     {
        //         "symbol" => "SPOT_BTC_USDT",
        //         "side" => "SELL",
        //         "executed_price" => 46222.35,
        //         "executed_quantity" => 0.0012,
        //         "executed_timestamp" => "1683878609166"
        //     }
        //
        // fetchOrderTrades, fetchOrder
        //
        //     {
        //         "id" => "99119876",
        //         "symbol" => "SPOT_WOO_USDT",
        //         "fee" => "0.0024",
        //         "side" => "BUY",
        //         "executed_timestamp" => "1641481113084",
        //         "order_id" => "87001234",
        //         "order_tag" => "default", <-- this param only in "fetchOrderTrades"
        //         "executed_price" => "1",
        //         "executed_quantity" => "12",
        //         "fee_asset" => "WOO",
        //         "is_maker" => "1"
        //     }
        //
        $isFromFetchOrder = (is_array($trade) && array_key_exists('id', $trade));
        $timestamp = $this->safe_integer($trade, 'executed_timestamp');
        $marketId = $this->safe_string($trade, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->safe_string($trade, 'executed_price');
        $amount = $this->safe_string($trade, 'executed_quantity');
        $order_id = $this->safe_string($trade, 'order_id');
        $fee = $this->parse_token_and_fee_temp($trade, 'fee_asset', '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($trade, 'is_maker') === '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 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://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-$market-trades
             *
             * @param {string} $symbol unified $symbol of the $market to fetch trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch
             * @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->v1PublicGetPublicMarketTrades ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => [array(
            //         "symbol" => "PERP_ETH_USDC",
            //         "side" => "BUY",
            //         "executed_price" => 2050,
            //         "executed_quantity" => 1,
            //         "executed_timestamp" => 1683878609166
            //       )]
            //     }
            // }
            //
            $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_funding_rate($fundingRate, ?array $market = null): array {
        //
        //         {
        //             "symbol":"PERP_AAVE_USDT",
        //             "est_funding_rate":-0.00003447,
        //             "est_funding_rate_timestamp":1653633959001,
        //             "last_funding_rate":-0.00002094,
        //             "last_funding_rate_timestamp":1653631200000,
        //             "next_funding_time":1653634800000,
        //            "sum_unitary_funding" => 521.367
        //         }
        //
        $symbol = $this->safe_string($fundingRate, 'symbol');
        $market = $this->market($symbol);
        $nextFundingTimestamp = $this->safe_integer($fundingRate, 'next_funding_time');
        $estFundingRateTimestamp = $this->safe_integer($fundingRate, 'est_funding_rate_timestamp');
        $lastFundingRateTimestamp = $this->safe_integer($fundingRate, 'last_funding_rate_timestamp');
        $fundingTimeString = $this->safe_string($fundingRate, 'last_funding_rate_timestamp');
        $nextFundingTimeString = $this->safe_string($fundingRate, 'next_funding_time');
        $millisecondsInterval = Precise::string_sub($nextFundingTimeString, $fundingTimeString);
        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, 'est_funding_rate'),
            'fundingTimestamp' => $nextFundingTimestamp,
            'fundingDatetime' => $this->iso8601($nextFundingTimestamp),
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => $this->safe_number($fundingRate, 'last_funding_rate'),
            'previousFundingTimestamp' => $lastFundingRateTimestamp,
            'previousFundingDatetime' => $this->iso8601($lastFundingRateTimestamp),
            'interval' => $this->parse_funding_interval($millisecondsInterval),
        );
    }

    public function parse_funding_interval($interval) {
        $intervals = array(
            '3600000' => '1h',
            '14400000' => '4h',
            '28800000' => '8h',
            '57600000' => '16h',
            '86400000' => '24h',
        );
        return $this->safe_string($intervals, $interval, $interval);
    }

    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://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-predicted-funding-rate-for-one-market
             *
             * @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://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-predicted-funding-rate-for-one-$market
             *
             * @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->v1PublicGetPublicFundingRateSymbol ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "symbol" => "PERP_ETH_USDC",
            //         "est_funding_rate" => 123,
            //         "est_funding_rate_timestamp" => 1683880020000,
            //         "last_funding_rate" => 0.0001,
            //         "last_funding_rate_timestamp" => 1683878400000,
            //         "next_funding_time" => 1683907200000,
            //         "sum_unitary_funding" => 521.367
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_funding_rate($data, $market);
        }) ();
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch the current funding rate for multiple markets
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-predicted-funding-rates-for-all-markets
             *
             * @param {string[]} $symbols unified market $symbols
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of ~@link https://docs.ccxt.com/#/?id=funding-rate-structure funding rate structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $response = Async\await($this->v1PublicGetPublicFundingRates ($params));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => [array(
            //         "symbol" => "PERP_ETH_USDC",
            //         "est_funding_rate" => 123,
            //         "est_funding_rate_timestamp" => 1683880020000,
            //         "last_funding_rate" => 0.0001,
            //         "last_funding_rate_timestamp" => 1683878400000,
            //         "next_funding_time" => 1683907200000,
            //         "sum_unitary_funding" => 521.367
            //       )]
            //     }
            // }
            //
            $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://orderly.network/docs/build-on-evm/evm-api/restful-api/public/get-funding-rate-history-for-one-$market
             *
             * @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));
            }
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['start_t'] = $since;
            }
            list($request, $params) = $this->handle_until_option('end_t', $request, $params, 0.001);
            $response = Async\await($this->v1PublicGetPublicFundingRateHistory ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => [array(
            //         "symbol" => "PERP_ETH_USDC",
            //         "funding_rate" => 0.0001,
            //         "funding_rate_timestamp" => 1684224000000,
            //         "next_funding_time" => 1684252800000
            //       )],
            //       "meta" => {
            //         "total" => 9,
            //         "records_per_page" => 25,
            //         "current_page" => 1
            //       }
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'rows', array());
            $rates = array();
            for ($i = 0; $i < count($result); $i++) {
                $entry = $result[$i];
                $marketId = $this->safe_string($entry, 'symbol');
                $timestamp = $this->safe_integer($entry, 'funding_rate_timestamp');
                $rates[] = array(
                    'info' => $entry,
                    'symbol' => $this->safe_symbol($marketId),
                    'fundingRate' => $this->safe_number($entry, 'funding_rate'),
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                );
            }
            $sorted = $this->sort_by($rates, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
        }) ();
    }

    public function parse_income($income, ?array $market = null) {
        //
        // {
        //         "symbol" => "PERP_ETH_USDC",
        //         "funding_rate" => 0.00046875,
        //         "mark_price" => 2100,
        //         "funding_fee" => 0.000016,
        //         "payment_type" => "Pay",
        //         "status" => "Accrued",
        //         "created_time" => 1682235722003,
        //         "updated_time" => 1682235722003
        // }
        //
        $marketId = $this->safe_string($income, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $amount = $this->safe_string($income, 'funding_fee');
        $code = $this->safe_currency_code('USDC');
        $timestamp = $this->safe_integer($income, 'updated_time');
        $rate = $this->safe_number($income, 'funding_rate');
        $paymentType = $this->safe_string($income, 'payment_type');
        $amount = ($paymentType === 'Pay') ? Precise::string_neg($amount) : $amount;
        return array(
            'info' => $income,
            'symbol' => $symbol,
            'code' => $code,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => null,
            '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://orderly.network/docs/build-on-omnichain/evm-api/restful-api/private/get-funding-fee-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['start_t'] = $since;
            }
            $until = $this->safe_integer($params, 'until'); // unified in milliseconds
            $params = $this->omit($params, array( 'until' ));
            if ($until !== null) {
                $request['end_t'] = $until;
            }
            if ($limit !== null) {
                $request['size'] = min ($limit, 500);
            }
            $response = Async\await($this->v1PrivateGetFundingFeeHistory ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "meta" => array(
            //             "total" => 9,
            //             "records_per_page" => 25,
            //             "current_page" => 1
            //         ),
            //         "rows" => [array(
            //                 "symbol" => "PERP_ETH_USDC",
            //                 "funding_rate" => 0.00046875,
            //                 "mark_price" => 2100,
            //                 "funding_fee" => 0.000016,
            //                 "payment_type" => "Pay",
            //                 "status" => "Accrued",
            //                 "created_time" => 1682235722003,
            //                 "updated_time" => 1682235722003
            //         )]
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_incomes($rows, $market, $since, $limit);
        }) ();
    }

    public function fetch_trading_fees($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch the trading fees for multiple markets
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-account-information
             *
             * @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->v1PrivateGetClientInfo ($params));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "account_id" => "<string>",
            //         "email" => "test@test.com",
            //         "account_mode" => "FUTURES",
            //         "max_leverage" => 20,
            //         "taker_fee_rate" => 123,
            //         "maker_fee_rate" => 123,
            //         "futures_taker_fee_rate" => 123,
            //         "futures_maker_fee_rate" => 123,
            //         "maintenance_cancel_orders" => true,
            //         "imr_factor" => array(
            //             "PERP_BTC_USDC" => 123,
            //             "PERP_ETH_USDC" => 123,
            //             "PERP_NEAR_USDC" => 123
            //         ),
            //         "max_notional" => {
            //             "PERP_BTC_USDC" => 123,
            //             "PERP_ETH_USDC" => 123,
            //             "PERP_NEAR_USDC" => 123
            //         }
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $maker = $this->safe_string($data, 'futures_maker_fee_rate');
            $taker = $this->safe_string($data, 'futures_taker_fee_rate');
            $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_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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/orderbook-snapshot
             *
             * @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) {
                $limit = min ($limit, 1000);
                $request['max_level'] = $limit;
            }
            $response = Async\await($this->v1PrivateGetOrderbookSymbol ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "asks" => [array(
            //         "price" => 10669.4,
            //         "quantity" => 1.56263218
            //       )],
            //       "bids" => [array(
            //         "price" => 10669.4,
            //         "quantity" => 1.56263218
            //       )],
            //       "timestamp" => 123
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $timestamp = $this->safe_integer($data, 'timestamp');
            return $this->parse_order_book($data, $symbol, $timestamp, 'bids', 'asks', 'price', 'quantity');
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        return array(
            $this->safe_integer($ohlcv, 'start_timestamp'),
            $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_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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-kline
             *
             * 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
             * @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);
            }
            $response = Async\await($this->v1PrivateGetKline ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data', array());
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => [array(
            //         "open" => 66166.23,
            //         "close" => 66124.56,
            //         "low" => 66038.06,
            //         "high" => 66176.97,
            //         "volume" => 23.45528526,
            //         "amount" => 1550436.21725288,
            //         "symbol" => "PERP_BTC_USDC",
            //         "type" => "1m",
            //         "start_timestamp" => 1636388220000,
            //         "end_timestamp" => 1636388280000
            //       )]
            //     }
            // }
            //
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_ohlcvs($rows, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // Possible input functions:
        // * createOrder
        // * createOrders
        // * cancelOrder
        // * fetchOrder
        // * fetchOrders
        // $isFromFetchOrder = (is_array($order) && array_key_exists('order_tag', $order)); TO_DO
        //
        // stop $order after creating it:
        //   {
        //     "orderId" => "1578938",
        //     "clientOrderId" => "0",
        //     "algoType" => "STOP_LOSS",
        //     "quantity" => "0.1"
        //   }
        // stop $order after fetching it:
        //   {
        //       "algoOrderId" => "1578958",
        //       "clientOrderId" => "0",
        //       "rootAlgoOrderId" => "1578958",
        //       "parentAlgoOrderId" => "0",
        //       "symbol" => "SPOT_LTC_USDT",
        //       "orderTag" => "default",
        //       "algoType" => "STOP_LOSS",
        //       "side" => "BUY",
        //       "quantity" => "0.1",
        //       "isTriggered" => false,
        //       "triggerPrice" => "100",
        //       "triggerStatus" => "USELESS",
        //       "type" => "LIMIT",
        //       "rootAlgoStatus" => "CANCELLED",
        //       "algoStatus" => "CANCELLED",
        //       "triggerPriceType" => "MARKET_PRICE",
        //       "price" => "75",
        //       "triggerTime" => "0",
        //       "totalExecutedQuantity" => "0",
        //       "averageExecutedPrice" => "0",
        //       "totalFee" => "0",
        //       "feeAsset" => '',
        //       "reduceOnly" => false,
        //       "createdTime" => "1686149609.744",
        //       "updatedTime" => "1686149903.362"
        //   }
        //
        $timestamp = $this->safe_integer_n($order, array( 'timestamp', 'created_time', 'createdTime' ));
        $orderId = $this->safe_string_n($order, array( 'order_id', 'orderId', 'algoOrderId' ));
        $clientOrderId = $this->omit_zero($this->safe_string_2($order, 'client_order_id', 'clientOrderId')); // 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_2($order, 'order_price', 'price');
        $amount = $this->safe_string_2($order, 'order_quantity', 'quantity'); // This is base $amount
        $cost = $this->safe_string_2($order, 'order_amount', 'amount'); // This is quote $amount
        $orderType = $this->safe_string_lower_2($order, 'order_type', 'type');
        $status = $this->safe_value_2($order, 'status', 'algoStatus');
        $success = $this->safe_bool($order, 'success');
        if ($success !== null) {
            $status = ($success) ? 'NEW' : 'REJECTED';
        }
        $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_2($order, 'average_executed_price', 'averageExecutedPrice'));
        $remaining = Precise::string_sub($cost, $filled);
        $fee = $this->safe_value_2($order, 'total_fee', 'totalFee');
        $feeCurrency = $this->safe_string_2($order, 'fee_asset', 'feeAsset');
        $transactions = $this->safe_value($order, 'Transactions');
        $triggerPrice = $this->safe_number($order, 'triggerPrice');
        $takeProfitPrice = null;
        $stopLossPrice = null;
        $childOrders = $this->safe_value($order, 'childOrders');
        if ($childOrders !== null) {
            $first = $this->safe_value($childOrders, 0);
            $innerChildOrders = $this->safe_value($first, 'childOrders', array());
            $innerChildOrdersLength = count($innerChildOrders);
            if ($innerChildOrdersLength > 0) {
                $takeProfitOrder = $this->safe_value($innerChildOrders, 0);
                $stopLossOrder = $this->safe_value($innerChildOrders, 1);
                $takeProfitPrice = $this->safe_number($takeProfitOrder, 'triggerPrice');
                $stopLossPrice = $this->safe_number($stopLossOrder, 'triggerPrice');
            }
        }
        $lastUpdateTimestamp = $this->safe_integer_2($order, 'updatedTime', 'updated_time');
        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' => $this->parse_order_type($orderType),
            'timeInForce' => $this->parse_time_in_force($orderType),
            'postOnly' => null, // TO_DO
            'reduceOnly' => $this->safe_bool($order, 'reduce_only'),
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $triggerPrice,
            'takeProfitPrice' => $takeProfitPrice,
            'stopLossPrice' => $stopLossPrice,
            'average' => $average,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining, // TO_DO
            'cost' => $cost,
            'trades' => $transactions,
            'fee' => array(
                'cost' => $fee,
                'currency' => $feeCurrency,
            ),
            'info' => $order,
        ), $market);
    }

    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_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 parse_order_type(?string $type) {
        $types = array(
            'LIMIT' => 'limit',
            'MARKET' => 'market',
            'POST_ONLY' => 'limit',
        );
        return $this->safe_string_lower($types, $type, $type);
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * @ignore
         * helper function to build the $request
         * @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
         * @param {float} [$price] the $price that the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} $request to be sent to the exchange
         */
        $reduceOnly = $this->safe_bool_2($params, 'reduceOnly', 'reduce_only');
        $orderType = strtoupper($type);
        $market = $this->market($symbol);
        $orderSide = strtoupper($side);
        $request = array(
            'symbol' => $market['id'],
            'side' => $orderSide,
        );
        $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');
        $isConditional = $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);
        $orderQtyKey = $isConditional ? 'quantity' : 'order_quantity';
        $priceKey = $isConditional ? 'price' : 'order_price';
        $typeKey = $isConditional ? 'type' : 'order_type';
        $request[$typeKey] = $orderType; // LIMIT/MARKET/IOC/FOK/POST_ONLY/ASK/BID
        if (!$isConditional) {
            if ($postOnly) {
                $request['order_type'] = 'POST_ONLY';
            } elseif ($timeInForce === 'fok') {
                $request['order_type'] = 'FOK';
            } elseif ($timeInForce === 'ioc') {
                $request['order_type'] = 'IOC';
            }
        }
        if ($reduceOnly) {
            $request['reduce_only'] = $reduceOnly;
        }
        if ($price !== null) {
            $request[$priceKey] = $this->price_to_precision($symbol, $price);
        }
        if ($isMarket && !$isConditional) {
            $request[$orderQtyKey] = $this->amount_to_precision($symbol, $amount);
        } elseif ($algoType !== 'POSITIONAL_TP_SL') {
            $request[$orderQtyKey] = $this->amount_to_precision($symbol, $amount);
        }
        $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
        if ($clientOrderId !== null) {
            $request['client_order_id'] = $clientOrderId;
        }
        if ($triggerPrice !== null) {
            $request['trigger_price'] = $this->price_to_precision($symbol, $triggerPrice);
            $request['algo_type'] = 'STOP';
        } elseif (($stopLoss !== null) || ($takeProfit !== null)) {
            $request['algo_type'] = 'TP_SL';
            $outterOrder = array(
                'symbol' => $market['id'],
                'reduce_only' => false,
                'algo_type' => 'POSITIONAL_TP_SL',
                'child_orders' => array(),
            );
            $childOrders = $outterOrder['child_orders'];
            $closeSide = ($orderSide === 'BUY') ? 'SELL' : 'BUY';
            if ($stopLoss !== null) {
                $stopLossPrice = $this->safe_number_2($stopLoss, 'triggerPrice', 'price', $stopLoss);
                $stopLossOrder = array(
                    'side' => $closeSide,
                    'algo_type' => 'TP_SL',
                    'trigger_price' => $this->price_to_precision($symbol, $stopLossPrice),
                    'type' => 'LIMIT',
                    'reduce_only' => true,
                );
                $childOrders[] = $stopLossOrder;
            }
            if ($takeProfit !== null) {
                $takeProfitPrice = $this->safe_number_2($takeProfit, 'triggerPrice', 'price', $takeProfit);
                $takeProfitOrder = array(
                    'side' => $closeSide,
                    'algo_type' => 'TP_SL',
                    'trigger_price' => $this->price_to_precision($symbol, $takeProfitPrice),
                    'type' => 'LIMIT',
                    'reduce_only' => true,
                );
                $outterOrder[] = $takeProfitOrder;
            }
            $request['child_orders'] = array( $outterOrder );
        }
        $params = $this->omit($params, array( 'reduceOnly', 'reduce_only', 'clOrdID', 'clientOrderId', 'client_order_id', 'postOnly', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLoss', 'takeProfit' ));
        return $this->extend($request, $params);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade $order
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/create-$order
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/create-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 {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 'TP_SL' or 'POSITIONAL_TP_SL'
             * @param {float} [$params->cost] *spot $market buy only* the quote quantity that can be used alternative for the $amount
             * @param {string} [$params->clientOrderId] a unique id for the $order
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
            $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
            $stopLoss = $this->safe_value($params, 'stopLoss');
            $takeProfit = $this->safe_value($params, 'takeProfit');
            $isConditional = $triggerPrice !== null || $stopLoss !== null || $takeProfit !== null || ($this->safe_value($params, 'childOrders') !== null);
            $response = null;
            if ($isConditional) {
                $response = Async\await($this->v1PrivatePostAlgoOrder ($request));
                //
                // {
                //     "success" => true,
                //     "timestamp" => 1702989203989,
                //     "data" => {
                //       "order_id" => 13,
                //       "client_order_id" => "testclientid",
                //       "algo_type" => "STOP",
                //       "quantity" => 100.12
                //     }
                // }
                //
            } else {
                $response = Async\await($this->v1PrivatePostOrder ($request));
                //
                // {
                //     "success" => true,
                //     "timestamp" => 1702989203989,
                //     "data" => {
                //       "order_id" => 13,
                //       "client_order_id" => "testclientid",
                //       "order_type" => "LIMIT",
                //       "order_price" => 100.12,
                //       "order_quantity" => 0.987654,
                //       "order_amount" => 0.8,
                //       "error_message" => "none"
                //     }
                // }
                //
            }
            $data = $this->safe_dict($response, 'data');
            $data['timestamp'] = $this->safe_integer($response, 'timestamp');
            $order = $this->parse_order($data, $market);
            $order['type'] = $type;
            return $order;
        }) ();
    }

    public function create_orders(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * *contract only* create a list of trade $orders
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/batch-create-order
             *
             * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely symbol, $type, $side, $amount, $price and $params
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $ordersRequests = array();
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $marketId = $this->safe_string($rawOrder, 'symbol');
                $type = $this->safe_string($rawOrder, 'type');
                $side = $this->safe_string($rawOrder, 'side');
                $amount = $this->safe_value($rawOrder, 'amount');
                $price = $this->safe_value($rawOrder, 'price');
                $orderParams = $this->safe_dict($rawOrder, 'params', array());
                $triggerPrice = $this->safe_string_2($orderParams, 'triggerPrice', 'stopPrice');
                $stopLoss = $this->safe_value($orderParams, 'stopLoss');
                $takeProfit = $this->safe_value($orderParams, 'takeProfit');
                $isConditional = $triggerPrice !== null || $stopLoss !== null || $takeProfit !== null || ($this->safe_value($orderParams, 'childOrders') !== null);
                if ($isConditional) {
                    throw new NotSupported($this->id . ' createOrders() only support non-stop order');
                }
                $orderRequest = $this->create_order_request($marketId, $type, $side, $amount, $price, $orderParams);
                $ordersRequests[] = $orderRequest;
            }
            $request = array(
                'orders' => $ordersRequests,
            );
            $response = Async\await($this->v1PrivatePostBatchOrder ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "timestamp" => 1702989203989,
            //         "data" => {
            //             "rows" => [array(
            //                 "order_id" => 13,
            //                 "client_order_id" => "testclientid",
            //                 "order_type" => "LIMIT",
            //                 "order_price" => 100.12,
            //                 "order_quantity" => 0.987654,
            //                 "order_amount" => 0.8,
            //                 "error_message" => "none"
            //             )]
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'rows', array());
            return $this->parse_orders($rows);
        }) ();
    }

    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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/edit-order
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/edit-algo-order
             *
             * @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
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'order_id' => $id,
            );
            $triggerPrice = $this->safe_string_n($params, array( 'triggerPrice', 'stopPrice', 'takeProfitPrice', 'stopLossPrice' ));
            if ($triggerPrice !== null) {
                $request['triggerPrice'] = $this->price_to_precision($symbol, $triggerPrice);
            }
            $isConditional = ($triggerPrice !== null) || ($this->safe_value($params, 'childOrders') !== null);
            $orderQtyKey = $isConditional ? 'quantity' : 'order_quantity';
            $priceKey = $isConditional ? 'price' : 'order_price';
            if ($price !== null) {
                $request[$priceKey] = $this->price_to_precision($symbol, $price);
            }
            if ($amount !== null) {
                $request[$orderQtyKey] = $this->amount_to_precision($symbol, $amount);
            }
            $params = $this->omit($params, array( 'stopPrice', 'triggerPrice', 'takeProfitPrice', 'stopLossPrice', 'trailingTriggerPrice', 'trailingAmount', 'trailingPercent' ));
            $response = null;
            if ($isConditional) {
                $response = Async\await($this->v1PrivatePutAlgoOrder ($this->extend($request, $params)));
            } else {
                $request['symbol'] = $market['id'];
                $request['side'] = strtoupper($side);
                $orderType = strtoupper($type);
                $timeInForce = $this->safe_string_lower($params, 'timeInForce');
                $isMarket = $orderType === 'MARKET';
                $postOnly = $this->is_post_only($isMarket, null, $params);
                if ($postOnly) {
                    $request['order_type'] = 'POST_ONLY';
                } elseif ($timeInForce === 'fok') {
                    $request['order_type'] = 'FOK';
                } elseif ($timeInForce === 'ioc') {
                    $request['order_type'] = 'IOC';
                } else {
                    $request['order_type'] = $orderType;
                }
                $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
                $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id', 'postOnly', 'timeInForce' ));
                if ($clientOrderId !== null) {
                    $request['client_order_id'] = $clientOrderId;
                }
                // $request['side'] = strtoupper($side);
                // $request['symbol'] = $market['id'];
                $response = Async\await($this->v1PrivatePutOrder ($this->extend($request, $params)));
            }
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "status" => "EDIT_SENT"
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $data['timestamp'] = $this->safe_integer($response, 'timestamp');
            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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/cancel-order
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/cancel-order-by-client_order_id
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/cancel-algo-order
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/cancel-algo-order-by-client_order_id
             *
             * 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 stop/algo order
             * @param {string} [$params->clientOrderId] a unique $id for the order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            $trigger = $this->safe_bool_2($params, 'stop', 'trigger', false);
            $params = $this->omit($params, array( 'stop', 'trigger' ));
            if (!$trigger && ($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(
                'symbol' => $market['id'],
            );
            $clientOrderIdUnified = $this->safe_string_2($params, 'clOrdID', 'clientOrderId');
            $clientOrderIdExchangeSpecific = $this->safe_string($params, 'client_order_id', $clientOrderIdUnified);
            $isByClientOrder = $clientOrderIdExchangeSpecific !== null;
            $response = null;
            if ($trigger) {
                if ($isByClientOrder) {
                    $request['client_order_id'] = $clientOrderIdExchangeSpecific;
                    $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
                    $response = Async\await($this->v1PrivateDeleteAlgoClientOrder ($this->extend($request, $params)));
                } else {
                    $request['order_id'] = $id;
                    $response = Async\await($this->v1PrivateDeleteAlgoOrder ($this->extend($request, $params)));
                }
            } else {
                if ($isByClientOrder) {
                    $request['client_order_id'] = $clientOrderIdExchangeSpecific;
                    $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
                    $response = Async\await($this->v1PrivateDeleteClientOrder ($this->extend($request, $params)));
                } else {
                    $request['order_id'] = $id;
                    $response = Async\await($this->v1PrivateDeleteOrder ($this->extend($request, $params)));
                }
            }
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "status" => "CANCEL_SENT"
            //     }
            // }
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "status" => "CANCEL_SENT"
            // }
            //
            $extendParams = array( 'symbol' => $symbol );
            if ($isByClientOrder) {
                $extendParams['client_order_id'] = $clientOrderIdExchangeSpecific;
            } else {
                $extendParams['id'] = $id;
            }
            if ($trigger) {
                return $this->extend($this->parse_order($response), $extendParams);
            }
            $data = $this->safe_dict($response, 'data', array());
            return $this->extend($this->parse_order($data), $extendParams);
        }) ();
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple orders
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/batch-cancel-orders
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/batch-cancel-orders-by-client_order_id
             *
             * @param {string[]} $ids order $ids
             * @param {string} [$symbol] unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string[]} [$params->client_order_ids] max length 10 e.g. ["my_id_1","my_id_2"], encode the double quotes. No space after comma
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $clientOrderIds = $this->safe_list_n($params, array( 'clOrdIDs', 'clientOrderIds', 'client_order_ids' ));
            $params = $this->omit($params, array( 'clOrdIDs', 'clientOrderIds', 'client_order_ids' ));
            $request = array();
            $response = null;
            if ($clientOrderIds) {
                $request['client_order_ids'] = implode(',', $clientOrderIds);
                $response = Async\await($this->v1PrivateDeleteClientBatchOrder ($this->extend($request, $params)));
            } else {
                $request['order_ids'] = implode(',', $ids);
                $response = Async\await($this->v1PrivateDeleteBatchOrder ($this->extend($request, $params)));
            }
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "status" => "CANCEL_ALL_SENT"
            //     }
            // }
            //
            return array( $this->safe_order(array(
                'info' => $response,
            )) );
        }) ();
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/cancel-all-pending-algo-orders
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/cancel-orders-in-bulk
             *
             * 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 stop/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->v1PrivateDeleteAlgoOrders ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v1PrivateDeleteOrders ($this->extend($request, $params)));
            }
            // $trigger
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //      "status" => "CANCEL_ALL_SENT"
            // }
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "status" => "CANCEL_ALL_SENT"
            //     }
            // }
            //
            return array(
                $this->safe_order(array(
                    'info' => $response,
                )),
            );
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-order-by-order_id
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-order-by-client_order_id
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-algo-order-by-order_id
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-algo-order-by-client_order_id
             *
             * 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 stop/algo order
             * @param {string} [$params->clientOrderId] a unique $id for the 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', false);
            $request = array();
            $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
            $params = $this->omit($params, array( 'stop', 'trigger', 'clOrdID', 'clientOrderId', 'client_order_id' ));
            $response = null;
            if ($trigger) {
                if ($clientOrderId) {
                    $request['client_order_id'] = $clientOrderId;
                    $response = Async\await($this->v1PrivateGetAlgoClientOrderClientOrderId ($this->extend($request, $params)));
                } else {
                    $request['oid'] = $id;
                    $response = Async\await($this->v1PrivateGetAlgoOrderOid ($this->extend($request, $params)));
                }
            } else {
                if ($clientOrderId) {
                    $request['client_order_id'] = $clientOrderId;
                    $response = Async\await($this->v1PrivateGetClientOrderClientOrderId ($this->extend($request, $params)));
                } else {
                    $request['oid'] = $id;
                    $response = Async\await($this->v1PrivateGetOrderOid ($this->extend($request, $params)));
                }
            }
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "order_id" => 78151,
            //         "user_id" => 12345,
            //         "price" => 0.67772,
            //         "type" => "LIMIT",
            //         "quantity" => 20,
            //         "amount" => 10,
            //         "executed_quantity" => 20,
            //         "total_executed_quantity" => 20,
            //         "visible_quantity" => 1,
            //         "symbol" => "PERP_WOO_USDC",
            //         "side" => "BUY",
            //         "status" => "FILLED",
            //         "total_fee" => 0.5,
            //         "fee_asset" => "WOO",
            //         "client_order_id" => 1,
            //         "average_executed_price" => 0.67772,
            //         "created_time" => 1653563963000,
            //         "updated_time" => 1653564213000,
            //         "realized_pnl" => 123
            //     }
            // }
            //
            $orders = $this->safe_dict($response, 'data', $response);
            return $this->parse_order($orders, $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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-$orders
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/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 stop/algo order
             * @param {boolean} [$params->is_triggered] 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
             * @param {int} $params->until timestamp in ms of the latest order to fetch
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $paginate = false;
            $isTrigger = $this->safe_bool_2($params, 'stop', 'trigger', false);
            $maxLimit = ($isTrigger) ? 100 : 500;
            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', $maxLimit));
            }
            $request = array();
            $market = null;
            $params = $this->omit($params, array( 'stop', 'trigger' ));
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            if ($since !== null) {
                $request['start_t'] = $since;
            }
            if ($limit !== null) {
                $request['size'] = $limit;
            } else {
                $request['size'] = $maxLimit;
            }
            if ($isTrigger) {
                $request['algo_type'] = 'STOP';
            }
            list($request, $params) = $this->handle_until_option('end_t', $request, $params);
            $response = null;
            if ($isTrigger) {
                $response = Async\await($this->v1PrivateGetAlgoOrders ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->v1PrivateGetOrders ($this->extend($request, $params)));
            }
            //
            //     {
            //         "success" => true,
            //         "timestamp" => 1702989203989,
            //         "data" => {
            //             "meta" => array(
            //                 "total" => 9,
            //                 "records_per_page" => 25,
            //                 "current_page" => 1
            //             ),
            //             "rows" => [array(
            //                 "order_id" => 78151,
            //                 "user_id" => 12345,
            //                 "price" => 0.67772,
            //                 "type" => "LIMIT",
            //                 "quantity" => 20,
            //                 "amount" => 10,
            //                 "executed_quantity" => 20,
            //                 "total_executed_quantity" => 20,
            //                 "visible_quantity" => 1,
            //                 "symbol" => "PERP_WOO_USDC",
            //                 "side" => "BUY",
            //                 "status" => "FILLED",
            //                 "total_fee" => 0.5,
            //                 "fee_asset" => "WOO",
            //                 "client_order_id" => 1,
            //                 "average_executed_price" => 0.67772,
            //                 "created_time" => 1653563963000,
            //                 "updated_time" => 1653564213000,
            //                 "realized_pnl" => 123
            //             )]
            //         }
            //     }
            //
            $data = $this->safe_value($response, 'data', $response);
            $orders = $this->safe_list($data, 'rows');
            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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-orders
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/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 stop/algo order
             * @param {boolean} [$params->is_triggered] whether the order has been triggered (false by default)
             * @param {string} [$params->side] 'buy' or 'sell'
             * @param {int} $params->until timestamp in ms of the latest order to fetch
             * @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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-orders
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/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 stop/algo order
             * @param {boolean} [$params->is_triggered] whether the order has been triggered (false by default)
             * @param {string} [$params->side] 'buy' or 'sell'
             * @param {int} $params->until timestamp in ms of the latest order to fetch
             * @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 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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-all-$trades-of-specific-order
             *
             * @param {string} $id order $id
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch $trades for
             * @param {int} [$limit] the maximum number of $trades to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @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,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "rows" => [array(
            //         "id" => 2,
            //         "symbol" => "PERP_BTC_USDC",
            //         "fee" => 0.0001,
            //         "fee_asset" => "USDC",
            //         "side" => "BUY",
            //         "order_id" => 1,
            //         "executed_price" => 123,
            //         "executed_quantity" => 0.05,
            //         "executed_timestamp" => 1567382401000,
            //         "is_maker" => 1,
            //         "realized_pnl" => 123
            //       )]
            //     }
            // }
            //
            $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_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-$trades
             *
             * fetch all $trades made by the user
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch $trades for
             * @param {int} [$limit] the maximum number of $trades structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->paginate] set to true if you want to fetch $trades with pagination
             * @param {int} $params->until timestamp in ms of the latest trade to fetch
             * @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['start_t'] = $since;
            }
            if ($limit !== null) {
                $request['size'] = $limit;
            } else {
                $request['size'] = 500;
            }
            list($request, $params) = $this->handle_until_option('end_t', $request, $params);
            $response = Async\await($this->v1PrivateGetTrades ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "meta" => array(
            //         "total" => 9,
            //         "records_per_page" => 25,
            //         "current_page" => 1
            //       ),
            //       "rows" => [array(
            //         "id" => 2,
            //         "symbol" => "PERP_BTC_USDC",
            //         "fee" => 0.0001,
            //         "fee_asset" => "USDC",
            //         "side" => "BUY",
            //         "order_id" => 1,
            //         "executed_price" => 123,
            //         "executed_quantity" => 0.05,
            //         "executed_timestamp" => 1567382401000,
            //         "is_maker" => 1,
            //         "realized_pnl" => 123
            //       )]
            //     }
            // }
            //
            $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 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['frozen'] = $this->safe_string($balance, 'frozen');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * query for balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-current-holding
             *
             * @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->v1PrivateGetClientHolding ($params));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "holding" => [array(
            //         "updated_time" => 1580794149000,
            //         "token" => "BTC",
            //         "holding" => -28.000752,
            //         "frozen" => 123,
            //         "pending_short" => -2000
            //       )]
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data');
            return $this->parse_balance($data);
        }) ();
    }

    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['balance_token'] = $currency['id'];
            }
            if ($since !== null) {
                $request['start_t'] = $since;
            }
            if ($limit !== null) {
                $request['pageSize'] = $limit;
            }
            $transactionType = $this->safe_string($params, 'type');
            $params = $this->omit($params, 'type');
            if ($transactionType !== null) {
                $request['type'] = $transactionType;
            }
            $response = Async\await($this->v1PrivateGetAssetHistory ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //       "meta" => array(
            //         "total" => 9,
            //         "records_per_page" => 25,
            //         "current_page" => 1
            //       ),
            //       "rows" => [array(
            //         "id" => "230707030600002",
            //         "tx_id" => "0x4b0714c63cc7abae72bf68e84e25860b88ca651b7d27dad1e32bf4c027fa5326",
            //         "side" => "WITHDRAW",
            //         "token" => "USDC",
            //         "amount" => 555,
            //         "fee" => 123,
            //         "trans_status" => "FAILED",
            //         "created_time" => 1688699193034,
            //         "updated_time" => 1688699193096,
            //         "chain_id" => "986532"
            //       )]
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            return array( $currency, $this->safe_list($data, 'rows', array()) );
        }) ();
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        $currencyId = $this->safe_string($item, 'token');
        $code = $this->safe_currency_code($currencyId, $currency);
        $currency = $this->safe_currency($currencyId, $currency);
        $amount = $this->safe_number($item, 'amount');
        $side = $this->safe_string($item, 'token_side');
        $direction = ($side === 'DEPOSIT') ? 'in' : 'out';
        $timestamp = $this->safe_integer($item, 'created_time');
        $fee = $this->parse_token_and_fee_temp($item, 'fee_token', 'fee_amount');
        return $this->safe_ledger_entry(array(
            'id' => $this->safe_string($item, 'id'),
            'currency' => $code,
            'account' => $this->safe_string($item, 'account'),
            'referenceAccount' => null,
            'referenceId' => $this->safe_string($item, 'tx_id'),
            'status' => $this->parse_transaction_status($this->safe_string($item, 'status')),
            'amount' => $amount,
            'before' => null,
            'after' => null,
            'fee' => $fee,
            'direction' => $direction,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'type' => $this->parse_ledger_entry_type($this->safe_string($item, 'type')),
            'info' => $item,
        ), $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 fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the history of changes, actions done by the user or operations that altered the balance of the user
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-asset-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_transaction(array $transaction, ?array $currency = null): array {
        // example in fetchLedger
        $code = $this->safe_string($transaction, 'token');
        $movementDirection = $this->safe_string_lower($transaction, 'token_side');
        if ($movementDirection === 'withdraw') {
            $movementDirection = 'withdrawal';
        }
        $fee = $this->parse_token_and_fee_temp($transaction, 'fee_token', 'fee_amount');
        $addressTo = $this->safe_string($transaction, 'target_address');
        $addressFrom = $this->safe_string($transaction, 'source_address');
        $timestamp = $this->safe_integer($transaction, 'created_time');
        return array(
            'info' => $transaction,
            'id' => $this->safe_string_2($transaction, 'id', 'withdraw_id'),
            'txid' => $this->safe_string($transaction, 'tx_id'),
            '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_integer($transaction, 'updated_time'),
            '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 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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-asset-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(
                'side' => '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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-asset-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(
                'side' => '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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-asset-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();
            $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);
            //
            //     {
            //         "rows":array(),
            //         "meta":array(
            //             "total":0,
            //             "records_per_page":25,
            //             "current_page":1
            //         ),
            //         "success":true
            //     }
            //
            return $this->parse_transactions($rows, $currency, $since, $limit, $params);
        }) ();
    }

    public function get_withdraw_nonce($params = array ()) {
        return Async\async(function () use ($params) {
            $response = Async\await($this->v1PrivateGetWithdrawNonce ($params));
            //
            //     {
            //         "success" => true,
            //         "timestamp" => 1702989203989,
            //         "data" => {
            //             "withdraw_nonce" => 1
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->safe_number($data, 'withdraw_nonce');
        }) ();
    }

    public function hash_message($message) {
        return '0x' . $this->hash($message, 'keccak', 'hex');
    }

    public function sign_hash($hash, $privateKey) {
        $signature = $this->ecdsa(mb_substr($hash, -64), mb_substr($privateKey, -64), 'secp256k1', null);
        $r = $signature['r'];
        $s = $signature['s'];
        $v = $this->int_to_base16($this->sum(27, $signature['v']));
        return '0x' . str_pad($r, 64, '0', STR_PAD_LEFT) . str_pad($s, 64, '0', STR_PAD_LEFT) . $v;
    }

    public function sign_message($message, $privateKey) {
        return $this->sign_hash($this->hash_message($message), mb_substr($privateKey, -64));
    }

    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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/create-withdraw-$request
             *
             * @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~
             */
            Async\await($this->load_markets());
            $this->check_address($address);
            if ($code !== null) {
                $code = strtoupper($code);
                if ($code !== 'USDC') {
                    throw new NotSupported($this->id . ' withdraw() only support USDC');
                }
            }
            $currency = $this->currency($code);
            $verifyingContractAddress = $this->safe_string($this->options, 'verifyingContractAddress');
            $chainId = $this->safe_string($params, 'chainId');
            $currencyNetworks = $this->safe_dict($currency, 'networks', array());
            $coinNetwork = $this->safe_dict($currencyNetworks, $chainId, array());
            $coinNetworkId = $this->safe_number($coinNetwork, 'id');
            if ($coinNetworkId === null) {
                throw new BadRequest($this->id . ' withdraw() require $chainId parameter');
            }
            $withdrawNonce = Async\await($this->get_withdraw_nonce($params));
            $nonce = $this->nonce();
            $domain = array(
                'chainId' => $chainId,
                'name' => 'Orderly',
                'verifyingContract' => $verifyingContractAddress,
                'version' => '1',
            );
            $messageTypes = array(
                'Withdraw' => array(
                    array( 'name' => 'brokerId', 'type' => 'string' ),
                    array( 'name' => 'chainId', 'type' => 'uint256' ),
                    array( 'name' => 'receiver', 'type' => 'address' ),
                    array( 'name' => 'token', 'type' => 'string' ),
                    array( 'name' => 'amount', 'type' => 'uint256' ),
                    array( 'name' => 'withdrawNonce', 'type' => 'uint64' ),
                    array( 'name' => 'timestamp', 'type' => 'uint64' ),
                ),
            );
            $withdrawRequest = array(
                'brokerId' => $this->safe_string($this->options, 'keyBrokerId', 'woofi_pro'),
                'chainId' => $this->parse_to_int($chainId),
                'receiver' => $address,
                'token' => $code,
                'amount' => (string) $amount,
                'withdrawNonce' => $withdrawNonce,
                'timestamp' => $nonce,
            );
            $msg = $this->eth_encode_structured_data($domain, $messageTypes, $withdrawRequest);
            $signature = $this->sign_message($msg, $this->privateKey);
            $request = array(
                'signature' => $signature,
                'userAddress' => $address,
                'verifyingContract' => $verifyingContractAddress,
                'message' => $withdrawRequest,
            );
            $params = $this->omit($params, 'chainId');
            $response = Async\await($this->v1PrivatePostWithdrawRequest ($this->extend($request, $params)));
            //
            //     {
            //         "success" => true,
            //         "timestamp" => 1702989203989,
            //         "data" => {
            //             "withdraw_id" => 123
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_transaction($data, $currency);
        }) ();
    }

    public function parse_leverage($leverage, $market = null): array {
        $leverageValue = $this->safe_integer($leverage, 'max_leverage');
        return array(
            'info' => $leverage,
            'symbol' => $market['symbol'],
            'marginMode' => null,
            'longLeverage' => $leverageValue,
            'shortLeverage' => $leverageValue,
        );
    }

    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://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-account-information
             *
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $response = Async\await($this->v1PrivateGetClientInfo ($params));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "account_id" => "<string>",
            //         "email" => "test@test.com",
            //         "account_mode" => "FUTURES",
            //         "max_leverage" => 20,
            //         "taker_fee_rate" => 123,
            //         "maker_fee_rate" => 123,
            //         "futures_taker_fee_rate" => 123,
            //         "futures_maker_fee_rate" => 123,
            //         "maintenance_cancel_orders" => true,
            //         "imr_factor" => array(
            //             "PERP_BTC_USDC" => 123,
            //             "PERP_ETH_USDC" => 123,
            //             "PERP_NEAR_USDC" => 123
            //         ),
            //         "max_notional" => {
            //             "PERP_BTC_USDC" => 123,
            //             "PERP_ETH_USDC" => 123,
            //             "PERP_NEAR_USDC" => 123
            //         }
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_leverage($data, $market);
        }) ();
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($leverage, $symbol, $params) {
            /**
             * set the level of $leverage for a market
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/update-$leverage-setting
             *
             * @param {int} [$leverage] the rate of $leverage
             * @param {string} [$symbol] unified market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} response from the exchange
             */
            Async\await($this->load_markets());
            if (($leverage < 1) || ($leverage > 50)) {
                throw new BadRequest($this->id . ' $leverage should be between 1 and 50');
            }
            $request = array(
                'leverage' => $leverage,
            );
            return Async\await($this->v1PrivatePostClientLeverage ($this->extend($request, $params)));
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        // {
        //     "IMR_withdraw_orders" => 0.1,
        //     "MMR_with_orders" => 0.05,
        //     "average_open_price" => 27908.14386047,
        //     "cost_position" => -139329.358492,
        //     "est_liq_price" => 117335.92899428,
        //     "fee_24_h" => 123,
        //     "imr" => 0.1,
        //     "last_sum_unitary_funding" => 70.38,
        //     "mark_price" => 27794.9,
        //     "mmr" => 0.05,
        //     "pending_long_qty" => 123,
        //     "pending_short_qty" => 123,
        //     "pnl_24_h" => 123,
        //     "position_qty" => -5,
        //     "settle_price" => 27865.8716984,
        //     "symbol" => "PERP_BTC_USDC",
        //     "timestamp" => 1685429350571,
        //     "unsettled_pnl" => 354.858492
        // }
        //
        $contract = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($contract, $market);
        $size = $this->safe_string($position, 'position_qty');
        $side = null;
        if (Precise::string_gt($size, '0')) {
            $side = 'long';
        } else {
            $side = 'short';
        }
        $contractSize = $this->safe_string($market, 'contractSize');
        $markPrice = $this->safe_string($position, 'mark_price');
        $timestamp = $this->safe_integer($position, 'timestamp');
        $entryPrice = $this->safe_string($position, 'average_open_price');
        $unrealisedPnl = $this->safe_string($position, 'unsettled_pnl');
        $size = Precise::string_abs($size);
        $notional = Precise::string_mul($size, $markPrice);
        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' => null,
            'unrealizedPnl' => $this->parse_number($unrealisedPnl),
            'contracts' => $this->parse_number($size),
            'contractSize' => $this->parse_number($contractSize),
            'marginRatio' => null,
            'liquidationPrice' => $this->safe_number($position, 'est_liq_price'),
            'markPrice' => $this->parse_number($markPrice),
            'lastPrice' => null,
            'collateral' => null,
            'marginMode' => 'cross',
            'marginType' => null,
            'side' => $side,
            'percentage' => null,
            'hedged' => null,
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function fetch_position(?string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-one-position-info
             *
             * fetch $data on an open position
             * @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->v1PrivateGetPositionSymbol ($this->extend($request, $params)));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "IMR_withdraw_orders" => 0.1,
            //         "MMR_with_orders" => 0.05,
            //         "average_open_price" => 27908.14386047,
            //         "cost_position" => -139329.358492,
            //         "est_liq_price" => 117335.92899428,
            //         "fee_24_h" => 123,
            //         "imr" => 0.1,
            //         "last_sum_unitary_funding" => 70.38,
            //         "mark_price" => 27794.9,
            //         "mmr" => 0.05,
            //         "pending_long_qty" => 123,
            //         "pending_short_qty" => 123,
            //         "pnl_24_h" => 123,
            //         "position_qty" => -5,
            //         "settle_price" => 27865.8716984,
            //         "symbol" => "PERP_BTC_USDC",
            //         "timestamp" => 1685429350571,
            //         "unsettled_pnl" => 354.858492
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data');
            return $this->parse_position($data, $market);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open $positions
             *
             * @see https://orderly.network/docs/build-on-evm/evm-api/restful-api/private/get-all-$positions-info
             *
             * @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->v1PrivateGetPositions ($params));
            //
            // {
            //     "success" => true,
            //     "timestamp" => 1702989203989,
            //     "data" => {
            //         "current_margin_ratio_with_orders" => 1.2385,
            //         "free_collateral" => 450315.09115,
            //         "initial_margin_ratio" => 0.1,
            //         "initial_margin_ratio_with_orders" => 0.1,
            //         "maintenance_margin_ratio" => 0.05,
            //         "maintenance_margin_ratio_with_orders" => 0.05,
            //         "margin_ratio" => 1.2385,
            //         "open_margin_ratio" => 1.2102,
            //         "total_collateral_value" => 489865.71329,
            //         "total_pnl_24_h" => 123,
            //         "rows" => [array(
            //             "IMR_withdraw_orders" => 0.1,
            //             "MMR_with_orders" => 0.05,
            //             "average_open_price" => 27908.14386047,
            //             "cost_position" => -139329.358492,
            //             "est_liq_price" => 117335.92899428,
            //             "fee_24_h" => 123,
            //             "imr" => 0.1,
            //             "last_sum_unitary_funding" => 70.38,
            //             "mark_price" => 27794.9,
            //             "mmr" => 0.05,
            //             "pending_long_qty" => 123,
            //             "pending_short_qty" => 123,
            //             "pnl_24_h" => 123,
            //             "position_qty" => -5,
            //             "settle_price" => 27865.8716984,
            //             "symbol" => "PERP_BTC_USDC",
            //             "timestamp" => 1685429350571,
            //             "unsettled_pnl" => 354.858492
            //         )]
            //     }
            // }
            //
            $result = $this->safe_dict($response, 'data', array());
            $positions = $this->safe_list($result, 'rows', array());
            return $this->parse_positions($positions, $symbols);
        }) ();
    }

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

    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 .= $pathWithParams;
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        } else {
            $this->check_required_credentials();
            if (($method === 'POST' || $method === 'PUT') && ($path === 'algo/order' || $path === 'order' || $path === 'batch-order')) {
                $isSandboxMode = $this->safe_bool($this->options, 'sandboxMode', false);
                if (!$isSandboxMode) {
                    $brokerId = $this->safe_string($this->options, 'brokerId', 'CCXT');
                    if ($path === 'batch-order') {
                        $ordersList = $this->safe_list($params, 'orders', array());
                        for ($i = 0; $i < count($ordersList); $i++) {
                            $params['orders'][$i]['order_tag'] = $brokerId;
                        }
                    } else {
                        $params['order_tag'] = $brokerId;
                    }
                }
                $params = $this->keysort($params);
            }
            $auth = '';
            $ts = (string) $this->nonce();
            $url .= $pathWithParams;
            $apiKey = $this->apiKey;
            if (mb_strpos($apiKey, 'ed25519:') === false) {
                $apiKey = 'ed25519:' . $apiKey;
            }
            $headers = array(
                'orderly-account-id' => $this->accountId,
                'orderly-key' => $apiKey,
                'orderly-timestamp' => $ts,
            );
            $auth = $ts . $method . '/' . $version . '/' . $pathWithParams;
            if ($method === 'POST' || $method === 'PUT') {
                $body = $this->json($params);
                $auth .= $body;
                $headers['content-type'] = 'application/json';
            } else {
                if ($params) {
                    $url .= '?' . $this->urlencode($params);
                    $auth .= '?' . $this->rawencode($params);
                }
                $headers['content-type'] = 'application/x-www-form-urlencoded';
                if ($method === 'DELETE') {
                    $body = '';
                }
            }
            $secret = $this->secret;
            if (mb_strpos($secret, 'ed25519:') !== false) {
                $parts = explode('ed25519:', $secret);
                $secret = $parts[1];
            }
            $signature = $this->eddsa($this->encode($auth), $this->base58_to_binary($secret), 'ed25519');
            $headers['orderly-signature'] = $this->urlencode_base64(base64_decode($signature));
        }
        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);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
