<?php

namespace ccxt;

// 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\abstract\hyperliquid as Exchange;

class hyperliquid extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'hyperliquid',
            'name' => 'Hyperliquid',
            'countries' => [ ],
            'version' => 'v1',
            'rateLimit' => 50, // 1200 requests per minute, 20 request per second
            'certified' => true,
            'pro' => true,
            'dex' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => true,
                'future' => true,
                'option' => false,
                'addMargin' => true,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'cancelAllOrders' => false,
                'cancelAllOrdersAfter' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'cancelOrdersForSymbols' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createMarketBuyOrderWithCost' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => true,
                'createOrderWithTakeProfitAndStopLoss' => true,
                'createReduceOnlyOrder' => true,
                'createStopOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => true,
                'editOrders' => true,
                'fetchAccounts' => false,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledAndClosedOrders' => true,
                'fetchCanceledOrders' => true,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => false,
                'fetchDepositAddresses' => false,
                'fetchDeposits' => true,
                'fetchDepositWithdrawFee' => 'emulated',
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchLedger' => true,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchMarginMode' => null,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyLiquidations' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchOrderTrades' => false,
                'fetchPosition' => true,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchTicker' => 'emulated',
                'fetchTickers' => true,
                'fetchTime' => false,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => false,
                'fetchTransfer' => false,
                'fetchTransfers' => false,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => true,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'sandbox' => true,
                'setLeverage' => true,
                'setMarginMode' => true,
                'setPositionMode' => false,
                'transfer' => true,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '8h' => '8h',
                '12h' => '12h',
                '1d' => '1d',
                '3d' => '3d',
                '1w' => '1w',
                '1M' => '1M',
            ),
            'hostname' => 'hyperliquid.xyz',
            'urls' => array(
                'logo' => 'https://github.com/ccxt/ccxt/assets/43336371/b371bc6c-4a8c-489f-87f4-20a913dd8d4b',
                'api' => array(
                    'public' => 'https://api.{hostname}',
                    'private' => 'https://api.{hostname}',
                ),
                'test' => array(
                    'public' => 'https://api.hyperliquid-testnet.xyz',
                    'private' => 'https://api.hyperliquid-testnet.xyz',
                ),
                'www' => 'https://hyperliquid.xyz',
                'doc' => 'https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api',
                'fees' => 'https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees',
                'referral' => 'https://app.hyperliquid.xyz/',
            ),
            'api' => array(
                'public' => array(
                    'post' => array(
                        'info' => array(
                            'cost' => 20,
                            'byType' => array(
                                'l2Book' => 2,
                                'allMids' => 2,
                                'clearinghouseState' => 2,
                                'orderStatus' => 2,
                                'spotClearinghouseState' => 2,
                                'exchangeStatus' => 2,
                                'candleSnapshot' => 4,
                            ),
                        ),
                    ),
                ),
                'private' => array(
                    'post' => array(
                        'exchange' => 1,
                    ),
                ),
            ),
            'fees' => array(
                'swap' => array(
                    'taker' => $this->parse_number('0.00045'),
                    'maker' => $this->parse_number('0.00015'),
                ),
                'spot' => array(
                    'taker' => $this->parse_number('0.0007'),
                    'maker' => $this->parse_number('0.0004'),
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => false,
                'secret' => false,
                'walletAddress' => true,
                'privateKey' => true,
            ),
            'exceptions' => array(
                'exact' => array(
                ),
                'broad' => array(
                    'Price must be divisible by tick size.' => '\\ccxt\\InvalidOrder',
                    'Order must have minimum value of $10' => '\\ccxt\\InvalidOrder',
                    'Insufficient margin to place order.' => '\\ccxt\\InsufficientFunds',
                    'Reduce only order would increase position.' => '\\ccxt\\InvalidOrder',
                    'Post only order would have immediately matched,' => '\\ccxt\\InvalidOrder',
                    'Order could not immediately match against any resting orders.' => '\\ccxt\\InvalidOrder',
                    'Invalid TP/SL price.' => '\\ccxt\\InvalidOrder',
                    'No liquidity available for market order.' => '\\ccxt\\InvalidOrder',
                    'Order was never placed, already canceled, or filled.' => '\\ccxt\\OrderNotFound',
                    'User or API Wallet ' => '\\ccxt\\InvalidOrder',
                    'Order has invalid size' => '\\ccxt\\InvalidOrder',
                    'Order price cannot be more than 80% away from the reference price' => '\\ccxt\\InvalidOrder',
                    'Order has zero size.' => '\\ccxt\\InvalidOrder',
                    'Insufficient spot balance asset' => '\\ccxt\\InsufficientFunds',
                    'Insufficient balance for withdrawal' => '\\ccxt\\InsufficientFunds',
                    'Insufficient balance for token transfer' => '\\ccxt\\InsufficientFunds',
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'commonCurrencies' => array(
            ),
            'options' => array(
                'defaultType' => 'swap',
                'sandboxMode' => false,
                'defaultSlippage' => 0.05,
                'zeroAddress' => '0x0000000000000000000000000000000000000000',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => false,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => array(
                            'triggerPriceType' => array(
                                'last' => false,
                                'mark' => false,
                                'index' => false,
                            ),
                            'triggerPrice' => true,
                            'type' => true,
                            'price' => true,
                        ),
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => false,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => array(
                        'max' => 1000,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 2000,
                        'daysBack' => null,
                        'untilDays' => null,
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 2000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 2000,
                        'daysBack' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 2000,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 5000,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'forPerps' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => null, // todo, in two orders
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forPerps',
                    ),
                    'inverse' => array(
                        'extends' => 'forPerps',
                    ),
                ),
                'future' => array(
                    'linear' => array(
                        'extends' => 'forPerps',
                    ),
                    'inverse' => array(
                        'extends' => 'forPerps',
                    ),
                ),
            ),
        ));
    }

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

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        if ($this->check_required_credentials(false)) {
            $this->handle_builder_fee_approval();
        }
        $request = array(
            'type' => 'meta',
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "universe" => array(
        //                 {
        //                     "maxLeverage" => 50,
        //                     "name" => "SOL",
        //                     "onlyIsolated" => false,
        //                     "szDecimals" => 2
        //                 }
        //             )
        //         }
        //     )
        //
        $meta = $this->safe_list($response, 'universe', array());
        $result = array();
        for ($i = 0; $i < count($meta); $i++) {
            $data = $this->safe_dict($meta, $i, array());
            $id = $i;
            $name = $this->safe_string($data, 'name');
            $code = $this->safe_currency_code($name);
            $result[$code] = $this->safe_currency_structure(array(
                'id' => $id,
                'name' => $name,
                'code' => $code,
                'precision' => null,
                'info' => $data,
                'active' => null,
                'deposit' => null,
                'withdraw' => null,
                'networks' => null,
                'fee' => null,
                'type' => 'crypto',
                'limits' => array(
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
            ));
        }
        return $result;
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all markets for hyperliquid
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        $rawPromises = array(
            $this->fetch_swap_markets($params),
            $this->fetch_spot_markets($params),
        );
        $promises = $rawPromises;
        $swapMarkets = $promises[0];
        $spotMarkets = $promises[1];
        return $this->array_concat($swapMarkets, $spotMarkets);
    }

    public function fetch_swap_markets($params = array ()): array {
        /**
         * retrieves $data on all swap markets for hyperliquid
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market $data
         */
        $request = array(
            'type' => 'metaAndAssetCtxs',
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "universe" => array(
        //                 array(
        //                     "maxLeverage" => 50,
        //                     "name" => "SOL",
        //                     "onlyIsolated" => false,
        //                     "szDecimals" => 2
        //                 }
        //             )
        //         ),
        //         array(
        //             {
        //                 "dayNtlVlm" => "9450588.2273",
        //                 "funding" => "0.0000198",
        //                 "impactPxs" => array(
        //                     "108.04",
        //                     "108.06"
        //                 ),
        //                 "markPx" => "108.04",
        //                 "midPx" => "108.05",
        //                 "openInterest" => "10764.48",
        //                 "oraclePx" => "107.99",
        //                 "premium" => "0.00055561",
        //                 "prevDayPx" => "111.81"
        //             }
        //         )
        //     )
        //
        //
        $meta = $this->safe_dict($response, 0, array());
        $universe = $this->safe_list($meta, 'universe', array());
        $assetCtxs = $this->safe_list($response, 1, array());
        $result = array();
        for ($i = 0; $i < count($universe); $i++) {
            $data = $this->extend(
                $this->safe_dict($universe, $i, array()),
                $this->safe_dict($assetCtxs, $i, array())
            );
            $data['baseId'] = $i;
            $result[] = $data;
        }
        return $this->parse_markets($result);
    }

    public function calculate_price_precision(float $price, float $amountPrecision, float $maxDecimals) {
        /**
         * Helper function to calculate the Hyperliquid DECIMAL_PLACES $price precision
         * @param {float} $price the $price to use in the calculation
         * @param {int} $amountPrecision the $amountPrecision to use in the calculation
         * @param {int} $maxDecimals the $maxDecimals to use in the calculation
         * @return {int} The calculated $price precision
         */
        $pricePrecision = 0;
        $priceStr = $this->number_to_string($price);
        if ($priceStr === null) {
            return 0;
        }
        $priceSplitted = explode('.', $priceStr);
        if (Precise::string_eq($priceStr, '0')) {
            // Significant digits is always property_exists($this, 5) case
            $significantDigits = 5;
            // Integer digits is always property_exists($this, 0) case (0 doesn't count)
            $integerDigits = 0;
            // Calculate the $price precision
            $pricePrecision = min ($maxDecimals - $amountPrecision, $significantDigits - $integerDigits);
        } elseif (Precise::string_gt($priceStr, '0') && Precise::string_lt($priceStr, '1')) {
            // Significant digits, always property_exists($this, 5) case
            $significantDigits = 5;
            // Get the part after the decimal separator
            $decimalPart = $this->safe_string($priceSplitted, 1, '');
            // Count the number of leading zeros in the decimal part
            $leadingZeros = 0;
            while (($leadingZeros <= strlen($decimalPart)) && ($decimalPart[$leadingZeros] === '0')) {
                $leadingZeros = $leadingZeros + 1;
            }
            // Calculate $price precision based on leading zeros and significant digits
            $pricePrecision = $leadingZeros . $significantDigits;
            // Calculate the $price precision based on $maxDecimals - szDecimals and the calculated $price precision from the previous step
            $pricePrecision = min ($maxDecimals - $amountPrecision, $pricePrecision);
        } else {
            // Count the numbers before the decimal separator
            $integerPart = $this->safe_string($priceSplitted, 0, '');
            // Get significant digits, take the max() of 5 and the integer digits count
            $significantDigits = max (5, strlen($integerPart));
            // Calculate $price precision based on $maxDecimals - szDecimals and $significantDigits - strlen($integerPart)
            $pricePrecision = min ($maxDecimals - $amountPrecision, $significantDigits - strlen($integerPart));
        }
        return $this->parse_to_int($pricePrecision);
    }

    public function fetch_spot_markets($params = array ()): array {
        /**
         * retrieves data on all spot $markets for hyperliquid
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $request = array(
            'type' => 'spotMetaAndAssetCtxs',
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        // array(
        //     {
        //         "tokens" => [
        //             array(
        //                 "name" => "USDC",
        //                 "szDecimals" => 8,
        //                 "weiDecimals" 8,
        //                 "index" => 0,
        //                 "tokenId" => "0x6d1e7cde53ba9467b783cb7c530ce054",
        //                 "isCanonical" => true,
        //                 "evmContract":null,
        //                 "fullName":null
        //             ),
        //             {
        //                 "name" => "PURR",
        //                 "szDecimals" => 0,
        //                 "weiDecimals" => 5,
        //                 "index" => 1,
        //                 "tokenId" => "0xc1fb593aeffbeb02f85e0308e9956a90",
        //                 "isCanonical" => true,
        //                 "evmContract":null,
        //                 "fullName":null
        //             }
        //         ),
        //         "universe" => [
        //             array(
        //                 "name" => "PURR/USDC",
        //                 "tokens" => [1, 0],
        //                 "index" => 0,
        //                 "isCanonical" => true
        //             }
        //         ]
        //     ),
        //     array(
        //         {
        //             "dayNtlVlm":"8906.0",
        //             "markPx":"0.14",
        //             "midPx":"0.209265",
        //             "prevDayPx":"0.20432"
        //         }
        //     )
        // ]
        //
        $first = $this->safe_dict($response, 0, array());
        $second = $this->safe_list($response, 1, array());
        $meta = $this->safe_list($first, 'universe', array());
        $tokens = $this->safe_list($first, 'tokens', array());
        $markets = array();
        for ($i = 0; $i < count($meta); $i++) {
            $market = $this->safe_dict($meta, $i, array());
            $index = $this->safe_integer($market, 'index');
            $extraData = $this->safe_dict($second, $index, array());
            $marketName = $this->safe_string($market, 'name');
            // if (mb_strpos($marketName, '/') === false) {
            //     // there are some weird spot $markets in testnet, eg @2
            //     continue;
            // }
            // $marketParts = explode('/', $marketName);
            // $baseName = $this->safe_string($marketParts, 0);
            // $quoteId = $this->safe_string($marketParts, 1);
            $fees = $this->safe_dict($this->fees, 'spot', array());
            $taker = $this->safe_number($fees, 'taker');
            $maker = $this->safe_number($fees, 'maker');
            $tokensPos = $this->safe_list($market, 'tokens', array());
            $baseTokenPos = $this->safe_integer($tokensPos, 0);
            $quoteTokenPos = $this->safe_integer($tokensPos, 1);
            $baseTokenInfo = $this->safe_dict($tokens, $baseTokenPos, array());
            $quoteTokenInfo = $this->safe_dict($tokens, $quoteTokenPos, array());
            $baseName = $this->safe_string($baseTokenInfo, 'name');
            $quoteId = $this->safe_string($quoteTokenInfo, 'name');
            $base = $this->safe_currency_code($baseName);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $base . '/' . $quote;
            $innerBaseTokenInfo = $this->safe_dict($baseTokenInfo, 'spec', $baseTokenInfo);
            // $innerQuoteTokenInfo = $this->safe_dict($quoteTokenInfo, 'spec', $quoteTokenInfo);
            $amountPrecisionStr = $this->safe_string($innerBaseTokenInfo, 'szDecimals');
            $amountPrecision = intval($amountPrecisionStr);
            $price = $this->safe_number($extraData, 'midPx');
            $pricePrecision = 0;
            if ($price !== null) {
                $pricePrecision = $this->calculate_price_precision($price, $amountPrecision, 8);
            }
            $pricePrecisionStr = $this->number_to_string($pricePrecision);
            // $quotePrecision = $this->parse_number($this->parse_precision($this->safe_string($innerQuoteTokenInfo, 'szDecimals')));
            $baseId = $this->number_to_string($index + 10000);
            $markets[] = $this->safe_market_structure(array(
                'id' => $marketName,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'settle' => null,
                'baseId' => $baseId,
                'baseName' => $baseName,
                'quoteId' => $quoteId,
                'settleId' => null,
                'type' => 'spot',
                'spot' => true,
                'subType' => null,
                'margin' => null,
                'swap' => false,
                'future' => false,
                'option' => false,
                'active' => true,
                'contract' => false,
                'linear' => null,
                'inverse' => null,
                'taker' => $taker,
                'maker' => $maker,
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->parse_number($this->parse_precision($amountPrecisionStr)),
                    'price' => $this->parse_number($this->parse_precision($pricePrecisionStr)),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => $this->parse_number('10'),
                        'max' => null,
                    ),
                ),
                'created' => null,
                'info' => $this->extend($extraData, $market),
            ));
        }
        return $markets;
    }

    public function parse_market(array $market): array {
        //
        //     {
        //         "maxLeverage" => "50",
        //         "name" => "ETH",
        //         "onlyIsolated" => false,
        //         "szDecimals" => "4",
        //         "dayNtlVlm" => "1709813.11535",
        //         "funding" => "0.00004807",
        //         "impactPxs" => array(
        //             "2369.3",
        //             "2369.6"
        //         ),
        //         "markPx" => "2369.6",
        //         "midPx" => "2369.45",
        //         "openInterest" => "1815.4712",
        //         "oraclePx" => "2367.3",
        //         "premium" => "0.00090821",
        //         "prevDayPx" => "2381.5"
        //     }
        //
        $quoteId = 'USDC';
        $baseName = $this->safe_string($market, 'name');
        $base = $this->safe_currency_code($baseName);
        $quote = $this->safe_currency_code($quoteId);
        $baseId = $this->safe_string($market, 'baseId');
        $settleId = 'USDC';
        $settle = $this->safe_currency_code($settleId);
        $symbol = $base . '/' . $quote;
        $contract = true;
        $swap = true;
        if ($contract) {
            if ($swap) {
                $symbol = $symbol . ':' . $settle;
            }
        }
        $fees = $this->safe_dict($this->fees, 'swap', array());
        $taker = $this->safe_number($fees, 'taker');
        $maker = $this->safe_number($fees, 'maker');
        $amountPrecisionStr = $this->safe_string($market, 'szDecimals');
        $amountPrecision = intval($amountPrecisionStr);
        $price = $this->safe_number($market, 'markPx', 0);
        $pricePrecision = 0;
        if ($price !== null) {
            $pricePrecision = $this->calculate_price_precision($price, $amountPrecision, 6);
        }
        $pricePrecisionStr = $this->number_to_string($pricePrecision);
        $isDelisted = $this->safe_bool($market, 'isDelisted');
        $active = true;
        if ($isDelisted !== null) {
            $active = !$isDelisted;
        }
        return $this->safe_market_structure(array(
            'id' => $baseId,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'baseName' => $baseName,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => 'swap',
            'spot' => false,
            'margin' => null,
            'swap' => $swap,
            'future' => false,
            'option' => false,
            'active' => $active,
            'contract' => $contract,
            'linear' => true,
            'inverse' => false,
            'taker' => $taker,
            'maker' => $maker,
            'contractSize' => $this->parse_number('1'),
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->parse_number($this->parse_precision($amountPrecisionStr)),
                'price' => $this->parse_number($this->parse_precision($pricePrecisionStr)),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => $this->safe_integer($market, 'maxLeverage'),
                ),
                'amount' => array(
                    'min' => null,
                    'max' => null,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->parse_number('10'),
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for $balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-a-users-token-$balances
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-$account-summary
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @param {string} [$params->type] wallet $type, ['spot', 'swap'], defaults to swap
         * @param {string} [$params->marginMode] 'cross' or 'isolated', for margin trading, uses $this->options.defaultMarginMode if not passed, defaults to null/None/null
         * @param {string} [$params->subAccountAddress] sub $account user address
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$balance-structure $balance structure~
         */
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchBalance', $params);
        $type = null;
        list($type, $params) = $this->handle_market_type_and_params('fetchBalance', null, $params);
        $marginMode = null;
        list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchBalance', $params);
        $isSpot = ($type === 'spot');
        $request = array(
            'type' => ($isSpot) ? 'spotClearinghouseState' : 'clearinghouseState',
            'user' => $userAddress,
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     {
        //         "assetPositions" => array(),
        //         "crossMaintenanceMarginUsed" => "0.0",
        //         "crossMarginSummary" => array(
        //             "accountValue" => "100.0",
        //             "totalMarginUsed" => "0.0",
        //             "totalNtlPos" => "0.0",
        //             "totalRawUsd" => "100.0"
        //         ),
        //         "marginSummary" => array(
        //             "accountValue" => "100.0",
        //             "totalMarginUsed" => "0.0",
        //             "totalNtlPos" => "0.0",
        //             "totalRawUsd" => "100.0"
        //         ),
        //         "time" => "1704261007014",
        //         "withdrawable" => "100.0"
        //     }
        // spot
        //
        //     {
        //         "balances":[
        //            array(
        //               "coin":"USDC",
        //               "hold":"0.0",
        //               "total":"1481.844"
        //            ),
        //            {
        //               "coin":"PURR",
        //               "hold":"0.0",
        //               "total":"999.65004"
        //            }
        //     }
        //
        $balances = $this->safe_list($response, 'balances');
        if ($balances !== null) {
            $spotBalances = array( 'info' => $response );
            for ($i = 0; $i < count($balances); $i++) {
                $balance = $balances[$i];
                $code = $this->safe_currency_code($this->safe_string($balance, 'coin'));
                $account = $this->account();
                $total = $this->safe_string($balance, 'total');
                $used = $this->safe_string($balance, 'hold');
                $account['total'] = $total;
                $account['used'] = $used;
                $spotBalances[$code] = $account;
            }
            return $this->safe_balance($spotBalances);
        }
        $data = $this->safe_dict($response, 'marginSummary', array());
        $usdcBalance = array(
            'total' => $this->safe_number($data, 'accountValue'),
        );
        if (($marginMode !== null) && ($marginMode === 'isolated')) {
            $usdcBalance['free'] = $this->safe_number($response, 'withdrawable');
        } else {
            $usdcBalance['used'] = $this->safe_number($data, 'totalMarginUsed');
        }
        $result = array(
            'info' => $response,
            'USDC' => $usdcBalance,
        );
        $timestamp = $this->safe_integer($response, 'time');
        $result['timestamp'] = $timestamp;
        $result['datetime'] = $this->iso8601($timestamp);
        return $this->safe_balance($result);
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#l2-book-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
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'type' => 'l2Book',
            'coin' => $market['swap'] ? $market['baseName'] : $market['id'],
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     {
        //         "coin" => "ETH",
        //         "levels" => array(
        //             array(
        //                 {
        //                     "n" => "2",
        //                     "px" => "2216.2",
        //                     "sz" => "74.0637"
        //                 }
        //             ),
        //             array(
        //                 {
        //                     "n" => "2",
        //                     "px" => "2216.5",
        //                     "sz" => "70.5893"
        //                 }
        //             )
        //         ),
        //         "time" => "1704290104840"
        //     }
        //
        $data = $this->safe_list($response, 'levels', array());
        $result = array(
            'bids' => $this->safe_list($data, 0, array()),
            'asks' => $this->safe_list($data, 1, array()),
        );
        $timestamp = $this->safe_integer($response, 'time');
        return $this->parse_order_book($result, $market['symbol'], $timestamp, 'bids', 'asks', 'px', 'sz');
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
         *
         * @param {string[]} [$symbols] unified $symbols of the markets to fetch the $ticker for, all $market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->type] 'spot' or 'swap', by default fetches both
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structures~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        // at this stage, to get tickers data, we use fetchMarkets endpoints
        $response = array();
        $type = $this->safe_string($params, 'type');
        $params = $this->omit($params, 'type');
        if ($type === 'spot') {
            $response = $this->fetch_spot_markets($params);
        } elseif ($type === 'swap') {
            $response = $this->fetch_swap_markets($params);
        } else {
            $response = $this->fetch_markets($params);
        }
        // same $response "fetchMarkets"
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $market = $response[$i];
            $info = $market['info'];
            $ticker = $this->parse_ticker($info, $market);
            $symbol = $this->safe_string($ticker, 'symbol');
            $result[$symbol] = $ticker;
        }
        return $this->filter_by_array_tickers($result, 'symbol', $symbols);
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): array {
        /**
         * retrieves $data on all swap markets for hyperliquid
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
         *
         * @param {string[]} [$symbols] list of unified market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market $data
         */
        $request = array(
            'type' => 'metaAndAssetCtxs',
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "universe" => array(
        //                 array(
        //                     "maxLeverage" => 50,
        //                     "name" => "SOL",
        //                     "onlyIsolated" => false,
        //                     "szDecimals" => 2
        //                 }
        //             )
        //         ),
        //         array(
        //             {
        //                 "dayNtlVlm" => "9450588.2273",
        //                 "funding" => "0.0000198",
        //                 "impactPxs" => array(
        //                     "108.04",
        //                     "108.06"
        //                 ),
        //                 "markPx" => "108.04",
        //                 "midPx" => "108.05",
        //                 "openInterest" => "10764.48",
        //                 "oraclePx" => "107.99",
        //                 "premium" => "0.00055561",
        //                 "prevDayPx" => "111.81"
        //             }
        //         )
        //     )
        //
        //
        $meta = $this->safe_dict($response, 0, array());
        $universe = $this->safe_list($meta, 'universe', array());
        $assetCtxs = $this->safe_list($response, 1, array());
        $result = array();
        for ($i = 0; $i < count($universe); $i++) {
            $data = $this->extend(
                $this->safe_dict($universe, $i, array()),
                $this->safe_dict($assetCtxs, $i, array())
            );
            $result[] = $data;
        }
        return $this->parse_funding_rates($result, $symbols);
    }

    public function parse_funding_rate($info, ?array $market = null): array {
        //
        //     {
        //         "maxLeverage" => "50",
        //         "name" => "ETH",
        //         "onlyIsolated" => false,
        //         "szDecimals" => "4",
        //         "dayNtlVlm" => "1709813.11535",
        //         "funding" => "0.00004807",
        //         "impactPxs" => array(
        //             "2369.3",
        //             "2369.6"
        //         ),
        //         "markPx" => "2369.6",
        //         "midPx" => "2369.45",
        //         "openInterest" => "1815.4712",
        //         "oraclePx" => "2367.3",
        //         "premium" => "0.00090821",
        //         "prevDayPx" => "2381.5"
        //     }
        //
        $base = $this->safe_string($info, 'name');
        $marketId = $this->coin_to_market_id($base);
        $symbol = $this->safe_symbol($marketId, $market);
        $funding = $this->safe_number($info, 'funding');
        $markPx = $this->safe_number($info, 'markPx');
        $oraclePx = $this->safe_number($info, 'oraclePx');
        $fundingTimestamp = ((int) floor($this->milliseconds() / 60 / 60 / 1000) + 1) * 60 * 60 * 1000;
        return array(
            'info' => $info,
            'symbol' => $symbol,
            'markPrice' => $markPx,
            'indexPrice' => $oraclePx,
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => null,
            'datetime' => null,
            'fundingRate' => $funding,
            'fundingTimestamp' => $fundingTimestamp,
            'fundingDatetime' => $this->iso8601($fundingTimestamp),
            'nextFundingRate' => null,
            'nextFundingTimestamp' => null,
            'nextFundingDatetime' => null,
            'previousFundingRate' => null,
            'previousFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'interval' => '1h',
        );
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     array(
        //         "prevDayPx" => "3400.5",
        //         "dayNtlVlm" => "511297257.47936022",
        //         "markPx" => "3464.7",
        //         "midPx" => "3465.05",
        //         "oraclePx" => "3460.1", // only in swap
        //         "openInterest" => "64638.1108", // only in swap
        //         "premium" => "0.00141614", // only in swap
        //         "funding" => "0.00008727", // only in swap
        //         "impactPxs" => array( "3465.0", "3465.1" ), // only in swap
        //         "coin" => "PURR", // only in spot
        //         "circulatingSupply" => "998949190.03400207", // only in spot
        //     ),
        //
        $bidAsk = $this->safe_list($ticker, 'impactPxs');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => null,
            'datetime' => null,
            'previousClose' => $this->safe_number($ticker, 'prevDayPx'),
            'close' => $this->safe_number($ticker, 'midPx'),
            'bid' => $this->safe_number($bidAsk, 0),
            'ask' => $this->safe_number($bidAsk, 1),
            'quoteVolume' => $this->safe_number($ticker, 'dayNtlVlm'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#candle-snapshot
         *
         * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
         * @param {string} $timeframe the length of time each candle represents, support '1m', '15m', '1h', '1d'
         * @param {int} [$since] timestamp in ms of the earliest candle to fetch
         * @param {int} [$limit] the maximum amount of candles to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $until = $this->safe_integer($params, 'until', $this->milliseconds());
        $useTail = $since === null;
        $originalSince = $since;
        if ($since === null) {
            if ($limit !== null) {
                // optimization if $limit is provided
                $timeframeInMilliseconds = $this->parse_timeframe($timeframe) * 1000;
                $since = $this->sum($until, $timeframeInMilliseconds * $limit * -1);
                $useTail = false;
            } else {
                $since = 0;
            }
        }
        $params = $this->omit($params, array( 'until' ));
        $request = array(
            'type' => 'candleSnapshot',
            'req' => array(
                'coin' => $market['swap'] ? $market['baseName'] : $market['id'],
                'interval' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
                'startTime' => $since,
                'endTime' => $until,
            ),
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "T" => 1704287699999,
        //             "c" => "2226.4",
        //             "h" => "2247.9",
        //             "i" => "15m",
        //             "l" => "2224.6",
        //             "n" => 46,
        //             "o" => "2247.9",
        //             "s" => "ETH",
        //             "t" => 1704286800000,
        //             "v" => "591.6427"
        //         }
        //     )
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $originalSince, $limit, $useTail);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "T" => 1704287699999,
        //         "c" => "2226.4",
        //         "h" => "2247.9",
        //         "i" => "15m",
        //         "l" => "2224.6",
        //         "n" => 46,
        //         "o" => "2247.9",
        //         "s" => "ETH",
        //         "t" => 1704286800000,
        //         "v" => "591.6427"
        //     }
        //
        return array(
            $this->safe_integer($ohlcv, 't'),
            $this->safe_number($ohlcv, 'o'),
            $this->safe_number($ohlcv, 'h'),
            $this->safe_number($ohlcv, 'l'),
            $this->safe_number($ohlcv, 'c'),
            $this->safe_number($ohlcv, 'v'),
        );
    }

    public function fetch_trades(?string $symbol, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * get the list of most recent trades for a particular $symbol
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills-by-time
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest trade
         * @param {string} [$params->address] wallet address that made trades
         * @param {string} [$params->user] wallet address that made trades
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchTrades', $params);
        $this->load_markets();
        $market = $this->safe_market($symbol);
        $request = array(
            'user' => $userAddress,
        );
        if ($since !== null) {
            $request['type'] = 'userFillsByTime';
            $request['startTime'] = $since;
        } else {
            $request['type'] = 'userFills';
        }
        $until = $this->safe_integer($params, 'until');
        $params = $this->omit($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "closedPnl" => "0.19343",
        //             "coin" => "ETH",
        //             "crossed" => true,
        //             "dir" => "Close Long",
        //             "fee" => "0.050062",
        //             "hash" => "0x09d77c96791e98b5775a04092584ab010d009445119c71e4005c0d634ea322bc",
        //             "liquidationMarkPx" => null,
        //             "oid" => 3929354691,
        //             "px" => "2381.1",
        //             "side" => "A",
        //             "startPosition" => "0.0841",
        //             "sz" => "0.0841",
        //             "tid" => 128423918764978,
        //             "time" => 1704262888911
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function amount_to_precision($symbol, $amount) {
        $market = $this->market($symbol);
        return $this->decimal_to_precision($amount, ROUND, $market['precision']['amount'], $this->precisionMode, $this->paddingMode);
    }

    public function price_to_precision(string $symbol, $price): string {
        $market = $this->market($symbol);
        $priceStr = $this->number_to_string($price);
        $integerPart = explode('.', $priceStr)[0];
        $significantDigits = max (5, strlen($integerPart));
        $result = $this->decimal_to_precision($price, ROUND, $significantDigits, SIGNIFICANT_DIGITS, $this->paddingMode);
        $maxDecimals = $market['spot'] ? 8 : 6;
        $subtractedValue = $maxDecimals - $this->precision_from_string($this->safe_string($market['precision'], 'amount'));
        return $this->decimal_to_precision($result, ROUND, $subtractedValue, DECIMAL_PLACES, $this->paddingMode);
    }

    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);
        return array(
            'r' => '0x' . $signature['r'],
            's' => '0x' . $signature['s'],
            'v' => $this->sum(27, $signature['v']),
        );
    }

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

    public function construct_phantom_agent($hash, $isTestnet = true) {
        $source = ($isTestnet) ? 'b' : 'a';
        return array(
            'source' => $source,
            'connectionId' => $hash,
        );
    }

    public function action_hash($action, $vaultAddress, $nonce) {
        $dataBinary = $this->packb($action);
        $dataHex = bin2hex($dataBinary);
        $data = $dataHex;
        $data .= '00000' . $this->int_to_base16($nonce);
        if ($vaultAddress === null) {
            $data .= '00';
        } else {
            $data .= '01';
            $data .= $vaultAddress;
        }
        return $this->hash($this->base16_to_binary($data), 'keccak', 'binary');
    }

    public function sign_l1_action($action, $nonce, $vaultAdress = null): array {
        $hash = $this->action_hash($action, $vaultAdress, $nonce);
        $isTestnet = $this->safe_bool($this->options, 'sandboxMode', false);
        $phantomAgent = $this->construct_phantom_agent($hash, $isTestnet);
        // $data = array(
        //     'domain' => array(
        //         'chainId' => 1337,
        //         'name' => 'Exchange',
        //         'verifyingContract' => '0x0000000000000000000000000000000000000000',
        //         'version' => '1',
        //     ),
        //     'types' => array(
        //         'Agent' => array(
        //             array( 'name' => 'source', 'type' => 'string' ),
        //             array( 'name' => 'connectionId', 'type' => 'bytes32' ),
        //         ),
        //         'EIP712Domain' => array(
        //             array( 'name' => 'name', 'type' => 'string' ),
        //             array( 'name' => 'version', 'type' => 'string' ),
        //             array( 'name' => 'chainId', 'type' => 'uint256' ),
        //             array( 'name' => 'verifyingContract', 'type' => 'address' ),
        //         ),
        //     ),
        //     'primaryType' => 'Agent',
        //     'message' => $phantomAgent,
        // );
        $zeroAddress = $this->safe_string($this->options, 'zeroAddress');
        $chainId = 1337; // check this out
        $domain = array(
            'chainId' => $chainId,
            'name' => 'Exchange',
            'verifyingContract' => $zeroAddress,
            'version' => '1',
        );
        $messageTypes = array(
            'Agent' => array(
                array( 'name' => 'source', 'type' => 'string' ),
                array( 'name' => 'connectionId', 'type' => 'bytes32' ),
            ),
        );
        $msg = $this->eth_encode_structured_data($domain, $messageTypes, $phantomAgent);
        $signature = $this->sign_message($msg, $this->privateKey);
        return $signature;
    }

    public function sign_user_signed_action($messageTypes, $message) {
        $zeroAddress = $this->safe_string($this->options, 'zeroAddress');
        $chainId = 421614; // check this out
        $domain = array(
            'chainId' => $chainId,
            'name' => 'HyperliquidSignTransaction',
            'verifyingContract' => $zeroAddress,
            'version' => '1',
        );
        $msg = $this->eth_encode_structured_data($domain, $messageTypes, $message);
        $signature = $this->sign_message($msg, $this->privateKey);
        return $signature;
    }

    public function build_usd_send_sig($message) {
        $messageTypes = array(
            'HyperliquidTransaction:UsdSend' => array(
                array( 'name' => 'hyperliquidChain', 'type' => 'string' ),
                array( 'name' => 'destination', 'type' => 'string' ),
                array( 'name' => 'amount', 'type' => 'string' ),
                array( 'name' => 'time', 'type' => 'uint64' ),
            ),
        );
        return $this->sign_user_signed_action($messageTypes, $message);
    }

    public function build_usd_class_send_sig($message) {
        $messageTypes = array(
            'HyperliquidTransaction:UsdClassTransfer' => array(
                array( 'name' => 'hyperliquidChain', 'type' => 'string' ),
                array( 'name' => 'amount', 'type' => 'string' ),
                array( 'name' => 'toPerp', 'type' => 'bool' ),
                array( 'name' => 'nonce', 'type' => 'uint64' ),
            ),
        );
        return $this->sign_user_signed_action($messageTypes, $message);
    }

    public function build_withdraw_sig($message) {
        $messageTypes = array(
            'HyperliquidTransaction:Withdraw' => array(
                array( 'name' => 'hyperliquidChain', 'type' => 'string' ),
                array( 'name' => 'destination', 'type' => 'string' ),
                array( 'name' => 'amount', 'type' => 'string' ),
                array( 'name' => 'time', 'type' => 'uint64' ),
            ),
        );
        return $this->sign_user_signed_action($messageTypes, $message);
    }

    public function build_approve_builder_fee_sig($message) {
        $messageTypes = array(
            'HyperliquidTransaction:ApproveBuilderFee' => array(
                array( 'name' => 'hyperliquidChain', 'type' => 'string' ),
                array( 'name' => 'maxFeeRate', 'type' => 'string' ),
                array( 'name' => 'builder', 'type' => 'address' ),
                array( 'name' => 'nonce', 'type' => 'uint64' ),
            ),
        );
        return $this->sign_user_signed_action($messageTypes, $message);
    }

    public function approve_builder_fee(string $builder, string $maxFeeRate) {
        $nonce = $this->milliseconds();
        $isSandboxMode = $this->safe_bool($this->options, 'sandboxMode', false);
        $payload = array(
            'hyperliquidChain' => $isSandboxMode ? 'Testnet' : 'Mainnet',
            'maxFeeRate' => $maxFeeRate,
            'builder' => $builder,
            'nonce' => $nonce,
        );
        $sig = $this->build_approve_builder_fee_sig($payload);
        $action = array(
            'hyperliquidChain' => $payload['hyperliquidChain'],
            'signatureChainId' => '0x66eee',
            'maxFeeRate' => $payload['maxFeeRate'],
            'builder' => $payload['builder'],
            'nonce' => $nonce,
            'type' => 'approveBuilderFee',
        );
        $request = array(
            'action' => $action,
            'nonce' => $nonce,
            'signature' => $sig,
            'vaultAddress' => null,
        );
        //
        // {
        //     "status" => "ok",
        //     "response" => {
        //         "type" => "default"
        //     }
        // }
        //
        return $this->privatePostExchange ($request);
    }

    public function handle_builder_fee_approval() {
        $buildFee = $this->safe_bool($this->options, 'builderFee', true);
        if (!$buildFee) {
            return false; // skip if $builder fee is not enabled
        }
        $approvedBuilderFee = $this->safe_bool($this->options, 'approvedBuilderFee', false);
        if ($approvedBuilderFee) {
            return true; // skip if $builder fee is already approved
        }
        try {
            $builder = $this->safe_string($this->options, 'builder', '0x6530512A6c89C7cfCEbC3BA7fcD9aDa5f30827a6');
            $maxFeeRate = $this->safe_string($this->options, 'feeRate', '0.01%');
            $this->approve_builder_fee($builder, $maxFeeRate);
            $this->options['approvedBuilderFee'] = true;
        } catch (Exception $e) {
            $this->options['builderFee'] = false; // disable $builder fee if an error occurs
        }
        return true;
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade $order
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-$order
         *
         * @param {string} $symbol unified $symbol of the market to create an $order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in market $orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->timeInForce] 'Gtc', 'Ioc', 'Alo'
         * @param {bool} [$params->postOnly] true or false whether the $order is post-only
         * @param {bool} [$params->reduceOnly] true or false whether the $order is reduce-only
         * @param {float} [$params->triggerPrice] The $price at which a trigger $order is triggered at
         * @param {string} [$params->clientOrderId] client $order id, (optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
         * @param {string} [$params->slippage] the slippage for market $order
         * @param {string} [$params->vaultAddress] the vault address for $order
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
         */
        $this->load_markets();
        list($order, $globalParams) = $this->parse_create_edit_order_args(null, $symbol, $type, $side, $amount, $price, $params);
        $orders = $this->create_orders(array( $order ), $globalParams);
        return $orders[0];
    }

    public function create_orders(array $orders, $params = array ()) {
        /**
         * create a list of trade $orders
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-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~
         */
        $this->load_markets();
        $this->handle_builder_fee_approval();
        $request = $this->create_orders_request($orders, $params);
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         "status" => "ok",
        //         "response" => {
        //             "type" => "order",
        //             "data" => {
        //                 "statuses" => array(
        //                     {
        //                         "resting" => {
        //                             "oid" => 5063830287
        //                         }
        //                     }
        //                 )
        //             }
        //         }
        //     }
        //
        $responseObj = $this->safe_dict($response, 'response', array());
        $data = $this->safe_dict($responseObj, 'data', array());
        $statuses = $this->safe_list($data, 'statuses', array());
        return $this->parse_orders($statuses, null);
    }

    public function create_order_request(string $symbol, string $type, string $side, string $amount, ?string $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $type = strtoupper($type);
        $side = strtoupper($side);
        $isMarket = ($type === 'MARKET');
        $isBuy = ($side === 'BUY');
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'client_id');
        $slippage = $this->safe_string($params, 'slippage');
        $defaultTimeInForce = ($isMarket) ? 'ioc' : 'gtc';
        $postOnly = $this->safe_bool($params, 'postOnly', false);
        if ($postOnly) {
            $defaultTimeInForce = 'alo';
        }
        $timeInForce = $this->safe_string_lower($params, 'timeInForce', $defaultTimeInForce);
        $timeInForce = $this->capitalize($timeInForce);
        $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
        $stopLossPrice = $this->safe_string($params, 'stopLossPrice', $triggerPrice);
        $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
        $isTrigger = ($stopLossPrice || $takeProfitPrice);
        $px = null;
        if ($isMarket) {
            if ($price === null) {
                throw new ArgumentsRequired($this->id . '  $market orders require $price to calculate the max $slippage $price-> Default $slippage can be set in options (default is 5%).');
            }
            $px = ($isBuy) ? Precise::string_mul($price, Precise::string_add('1', $slippage)) : Precise::string_mul($price, Precise::string_sub('1', $slippage));
            $px = $this->price_to_precision($symbol, $px); // round after adding $slippage
        } else {
            $px = $this->price_to_precision($symbol, $price);
        }
        $sz = $this->amount_to_precision($symbol, $amount);
        $reduceOnly = $this->safe_bool($params, 'reduceOnly', false);
        $orderType = array();
        if ($isTrigger) {
            $isTp = false;
            if ($takeProfitPrice !== null) {
                $triggerPrice = $this->price_to_precision($symbol, $takeProfitPrice);
                $isTp = true;
            } else {
                $triggerPrice = $this->price_to_precision($symbol, $stopLossPrice);
            }
            $orderType['trigger'] = array(
                'isMarket' => $isMarket,
                'triggerPx' => $triggerPrice,
                'tpsl' => ($isTp) ? 'tp' : 'sl',
            );
        } else {
            $orderType['limit'] = array(
                'tif' => $timeInForce,
            );
        }
        $params = $this->omit($params, array( 'clientOrderId', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce', 'client_id', 'reduceOnly', 'postOnly' ));
        $orderObj = array(
            'a' => $this->parse_to_int($market['baseId']),
            'b' => $isBuy,
            'p' => $px,
            's' => $sz,
            'r' => $reduceOnly,
            't' => $orderType,
            // 'c' => $clientOrderId,
        );
        if ($clientOrderId !== null) {
            $orderObj['c'] = $clientOrderId;
        }
        return $orderObj;
    }

    public function create_orders_request($orders, $params = array ()): array {
        /**
         * create a list of trade $orders
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-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
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->check_required_credentials();
        $defaultSlippage = $this->safe_string($this->options, 'defaultSlippage');
        $defaultSlippage = $this->safe_string($params, 'slippage', $defaultSlippage);
        $hasClientOrderId = false;
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $orderParams = $this->safe_dict($rawOrder, 'params', array());
            $clientOrderId = $this->safe_string_2($orderParams, 'clientOrderId', 'client_id');
            if ($clientOrderId !== null) {
                $hasClientOrderId = true;
            }
        }
        if ($hasClientOrderId) {
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $orderParams = $this->safe_dict($rawOrder, 'params', array());
                $clientOrderId = $this->safe_string_2($orderParams, 'clientOrderId', 'client_id');
                if ($clientOrderId === null) {
                    throw new ArgumentsRequired($this->id . ' createOrders() all $orders must have $clientOrderId if at least one has a clientOrderId');
                }
            }
        }
        $params = $this->omit($params, array( 'slippage', 'clientOrderId', 'client_id', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce' ));
        $nonce = $this->milliseconds();
        $orderReq = array();
        $grouping = 'na';
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $marketId = $this->safe_string($rawOrder, 'symbol');
            $market = $this->market($marketId);
            $symbol = $market['symbol'];
            $type = $this->safe_string_upper($rawOrder, 'type');
            $side = $this->safe_string_upper($rawOrder, 'side');
            $amount = $this->safe_string($rawOrder, 'amount');
            $price = $this->safe_string($rawOrder, 'price');
            $orderParams = $this->safe_dict($rawOrder, 'params', array());
            $slippage = $this->safe_string($orderParams, 'slippage', $defaultSlippage);
            $orderParams['slippage'] = $slippage;
            $stopLoss = $this->safe_value($orderParams, 'stopLoss');
            $takeProfit = $this->safe_value($orderParams, 'takeProfit');
            $isTrigger = ($stopLoss || $takeProfit);
            $orderParams = $this->omit($orderParams, array( 'stopLoss', 'takeProfit' ));
            $mainOrderObj = $this->create_order_request($symbol, $type, $side, $amount, $price, $orderParams);
            $orderReq[] = $mainOrderObj;
            if ($isTrigger) {
                // $grouping opposed $orders for sl/tp
                $stopLossOrderTriggerPrice = $this->safe_string_n($stopLoss, array( 'triggerPrice', 'stopPrice' ));
                $stopLossOrderType = $this->safe_string($stopLoss, 'type');
                $stopLossOrderLimitPrice = $this->safe_string_n($stopLoss, array( 'price', 'stopLossPrice' ), $stopLossOrderTriggerPrice);
                $takeProfitOrderTriggerPrice = $this->safe_string_n($takeProfit, array( 'triggerPrice', 'stopPrice' ));
                $takeProfitOrderType = $this->safe_string($takeProfit, 'type');
                $takeProfitOrderLimitPrice = $this->safe_string_n($takeProfit, array( 'price', 'takeProfitPrice' ), $takeProfitOrderTriggerPrice);
                $grouping = 'normalTpsl';
                $orderParams = $this->omit($orderParams, array( 'stopLoss', 'takeProfit' ));
                $triggerOrderSide = '';
                if ($side === 'BUY') {
                    $triggerOrderSide = 'sell';
                } else {
                    $triggerOrderSide = 'buy';
                }
                if ($takeProfit !== null) {
                    $orderObj = $this->create_order_request($symbol, $takeProfitOrderType, $triggerOrderSide, $amount, $takeProfitOrderLimitPrice, $this->extend($orderParams, array(
                        'takeProfitPrice' => $takeProfitOrderTriggerPrice,
                        'reduceOnly' => true,
                    )));
                    $orderReq[] = $orderObj;
                }
                if ($stopLoss !== null) {
                    $orderObj = $this->create_order_request($symbol, $stopLossOrderType, $triggerOrderSide, $amount, $stopLossOrderLimitPrice, $this->extend($orderParams, array(
                        'stopLossPrice' => $stopLossOrderTriggerPrice,
                        'reduceOnly' => true,
                    )));
                    $orderReq[] = $orderObj;
                }
            }
        }
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params($params, 'createOrder', 'vaultAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $orderAction = array(
            'type' => 'order',
            'orders' => $orderReq,
            'grouping' => $grouping,
        );
        if ($this->safe_bool($this->options, 'approvedBuilderFee', false)) {
            $wallet = $this->safe_string_lower($this->options, 'builder', '0x6530512A6c89C7cfCEbC3BA7fcD9aDa5f30827a6');
            $orderAction['builder'] = array( 'b' => $wallet, 'f' => $this->safe_integer($this->options, 'feeInt', 10) );
        }
        $signature = $this->sign_l1_action($orderAction, $nonce, $vaultAddress);
        $request = array(
            'action' => $orderAction,
            'nonce' => $nonce,
            'signature' => $signature,
            // 'vaultAddress' => $vaultAddress,
        );
        if ($vaultAddress !== null) {
            $params = $this->omit($params, 'vaultAddress');
            $request['vaultAddress'] = $vaultAddress;
        }
        return $request;
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] client order $id, (optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
         * @param {string} [$params->vaultAddress] the vault address for order
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $orders = $this->cancel_orders(array( $id ), $symbol, $params);
        return $this->safe_dict($orders, 0);
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()) {
        /**
         * cancel multiple $orders
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
         *
         * @param {string[]} $ids order $ids
         * @param {string} [$symbol] unified market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string|string[]} [$params->clientOrderId] client order $ids, (optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
         * @param {string} [$params->vaultAddress] the vault address
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->check_required_credentials();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $request = $this->cancel_orders_request($ids, $symbol, $params);
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         "status":"ok",
        //         "response":{
        //             "type":"cancel",
        //             "data":{
        //                 "statuses":array(
        //                     "success"
        //                 )
        //             }
        //         }
        //     }
        //
        $innerResponse = $this->safe_dict($response, 'response');
        $data = $this->safe_dict($innerResponse, 'data');
        $statuses = $this->safe_list($data, 'statuses');
        $orders = array();
        for ($i = 0; $i < count($statuses); $i++) {
            $status = $statuses[$i];
            $orders[] = $this->safe_order(array(
                'info' => $status,
                'status' => $status,
            ));
        }
        return $orders;
    }

    public function cancel_orders_request(array $ids, ?string $symbol = null, $params = array ()): array {
        /**
         * build the $request payload for cancelling multiple orders
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
         * @param {string[]} $ids order $ids
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params]
         * @return {array} the raw $request object to be sent to the exchange
         */
        $market = $this->market($symbol);
        $clientOrderId = $this->safe_value_2($params, 'clientOrderId', 'client_id');
        $params = $this->omit($params, array( 'clientOrderId', 'client_id' ));
        $nonce = $this->milliseconds();
        $request = array(
            'nonce' => $nonce,
            // 'vaultAddress' => $vaultAddress,
        );
        $cancelReq = array();
        $cancelAction = array(
            'type' => '',
            'cancels' => array(),
        );
        $baseId = $this->parse_to_numeric($market['baseId']);
        if ($clientOrderId !== null) {
            if (gettype($clientOrderId) !== 'array' || array_keys($clientOrderId) !== array_keys(array_keys($clientOrderId))) {
                $clientOrderId = array( $clientOrderId );
            }
            $cancelAction['type'] = 'cancelByCloid';
            for ($i = 0; $i < count($clientOrderId); $i++) {
                $cancelReq[] = array(
                    'asset' => $baseId,
                    'cloid' => $clientOrderId[$i],
                );
            }
        } else {
            $cancelAction['type'] = 'cancel';
            for ($i = 0; $i < count($ids); $i++) {
                $cancelReq[] = array(
                    'a' => $baseId,
                    'o' => $this->parse_to_numeric($ids[$i]),
                );
            }
        }
        $cancelAction['cancels'] = $cancelReq;
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'cancelOrders', 'vaultAddress', 'subAccountAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $signature = $this->sign_l1_action($cancelAction, $nonce, $vaultAddress);
        $request['action'] = $cancelAction;
        $request['signature'] = $signature;
        if ($vaultAddress !== null) {
            $params = $this->omit($params, 'vaultAddress');
            $request['vaultAddress'] = $vaultAddress;
        }
        return $request;
    }

    public function cancel_orders_for_symbols(array $orders, $params = array ()) {
        /**
         * cancel multiple $orders for multiple symbols
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-$order-s
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-$order-s-by-cloid
         *
         * @param {CancellationRequest[]} $orders each $order should contain the parameters required by cancelOrder namely $id and $symbol, example [array("id" => "a", "symbol" => "BTC/USDT"), array("id" => "b", "symbol" => "ETH/USDT")]
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->vaultAddress] the vault address
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structures~
         */
        $this->check_required_credentials();
        $this->load_markets();
        $nonce = $this->milliseconds();
        $request = array(
            'nonce' => $nonce,
            // 'vaultAddress' => $vaultAddress,
        );
        $cancelReq = array();
        $cancelAction = array(
            'type' => '',
            'cancels' => array(),
        );
        $cancelByCloid = false;
        for ($i = 0; $i < count($orders); $i++) {
            $order = $orders[$i];
            $clientOrderId = $this->safe_string($order, 'clientOrderId');
            if ($clientOrderId !== null) {
                $cancelByCloid = true;
            }
            $id = $this->safe_string($order, 'id');
            $symbol = $this->safe_string($order, 'symbol');
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrdersForSymbols() requires a $symbol argument in each order');
            }
            if ($id !== null && $cancelByCloid) {
                throw new BadRequest($this->id . ' cancelOrdersForSymbols() all $orders must have either $id or clientOrderId');
            }
            $assetKey = $cancelByCloid ? 'asset' : 'a';
            $idKey = $cancelByCloid ? 'cloid' : 'o';
            $market = $this->market($symbol);
            $cancelObj = array();
            $cancelObj[$assetKey] = $this->parse_to_numeric($market['baseId']);
            $cancelObj[$idKey] = $cancelByCloid ? $clientOrderId : $this->parse_to_numeric($id);
            $cancelReq[] = $cancelObj;
        }
        $cancelAction['type'] = $cancelByCloid ? 'cancelByCloid' : 'cancel';
        $cancelAction['cancels'] = $cancelReq;
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'cancelOrdersForSymbols', 'vaultAddress', 'subAccountAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $signature = $this->sign_l1_action($cancelAction, $nonce, $vaultAddress);
        $request['action'] = $cancelAction;
        $request['signature'] = $signature;
        if ($vaultAddress !== null) {
            $params = $this->omit($params, 'vaultAddress');
            $request['vaultAddress'] = $vaultAddress;
        }
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         "status":"ok",
        //         "response":{
        //             "type":"cancel",
        //             "data":{
        //                 "statuses":array(
        //                     "success"
        //                 )
        //             }
        //         }
        //     }
        //
        return array( $this->safe_order(array( 'info' => $response )) );
    }

    public function cancel_all_orders_after(?int $timeout, $params = array ()) {
        /**
         * dead man's switch, cancel all orders after the given $timeout
         * @param {number} $timeout time in milliseconds, 0 represents cancel the timer
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->vaultAddress] the vault address
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} the api result
         */
        $this->check_required_credentials();
        $this->load_markets();
        $params = $this->omit($params, array( 'clientOrderId', 'client_id' ));
        $nonce = $this->milliseconds();
        $request = array(
            'nonce' => $nonce,
            // 'vaultAddress' => $vaultAddress,
        );
        $cancelAction = array(
            'type' => 'scheduleCancel',
            'time' => $nonce . $timeout,
        );
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'cancelAllOrdersAfter', 'vaultAddress', 'subAccountAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $signature = $this->sign_l1_action($cancelAction, $nonce, $vaultAddress);
        $request['action'] = $cancelAction;
        $request['signature'] = $signature;
        if ($vaultAddress !== null) {
            $params = $this->omit($params, 'vaultAddress');
            $request['vaultAddress'] = $vaultAddress;
        }
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         "status":"err",
        //         "response":"Cannot set scheduled cancel time until enough volume traded. Required => $1000000. Traded => $373.47205."
        //     }
        //
        return $response;
    }

    public function edit_orders_request($orders, $params = array ()) {
        $this->check_required_credentials();
        $hasClientOrderId = false;
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $orderParams = $this->safe_dict($rawOrder, 'params', array());
            $clientOrderId = $this->safe_string_2($orderParams, 'clientOrderId', 'client_id');
            if ($clientOrderId !== null) {
                $hasClientOrderId = true;
            }
        }
        if ($hasClientOrderId) {
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $orderParams = $this->safe_dict($rawOrder, 'params', array());
                $clientOrderId = $this->safe_string_2($orderParams, 'clientOrderId', 'client_id');
                if ($clientOrderId === null) {
                    throw new ArgumentsRequired($this->id . ' editOrders() all $orders must have $clientOrderId if at least one has a clientOrderId');
                }
            }
        }
        $params = $this->omit($params, array( 'slippage', 'clientOrderId', 'client_id', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce' ));
        $modifies = array();
        for ($i = 0; $i < count($orders); $i++) {
            $rawOrder = $orders[$i];
            $id = $this->safe_string($rawOrder, 'id');
            $marketId = $this->safe_string($rawOrder, 'symbol');
            $market = $this->market($marketId);
            $symbol = $market['symbol'];
            $type = $this->safe_string_upper($rawOrder, 'type');
            $isMarket = ($type === 'MARKET');
            $side = $this->safe_string_upper($rawOrder, 'side');
            $isBuy = ($side === 'BUY');
            $amount = $this->safe_string($rawOrder, 'amount');
            $price = $this->safe_string($rawOrder, 'price');
            $orderParams = $this->safe_dict($rawOrder, 'params', array());
            $defaultSlippage = $this->safe_string($this->options, 'defaultSlippage');
            $slippage = $this->safe_string($orderParams, 'slippage', $defaultSlippage);
            $defaultTimeInForce = ($isMarket) ? 'ioc' : 'gtc';
            $postOnly = $this->safe_bool($orderParams, 'postOnly', false);
            if ($postOnly) {
                $defaultTimeInForce = 'alo';
            }
            $timeInForce = $this->safe_string_lower($orderParams, 'timeInForce', $defaultTimeInForce);
            $timeInForce = $this->capitalize($timeInForce);
            $clientOrderId = $this->safe_string_2($orderParams, 'clientOrderId', 'client_id');
            $triggerPrice = $this->safe_string_2($orderParams, 'triggerPrice', 'stopPrice');
            $stopLossPrice = $this->safe_string($orderParams, 'stopLossPrice', $triggerPrice);
            $takeProfitPrice = $this->safe_string($orderParams, 'takeProfitPrice');
            $isTrigger = ($stopLossPrice || $takeProfitPrice);
            $reduceOnly = $this->safe_bool($orderParams, 'reduceOnly', false);
            $orderParams = $this->omit($orderParams, array( 'slippage', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'clientOrderId', 'client_id', 'postOnly', 'reduceOnly' ));
            $px = $this->number_to_string($price);
            if ($isMarket) {
                $px = ($isBuy) ? Precise::string_mul($px, Precise::string_add('1', $slippage)) : Precise::string_mul($px, Precise::string_sub('1', $slippage));
                $px = $this->price_to_precision($symbol, $px);
            } else {
                $px = $this->price_to_precision($symbol, $px);
            }
            $sz = $this->amount_to_precision($symbol, $amount);
            $orderType = array();
            if ($isTrigger) {
                $isTp = false;
                if ($takeProfitPrice !== null) {
                    $triggerPrice = $this->price_to_precision($symbol, $takeProfitPrice);
                    $isTp = true;
                } else {
                    $triggerPrice = $this->price_to_precision($symbol, $stopLossPrice);
                }
                $orderType['trigger'] = array(
                    'isMarket' => $isMarket,
                    'triggerPx' => $triggerPrice,
                    'tpsl' => ($isTp) ? 'tp' : 'sl',
                );
            } else {
                $orderType['limit'] = array(
                    'tif' => $timeInForce,
                );
            }
            if ($triggerPrice === null) {
                $triggerPrice = '0';
            }
            $orderReq = array(
                'a' => $this->parse_to_int($market['baseId']),
                'b' => $isBuy,
                'p' => $px,
                's' => $sz,
                'r' => $reduceOnly,
                't' => $orderType,
                // 'c' => $clientOrderId,
            );
            if ($clientOrderId !== null) {
                $orderReq['c'] = $clientOrderId;
            }
            $modifyReq = array(
                'oid' => $this->parse_to_int($id),
                'order' => $orderReq,
            );
            $modifies[] = $modifyReq;
        }
        $nonce = $this->milliseconds();
        $modifyAction = array(
            'type' => 'batchModify',
            'modifies' => $modifies,
        );
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params($params, 'editOrder', 'vaultAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $signature = $this->sign_l1_action($modifyAction, $nonce, $vaultAddress);
        $request = array(
            'action' => $modifyAction,
            'nonce' => $nonce,
            'signature' => $signature,
            // 'vaultAddress' => $vaultAddress,
        );
        if ($vaultAddress !== null) {
            $request['vaultAddress'] = $vaultAddress;
        }
        return $request;
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        /**
         * edit a trade $order
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-multiple-$orders
         *
         * @param {string} $id cancel $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 {string} [$params->timeInForce] 'Gtc', 'Ioc', 'Alo'
         * @param {bool} [$params->postOnly] true or false whether the $order is post-only
         * @param {bool} [$params->reduceOnly] true or false whether the $order is reduce-only
         * @param {float} [$params->triggerPrice] The $price at which a trigger $order is triggered at
         * @param {string} [$params->clientOrderId] client $order $id, (optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
         * @param {string} [$params->vaultAddress] the vault address for $order
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
         */
        $this->load_markets();
        if ($id === null) {
            throw new ArgumentsRequired($this->id . ' editOrder() requires an $id argument');
        }
        list($order, $globalParams) = $this->parse_create_edit_order_args($id, $symbol, $type, $side, $amount, $price, $params);
        $orders = $this->edit_orders(array( $order ), $globalParams);
        return $orders[0];
    }

    public function edit_orders(array $orders, $params = array ()) {
        /**
         * edit a list of trade $orders
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-multiple-$orders
         *
         * @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~
         */
        $this->load_markets();
        $request = $this->edit_orders_request($orders, $params);
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         "status" => "ok",
        //         "response" => {
        //             "type" => "order",
        //             "data" => {
        //                 "statuses" => array(
        //                     {
        //                         "resting" => {
        //                             "oid" => 5063830287
        //                         }
        //                     }
        //                 )
        //             }
        //         }
        //     }
        // when the order is filled immediately
        //     {
        //         "status":"ok",
        //         "response":{
        //            "type":"order",
        //            "data":{
        //               "statuses":array(
        //                  {
        //                     "filled":{
        //                        "totalSz":"0.1",
        //                        "avgPx":"100.84",
        //                        "oid":6195281425
        //                     }
        //                  }
        //               )
        //            }
        //         }
        //     }
        //
        $responseObject = $this->safe_dict($response, 'response', array());
        $dataObject = $this->safe_dict($responseObject, 'data', array());
        $statuses = $this->safe_list($dataObject, 'statuses', array());
        return $this->parse_orders($statuses);
    }

    public function create_vault(string $name, string $description, int $initialUsd, $params = array ()) {
        /**
         * creates a value
         * @param {string} $name The $name of the vault
         * @param {string} $description The $description of the vault
         * @param {number} $initialUsd The $initialUsd of the vault
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} the api result
         */
        $this->check_required_credentials();
        $this->load_markets();
        $nonce = $this->milliseconds();
        $request = array(
            'nonce' => $nonce,
        );
        $usd = $this->parse_to_int(Precise::string_mul($this->number_to_string($initialUsd), '1000000'));
        $action = array(
            'type' => 'createVault',
            'name' => $name,
            'description' => $description,
            'initialUsd' => $usd,
            'nonce' => $nonce,
        );
        $signature = $this->sign_l1_action($action, $nonce);
        $request['action'] = $action;
        $request['signature'] = $signature;
        $response = $this->privatePostExchange ($this->extend($request, $params));
        //
        // {
        //     "status" => "ok",
        //     "response" => {
        //         "type" => "createVault",
        //         "data" => "0x04fddcbc9ce80219301bd16f18491bedf2a8c2b8"
        //     }
        // }
        //
        return $response;
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches historical funding rate prices
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-historical-funding-rates
         *
         * @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
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
         */
        $this->load_markets();
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
        }
        $market = $this->market($symbol);
        $request = array(
            'type' => 'fundingHistory',
            'coin' => $market['baseName'],
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        } else {
            $maxLimit = ($limit === null) ? 500 : $limit;
            $request['startTime'] = $this->milliseconds() - $maxLimit * 60 * 60 * 1000;
        }
        $until = $this->safe_integer($params, 'until');
        $params = $this->omit($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "coin" => "ETH",
        //             "fundingRate" => "0.0000125",
        //             "premium" => "0.00057962",
        //             "time" => 1704290400031
        //         }
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $entry = $response[$i];
            $timestamp = $this->safe_integer($entry, 'time');
            $result[] = array(
                'info' => $entry,
                'symbol' => $this->safe_symbol(null, $market),
                'fundingRate' => $this->safe_number($entry, 'fundingRate'),
                'timestamp' => $timestamp,
                'datetime' => $this->iso8601($timestamp),
            );
        }
        $sorted = $this->sort_by($result, 'timestamp');
        return $this->filter_by_symbol_since_limit($sorted, $symbol, $since, $limit);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently open orders
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-open-orders
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of open orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @param {string} [$params->method] 'openOrders' or 'frontendOpenOrders' default is 'frontendOpenOrders'
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=$order-structure $order structures~
         */
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchOpenOrders', $params);
        $method = null;
        list($method, $params) = $this->handle_option_and_params($params, 'fetchOpenOrders', 'method', 'frontendOpenOrders');
        $this->load_markets();
        $market = $this->safe_market($symbol);
        $request = array(
            'type' => $method,
            'user' => $userAddress,
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "coin" => "ETH",
        //             "limitPx" => "2000.0",
        //             "oid" => 3991946565,
        //             "origSz" => "0.1",
        //             "side" => "B",
        //             "sz" => "0.1",
        //             "timestamp" => 1704346468838
        //         }
        //     )
        //
        $orderWithStatus = array();
        for ($i = 0; $i < count($response); $i++) {
            $order = $response[$i];
            $extendOrder = array();
            if ($this->safe_string($order, 'status') === null) {
                $extendOrder['ccxtStatus'] = 'open';
            }
            $orderWithStatus[] = $this->extend($order, $extendOrder);
        }
        return $this->parse_orders($orderWithStatus, $market, $since, $limit);
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently closed $orders
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open $orders for
         * @param {int} [$limit] the maximum number of open $orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $orders = $this->fetch_orders($symbol, null, null, $params); // don't filter here because we don't want to catch open $orders
        $closedOrders = $this->filter_by_array($orders, 'status', array( 'closed' ), false);
        return $this->filter_by_symbol_since_limit($closedOrders, $symbol, $since, $limit);
    }

    public function fetch_canceled_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all canceled $orders
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open $orders for
         * @param {int} [$limit] the maximum number of open $orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $orders = $this->fetch_orders($symbol, null, null, $params); // don't filter here because we don't want to catch open $orders
        $closedOrders = $this->filter_by_array($orders, 'status', array( 'canceled' ), false);
        return $this->filter_by_symbol_since_limit($closedOrders, $symbol, $since, $limit);
    }

    public function fetch_canceled_and_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all closed and canceled $orders
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open $orders for
         * @param {int} [$limit] the maximum number of open $orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $orders = $this->fetch_orders($symbol, null, null, $params); // don't filter here because we don't want to catch open $orders
        $closedOrders = $this->filter_by_array($orders, 'status', array( 'canceled', 'closed', 'rejected' ), false);
        return $this->filter_by_symbol_since_limit($closedOrders, $symbol, $since, $limit);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all orders
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of open orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchOrders', $params);
        $this->load_markets();
        $market = $this->safe_market($symbol);
        $request = array(
            'type' => 'historicalOrders',
            'user' => $userAddress,
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "coin" => "ETH",
        //             "limitPx" => "2000.0",
        //             "oid" => 3991946565,
        //             "origSz" => "0.1",
        //             "side" => "B",
        //             "sz" => "0.1",
        //             "timestamp" => 1704346468838
        //         }
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#query-order-status-by-oid-or-cloid
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] client order $id, (optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchOrder', $params);
        $this->load_markets();
        $market = $this->safe_market($symbol);
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        $request = array(
            'type' => 'orderStatus',
            // 'oid' => $isClientOrderId ? $id : $this->parse_to_numeric($id),
            'user' => $userAddress,
        );
        if ($clientOrderId !== null) {
            $params = $this->omit($params, 'clientOrderId');
            $request['oid'] = $clientOrderId;
        } else {
            $isClientOrderId = strlen($id) >= 34;
            $request['oid'] = $isClientOrderId ? $id : $this->parse_to_numeric($id);
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     {
        //         "order" => array(
        //             "order" => array(
        //                 "children" => array(),
        //                 "cloid" => null,
        //                 "coin" => "ETH",
        //                 "isPositionTpsl" => false,
        //                 "isTrigger" => false,
        //                 "limitPx" => "2000.0",
        //                 "oid" => "3991946565",
        //                 "orderType" => "Limit",
        //                 "origSz" => "0.1",
        //                 "reduceOnly" => false,
        //                 "side" => "B",
        //                 "sz" => "0.1",
        //                 "tif" => "Gtc",
        //                 "timestamp" => "1704346468838",
        //                 "triggerCondition" => "N/A",
        //                 "triggerPx" => "0.0"
        //             ),
        //             "status" => "open",
        //             "statusTimestamp" => "1704346468838"
        //         ),
        //         "status" => "order"
        //     }
        //
        $data = $this->safe_dict($response, 'order');
        return $this->parse_order($data, $market);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrdersWs $error
        //
        //  array($error => 'Insufficient margin to place $order-> asset=159')
        //
        //  fetchOpenOrders
        //
        //     {
        //         "coin" => "ETH",
        //         "limitPx" => "2000.0",
        //         "oid" => 3991946565,
        //         "origSz" => "0.1",
        //         "side" => "B",
        //         "sz" => "0.1",
        //         "timestamp" => 1704346468838
        //     }
        // fetchClosedorders
        //    {
        //        "cloid" => null,
        //        "closedPnl" => "0.0",
        //        "coin" => "SOL",
        //        "crossed" => true,
        //        "dir" => "Open Long",
        //        "fee" => "0.003879",
        //        "hash" => "0x4a2647998682b7f07bc5040ab531e1011400f9a51bfa0346a0b41ebe510e8875",
        //        "liquidationMarkPx" => null,
        //        "oid" => "6463280784",
        //        "px" => "110.83",
        //        "side" => "B",
        //        "startPosition" => "1.64",
        //        "sz" => "0.1",
        //        "tid" => "232174667018988",
        //        "time" => "1709142268394"
        //    }
        //
        //  fetchOrder
        //
        //     {
        //         "order" => array(
        //             "children" => array(),
        //             "cloid" => null,
        //             "coin" => "ETH",
        //             "isPositionTpsl" => false,
        //             "isTrigger" => false,
        //             "limitPx" => "2000.0",
        //             "oid" => "3991946565",
        //             "orderType" => "Limit",
        //             "origSz" => "0.1",
        //             "reduceOnly" => false,
        //             "side" => "B",
        //             "sz" => "0.1",
        //             "tif" => "Gtc",
        //             "timestamp" => "1704346468838",
        //             "triggerCondition" => "N/A",
        //             "triggerPx" => "0.0"
        //         ),
        //         "status" => "open",
        //         "statusTimestamp" => "1704346468838"
        //     }
        //
        // createOrder
        //
        //     {
        //         "resting" => {
        //             "oid" => 5063830287
        //         }
        //     }
        //
        //     {
        //        "filled":{
        //           "totalSz":"0.1",
        //           "avgPx":"100.84",
        //           "oid":6195281425
        //        }
        //     }
        // frontendOrder
        // {
        //     "children" => array(),
        //     "cloid" => null,
        //     "coin" => "BLUR",
        //     "isPositionTpsl" => false,
        //     "isTrigger" => true,
        //     "limitPx" => "0.5",
        //     "oid" => 8670487141,
        //     "orderType" => "Stop Limit",
        //     "origSz" => "20.0",
        //     "reduceOnly" => false,
        //     "side" => "B",
        //     "sz" => "20.0",
        //     "tif" => null,
        //     "timestamp" => 1715523663687,
        //     "triggerCondition" => "Price above 0.6",
        //     "triggerPx" => "0.6"
        // }
        //
        $error = $this->safe_string($order, 'error');
        if ($error !== null) {
            return $this->safe_order(array(
                'info' => $order,
                'status' => 'rejected',
            ));
        }
        $entry = $this->safe_dict_n($order, array( 'order', 'resting', 'filled' ));
        if ($entry === null) {
            $entry = $order;
        }
        $coin = $this->safe_string($entry, 'coin');
        $marketId = null;
        if ($coin !== null) {
            $marketId = $this->coin_to_market_id($coin);
        }
        if ($this->safe_string($entry, 'id') === null) {
            $market = $this->safe_market($marketId, null);
        } else {
            $market = $this->safe_market($marketId, $market);
        }
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($entry, 'timestamp');
        $status = $this->safe_string_2($order, 'status', 'ccxtStatus');
        $order = $this->omit($order, array( 'ccxtStatus' ));
        $side = $this->safe_string($entry, 'side');
        if ($side !== null) {
            $side = ($side === 'A') ? 'sell' : 'buy';
        }
        $totalAmount = $this->safe_string_2($entry, 'origSz', 'totalSz');
        $remaining = $this->safe_string($entry, 'sz');
        $tif = $this->safe_string_upper($entry, 'tif');
        $postOnly = null;
        if ($tif !== null) {
            $postOnly = ($tif === 'ALO');
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($entry, 'oid'),
            'clientOrderId' => $this->safe_string($entry, 'cloid'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $this->safe_integer($order, 'statusTimestamp'),
            'symbol' => $symbol,
            'type' => $this->parse_order_type($this->safe_string_lower($entry, 'orderType')),
            'timeInForce' => $tif,
            'postOnly' => $postOnly,
            'reduceOnly' => $this->safe_bool($entry, 'reduceOnly'),
            'side' => $side,
            'price' => $this->safe_string($entry, 'limitPx'),
            'triggerPrice' => $this->safe_bool($entry, 'isTrigger') ? $this->safe_number($entry, 'triggerPx') : null,
            'amount' => $totalAmount,
            'cost' => null,
            'average' => $this->safe_string($entry, 'avgPx'),
            'filled' => Precise::string_sub($totalAmount, $remaining),
            'remaining' => $remaining,
            'status' => $this->parse_order_status($status),
            'fee' => null,
            'trades' => null,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        if ($status === null) {
            return null;
        }
        $statuses = array(
            'triggered' => 'open',
            'filled' => 'closed',
            'open' => 'open',
            'canceled' => 'canceled',
            'rejected' => 'rejected',
            'marginCanceled' => 'canceled',
        );
        if (str_ends_with($status, 'Rejected')) {
            return 'rejected';
        }
        if (str_ends_with($status, 'Canceled')) {
            return 'canceled';
        }
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($status) {
        $statuses = array(
            'stop limit' => 'limit',
            'stop market' => 'market',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all trades made by the user
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills-by-time
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch trades for
         * @param {int} [$limit] the maximum number of trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest trade
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchMyTrades', $params);
        $this->load_markets();
        $market = $this->safe_market($symbol);
        $request = array(
            'user' => $userAddress,
        );
        if ($since !== null) {
            $request['type'] = 'userFillsByTime';
            $request['startTime'] = $since;
        } else {
            $request['type'] = 'userFills';
        }
        $until = $this->safe_integer($params, 'until');
        $params = $this->omit($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "closedPnl" => "0.19343",
        //             "coin" => "ETH",
        //             "crossed" => true,
        //             "dir" => "Close Long",
        //             "fee" => "0.050062",
        //             "feeToken" => "USDC",
        //             "hash" => "0x09d77c96791e98b5775a04092584ab010d009445119c71e4005c0d634ea322bc",
        //             "liquidationMarkPx" => null,
        //             "oid" => 3929354691,
        //             "px" => "2381.1",
        //             "side" => "A",
        //             "startPosition" => "0.0841",
        //             "sz" => "0.0841",
        //             "tid" => 128423918764978,
        //             "time" => 1704262888911
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        //     {
        //         "closedPnl" => "0.19343",
        //         "coin" => "ETH",
        //         "crossed" => true,
        //         "dir" => "Close Long",
        //         "fee" => "0.050062",
        //         "hash" => "0x09d77c96791e98b5775a04092584ab010d009445119c71e4005c0d634ea322bc",
        //         "liquidationMarkPx" => null,
        //         "oid" => 3929354691,
        //         "px" => "2381.1",
        //         "side" => "A",
        //         "startPosition" => "0.0841",
        //         "sz" => "0.0841",
        //         "tid" => 128423918764978,
        //         "time" => 1704262888911
        //     }
        //
        $timestamp = $this->safe_integer($trade, 'time');
        $price = $this->safe_string($trade, 'px');
        $amount = $this->safe_string($trade, 'sz');
        $coin = $this->safe_string($trade, 'coin');
        $marketId = $this->coin_to_market_id($coin);
        $market = $this->safe_market($marketId, null);
        $symbol = $market['symbol'];
        $id = $this->safe_string($trade, 'tid');
        $side = $this->safe_string($trade, 'side');
        if ($side !== null) {
            $side = ($side === 'A') ? 'sell' : 'buy';
        }
        $fee = $this->safe_string($trade, 'fee');
        $takerOrMaker = null;
        $crossed = $this->safe_bool($trade, 'crossed');
        if ($crossed !== null) {
            $takerOrMaker = $crossed ? 'taker' : 'maker';
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'id' => $id,
            'order' => $this->safe_string($trade, 'oid'),
            'type' => null,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => null,
            'fee' => array(
                'cost' => $fee,
                'currency' => $this->safe_string($trade, 'feeToken'),
                'rate' => null,
            ),
        ), $market);
    }

    public function fetch_position(string $symbol, $params = array ()) {
        /**
         * fetch data on an open position
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
         *
         * @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
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $positions = $this->fetch_positions(array( $symbol ), $params);
        return $this->safe_dict($positions, 0, array());
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open positions
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
         *
         * @param {string[]} [$symbols] list of unified market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $this->load_markets();
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchPositions', $params);
        $symbols = $this->market_symbols($symbols);
        $request = array(
            'type' => 'clearinghouseState',
            'user' => $userAddress,
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     {
        //         "assetPositions" => array(
        //             {
        //                 "position" => array(
        //                     "coin" => "ETH",
        //                     "cumFunding" => array(
        //                         "allTime" => "0.0",
        //                         "sinceChange" => "0.0",
        //                         "sinceOpen" => "0.0"
        //                     ),
        //                     "entryPx" => "2213.9",
        //                     "leverage" => array(
        //                         "rawUsd" => "-475.23904",
        //                         "type" => "isolated",
        //                         "value" => "20"
        //                     ),
        //                     "liquidationPx" => "2125.00856238",
        //                     "marginUsed" => "24.88097",
        //                     "maxLeverage" => "50",
        //                     "positionValue" => "500.12001",
        //                     "returnOnEquity" => "0.0",
        //                     "szi" => "0.2259",
        //                     "unrealizedPnl" => "0.0"
        //                 ),
        //                 "type" => "oneWay"
        //             }
        //         ),
        //         "crossMaintenanceMarginUsed" => "0.0",
        //         "crossMarginSummary" => array(
        //             "accountValue" => "100.0",
        //             "totalMarginUsed" => "0.0",
        //             "totalNtlPos" => "0.0",
        //             "totalRawUsd" => "100.0"
        //         ),
        //         "marginSummary" => array(
        //             "accountValue" => "100.0",
        //             "totalMarginUsed" => "0.0",
        //             "totalNtlPos" => "0.0",
        //             "totalRawUsd" => "100.0"
        //         ),
        //         "time" => "1704261007014",
        //         "withdrawable" => "100.0"
        //     }
        //
        $data = $this->safe_list($response, 'assetPositions', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $result[] = $this->parse_position($data[$i], null);
        }
        return $this->filter_by_array_positions($result, 'symbol', $symbols, false);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //         "position" => array(
        //             "coin" => "ETH",
        //             "cumFunding" => array(
        //                 "allTime" => "0.0",
        //                 "sinceChange" => "0.0",
        //                 "sinceOpen" => "0.0"
        //             ),
        //             "entryPx" => "2213.9",
        //             "leverage" => array(
        //                 "rawUsd" => "-475.23904",
        //                 "type" => "isolated",
        //                 "value" => "20"
        //             ),
        //             "liquidationPx" => "2125.00856238",
        //             "marginUsed" => "24.88097",
        //             "maxLeverage" => "50",
        //             "positionValue" => "500.12001",
        //             "returnOnEquity" => "0.0",
        //             "szi" => "0.2259",
        //             "unrealizedPnl" => "0.0"
        //         ),
        //         "type" => "oneWay"
        //     }
        //
        $entry = $this->safe_dict($position, 'position', array());
        $coin = $this->safe_string($entry, 'coin');
        $marketId = $this->coin_to_market_id($coin);
        $market = $this->safe_market($marketId, null);
        $symbol = $market['symbol'];
        $leverage = $this->safe_dict($entry, 'leverage', array());
        $marginMode = $this->safe_string($leverage, 'type');
        $isIsolated = ($marginMode === 'isolated');
        $rawSize = $this->safe_string($entry, 'szi');
        $size = $rawSize;
        $side = null;
        if ($size !== null) {
            $side = Precise::string_gt($rawSize, '0') ? 'long' : 'short';
            $size = Precise::string_abs($size);
        }
        $rawUnrealizedPnl = $this->safe_string($entry, 'unrealizedPnl');
        $absRawUnrealizedPnl = Precise::string_abs($rawUnrealizedPnl);
        $initialMargin = $this->safe_string($entry, 'marginUsed');
        $percentage = Precise::string_mul(Precise::string_div($absRawUnrealizedPnl, $initialMargin), '100');
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $symbol,
            'timestamp' => null,
            'datetime' => null,
            'isolated' => $isIsolated,
            'hedged' => null,
            'side' => $side,
            'contracts' => $this->parse_number($size),
            'contractSize' => null,
            'entryPrice' => $this->safe_number($entry, 'entryPx'),
            'markPrice' => null,
            'notional' => $this->safe_number($entry, 'positionValue'),
            'leverage' => $this->safe_number($leverage, 'value'),
            'collateral' => $this->safe_number($entry, 'marginUsed'),
            'initialMargin' => $this->parse_number($initialMargin),
            'maintenanceMargin' => null,
            'initialMarginPercentage' => null,
            'maintenanceMarginPercentage' => null,
            'unrealizedPnl' => $this->parse_number($rawUnrealizedPnl),
            'liquidationPrice' => $this->safe_number($entry, 'liquidationPx'),
            'marginMode' => $marginMode,
            'percentage' => $this->parse_number($percentage),
        ));
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        /**
         * set margin mode ($symbol)
         * @param {string} $marginMode margin mode must be either [isolated, cross]
         * @param {string} $symbol unified $market $symbol of the $market the position is held in, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->leverage] the rate of $leverage, is required if setting trade mode ($symbol)
         * @param {string} [$params->vaultAddress] the vault address
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} $response from the exchange
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $leverage = $this->safe_integer($params, 'leverage');
        if ($leverage === null) {
            throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $leverage parameter');
        }
        $asset = $this->parse_to_int($market['baseId']);
        $isCross = ($marginMode === 'cross');
        $nonce = $this->milliseconds();
        $params = $this->omit($params, array( 'leverage' ));
        $updateAction = array(
            'type' => 'updateLeverage',
            'asset' => $asset,
            'isCross' => $isCross,
            'leverage' => $leverage,
        );
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'setMarginMode', 'vaultAddress', 'subAccountAddress');
        if ($vaultAddress !== null) {
            if (str_starts_with($vaultAddress, '0x')) {
                $vaultAddress = str_replace('0x', '', $vaultAddress);
            }
        }
        $signature = $this->sign_l1_action($updateAction, $nonce, $vaultAddress);
        $request = array(
            'action' => $updateAction,
            'nonce' => $nonce,
            'signature' => $signature,
            // 'vaultAddress' => $vaultAddress,
        );
        if ($vaultAddress !== null) {
            $request['vaultAddress'] = $vaultAddress;
        }
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         'response' => array(
        //             'type' => 'default'
        //         ),
        //         'status' => 'ok'
        //     }
        //
        return $response;
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        /**
         * set the level of $leverage for a $market
         * @param {float} $leverage the rate of $leverage
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->marginMode] margin mode must be either [isolated, cross], default is cross
         * @return {array} $response from the exchange
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $marginMode = $this->safe_string($params, 'marginMode', 'cross');
        $isCross = ($marginMode === 'cross');
        $asset = $this->parse_to_int($market['baseId']);
        $nonce = $this->milliseconds();
        $params = $this->omit($params, 'marginMode');
        $updateAction = array(
            'type' => 'updateLeverage',
            'asset' => $asset,
            'isCross' => $isCross,
            'leverage' => $leverage,
        );
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'setLeverage', 'vaultAddress', 'subAccountAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $signature = $this->sign_l1_action($updateAction, $nonce, $vaultAddress);
        $request = array(
            'action' => $updateAction,
            'nonce' => $nonce,
            'signature' => $signature,
            // 'vaultAddress' => $vaultAddress,
        );
        if ($vaultAddress !== null) {
            $params = $this->omit($params, 'vaultAddress');
            $request['vaultAddress'] = $vaultAddress;
        }
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         'response' => array(
        //             'type' => 'default'
        //         ),
        //         'status' => 'ok'
        //     }
        //
        return $response;
    }

    public function add_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         * add margin
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin
         *
         * @param {string} $symbol unified market $symbol
         * @param {float} $amount amount of margin to add
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->vaultAddress] the vault address
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=add-margin-structure margin structure~
         */
        return $this->modify_margin_helper($symbol, $amount, 'add', $params);
    }

    public function reduce_margin(string $symbol, float $amount, $params = array ()): array {
        /**
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin
         *
         * remove margin from a position
         * @param {string} $symbol unified market $symbol
         * @param {float} $amount the $amount of margin to remove
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->vaultAddress] the vault address
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=reduce-margin-structure margin structure~
         */
        return $this->modify_margin_helper($symbol, $amount, 'reduce', $params);
    }

    public function modify_margin_helper(string $symbol, $amount, $type, $params = array ()): array {
        $this->load_markets();
        $market = $this->market($symbol);
        $asset = $this->parse_to_int($market['baseId']);
        $sz = $this->parse_to_int(Precise::string_mul($this->amount_to_precision($symbol, $amount), '1000000'));
        if ($type === 'reduce') {
            $sz = -$sz;
        }
        $nonce = $this->milliseconds();
        $updateAction = array(
            'type' => 'updateIsolatedMargin',
            'asset' => $asset,
            'isBuy' => true,
            'ntli' => $sz,
        );
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'modifyMargin', 'vaultAddress', 'subAccountAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $signature = $this->sign_l1_action($updateAction, $nonce, $vaultAddress);
        $request = array(
            'action' => $updateAction,
            'nonce' => $nonce,
            'signature' => $signature,
            // 'vaultAddress' => $vaultAddress,
        );
        if ($vaultAddress !== null) {
            $request['vaultAddress'] = $vaultAddress;
        }
        $response = $this->privatePostExchange ($request);
        //
        //     {
        //         'response' => array(
        //             'type' => 'default'
        //         ),
        //         'status' => 'ok'
        //     }
        //
        return $this->extend($this->parse_margin_modification($response, $market), array(
            'code' => $this->safe_string($response, 'status'),
        ));
    }

    public function parse_margin_modification(array $data, ?array $market = null): array {
        //
        //    {
        //        'type' => 'default'
        //    }
        //
        return array(
            'info' => $data,
            'symbol' => $this->safe_symbol(null, $market),
            'type' => null,
            'marginMode' => 'isolated',
            'amount' => null,
            'total' => null,
            'code' => $this->safe_string($market, 'settle'),
            'status' => null,
            'timestamp' => null,
            'datetime' => null,
        );
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         * transfer currency internally between wallets on the same account
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#l1-usdc-transfer
         *
         * @param {string} $code unified currency $code
         * @param {float} $amount amount to transfer
         * @param {string} $fromAccount account to transfer from *spot, swap*
         * @param {string} $toAccount account to transfer to *swap, spot or address*
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->vaultAddress] the vault address for order
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
         */
        $this->check_required_credentials();
        $this->load_markets();
        $isSandboxMode = $this->safe_bool($this->options, 'sandboxMode');
        $nonce = $this->milliseconds();
        if ($this->in_array($fromAccount, array( 'spot', 'swap', 'perp' ))) {
            // handle swap <> spot account transfer
            if (!$this->in_array($toAccount, array( 'spot', 'swap', 'perp' ))) {
                throw new NotSupported($this->id . ' transfer() only support spot <> swap transfer');
            }
            $strAmount = $this->number_to_string($amount);
            $vaultAddress = $this->safe_string_2($params, 'vaultAddress', 'subAccountAddress');
            if ($vaultAddress !== null) {
                $vaultAddress = $this->format_vault_address($vaultAddress);
                $strAmount = $strAmount . ' subaccount:' . $vaultAddress;
            }
            $toPerp = ($toAccount === 'perp') || ($toAccount === 'swap');
            $transferPayload = array(
                'hyperliquidChain' => $isSandboxMode ? 'Testnet' : 'Mainnet',
                'amount' => $strAmount,
                'toPerp' => $toPerp,
                'nonce' => $nonce,
            );
            $transferSig = $this->build_usd_class_send_sig($transferPayload);
            $transferRequest = array(
                'action' => array(
                    'hyperliquidChain' => $transferPayload['hyperliquidChain'],
                    'signatureChainId' => '0x66eee',
                    'type' => 'usdClassTransfer',
                    'amount' => $strAmount,
                    'toPerp' => $toPerp,
                    'nonce' => $nonce,
                ),
                'nonce' => $nonce,
                'signature' => $transferSig,
            );
            $transferResponse = $this->privatePostExchange ($transferRequest);
            return $transferResponse;
        }
        // transfer between main account and subaccount
        $isDeposit = false;
        $subAccountAddress = null;
        if ($fromAccount === 'main') {
            $subAccountAddress = $toAccount;
            $isDeposit = true;
        } elseif ($toAccount === 'main') {
            $subAccountAddress = $fromAccount;
        } else {
            throw new NotSupported($this->id . ' transfer() only support main <> subaccount transfer');
        }
        $this->check_address($subAccountAddress);
        if ($code === null || strtoupper($code) === 'USDC') {
            // Transfer USDC with subAccountTransfer
            $usd = $this->parse_to_int(Precise::string_mul($this->number_to_string($amount), '1000000'));
            $action = array(
                'type' => 'subAccountTransfer',
                'subAccountUser' => $subAccountAddress,
                'isDeposit' => $isDeposit,
                'usd' => $usd,
            );
            $sig = $this->sign_l1_action($action, $nonce);
            $request = array(
                'action' => $action,
                'nonce' => $nonce,
                'signature' => $sig,
            );
            $response = $this->privatePostExchange ($request);
            //
            // array('response' => array('type' => 'default'), 'status' => 'ok')
            //
            return $this->parse_transfer($response);
        } else {
            // Transfer non-USDC with subAccountSpotTransfer
            $symbol = $this->symbol($code);
            $action = array(
                'type' => 'subAccountSpotTransfer',
                'subAccountUser' => $subAccountAddress,
                'isDeposit' => $isDeposit,
                'token' => $symbol,
                'amount' => $this->number_to_string($amount),
            );
            $sig = $this->sign_l1_action($action, $nonce);
            $request = array(
                'action' => $action,
                'nonce' => $nonce,
                'signature' => $sig,
            );
            $response = $this->privatePostExchange ($request);
            return $this->parse_transfer($response);
        }
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // array('response' => array('type' => 'default'), 'status' => 'ok')
        //
        return array(
            'info' => $transfer,
            'id' => null,
            'timestamp' => null,
            'datetime' => null,
            'currency' => null,
            'amount' => null,
            'fromAccount' => null,
            'toAccount' => null,
            'status' => 'ok',
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal (only support USDC)
         *
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#initiate-a-withdrawal-$request
         * @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#deposit-or-withdraw-from-a-vault
         *
         * @param {string} $code unified currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->vaultAddress] vault $address withdraw from
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        $this->check_required_credentials();
        $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');
            }
        }
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params($params, 'withdraw', 'vaultAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $params = $this->omit($params, 'vaultAddress');
        $nonce = $this->milliseconds();
        $action = array();
        $sig = null;
        if ($vaultAddress !== null) {
            $action = array(
                'type' => 'vaultTransfer',
                'vaultAddress' => '0x' . $vaultAddress,
                'isDeposit' => false,
                'usd' => $amount,
            );
            $sig = $this->sign_l1_action($action, $nonce);
        } else {
            $isSandboxMode = $this->safe_bool($this->options, 'sandboxMode', false);
            $payload = array(
                'hyperliquidChain' => $isSandboxMode ? 'Testnet' : 'Mainnet',
                'destination' => $address,
                'amount' => (string) $amount,
                'time' => $nonce,
            );
            $sig = $this->build_withdraw_sig($payload);
            $action = array(
                'hyperliquidChain' => $payload['hyperliquidChain'],
                'signatureChainId' => '0x66eee', // check this out
                'destination' => $address,
                'amount' => (string) $amount,
                'time' => $nonce,
                'type' => 'withdraw3',
            );
        }
        $request = array(
            'action' => $action,
            'nonce' => $nonce,
            'signature' => $sig,
        );
        $response = $this->privatePostExchange ($request);
        return $this->parse_transaction($response);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // array( status => 'ok', response => array( $type => 'default' ) )
        //
        // fetchDeposits / fetchWithdrawals
        // {
        //     "time":1724762307531,
        //     "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        //     "delta":{
        //         "type":"accountClassTransfer",
        //         "usdc":"50.0",
        //         "toPerp":false
        //     }
        // }
        //
        $timestamp = $this->safe_integer($transaction, 'time');
        $delta = $this->safe_dict($transaction, 'delta', array());
        $fee = null;
        $feeCost = $this->safe_integer($delta, 'fee');
        if ($feeCost !== null) {
            $fee = array(
                'currency' => 'USDC',
                'cost' => $feeCost,
            );
        }
        $internal = null;
        $type = $this->safe_string($delta, 'type');
        if ($type !== null) {
            $internal = ($type === 'internalTransfer');
        }
        return array(
            'info' => $transaction,
            'id' => null,
            'txid' => $this->safe_string($transaction, 'hash'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => null,
            'address' => null,
            'addressTo' => $this->safe_string($delta, 'destination'),
            'addressFrom' => $this->safe_string($delta, 'user'),
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => null,
            'amount' => $this->safe_number($delta, 'usdc'),
            'currency' => null,
            'status' => $this->safe_string($transaction, 'status'),
            'updated' => null,
            'comment' => null,
            'internal' => $internal,
            'fee' => $fee,
        );
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): array {
        /**
         * fetch the trading fees for a $market
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->user] user address, will default to $this->walletAddress if not provided
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
         */
        $this->load_markets();
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchTradingFee', $params);
        $market = $this->market($symbol);
        $request = array(
            'type' => 'userFees',
            'user' => $userAddress,
        );
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        //     {
        //         "dailyUserVlm" => array(
        //             {
        //                 "date" => "2024-07-08",
        //                 "userCross" => "0.0",
        //                 "userAdd" => "0.0",
        //                 "exchange" => "90597185.23639999"
        //             }
        //         ),
        //         "feeSchedule" => {
        //             "cross" => "0.00035",
        //             "add" => "0.0001",
        //             "tiers" => {
        //                 "vip" => array(
        //                     array(
        //                         "ntlCutoff" => "5000000.0",
        //                         "cross" => "0.0003",
        //                         "add" => "0.00005"
        //                     }
        //                 ),
        //                 "mm" => array(
        //                     array(
        //                         "makerFractionCutoff" => "0.005",
        //                         "add" => "-0.00001"
        //                     }
        //                 )
        //             ),
        //             "referralDiscount" => "0.04"
        //         ),
        //         "userCrossRate" => "0.00035",
        //         "userAddRate" => "0.0001",
        //         "activeReferralDiscount" => "0.0"
        //     }
        //
        $data = array(
            'userCrossRate' => $this->safe_string($response, 'userCrossRate'),
            'userAddRate' => $this->safe_string($response, 'userAddRate'),
        );
        return $this->parse_trading_fee($data, $market);
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        //
        //     {
        //         "dailyUserVlm" => array(
        //             {
        //                 "date" => "2024-07-08",
        //                 "userCross" => "0.0",
        //                 "userAdd" => "0.0",
        //                 "exchange" => "90597185.23639999"
        //             }
        //         ),
        //         "feeSchedule" => {
        //             "cross" => "0.00035",
        //             "add" => "0.0001",
        //             "tiers" => {
        //                 "vip" => array(
        //                     array(
        //                         "ntlCutoff" => "5000000.0",
        //                         "cross" => "0.0003",
        //                         "add" => "0.00005"
        //                     }
        //                 ),
        //                 "mm" => array(
        //                     array(
        //                         "makerFractionCutoff" => "0.005",
        //                         "add" => "-0.00001"
        //                     }
        //                 )
        //             ),
        //             "referralDiscount" => "0.04"
        //         ),
        //         "userCrossRate" => "0.00035",
        //         "userAddRate" => "0.0001",
        //         "activeReferralDiscount" => "0.0"
        //     }
        //
        $symbol = $this->safe_symbol(null, $market);
        return array(
            'info' => $fee,
            'symbol' => $symbol,
            'maker' => $this->safe_number($fee, 'userAddRate'),
            'taker' => $this->safe_number($fee, 'userCrossRate'),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch the history of changes, actions done by the user or operations that altered the balance of the user
         * @param {string} [$code] unified currency $code
         * @param {int} [$since] timestamp in ms of the earliest ledger entry
         * @param {int} [$limit] max number of ledger entries to return
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest ledger entry
         * @param {string} [$params->subAccountAddress] sub account user address
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger ledger structure~
         */
        $this->load_markets();
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchLedger', $params);
        $request = array(
            'type' => 'userNonFundingLedgerUpdates',
            'user' => $userAddress,
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = $this->safe_integer($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
            $params = $this->omit($params, array( 'until' ));
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        // array(
        //     {
        //         "time":1724762307531,
        //         "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        //         "delta":{
        //             "type":"accountClassTransfer",
        //             "usdc":"50.0",
        //             "toPerp":false
        //         }
        //     }
        // )
        //
        return $this->parse_ledger($response, null, $since, $limit);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        // {
        //     "time":1724762307531,
        //     "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        //     "delta":{
        //         "type":"accountClassTransfer",
        //         "usdc":"50.0",
        //         "toPerp":false
        //     }
        // }
        //
        $timestamp = $this->safe_integer($item, 'time');
        $delta = $this->safe_dict($item, 'delta', array());
        $fee = null;
        $feeCost = $this->safe_integer($delta, 'fee');
        if ($feeCost !== null) {
            $fee = array(
                'currency' => 'USDC',
                'cost' => $feeCost,
            );
        }
        $type = $this->safe_string($delta, 'type');
        $amount = $this->safe_string($delta, 'usdc');
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $this->safe_string($item, 'hash'),
            'direction' => null,
            'account' => null,
            'referenceAccount' => $this->safe_string($delta, 'user'),
            'referenceId' => $this->safe_string($item, 'hash'),
            'type' => $this->parse_ledger_entry_type($type),
            'currency' => null,
            'amount' => $this->parse_number($amount),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'before' => null,
            'after' => null,
            'status' => null,
            'fee' => $fee,
        ), $currency);
    }

    public function parse_ledger_entry_type($type) {
        $ledgerType = array(
            'internalTransfer' => 'transfer',
            'accountClassTransfer' => 'transfer',
        );
        return $this->safe_string($ledgerType, $type, $type);
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all $deposits made to an account
         * @param {string} $code unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch $deposits for
         * @param {int} [$limit] the maximum number of $deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch withdrawals for
         * @param {string} [$params->subAccountAddress] sub account user address
         * @param {string} [$params->vaultAddress] vault address
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchDepositsWithdrawals', $params);
        $request = array(
            'type' => 'userNonFundingLedgerUpdates',
            'user' => $userAddress,
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = $this->safe_integer($params, 'until');
        if ($until !== null) {
            if ($since === null) {
                throw new ArgumentsRequired($this->id . ' fetchDeposits requires $since while $until is set');
            }
            $request['endTime'] = $until;
            $params = $this->omit($params, array( 'until' ));
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        // array(
        //     {
        //         "time":1724762307531,
        //         "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        //         "delta":{
        //             "type":"accountClassTransfer",
        //             "usdc":"50.0",
        //             "toPerp":false
        //         }
        //     }
        // )
        //
        $records = $this->extract_type_from_delta($response);
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params($params, 'fetchDepositsWithdrawals', 'vaultAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $deposits = array();
        if ($vaultAddress !== null) {
            for ($i = 0; $i < count($records); $i++) {
                $record = $records[$i];
                if ($record['type'] === 'vaultDeposit') {
                    $delta = $this->safe_dict($record, 'delta');
                    if ($delta['vault'] === '0x' . $vaultAddress) {
                        $deposits[] = $record;
                    }
                }
            }
        } else {
            $deposits = $this->filter_by_array($records, 'type', array( 'deposit' ), false);
        }
        return $this->parse_transactions($deposits, null, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all $withdrawals made from an account
         * @param {string} $code unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch $withdrawals for
         * @param {int} [$limit] the maximum number of $withdrawals structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch $withdrawals for
         * @param {string} [$params->subAccountAddress] sub account user address
         * @param {string} [$params->vaultAddress] vault address
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchDepositsWithdrawals', $params);
        $request = array(
            'type' => 'userNonFundingLedgerUpdates',
            'user' => $userAddress,
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = $this->safe_integer($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
            $params = $this->omit($params, array( 'until' ));
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        // array(
        //     {
        //         "time":1724762307531,
        //         "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        //         "delta":{
        //             "type":"accountClassTransfer",
        //             "usdc":"50.0",
        //             "toPerp":false
        //         }
        //     }
        // )
        //
        $records = $this->extract_type_from_delta($response);
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params($params, 'fetchDepositsWithdrawals', 'vaultAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $withdrawals = array();
        if ($vaultAddress !== null) {
            for ($i = 0; $i < count($records); $i++) {
                $record = $records[$i];
                if ($record['type'] === 'vaultWithdraw') {
                    $delta = $this->safe_dict($record, 'delta');
                    if ($delta['vault'] === '0x' . $vaultAddress) {
                        $withdrawals[] = $record;
                    }
                }
            }
        } else {
            $withdrawals = $this->filter_by_array($records, 'type', array( 'withdraw' ), false);
        }
        return $this->parse_transactions($withdrawals, null, $since, $limit);
    }

    public function fetch_open_interests(?array $symbols = null, $params = array ()) {
        /**
         * Retrieves the open interest for a list of $symbols
         * @param {string[]} [$symbols] Unified CCXT market symbol
         * @param {array} [$params] exchange specific parameters
         * @return {array} an open interest structurearray(@link https://docs.ccxt.com/#/?id=open-interest-structure)
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $swapMarkets = $this->fetch_swap_markets();
        return $this->parse_open_interests($swapMarkets, $symbols);
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        /**
         * retrieves the open interest of a contract trading pair
         * @param {string} $symbol unified CCXT market $symbol
         * @param {array} [$params] exchange specific parameters
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=open-interest-structure open interest structure~
         */
        $symbol = $this->symbol($symbol);
        $this->load_markets();
        $ois = $this->fetch_open_interests(array( $symbol ), $params);
        return $ois[$symbol];
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        //  {
        //      szDecimals => '2',
        //      name => 'HYPE',
        //      maxLeverage => '3',
        //      funding => '0.00014735',
        //      openInterest => '14677900.74',
        //      prevDayPx => '26.145',
        //      dayNtlVlm => '299643445.12560016',
        //      premium => '0.00081613',
        //      oraclePx => '27.569',
        //      markPx => '27.63',
        //      midPx => '27.599',
        //      impactPxs => array( '27.5915', '27.6319' ),
        //      dayBaseVlm => '10790652.83',
        //      baseId => 159
        //  }
        //
        $interest = $this->safe_dict($interest, 'info', array());
        $coin = $this->safe_string($interest, 'name');
        $marketId = null;
        if ($coin !== null) {
            $marketId = $this->coin_to_market_id($coin);
        }
        return $this->safe_open_interest(array(
            'symbol' => $this->safe_symbol($marketId),
            'openInterestAmount' => $this->safe_number($interest, 'openInterest'),
            'openInterestValue' => null,
            'timestamp' => null,
            'datetime' => null,
            'info' => $interest,
        ), $market);
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch the history of funding payments paid and received on this account
         * @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 {string} [$params->subAccountAddress] sub account user address
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structure~
         */
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $userAddress = null;
        list($userAddress, $params) = $this->handle_public_address('fetchFundingHistory', $params);
        $request = array(
            'user' => $userAddress,
            'type' => 'userFunding',
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = $this->safe_integer($params, 'until');
        $params = $this->omit($params, 'until');
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = $this->publicPostInfo ($this->extend($request, $params));
        //
        // array(
        //     {
        //         "time" => 1734026400057,
        //         "hash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
        //         "delta" => {
        //             "type" => "funding",
        //             "coin" => "SOL",
        //             "usdc" => "75.635093",
        //             "szi" => "-7375.9",
        //             "fundingRate" => "0.00004381",
        //             "nSamples" => null
        //         }
        //     }
        // )
        //
        return $this->parse_incomes($response, $market, $since, $limit);
    }

    public function parse_income($income, ?array $market = null) {
        //
        // {
        //     "time" => 1734026400057,
        //     "hash" => "0x0000000000000000000000000000000000000000000000000000000000000000",
        //     "delta" => {
        //         "type" => "funding",
        //         "coin" => "SOL",
        //         "usdc" => "75.635093",
        //         "szi" => "-7375.9",
        //         "fundingRate" => "0.00004381",
        //         "nSamples" => null
        //     }
        // }
        //
        $id = $this->safe_string($income, 'hash');
        $timestamp = $this->safe_integer($income, 'time');
        $delta = $this->safe_dict($income, 'delta');
        $baseId = $this->safe_string($delta, 'coin');
        $marketSymbol = $baseId . '/USDC:USDC';
        $market = $this->safe_market($marketSymbol);
        $symbol = $market['symbol'];
        $amount = $this->safe_string($delta, 'usdc');
        $code = $this->safe_currency_code('USDC');
        $rate = $this->safe_number($delta, 'fundingRate');
        return array(
            'info' => $income,
            'symbol' => $symbol,
            'code' => $code,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $id,
            'amount' => $this->parse_number($amount),
            'rate' => $rate,
        );
    }

    public function reserve_request_weight(?float $weight, $params = array ()): array {
        /**
         * Instead of trading to increase the address based rate limits, this $action allows reserving additional actions for 0.0005 USDC per $request-> The cost is paid from the Perps balance.
         * @param {number} $weight the $weight to reserve, 1 $weight = 1 $action, 0.0005 USDC per $action
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a $response object
         */
        $nonce = $this->milliseconds();
        $request = array(
            'nonce' => $nonce,
        );
        $action = array(
            'type' => 'reserveRequestWeight',
            'weight' => $weight,
        );
        $signature = $this->sign_l1_action($action, $nonce);
        $request['action'] = $action;
        $request['signature'] = $signature;
        $response = $this->privatePostExchange ($this->extend($request, $params));
        return $response;
    }

    public function extract_type_from_delta($data = []) {
        $records = array();
        for ($i = 0; $i < count($data); $i++) {
            $record = $data[$i];
            $record['type'] = $record['delta']['type'];
            $records[] = $record;
        }
        return $records;
    }

    public function format_vault_address(?string $address = null) {
        if ($address === null) {
            return null;
        }
        if (str_starts_with($address, '0x')) {
            return str_replace('0x', '', $address);
        }
        return $address;
    }

    public function handle_public_address(string $methodName, array $params) {
        $userAux = null;
        list($userAux, $params) = $this->handle_option_and_params_2($params, $methodName, 'user', 'subAccountAddress');
        $user = $userAux;
        list($user, $params) = $this->handle_option_and_params($params, $methodName, 'address', $userAux);
        if (($user !== null) && ($user !== '')) {
            return array( $user, $params );
        }
        if (($this->walletAddress !== null) && ($this->walletAddress !== '')) {
            return array( $this->walletAddress, $params );
        }
        throw new ArgumentsRequired($this->id . ' ' . $methodName . '() requires a $user parameter inside \'params\' or the wallet address set');
    }

    public function coin_to_market_id(?string $coin) {
        if (mb_strpos($coin, '/') > -1 || mb_strpos($coin, '@') > -1) {
            return $coin; // spot
        }
        return $this->safe_currency_code($coin) . '/USDC:USDC';
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return null; // fallback to default $error handler
        }
        // array("status":"err","response":"User or API Wallet 0xb8a6f8b26223de27c31938d56e470a5b832703a5 does not exist.")
        //
        //     {
        //         $status => 'ok',
        //         $response => array( type => 'order', $data => array( $statuses => array( array( $error => 'Insufficient margin to place order. asset=4' ) ) ) )
        //     }
        // array("status":"ok","response":array("type":"order","data":array("statuses":[array("error":"Insufficient margin to place order. asset=84")])))
        //
        // array("status":"unknownOid")
        //
        $status = $this->safe_string($response, 'status', '');
        $error = $this->safe_string($response, 'error');
        $message = null;
        if ($status === 'err') {
            $message = $this->safe_string($response, 'response');
        } elseif ($status === 'unknownOid') {
            throw new OrderNotFound($this->id . ' ' . $body); // array("status":"unknownOid")
        } elseif ($error !== null) {
            $message = $error;
        } else {
            $responsePayload = $this->safe_dict($response, 'response', array());
            $data = $this->safe_dict($responsePayload, 'data', array());
            $statuses = $this->safe_list($data, 'statuses', array());
            for ($i = 0; $i < count($statuses); $i++) {
                $message = $this->safe_string($statuses[$i], 'error');
                if ($message !== null) {
                    break;
                }
            }
        }
        $feedback = $this->id . ' ' . $body;
        $nonEmptyMessage = (($message !== null) && ($message !== ''));
        if ($nonEmptyMessage) {
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
        }
        if ($nonEmptyMessage) {
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->implode_hostname($this->urls['api'][$api]) . '/' . $path;
        if ($method === 'POST') {
            $headers = array(
                'Content-Type' => 'application/json',
            );
            $body = $this->json($params);
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function calculate_rate_limiter_cost($api, $method, $path, $params, $config = array ()) {
        if ((is_array($config) && array_key_exists('byType', $config)) && (is_array($params) && array_key_exists('type', $params))) {
            $type = $params['type'];
            $byType = $config['byType'];
            if (is_array($byType) && array_key_exists($type, $byType)) {
                return $byType[$type];
            }
        }
        return $this->safe_value($config, 'cost', 1);
    }

    public function parse_create_edit_order_args(?string $id, string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $vaultAddress = null;
        list($vaultAddress, $params) = $this->handle_option_and_params_2($params, 'createOrder', 'vaultAddress', 'subAccountAddress');
        $vaultAddress = $this->format_vault_address($vaultAddress);
        $symbol = $market['symbol'];
        $order = array(
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'amount' => $amount,
            'price' => $price,
            'params' => $params,
        );
        $globalParams = array();
        if ($vaultAddress !== null) {
            $globalParams['vaultAddress'] = $vaultAddress;
        }
        if ($id !== null) {
            $order['id'] = $id;
        }
        return array( $order, $globalParams );
    }
}
