# -*- coding: utf-8 -*-

# 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

from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.hyperliquid import ImplicitAPI
import asyncio
import math
from ccxt.base.types import Any, Balances, Currencies, Currency, Int, LedgerEntry, MarginModification, Market, Num, Order, OrderBook, OrderRequest, CancellationRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, MarketInterface, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.decimal_to_precision import ROUND
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class hyperliquid(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(hyperliquid, self).describe(), {
            'id': 'hyperliquid',
            'name': 'Hyperliquid',
            'countries': [],
            'version': 'v1',
            'rateLimit': 50,  # 1200 requests per minute, 20 request per second
            'certified': True,
            'pro': True,
            'dex': True,
            'has': {
                'CORS': None,
                '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': None,
                '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': {
                '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': {
                'logo': 'https://github.com/ccxt/ccxt/assets/43336371/b371bc6c-4a8c-489f-87f4-20a913dd8d4b',
                'api': {
                    'public': 'https://api.{hostname}',
                    'private': 'https://api.{hostname}',
                },
                'test': {
                    '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': {
                'public': {
                    'post': {
                        'info': {
                            'cost': 20,
                            'byType': {
                                'l2Book': 2,
                                'allMids': 2,
                                'clearinghouseState': 2,
                                'orderStatus': 2,
                                'spotClearinghouseState': 2,
                                'exchangeStatus': 2,
                                'candleSnapshot': 4,
                            },
                        },
                    },
                },
                'private': {
                    'post': {
                        'exchange': 1,
                    },
                },
            },
            'fees': {
                'swap': {
                    'taker': self.parse_number('0.00045'),
                    'maker': self.parse_number('0.00015'),
                },
                'spot': {
                    'taker': self.parse_number('0.0007'),
                    'maker': self.parse_number('0.0004'),
                },
            },
            'requiredCredentials': {
                'apiKey': False,
                'secret': False,
                'walletAddress': True,
                'privateKey': True,
            },
            'exceptions': {
                'exact': {
                },
                'broad': {
                    'Price must be divisible by tick size.': InvalidOrder,
                    'Order must have minimum value of $10': InvalidOrder,
                    'Insufficient margin to place order.': InsufficientFunds,
                    'Reduce only order would increase position.': InvalidOrder,
                    'Post only order would have immediately matched,': InvalidOrder,
                    'Order could not immediately match against any resting orders.': InvalidOrder,
                    'Invalid TP/SL price.': InvalidOrder,
                    'No liquidity available for market order.': InvalidOrder,
                    'Order was never placed, already canceled, or filled.': OrderNotFound,
                    'User or API Wallet ': InvalidOrder,
                    'Order has invalid size': InvalidOrder,
                    'Order price cannot be more than 80% away from the reference price': InvalidOrder,
                    'Order has zero size.': InvalidOrder,
                    'Insufficient spot balance asset': InsufficientFunds,
                    'Insufficient balance for withdrawal': InsufficientFunds,
                    'Insufficient balance for token transfer': InsufficientFunds,
                },
            },
            'precisionMode': TICK_SIZE,
            'commonCurrencies': {
            },
            'options': {
                'defaultType': 'swap',
                'sandboxMode': False,
                'defaultSlippage': 0.05,
                'zeroAddress': '0x0000000000000000000000000000000000000000',
                'spotCurrencyMapping': {
                    'UDZ': '2Z',
                    'UBONK': 'BONK',
                    'UBTC': 'BTC',
                    'UETH': 'ETH',
                    'UFART': 'FARTCOIN',
                    'HPENGU': 'PENGU',
                    'UPUMP': 'PUMP',
                    'USOL': 'SOL',
                    'UUUSPX': 'SPX',
                    'USDT0': 'USDT',
                    'XAUT0': 'XAUT',
                    'UXPL': 'XPL',
                },
            },
            'features': {
                'default': {
                    'sandbox': True,
                    'createOrder': {
                        'marginMode': False,
                        'triggerPrice': False,
                        'triggerPriceType': None,
                        'triggerDirection': False,
                        'stopLossPrice': False,
                        'takeProfitPrice': False,
                        'attachedStopLossTakeProfit': {
                            'triggerPriceType': {
                                'last': False,
                                'mark': False,
                                'index': False,
                            },
                            'triggerPrice': True,
                            'type': True,
                            'price': True,
                        },
                        'timeInForce': {
                            'IOC': True,
                            'FOK': False,
                            'PO': True,
                            'GTD': False,
                        },
                        'hedged': False,
                        'trailing': False,
                        'leverage': False,
                        'marketBuyByCost': False,
                        'marketBuyRequiresPrice': False,
                        'selfTradePrevention': False,
                        'iceberg': False,
                    },
                    'createOrders': {
                        'max': 1000,
                    },
                    'fetchMyTrades': {
                        'marginMode': False,
                        'limit': 2000,
                        'daysBack': None,
                        'untilDays': None,
                        'symbolRequired': True,
                    },
                    'fetchOrder': {
                        'marginMode': False,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': 2000,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOrders': {
                        'marginMode': False,
                        'limit': 2000,
                        'daysBack': None,
                        'untilDays': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchClosedOrders': {
                        'marginMode': False,
                        'limit': 2000,
                        'daysBack': None,
                        'daysBackCanceled': None,
                        'untilDays': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOHLCV': {
                        'limit': 5000,
                    },
                },
                'spot': {
                    'extends': 'default',
                },
                'forPerps': {
                    'extends': 'default',
                    'createOrder': {
                        'stopLossPrice': True,
                        'takeProfitPrice': True,
                        'attachedStopLossTakeProfit': None,  # todo, in two orders
                    },
                },
                'swap': {
                    'linear': {
                        'extends': 'forPerps',
                    },
                    'inverse': {
                        'extends': 'forPerps',
                    },
                },
                'future': {
                    'linear': {
                        'extends': 'forPerps',
                    },
                    'inverse': {
                        'extends': 'forPerps',
                    },
                },
            },
        })

    def set_sandbox_mode(self, enabled):
        super(hyperliquid, self).set_sandbox_mode(enabled)
        self.options['sandboxMode'] = enabled

    def market(self, symbol: str) -> MarketInterface:
        if self.markets is None:
            raise ExchangeError(self.id + ' markets not loaded')
        if symbol in self.markets:
            market = self.markets[symbol]
            if market['spot']:
                baseName = self.safe_string(market, 'baseName')
                spotCurrencyMapping = self.safe_dict(self.options, 'spotCurrencyMapping', {})
                if baseName in spotCurrencyMapping:
                    unifiedBaseName = self.safe_string(spotCurrencyMapping, baseName)
                    quote = self.safe_string(market, 'quote')
                    newSymbol = self.safe_currency_code(unifiedBaseName) + '/' + quote
                    if newSymbol in self.markets:
                        return self.markets[newSymbol]
        res = super(hyperliquid, self).market(symbol)
        return res

    def safe_market(self, marketId: Str = None, market: Market = None, delimiter: Str = None, marketType: Str = None) -> MarketInterface:
        if marketId is not None:
            if (self.markets_by_id is not None) and (marketId in self.markets_by_id):
                markets = self.markets_by_id[marketId]
                numMarkets = len(markets)
                if numMarkets == 1:
                    return markets[0]
                else:
                    if numMarkets > 2:
                        raise ExchangeError(self.id + ' safeMarket() found more than two markets with the same market id ' + marketId)
                    firstMarket = markets[0]
                    secondMarket = markets[1]
                    if self.safe_string(firstMarket, 'type') != self.safe_string(secondMarket, 'type'):
                        raise ExchangeError(self.id + ' safeMarket() found two different market types with the same market id ' + marketId)
                    baseCurrency = self.safe_string(firstMarket, 'base')
                    spotCurrencyMapping = self.safe_dict(self.options, 'spotCurrencyMapping', {})
                    if baseCurrency in spotCurrencyMapping:
                        return secondMarket
                    return firstMarket
        return super(hyperliquid, self).safe_market(marketId, market, delimiter, marketType)

    async def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        if self.check_required_credentials(False):
            await self.initialize_client()
        request: dict = {
            'type': 'meta',
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "universe": [
        #                 {
        #                     "maxLeverage": 50,
        #                     "name": "SOL",
        #                     "onlyIsolated": False,
        #                     "szDecimals": 2
        #                 }
        #             ]
        #         }
        #     ]
        #
        meta = self.safe_list(response, 'universe', [])
        result: dict = {}
        for i in range(0, len(meta)):
            data = self.safe_dict(meta, i, {})
            id = i
            name = self.safe_string(data, 'name')
            code = self.safe_currency_code(name)
            result[code] = self.safe_currency_structure({
                'id': id,
                'name': name,
                'code': code,
                'precision': None,
                'info': data,
                'active': None,
                'deposit': None,
                'withdraw': None,
                'networks': None,
                'fee': None,
                'type': 'crypto',
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
            })
        return result

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for hyperliquid

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        rawPromises = [
            self.fetch_swap_markets(params),
            self.fetch_spot_markets(params),
        ]
        promises = await asyncio.gather(*rawPromises)
        swapMarkets = promises[0]
        spotMarkets = promises[1]
        return self.array_concat(swapMarkets, spotMarkets)

    async def fetch_swap_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all swap markets for hyperliquid

        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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        request: dict = {
            'type': 'metaAndAssetCtxs',
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "universe": [
        #                 {
        #                     "maxLeverage": 50,
        #                     "name": "SOL",
        #                     "onlyIsolated": False,
        #                     "szDecimals": 2
        #                 }
        #             ]
        #         },
        #         [
        #             {
        #                 "dayNtlVlm": "9450588.2273",
        #                 "funding": "0.0000198",
        #                 "impactPxs": [
        #                     "108.04",
        #                     "108.06"
        #                 ],
        #                 "markPx": "108.04",
        #                 "midPx": "108.05",
        #                 "openInterest": "10764.48",
        #                 "oraclePx": "107.99",
        #                 "premium": "0.00055561",
        #                 "prevDayPx": "111.81"
        #             }
        #         ]
        #     ]
        #
        #
        meta = self.safe_dict(response, 0, {})
        universe = self.safe_list(meta, 'universe', [])
        assetCtxs = self.safe_list(response, 1, [])
        result = []
        for i in range(0, len(universe)):
            data = self.extend(
                self.safe_dict(universe, i, {}),
                self.safe_dict(assetCtxs, i, {})
            )
            data['baseId'] = i
            result.append(data)
        return self.parse_markets(result)

    def calculate_price_precision(self, price: float, amountPrecision: float, maxDecimals: float):
        """
        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
        :returns int: The calculated price precision
        """
        pricePrecision = 0
        priceStr = self.number_to_string(price)
        if priceStr is None:
            return 0
        priceSplitted = priceStr.split('.')
        if Precise.string_eq(priceStr, '0'):
            # Significant digits is always hasattr(self, 5) case
            significantDigits = 5
            # Integer digits is always hasattr(self, 0) case(0 doesn't count)
            integerDigits = 0
            # Calculate the price precision
            pricePrecision = min(maxDecimals - amountPrecision, significantDigits - integerDigits)
        elif Precise.string_gt(priceStr, '0') and Precise.string_lt(priceStr, '1'):
            # Significant digits, always hasattr(self, 5) case
            significantDigits = 5
            # Get the part after the decimal separator
            decimalPart = self.safe_string(priceSplitted, 1, '')
            # Count the number of leading zeros in the decimal part
            leadingZeros = 0
            while((leadingZeros <= len(decimalPart)) and (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 = self.safe_string(priceSplitted, 0, '')
            # Get significant digits, take the max() of 5 and the integer digits count
            significantDigits = max(5, len(integerPart))
            # Calculate price precision based on maxDecimals - szDecimals and significantDigits - len(integerPart)
            pricePrecision = min(maxDecimals - amountPrecision, significantDigits - len(integerPart))
        return self.parse_to_int(pricePrecision)

    async def fetch_spot_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all spot markets for hyperliquid

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        request: dict = {
            'type': 'spotMetaAndAssetCtxs',
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        # [
        #     {
        #         "tokens": [
        #             {
        #                 "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": [
        #             {
        #                 "name": "PURR/USDC",
        #                 "tokens": [1, 0],
        #                 "index": 0,
        #                 "isCanonical": True
        #             }
        #         ]
        #     },
        #     [
        #         {
        #             "dayNtlVlm":"8906.0",
        #             "markPx":"0.14",
        #             "midPx":"0.209265",
        #             "prevDayPx":"0.20432"
        #         }
        #     ]
        # ]
        #
        first = self.safe_dict(response, 0, {})
        second = self.safe_list(response, 1, [])
        meta = self.safe_list(first, 'universe', [])
        tokens = self.safe_list(first, 'tokens', [])
        markets = []
        for i in range(0, len(meta)):
            market = self.safe_dict(meta, i, {})
            index = self.safe_integer(market, 'index')
            extraData = self.safe_dict(second, index, {})
            marketName = self.safe_string(market, 'name')
            # if marketName.find('/') < 0:
            #     # there are some weird spot markets in testnet, eg @2
            #     continue
            # }
            # marketParts = marketName.split('/')
            # baseName = self.safe_string(marketParts, 0)
            # quoteId = self.safe_string(marketParts, 1)
            fees = self.safe_dict(self.fees, 'spot', {})
            taker = self.safe_number(fees, 'taker')
            maker = self.safe_number(fees, 'maker')
            tokensPos = self.safe_list(market, 'tokens', [])
            baseTokenPos = self.safe_integer(tokensPos, 0)
            quoteTokenPos = self.safe_integer(tokensPos, 1)
            baseTokenInfo = self.safe_dict(tokens, baseTokenPos, {})
            quoteTokenInfo = self.safe_dict(tokens, quoteTokenPos, {})
            baseName = self.safe_string(baseTokenInfo, 'name')
            quoteId = self.safe_string(quoteTokenInfo, 'name')
            # do spot currency mapping
            spotCurrencyMapping = self.safe_dict(self.options, 'spotCurrencyMapping', {})
            mappedBaseName = self.safe_string(spotCurrencyMapping, baseName, baseName)
            mappedQuoteId = self.safe_string(spotCurrencyMapping, quoteId, quoteId)
            mappedBase = self.safe_currency_code(mappedBaseName)
            mappedQuote = self.safe_currency_code(mappedQuoteId)
            mappedSymbol = mappedBase + '/' + mappedQuote
            innerBaseTokenInfo = self.safe_dict(baseTokenInfo, 'spec', baseTokenInfo)
            # innerQuoteTokenInfo = self.safe_dict(quoteTokenInfo, 'spec', quoteTokenInfo)
            amountPrecisionStr = self.safe_string(innerBaseTokenInfo, 'szDecimals')
            amountPrecision = int(amountPrecisionStr)
            price = self.safe_number(extraData, 'midPx')
            pricePrecision = 0
            if price is not None:
                pricePrecision = self.calculate_price_precision(price, amountPrecision, 8)
            pricePrecisionStr = self.number_to_string(pricePrecision)
            # quotePrecision = self.parse_number(self.parse_precision(self.safe_string(innerQuoteTokenInfo, 'szDecimals')))
            baseId = self.number_to_string(index + 10000)
            entry = {
                'id': marketName,
                'symbol': mappedSymbol,
                'base': mappedBase,
                'quote': mappedQuote,
                'settle': None,
                'baseId': baseId,
                'baseName': baseName,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'subType': None,
                'margin': None,
                'swap': False,
                'future': False,
                'option': False,
                'active': True,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': taker,
                'maker': maker,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(amountPrecisionStr)),
                    'price': self.parse_number(self.parse_precision(pricePrecisionStr)),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.parse_number('10'),
                        'max': None,
                    },
                },
                'created': None,
                'info': self.extend(extraData, market),
            }
            markets.append(self.safe_market_structure(entry))
            # backward support
            base = self.safe_currency_code(baseName)
            quote = self.safe_currency_code(quoteId)
            newEntry = self.extend({}, entry)
            symbol = base + '/' + quote
            if symbol != mappedSymbol:
                newEntry['symbol'] = symbol
                newEntry['base'] = base
                newEntry['quote'] = quote
                newEntry['baseName'] = baseName
                markets.append(self.safe_market_structure(newEntry))
        return markets

    def parse_market(self, market: dict) -> Market:
        #
        #     {
        #         "maxLeverage": "50",
        #         "name": "ETH",
        #         "onlyIsolated": False,
        #         "szDecimals": "4",
        #         "dayNtlVlm": "1709813.11535",
        #         "funding": "0.00004807",
        #         "impactPxs": [
        #             "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 = self.safe_string(market, 'name')
        base = self.safe_currency_code(baseName)
        quote = self.safe_currency_code(quoteId)
        baseId = self.safe_string(market, 'baseId')
        settleId = 'USDC'
        settle = self.safe_currency_code(settleId)
        symbol = base + '/' + quote
        contract = True
        swap = True
        if contract:
            if swap:
                symbol = symbol + ':' + settle
        fees = self.safe_dict(self.fees, 'swap', {})
        taker = self.safe_number(fees, 'taker')
        maker = self.safe_number(fees, 'maker')
        amountPrecisionStr = self.safe_string(market, 'szDecimals')
        amountPrecision = int(amountPrecisionStr)
        price = self.safe_number(market, 'markPx', 0)
        pricePrecision = 0
        if price is not None:
            pricePrecision = self.calculate_price_precision(price, amountPrecision, 6)
        pricePrecisionStr = self.number_to_string(pricePrecision)
        isDelisted = self.safe_bool(market, 'isDelisted')
        active = True
        if isDelisted is not None:
            active = not isDelisted
        return self.safe_market_structure({
            'id': baseId,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'settle': settle,
            'baseId': baseId,
            'baseName': baseName,
            'quoteId': quoteId,
            'settleId': settleId,
            'type': 'swap',
            'spot': False,
            'margin': None,
            'swap': swap,
            'future': False,
            'option': False,
            'active': active,
            'contract': contract,
            'linear': True,
            'inverse': False,
            'taker': taker,
            'maker': maker,
            'contractSize': self.parse_number('1'),
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': self.parse_number(self.parse_precision(amountPrecisionStr)),
                'price': self.parse_number(self.parse_precision(pricePrecisionStr)),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': self.safe_integer(market, 'maxLeverage'),
                },
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.parse_number('10'),
                    'max': None,
                },
            },
            'created': None,
            'info': market,
        })

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-a-users-token-balances
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :param str [params.type]: wallet type, ['spot', 'swap'], defaults to swap
        :param str [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        userAddress = None
        userAddress, params = self.handle_public_address('fetchBalance', params)
        type = None
        type, params = self.handle_market_type_and_params('fetchBalance', None, params)
        marginMode = None
        marginMode, params = self.handle_margin_mode_and_params('fetchBalance', params)
        isSpot = (type == 'spot')
        request: dict = {
            'type': 'spotClearinghouseState' if (isSpot) else 'clearinghouseState',
            'user': userAddress,
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     {
        #         "assetPositions": [],
        #         "crossMaintenanceMarginUsed": "0.0",
        #         "crossMarginSummary": {
        #             "accountValue": "100.0",
        #             "totalMarginUsed": "0.0",
        #             "totalNtlPos": "0.0",
        #             "totalRawUsd": "100.0"
        #         },
        #         "marginSummary": {
        #             "accountValue": "100.0",
        #             "totalMarginUsed": "0.0",
        #             "totalNtlPos": "0.0",
        #             "totalRawUsd": "100.0"
        #         },
        #         "time": "1704261007014",
        #         "withdrawable": "100.0"
        #     }
        # spot
        #
        #     {
        #         "balances":[
        #            {
        #               "coin":"USDC",
        #               "hold":"0.0",
        #               "total":"1481.844"
        #            },
        #            {
        #               "coin":"PURR",
        #               "hold":"0.0",
        #               "total":"999.65004"
        #            }
        #     }
        #
        balances = self.safe_list(response, 'balances')
        if balances is not None:
            spotBalances: dict = {'info': response}
            for i in range(0, len(balances)):
                balance = balances[i]
                code = self.safe_currency_code(self.safe_string(balance, 'coin'))
                account = self.account()
                total = self.safe_string(balance, 'total')
                used = self.safe_string(balance, 'hold')
                account['total'] = total
                account['used'] = used
                spotBalances[code] = account
            return self.safe_balance(spotBalances)
        data = self.safe_dict(response, 'marginSummary', {})
        usdcBalance = {
            'total': self.safe_number(data, 'accountValue'),
        }
        if (marginMode is not None) and (marginMode == 'isolated'):
            usdcBalance['free'] = self.safe_number(response, 'withdrawable')
        else:
            usdcBalance['used'] = self.safe_number(data, 'totalMarginUsed')
        result: dict = {
            'info': response,
            'USDC': usdcBalance,
        }
        timestamp = self.safe_integer(response, 'time')
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.safe_balance(result)

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#l2-book-snapshot

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'type': 'l2Book',
            'coin': market['baseName'] if market['swap'] else market['id'],
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     {
        #         "coin": "ETH",
        #         "levels": [
        #             [
        #                 {
        #                     "n": "2",
        #                     "px": "2216.2",
        #                     "sz": "74.0637"
        #                 }
        #             ],
        #             [
        #                 {
        #                     "n": "2",
        #                     "px": "2216.5",
        #                     "sz": "70.5893"
        #                 }
        #             ]
        #         ],
        #         "time": "1704290104840"
        #     }
        #
        data = self.safe_list(response, 'levels', [])
        result: dict = {
            'bids': self.safe_list(data, 0, []),
            'asks': self.safe_list(data, 1, []),
        }
        timestamp = self.safe_integer(response, 'time')
        return self.parse_order_book(result, market['symbol'], timestamp, 'bids', 'asks', 'px', 'sz')

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts

        :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: 'spot' or 'swap', by default fetches both
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        # at self stage, to get tickers data, we use fetchMarkets endpoints
        response = []
        type = self.safe_string(params, 'type')
        params = self.omit(params, 'type')
        if type == 'spot':
            response = await self.fetch_spot_markets(params)
        elif type == 'swap':
            response = await self.fetch_swap_markets(params)
        else:
            response = await self.fetch_markets(params)
        # same response "fetchMarkets"
        result: dict = {}
        for i in range(0, len(response)):
            market = response[i]
            info = market['info']
            ticker = self.parse_ticker(info, market)
            symbol = self.safe_string(ticker, 'symbol')
            result[symbol] = ticker
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    async def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
        """
        retrieves data on all swap markets for hyperliquid

        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 str[] [symbols]: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        request: dict = {
            'type': 'metaAndAssetCtxs',
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "universe": [
        #                 {
        #                     "maxLeverage": 50,
        #                     "name": "SOL",
        #                     "onlyIsolated": False,
        #                     "szDecimals": 2
        #                 }
        #             ]
        #         },
        #         [
        #             {
        #                 "dayNtlVlm": "9450588.2273",
        #                 "funding": "0.0000198",
        #                 "impactPxs": [
        #                     "108.04",
        #                     "108.06"
        #                 ],
        #                 "markPx": "108.04",
        #                 "midPx": "108.05",
        #                 "openInterest": "10764.48",
        #                 "oraclePx": "107.99",
        #                 "premium": "0.00055561",
        #                 "prevDayPx": "111.81"
        #             }
        #         ]
        #     ]
        #
        #
        meta = self.safe_dict(response, 0, {})
        universe = self.safe_list(meta, 'universe', [])
        assetCtxs = self.safe_list(response, 1, [])
        result = []
        for i in range(0, len(universe)):
            data = self.extend(
                self.safe_dict(universe, i, {}),
                self.safe_dict(assetCtxs, i, {})
            )
            result.append(data)
        return self.parse_funding_rates(result, symbols)

    def parse_funding_rate(self, info, market: Market = None) -> FundingRate:
        #
        #     {
        #         "maxLeverage": "50",
        #         "name": "ETH",
        #         "onlyIsolated": False,
        #         "szDecimals": "4",
        #         "dayNtlVlm": "1709813.11535",
        #         "funding": "0.00004807",
        #         "impactPxs": [
        #             "2369.3",
        #             "2369.6"
        #         ],
        #         "markPx": "2369.6",
        #         "midPx": "2369.45",
        #         "openInterest": "1815.4712",
        #         "oraclePx": "2367.3",
        #         "premium": "0.00090821",
        #         "prevDayPx": "2381.5"
        #     }
        #
        base = self.safe_string(info, 'name')
        marketId = self.coin_to_market_id(base)
        symbol = self.safe_symbol(marketId, market)
        funding = self.safe_number(info, 'funding')
        markPx = self.safe_number(info, 'markPx')
        oraclePx = self.safe_number(info, 'oraclePx')
        fundingTimestamp = (int(math.floor(self.milliseconds()) / 60 / 60 / 1000) + 1) * 60 * 60 * 1000
        return {
            'info': info,
            'symbol': symbol,
            'markPrice': markPx,
            'indexPrice': oraclePx,
            'interestRate': None,
            'estimatedSettlePrice': None,
            'timestamp': None,
            'datetime': None,
            'fundingRate': funding,
            'fundingTimestamp': fundingTimestamp,
            'fundingDatetime': self.iso8601(fundingTimestamp),
            'nextFundingRate': None,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
            'interval': '1h',
        }

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "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": ["3465.0", "3465.1"],  # only in swap
        #         "coin": "PURR",  # only in spot
        #         "circulatingSupply": "998949190.03400207",  # only in spot
        #     },
        #
        bidAsk = self.safe_list(ticker, 'impactPxs')
        return self.safe_ticker({
            'symbol': market['symbol'],
            'timestamp': None,
            'datetime': None,
            'previousClose': self.safe_number(ticker, 'prevDayPx'),
            'close': self.safe_number(ticker, 'midPx'),
            'bid': self.safe_number(bidAsk, 0),
            'ask': self.safe_number(bidAsk, 1),
            'quoteVolume': self.safe_number(ticker, 'dayNtlVlm'),
            'info': ticker,
        }, market)

    async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#candle-snapshot

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest candle to fetch
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        until = self.safe_integer(params, 'until', self.milliseconds())
        useTail = since is None
        originalSince = since
        if since is None:
            if limit is not None:
                # optimization if limit is provided
                timeframeInMilliseconds = self.parse_timeframe(timeframe) * 1000
                since = self.sum(until, timeframeInMilliseconds * limit * -1)
                if since < 0:
                    since = 0
                useTail = False
            else:
                since = 0
        params = self.omit(params, ['until'])
        request: dict = {
            'type': 'candleSnapshot',
            'req': {
                'coin': market['baseName'] if market['swap'] else market['id'],
                'interval': self.safe_string(self.timeframes, timeframe, timeframe),
                'startTime': since,
                'endTime': until,
            },
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "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 self.parse_ohlcvs(response, market, timeframe, originalSince, limit, useTail)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     {
        #         "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 [
            self.safe_integer(ohlcv, 't'),
            self.safe_number(ohlcv, 'o'),
            self.safe_number(ohlcv, 'h'),
            self.safe_number(ohlcv, 'l'),
            self.safe_number(ohlcv, 'c'),
            self.safe_number(ohlcv, 'v'),
        ]

    async def fetch_trades(self, symbol: Str, since: Int = None, limit: Int = None, params={}):
        """
        get the list of most recent trades for a particular symbol

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills-by-time

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest trade
        :param str [params.address]: wallet address that made trades
        :param str [params.user]: wallet address that made trades
        :param str [params.subAccountAddress]: sub account user address
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        userAddress = None
        userAddress, params = self.handle_public_address('fetchTrades', params)
        await self.load_markets()
        market = self.safe_market(symbol)
        request: dict = {
            'user': userAddress,
        }
        if since is not None:
            request['type'] = 'userFillsByTime'
            request['startTime'] = since
        else:
            request['type'] = 'userFills'
        until = self.safe_integer(params, 'until')
        params = self.omit(params, 'until')
        if until is not None:
            request['endTime'] = until
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "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 self.parse_trades(response, market, since, limit)

    def amount_to_precision(self, symbol, amount):
        market = self.market(symbol)
        return self.decimal_to_precision(amount, ROUND, market['precision']['amount'], self.precisionMode, self.paddingMode)

    def price_to_precision(self, symbol: str, price) -> str:
        market = self.market(symbol)
        priceStr = self.number_to_string(price)
        integerPart = priceStr.split('.')[0]
        significantDigits = max(5, len(integerPart))
        result = self.decimal_to_precision(price, ROUND, significantDigits, SIGNIFICANT_DIGITS, self.paddingMode)
        maxDecimals = 8 if market['spot'] else 6
        subtractedValue = maxDecimals - self.precision_from_string(self.safe_string(market['precision'], 'amount'))
        return self.decimal_to_precision(result, ROUND, subtractedValue, DECIMAL_PLACES, self.paddingMode)

    def hash_message(self, message):
        return '0x' + self.hash(message, 'keccak', 'hex')

    def sign_hash(self, hash, privateKey):
        signature = self.ecdsa(hash[-64:], privateKey[-64:], 'secp256k1', None)
        return {
            'r': '0x' + signature['r'],
            's': '0x' + signature['s'],
            'v': self.sum(27, signature['v']),
        }

    def sign_message(self, message, privateKey):
        return self.sign_hash(self.hash_message(message), privateKey[-64:])

    def construct_phantom_agent(self, hash, isTestnet=True):
        source = 'b' if (isTestnet) else 'a'
        return {
            'source': source,
            'connectionId': hash,
        }

    def action_hash(self, action, vaultAddress, nonce):
        dataBinary = self.packb(action)
        dataHex = self.binary_to_base16(dataBinary)
        data = dataHex
        data += '00000' + self.int_to_base16(nonce)
        if vaultAddress is None:
            data += '00'
        else:
            data += '01'
            data += vaultAddress
        return self.hash(self.base16_to_binary(data), 'keccak', 'binary')

    def sign_l1_action(self, action, nonce, vaultAdress=None) -> object:
        hash = self.action_hash(action, vaultAdress, nonce)
        isTestnet = self.safe_bool(self.options, 'sandboxMode', False)
        phantomAgent = self.construct_phantom_agent(hash, isTestnet)
        # data: Dict = {
        #     'domain': {
        #         'chainId': 1337,
        #         'name': 'Exchange',
        #         'verifyingContract': '0x0000000000000000000000000000000000000000',
        #         'version': '1',
        #     },
        #     'types': {
        #         'Agent': [
        #             {'name': 'source', 'type': 'string'},
        #             {'name': 'connectionId', 'type': 'bytes32'},
        #         ],
        #         'EIP712Domain': [
        #             {'name': 'name', 'type': 'string'},
        #             {'name': 'version', 'type': 'string'},
        #             {'name': 'chainId', 'type': 'uint256'},
        #             {'name': 'verifyingContract', 'type': 'address'},
        #         ],
        #     },
        #     'primaryType': 'Agent',
        #     'message': phantomAgent,
        # }
        zeroAddress = self.safe_string(self.options, 'zeroAddress')
        chainId = 1337  # check self out
        domain: dict = {
            'chainId': chainId,
            'name': 'Exchange',
            'verifyingContract': zeroAddress,
            'version': '1',
        }
        messageTypes: dict = {
            'Agent': [
                {'name': 'source', 'type': 'string'},
                {'name': 'connectionId', 'type': 'bytes32'},
            ],
        }
        msg = self.eth_encode_structured_data(domain, messageTypes, phantomAgent)
        signature = self.sign_message(msg, self.privateKey)
        return signature

    def sign_user_signed_action(self, messageTypes, message):
        zeroAddress = self.safe_string(self.options, 'zeroAddress')
        chainId = 421614  # check self out
        domain: dict = {
            'chainId': chainId,
            'name': 'HyperliquidSignTransaction',
            'verifyingContract': zeroAddress,
            'version': '1',
        }
        msg = self.eth_encode_structured_data(domain, messageTypes, message)
        signature = self.sign_message(msg, self.privateKey)
        return signature

    def build_usd_send_sig(self, message):
        messageTypes: dict = {
            'HyperliquidTransaction:UsdSend': [
                {'name': 'hyperliquidChain', 'type': 'string'},
                {'name': 'destination', 'type': 'string'},
                {'name': 'amount', 'type': 'string'},
                {'name': 'time', 'type': 'uint64'},
            ],
        }
        return self.sign_user_signed_action(messageTypes, message)

    def build_usd_class_send_sig(self, message):
        messageTypes: dict = {
            'HyperliquidTransaction:UsdClassTransfer': [
                {'name': 'hyperliquidChain', 'type': 'string'},
                {'name': 'amount', 'type': 'string'},
                {'name': 'toPerp', 'type': 'bool'},
                {'name': 'nonce', 'type': 'uint64'},
            ],
        }
        return self.sign_user_signed_action(messageTypes, message)

    def build_withdraw_sig(self, message):
        messageTypes: dict = {
            'HyperliquidTransaction:Withdraw': [
                {'name': 'hyperliquidChain', 'type': 'string'},
                {'name': 'destination', 'type': 'string'},
                {'name': 'amount', 'type': 'string'},
                {'name': 'time', 'type': 'uint64'},
            ],
        }
        return self.sign_user_signed_action(messageTypes, message)

    def build_approve_builder_fee_sig(self, message):
        messageTypes: dict = {
            'HyperliquidTransaction:ApproveBuilderFee': [
                {'name': 'hyperliquidChain', 'type': 'string'},
                {'name': 'maxFeeRate', 'type': 'string'},
                {'name': 'builder', 'type': 'address'},
                {'name': 'nonce', 'type': 'uint64'},
            ],
        }
        return self.sign_user_signed_action(messageTypes, message)

    async def set_ref(self):
        if self.safe_bool(self.options, 'refSet', False):
            return True
        self.options['refSet'] = True
        action = {
            'type': 'setReferrer',
            'code': self.safe_string(self.options, 'ref', 'CCXT1'),
        }
        nonce = self.milliseconds()
        signature = self.sign_l1_action(action, nonce)
        request: dict = {
            'action': action,
            'nonce': nonce,
            'signature': signature,
        }
        response = None
        try:
            response = await self.privatePostExchange(request)
            return response
        except Exception as e:
            response = None  # ignore self
        return response

    async def approve_builder_fee(self, builder: str, maxFeeRate: str):
        nonce = self.milliseconds()
        isSandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
        payload: dict = {
            'hyperliquidChain': 'Testnet' if isSandboxMode else 'Mainnet',
            'maxFeeRate': maxFeeRate,
            'builder': builder,
            'nonce': nonce,
        }
        sig = self.build_approve_builder_fee_sig(payload)
        action = {
            'hyperliquidChain': payload['hyperliquidChain'],
            'signatureChainId': '0x66eee',
            'maxFeeRate': payload['maxFeeRate'],
            'builder': payload['builder'],
            'nonce': nonce,
            'type': 'approveBuilderFee',
        }
        request: dict = {
            'action': action,
            'nonce': nonce,
            'signature': sig,
            'vaultAddress': None,
        }
        #
        # {
        #     "status": "ok",
        #     "response": {
        #         "type": "default"
        #     }
        # }
        #
        return await self.privatePostExchange(request)

    async def initialize_client(self):
        try:
            await asyncio.gather(*[self.handle_builder_fee_approval(), self.set_ref()])
        except Exception as e:
            return False
        return True

    async def handle_builder_fee_approval(self):
        buildFee = self.safe_bool(self.options, 'builderFee', True)
        if not buildFee:
            return False  # skip if builder fee is not enabled
        approvedBuilderFee = self.safe_bool(self.options, 'approvedBuilderFee', False)
        if approvedBuilderFee:
            return True  # skip if builder fee is already approved
        try:
            builder = self.safe_string(self.options, 'builder', '0x6530512A6c89C7cfCEbC3BA7fcD9aDa5f30827a6')
            maxFeeRate = self.safe_string(self.options, 'feeRate', '0.01%')
            await self.approve_builder_fee(builder, maxFeeRate)
            self.options['approvedBuilderFee'] = True
        except Exception as e:
            self.options['builderFee'] = False  # disable builder fee if an error occurs
        return True

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order

        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [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 str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
        :param str [params.slippage]: the slippage for market order
        :param str [params.vaultAddress]: the vault address for order
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        order, globalParams = self.parse_create_edit_order_args(None, symbol, type, side, amount, price, params)
        orders = await self.create_orders([order], globalParams)
        return orders[0]

    async def create_orders(self, orders: List[OrderRequest], params={}):
        """
        create a list of trade orders

        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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        await self.initialize_client()
        request = self.create_orders_request(orders, params)
        response = await self.privatePostExchange(request)
        #
        #     {
        #         "status": "ok",
        #         "response": {
        #             "type": "order",
        #             "data": {
        #                 "statuses": [
        #                     {
        #                         "resting": {
        #                             "oid": 5063830287
        #                         }
        #                     }
        #                 ]
        #             }
        #         }
        #     }
        #
        responseObj = self.safe_dict(response, 'response', {})
        data = self.safe_dict(responseObj, 'data', {})
        statuses = self.safe_list(data, 'statuses', [])
        ordersToBeParsed = []
        for i in range(0, len(statuses)):
            order = statuses[i]
            if order == 'waitingForTrigger':
                ordersToBeParsed.append({'status': order})  # tp/sl orders can return a string like "waitingForTrigger",
            else:
                ordersToBeParsed.append(order)
        return self.parse_orders(ordersToBeParsed, None)

    def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: str, price: Str = None, params={}):
        market = self.market(symbol)
        type = type.upper()
        side = side.upper()
        isMarket = (type == 'MARKET')
        isBuy = (side == 'BUY')
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'client_id')
        slippage = self.safe_string(params, 'slippage')
        defaultTimeInForce = 'ioc' if (isMarket) else 'gtc'
        postOnly = self.safe_bool(params, 'postOnly', False)
        if postOnly:
            defaultTimeInForce = 'alo'
        timeInForce = self.safe_string_lower(params, 'timeInForce', defaultTimeInForce)
        timeInForce = self.capitalize(timeInForce)
        triggerPrice = self.safe_string_2(params, 'triggerPrice', 'stopPrice')
        stopLossPrice = self.safe_string(params, 'stopLossPrice', triggerPrice)
        takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
        isTrigger = (stopLossPrice or takeProfitPrice)
        px = None
        if isMarket:
            if price is None:
                raise ArgumentsRequired(self.id + '  market orders require price to calculate the max slippage price. Default slippage can be set in options(default is 5%).')
            px = Precise.string_mul(price, Precise.string_add('1', slippage)) if (isBuy) else Precise.string_mul(price, Precise.string_sub('1', slippage))
            px = self.price_to_precision(symbol, px)  # round after adding slippage
        else:
            px = self.price_to_precision(symbol, price)
        sz = self.amount_to_precision(symbol, amount)
        reduceOnly = self.safe_bool(params, 'reduceOnly', False)
        orderType: dict = {}
        if isTrigger:
            isTp = False
            if takeProfitPrice is not None:
                triggerPrice = self.price_to_precision(symbol, takeProfitPrice)
                isTp = True
            else:
                triggerPrice = self.price_to_precision(symbol, stopLossPrice)
            orderType['trigger'] = {
                'isMarket': isMarket,
                'triggerPx': triggerPrice,
                'tpsl': 'tp' if (isTp) else 'sl',
            }
        else:
            orderType['limit'] = {
                'tif': timeInForce,
            }
        params = self.omit(params, ['clientOrderId', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce', 'client_id', 'reduceOnly', 'postOnly'])
        orderObj: dict = {
            'a': self.parse_to_int(market['baseId']),
            'b': isBuy,
            'p': px,
            's': sz,
            'r': reduceOnly,
            't': orderType,
            # 'c': clientOrderId,
        }
        if clientOrderId is not None:
            orderObj['c'] = clientOrderId
        return orderObj

    def create_orders_request(self, orders, params={}) -> dict:
        """
        create a list of trade orders
        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
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.check_required_credentials()
        defaultSlippage = self.safe_string(self.options, 'defaultSlippage')
        defaultSlippage = self.safe_string(params, 'slippage', defaultSlippage)
        hasClientOrderId = False
        for i in range(0, len(orders)):
            rawOrder = orders[i]
            orderParams = self.safe_dict(rawOrder, 'params', {})
            clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
            if clientOrderId is not None:
                hasClientOrderId = True
        if hasClientOrderId:
            for i in range(0, len(orders)):
                rawOrder = orders[i]
                orderParams = self.safe_dict(rawOrder, 'params', {})
                clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
                if clientOrderId is None:
                    raise ArgumentsRequired(self.id + ' createOrders() all orders must have clientOrderId if at least one has a clientOrderId')
        params = self.omit(params, ['slippage', 'clientOrderId', 'client_id', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce'])
        nonce = self.milliseconds()
        orderReq = []
        grouping = 'na'
        for i in range(0, len(orders)):
            rawOrder = orders[i]
            marketId = self.safe_string(rawOrder, 'symbol')
            market = self.market(marketId)
            symbol = market['symbol']
            type = self.safe_string_upper(rawOrder, 'type')
            side = self.safe_string_upper(rawOrder, 'side')
            amount = self.safe_string(rawOrder, 'amount')
            price = self.safe_string(rawOrder, 'price')
            orderParams = self.safe_dict(rawOrder, 'params', {})
            slippage = self.safe_string(orderParams, 'slippage', defaultSlippage)
            orderParams['slippage'] = slippage
            stopLoss = self.safe_value(orderParams, 'stopLoss')
            takeProfit = self.safe_value(orderParams, 'takeProfit')
            isTrigger = (stopLoss or takeProfit)
            orderParams = self.omit(orderParams, ['stopLoss', 'takeProfit'])
            mainOrderObj: dict = self.create_order_request(symbol, type, side, amount, price, orderParams)
            orderReq.append(mainOrderObj)
            if isTrigger:
                # grouping opposed orders for sl/tp
                stopLossOrderTriggerPrice = self.safe_string_n(stopLoss, ['triggerPrice', 'stopPrice'])
                stopLossOrderType = self.safe_string(stopLoss, 'type', 'limit')
                stopLossOrderLimitPrice = self.safe_string_n(stopLoss, ['price', 'stopLossPrice'], stopLossOrderTriggerPrice)
                takeProfitOrderTriggerPrice = self.safe_string_n(takeProfit, ['triggerPrice', 'stopPrice'])
                takeProfitOrderType = self.safe_string(takeProfit, 'type', 'limit')
                takeProfitOrderLimitPrice = self.safe_string_n(takeProfit, ['price', 'takeProfitPrice'], takeProfitOrderTriggerPrice)
                grouping = 'normalTpsl'
                orderParams = self.omit(orderParams, ['stopLoss', 'takeProfit'])
                triggerOrderSide = ''
                if side == 'BUY':
                    triggerOrderSide = 'sell'
                else:
                    triggerOrderSide = 'buy'
                if takeProfit is not None:
                    orderObj: dict = self.create_order_request(symbol, takeProfitOrderType, triggerOrderSide, amount, takeProfitOrderLimitPrice, self.extend(orderParams, {
                        'takeProfitPrice': takeProfitOrderTriggerPrice,
                        'reduceOnly': True,
                    }))
                    orderReq.append(orderObj)
                if stopLoss is not None:
                    orderObj: dict = self.create_order_request(symbol, stopLossOrderType, triggerOrderSide, amount, stopLossOrderLimitPrice, self.extend(orderParams, {
                        'stopLossPrice': stopLossOrderTriggerPrice,
                        'reduceOnly': True,
                    }))
                    orderReq.append(orderObj)
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params(params, 'createOrder', 'vaultAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        orderAction: dict = {
            'type': 'order',
            'orders': orderReq,
            'grouping': grouping,
        }
        if self.safe_bool(self.options, 'approvedBuilderFee', False):
            wallet = self.safe_string_lower(self.options, 'builder', '0x6530512A6c89C7cfCEbC3BA7fcD9aDa5f30827a6')
            orderAction['builder'] = {'b': wallet, 'f': self.safe_integer(self.options, 'feeInt', 10)}
        signature = self.sign_l1_action(orderAction, nonce, vaultAddress)
        request: dict = {
            'action': orderAction,
            'nonce': nonce,
            'signature': signature,
            # 'vaultAddress': vaultAddress,
        }
        if vaultAddress is not None:
            params = self.omit(params, 'vaultAddress')
            request['vaultAddress'] = vaultAddress
        return request

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid

        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
        :param str [params.vaultAddress]: the vault address for order
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        orders = await self.cancel_orders([id], symbol, params)
        return self.safe_dict(orders, 0)

    async def cancel_orders(self, ids: List[str], symbol: Str = None, params={}):
        """
        cancel multiple orders

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid

        :param str[] ids: order ids
        :param str [symbol]: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param string|str[] [params.clientOrderId]: client order ids,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
        :param str [params.vaultAddress]: the vault address
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.check_required_credentials()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrders() requires a symbol argument')
        await self.load_markets()
        await self.initialize_client()
        request = self.cancel_orders_request(ids, symbol, params)
        response = await self.privatePostExchange(request)
        #
        #     {
        #         "status":"ok",
        #         "response":{
        #             "type":"cancel",
        #             "data":{
        #                 "statuses":[
        #                     "success"
        #                 ]
        #             }
        #         }
        #     }
        #
        innerResponse = self.safe_dict(response, 'response')
        data = self.safe_dict(innerResponse, 'data')
        statuses = self.safe_list(data, 'statuses')
        orders = []
        for i in range(0, len(statuses)):
            status = statuses[i]
            orders.append(self.safe_order({
                'info': status,
                'status': status,
            }))
        return orders

    def cancel_orders_request(self, ids: List[str], symbol: Str = None, params={}) -> dict:
        """
        build the request payload for cancelling multiple orders
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
        :param str[] ids: order ids
        :param str symbol: unified market symbol
        :param dict [params]:
        :returns dict: the raw request object to be sent to the exchange
        """
        market = self.market(symbol)
        clientOrderId = self.safe_value_2(params, 'clientOrderId', 'client_id')
        params = self.omit(params, ['clientOrderId', 'client_id'])
        nonce = self.milliseconds()
        request: dict = {
            'nonce': nonce,
            # 'vaultAddress': vaultAddress,
        }
        cancelReq = []
        cancelAction: dict = {
            'type': '',
            'cancels': [],
        }
        baseId = self.parse_to_numeric(market['baseId'])
        if clientOrderId is not None:
            if not isinstance(clientOrderId, list):
                clientOrderId = [clientOrderId]
            cancelAction['type'] = 'cancelByCloid'
            for i in range(0, len(clientOrderId)):
                cancelReq.append({
                    'asset': baseId,
                    'cloid': clientOrderId[i],
                })
        else:
            cancelAction['type'] = 'cancel'
            for i in range(0, len(ids)):
                cancelReq.append({
                    'a': baseId,
                    'o': self.parse_to_numeric(ids[i]),
                })
        cancelAction['cancels'] = cancelReq
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'cancelOrders', 'vaultAddress', 'subAccountAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        signature = self.sign_l1_action(cancelAction, nonce, vaultAddress)
        request['action'] = cancelAction
        request['signature'] = signature
        if vaultAddress is not None:
            params = self.omit(params, 'vaultAddress')
            request['vaultAddress'] = vaultAddress
        return request

    async def cancel_orders_for_symbols(self, orders: List[CancellationRequest], params={}):
        """
        cancel multiple orders for multiple symbols

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
        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 [{"id": "a", "symbol": "BTC/USDT"}, {"id": "b", "symbol": "ETH/USDT"}]
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.vaultAddress]: the vault address
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        self.check_required_credentials()
        await self.load_markets()
        await self.initialize_client()
        nonce = self.milliseconds()
        request: dict = {
            'nonce': nonce,
            # 'vaultAddress': vaultAddress,
        }
        cancelReq = []
        cancelAction: dict = {
            'type': '',
            'cancels': [],
        }
        cancelByCloid = False
        for i in range(0, len(orders)):
            order = orders[i]
            clientOrderId = self.safe_string(order, 'clientOrderId')
            if clientOrderId is not None:
                cancelByCloid = True
            id = self.safe_string(order, 'id')
            symbol = self.safe_string(order, 'symbol')
            if symbol is None:
                raise ArgumentsRequired(self.id + ' cancelOrdersForSymbols() requires a symbol argument in each order')
            if id is not None and cancelByCloid:
                raise BadRequest(self.id + ' cancelOrdersForSymbols() all orders must have either id or clientOrderId')
            assetKey = 'asset' if cancelByCloid else 'a'
            idKey = 'cloid' if cancelByCloid else 'o'
            market = self.market(symbol)
            cancelObj: dict = {}
            cancelObj[assetKey] = self.parse_to_numeric(market['baseId'])
            cancelObj[idKey] = clientOrderId if cancelByCloid else self.parse_to_numeric(id)
            cancelReq.append(cancelObj)
        cancelAction['type'] = 'cancelByCloid' if cancelByCloid else 'cancel'
        cancelAction['cancels'] = cancelReq
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'cancelOrdersForSymbols', 'vaultAddress', 'subAccountAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        signature = self.sign_l1_action(cancelAction, nonce, vaultAddress)
        request['action'] = cancelAction
        request['signature'] = signature
        if vaultAddress is not None:
            params = self.omit(params, 'vaultAddress')
            request['vaultAddress'] = vaultAddress
        response = await self.privatePostExchange(request)
        #
        #     {
        #         "status":"ok",
        #         "response":{
        #             "type":"cancel",
        #             "data":{
        #                 "statuses":[
        #                     "success"
        #                 ]
        #             }
        #         }
        #     }
        #
        return [self.safe_order({'info': response})]

    async def cancel_all_orders_after(self, timeout: Int, params={}):
        """
        dead man's switch, cancel all orders after the given timeout
        :param number timeout: time in milliseconds, 0 represents cancel the timer
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.vaultAddress]: the vault address
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: the api result
        """
        self.check_required_credentials()
        await self.load_markets()
        await self.initialize_client()
        params = self.omit(params, ['clientOrderId', 'client_id'])
        nonce = self.milliseconds()
        request: dict = {
            'nonce': nonce,
            # 'vaultAddress': vaultAddress,
        }
        cancelAction: dict = {
            'type': 'scheduleCancel',
            'time': nonce + timeout,
        }
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'cancelAllOrdersAfter', 'vaultAddress', 'subAccountAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        signature = self.sign_l1_action(cancelAction, nonce, vaultAddress)
        request['action'] = cancelAction
        request['signature'] = signature
        if vaultAddress is not None:
            params = self.omit(params, 'vaultAddress')
            request['vaultAddress'] = vaultAddress
        response = await self.privatePostExchange(request)
        #
        #     {
        #         "status":"err",
        #         "response":"Cannot set scheduled cancel time until enough volume traded. Required: $1000000. Traded: $373.47205."
        #     }
        #
        return response

    def edit_orders_request(self, orders, params={}):
        self.check_required_credentials()
        hasClientOrderId = False
        for i in range(0, len(orders)):
            rawOrder = orders[i]
            orderParams = self.safe_dict(rawOrder, 'params', {})
            clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
            if clientOrderId is not None:
                hasClientOrderId = True
        if hasClientOrderId:
            for i in range(0, len(orders)):
                rawOrder = orders[i]
                orderParams = self.safe_dict(rawOrder, 'params', {})
                clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
                if clientOrderId is None:
                    raise ArgumentsRequired(self.id + ' editOrders() all orders must have clientOrderId if at least one has a clientOrderId')
        params = self.omit(params, ['slippage', 'clientOrderId', 'client_id', 'slippage', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'timeInForce'])
        modifies = []
        for i in range(0, len(orders)):
            rawOrder = orders[i]
            id = self.safe_string(rawOrder, 'id')
            marketId = self.safe_string(rawOrder, 'symbol')
            market = self.market(marketId)
            symbol = market['symbol']
            type = self.safe_string_upper(rawOrder, 'type')
            isMarket = (type == 'MARKET')
            side = self.safe_string_upper(rawOrder, 'side')
            isBuy = (side == 'BUY')
            amount = self.safe_string(rawOrder, 'amount')
            price = self.safe_string(rawOrder, 'price')
            orderParams = self.safe_dict(rawOrder, 'params', {})
            defaultSlippage = self.safe_string(self.options, 'defaultSlippage')
            slippage = self.safe_string(orderParams, 'slippage', defaultSlippage)
            defaultTimeInForce = 'ioc' if (isMarket) else 'gtc'
            postOnly = self.safe_bool(orderParams, 'postOnly', False)
            if postOnly:
                defaultTimeInForce = 'alo'
            timeInForce = self.safe_string_lower(orderParams, 'timeInForce', defaultTimeInForce)
            timeInForce = self.capitalize(timeInForce)
            clientOrderId = self.safe_string_2(orderParams, 'clientOrderId', 'client_id')
            triggerPrice = self.safe_string_2(orderParams, 'triggerPrice', 'stopPrice')
            stopLossPrice = self.safe_string(orderParams, 'stopLossPrice', triggerPrice)
            takeProfitPrice = self.safe_string(orderParams, 'takeProfitPrice')
            isTrigger = (stopLossPrice or takeProfitPrice)
            reduceOnly = self.safe_bool(orderParams, 'reduceOnly', False)
            orderParams = self.omit(orderParams, ['slippage', 'timeInForce', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice', 'clientOrderId', 'client_id', 'postOnly', 'reduceOnly'])
            px = self.number_to_string(price)
            if isMarket:
                px = Precise.string_mul(px, Precise.string_add('1', slippage)) if (isBuy) else Precise.string_mul(px, Precise.string_sub('1', slippage))
                px = self.price_to_precision(symbol, px)
            else:
                px = self.price_to_precision(symbol, px)
            sz = self.amount_to_precision(symbol, amount)
            orderType: dict = {}
            if isTrigger:
                isTp = False
                if takeProfitPrice is not None:
                    triggerPrice = self.price_to_precision(symbol, takeProfitPrice)
                    isTp = True
                else:
                    triggerPrice = self.price_to_precision(symbol, stopLossPrice)
                orderType['trigger'] = {
                    'isMarket': isMarket,
                    'triggerPx': triggerPrice,
                    'tpsl': 'tp' if (isTp) else 'sl',
                }
            else:
                orderType['limit'] = {
                    'tif': timeInForce,
                }
            if triggerPrice is None:
                triggerPrice = '0'
            orderReq: dict = {
                'a': self.parse_to_int(market['baseId']),
                'b': isBuy,
                'p': px,
                's': sz,
                'r': reduceOnly,
                't': orderType,
                # 'c': clientOrderId,
            }
            if clientOrderId is not None:
                orderReq['c'] = clientOrderId
            modifyReq: dict = {
                'oid': self.parse_to_int(id),
                'order': orderReq,
            }
            modifies.append(modifyReq)
        nonce = self.milliseconds()
        modifyAction: dict = {
            'type': 'batchModify',
            'modifies': modifies,
        }
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params(params, 'editOrder', 'vaultAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        signature = self.sign_l1_action(modifyAction, nonce, vaultAddress)
        request: dict = {
            'action': modifyAction,
            'nonce': nonce,
            'signature': signature,
            # 'vaultAddress': vaultAddress,
        }
        if vaultAddress is not None:
            request['vaultAddress'] = vaultAddress
        return request

    async def edit_order(self, id: str, symbol: str, type: str, side: str, amount: Num = None, price: Num = None, params={}):
        """
        edit a trade order

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-multiple-orders

        :param str id: cancel order id
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [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 str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
        :param str [params.vaultAddress]: the vault address for order
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        if id is None:
            raise ArgumentsRequired(self.id + ' editOrder() requires an id argument')
        order, globalParams = self.parse_create_edit_order_args(id, symbol, type, side, amount, price, params)
        orders = await self.edit_orders([order], globalParams)
        return orders[0]

    async def edit_orders(self, orders: List[OrderRequest], params={}):
        """
        edit a list of trade orders

        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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        await self.initialize_client()
        request = self.edit_orders_request(orders, params)
        response = await self.privatePostExchange(request)
        #
        #     {
        #         "status": "ok",
        #         "response": {
        #             "type": "order",
        #             "data": {
        #                 "statuses": [
        #                     {
        #                         "resting": {
        #                             "oid": 5063830287
        #                         }
        #                     }
        #                 ]
        #             }
        #         }
        #     }
        # when the order is filled immediately
        #     {
        #         "status":"ok",
        #         "response":{
        #            "type":"order",
        #            "data":{
        #               "statuses":[
        #                  {
        #                     "filled":{
        #                        "totalSz":"0.1",
        #                        "avgPx":"100.84",
        #                        "oid":6195281425
        #                     }
        #                  }
        #               ]
        #            }
        #         }
        #     }
        #
        responseObject = self.safe_dict(response, 'response', {})
        dataObject = self.safe_dict(responseObject, 'data', {})
        statuses = self.safe_list(dataObject, 'statuses', [])
        return self.parse_orders(statuses)

    async def create_vault(self, name: str, description: str, initialUsd: int, params={}):
        """
        creates a value
        :param str name: The name of the vault
        :param str description: The description of the vault
        :param number initialUsd: The initialUsd of the vault
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: the api result
        """
        self.check_required_credentials()
        await self.load_markets()
        nonce = self.milliseconds()
        request: dict = {
            'nonce': nonce,
        }
        usd = self.parse_to_int(Precise.string_mul(self.number_to_string(initialUsd), '1000000'))
        action: dict = {
            'type': 'createVault',
            'name': name,
            'description': description,
            'initialUsd': usd,
            'nonce': nonce,
        }
        signature = self.sign_l1_action(action, nonce)
        request['action'] = action
        request['signature'] = signature
        response = await self.privatePostExchange(self.extend(request, params))
        #
        # {
        #     "status": "ok",
        #     "response": {
        #         "type": "createVault",
        #         "data": "0x04fddcbc9ce80219301bd16f18491bedf2a8c2b8"
        #     }
        # }
        #
        return response

    async def fetch_funding_rate_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches historical funding rate prices

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-historical-funding-rates

        :param str symbol: unified symbol of the market to fetch the funding rate history for
        :param int [since]: timestamp in ms of the earliest funding rate to fetch
        :param int [limit]: the maximum amount of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>` to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest funding rate
        :returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
        """
        await self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
        market = self.market(symbol)
        request: dict = {
            'type': 'fundingHistory',
            'coin': market['baseName'],
        }
        if since is not None:
            request['startTime'] = since
        else:
            maxLimit = 500 if (limit is None) else limit
            request['startTime'] = self.milliseconds() - maxLimit * 60 * 60 * 1000
        until = self.safe_integer(params, 'until')
        params = self.omit(params, 'until')
        if until is not None:
            request['endTime'] = until
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "coin": "ETH",
        #             "fundingRate": "0.0000125",
        #             "premium": "0.00057962",
        #             "time": 1704290400031
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            entry = response[i]
            timestamp = self.safe_integer(entry, 'time')
            result.append({
                'info': entry,
                'symbol': self.safe_symbol(None, market),
                'fundingRate': self.safe_number(entry, 'fundingRate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(result, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-open-orders

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :param str [params.method]: 'openOrders' or 'frontendOpenOrders' default is 'frontendOpenOrders'
        :param str [params.subAccountAddress]: sub account user address
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        userAddress = None
        userAddress, params = self.handle_public_address('fetchOpenOrders', params)
        method = None
        method, params = self.handle_option_and_params(params, 'fetchOpenOrders', 'method', 'frontendOpenOrders')
        await self.load_markets()
        market = self.safe_market(symbol)
        request: dict = {
            'type': method,
            'user': userAddress,
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "coin": "ETH",
        #             "limitPx": "2000.0",
        #             "oid": 3991946565,
        #             "origSz": "0.1",
        #             "side": "B",
        #             "sz": "0.1",
        #             "timestamp": 1704346468838
        #         }
        #     ]
        #
        orderWithStatus = []
        for i in range(0, len(response)):
            order = response[i]
            extendOrder = {}
            if self.safe_string(order, 'status') is None:
                extendOrder['ccxtStatus'] = 'open'
            orderWithStatus.append(self.extend(order, extendOrder))
        return self.parse_orders(orderWithStatus, market, since, limit)

    async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently closed orders
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        orders = await self.fetch_orders(symbol, None, None, params)  # don't filter here because we don't want to catch open orders
        closedOrders = self.filter_by_array(orders, 'status', ['closed'], False)
        return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)

    async def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all canceled orders
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        orders = await self.fetch_orders(symbol, None, None, params)  # don't filter here because we don't want to catch open orders
        closedOrders = self.filter_by_array(orders, 'status', ['canceled'], False)
        return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)

    async def fetch_canceled_and_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all closed and canceled orders
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        orders = await self.fetch_orders(symbol, None, None, params)  # don't filter here because we don't want to catch open orders
        closedOrders = self.filter_by_array(orders, 'status', ['canceled', 'closed', 'rejected'], False)
        return self.filter_by_symbol_since_limit(closedOrders, symbol, since, limit)

    async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all orders
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :param str [params.subAccountAddress]: sub account user address
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        userAddress = None
        userAddress, params = self.handle_public_address('fetchOrders', params)
        await self.load_markets()
        market = self.safe_market(symbol)
        request: dict = {
            'type': 'historicalOrders',
            'user': userAddress,
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "coin": "ETH",
        #             "limitPx": "2000.0",
        #             "oid": 3991946565,
        #             "origSz": "0.1",
        #             "side": "B",
        #             "sz": "0.1",
        #             "timestamp": 1704346468838
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#query-order-status-by-oid-or-cloid

        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.clientOrderId]: client order id,(optional 128 bit hex string e.g. 0x1234567890abcdef1234567890abcdef)
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        userAddress = None
        userAddress, params = self.handle_public_address('fetchOrder', params)
        await self.load_markets()
        market = self.safe_market(symbol)
        clientOrderId = self.safe_string(params, 'clientOrderId')
        request: dict = {
            'type': 'orderStatus',
            # 'oid': id if isClientOrderId else self.parse_to_numeric(id),
            'user': userAddress,
        }
        if clientOrderId is not None:
            params = self.omit(params, 'clientOrderId')
            request['oid'] = clientOrderId
        else:
            isClientOrderId = len(id) >= 34
            request['oid'] = id if isClientOrderId else self.parse_to_numeric(id)
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     {
        #         "order": {
        #             "order": {
        #                 "children": [],
        #                 "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 = self.safe_dict(response, 'order')
        return self.parse_order(data, market)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # createOrdersWs error
        #
        #  {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": {
        #             "children": [],
        #             "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": [],
        #     "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 = self.safe_string(order, 'error')
        if error is not None:
            return self.safe_order({
                'info': order,
                'status': 'rejected',
            })
        entry = self.safe_dict_n(order, ['order', 'resting', 'filled'])
        if entry is None:
            entry = order
        coin = self.safe_string(entry, 'coin')
        marketId = None
        if coin is not None:
            marketId = self.coin_to_market_id(coin)
        if self.safe_string(entry, 'id') is None:
            market = self.safe_market(marketId, None)
        else:
            market = self.safe_market(marketId, market)
        symbol = market['symbol']
        timestamp = self.safe_integer(entry, 'timestamp')
        status = self.safe_string_2(order, 'status', 'ccxtStatus')
        order = self.omit(order, ['ccxtStatus'])
        side = self.safe_string(entry, 'side')
        if side is not None:
            side = 'sell' if (side == 'A') else 'buy'
        totalAmount = self.safe_string_2(entry, 'origSz', 'totalSz')
        remaining = self.safe_string(entry, 'sz')
        tif = self.safe_string_upper(entry, 'tif')
        postOnly = None
        if tif is not None:
            postOnly = (tif == 'ALO')
        return self.safe_order({
            'info': order,
            'id': self.safe_string(entry, 'oid'),
            'clientOrderId': self.safe_string(entry, 'cloid'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'lastUpdateTimestamp': self.safe_integer(order, 'statusTimestamp'),
            'symbol': symbol,
            'type': self.parse_order_type(self.safe_string_lower(entry, 'orderType')),
            'timeInForce': tif,
            'postOnly': postOnly,
            'reduceOnly': self.safe_bool(entry, 'reduceOnly'),
            'side': side,
            'price': self.safe_string(entry, 'limitPx'),
            'triggerPrice': self.safe_number(entry, 'triggerPx') if self.safe_bool(entry, 'isTrigger') else None,
            'amount': totalAmount,
            'cost': None,
            'average': self.safe_string(entry, 'avgPx'),
            'filled': Precise.string_sub(totalAmount, remaining),
            'remaining': remaining,
            'status': self.parse_order_status(status),
            'fee': None,
            'trades': None,
        }, market)

    def parse_order_status(self, status: Str):
        if status is None:
            return None
        statuses: dict = {
            'triggered': 'open',
            'filled': 'closed',
            'open': 'open',
            'canceled': 'canceled',
            'rejected': 'rejected',
            'marginCanceled': 'canceled',
        }
        if status.endswith('Rejected'):
            return 'rejected'
        if status.endswith('Canceled'):
            return 'canceled'
        return self.safe_string(statuses, status, status)

    def parse_order_type(self, status):
        statuses: dict = {
            'stop limit': 'limit',
            'stop market': 'market',
        }
        return self.safe_string(statuses, status, status)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#retrieve-a-users-fills-by-time

        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest trade
        :param str [params.subAccountAddress]: sub account user address
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        userAddress = None
        userAddress, params = self.handle_public_address('fetchMyTrades', params)
        await self.load_markets()
        market = self.safe_market(symbol)
        request: dict = {
            'user': userAddress,
        }
        if since is not None:
            request['type'] = 'userFillsByTime'
            request['startTime'] = since
        else:
            request['type'] = 'userFills'
        until = self.safe_integer(params, 'until')
        params = self.omit(params, 'until')
        if until is not None:
            request['endTime'] = until
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     [
        #         {
        #             "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 self.parse_trades(response, market, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        #     {
        #         "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 = self.safe_integer(trade, 'time')
        price = self.safe_string(trade, 'px')
        amount = self.safe_string(trade, 'sz')
        coin = self.safe_string(trade, 'coin')
        marketId = self.coin_to_market_id(coin)
        market = self.safe_market(marketId, None)
        symbol = market['symbol']
        id = self.safe_string(trade, 'tid')
        side = self.safe_string(trade, 'side')
        if side is not None:
            side = 'sell' if (side == 'A') else 'buy'
        fee = self.safe_string(trade, 'fee')
        takerOrMaker = None
        crossed = self.safe_bool(trade, 'crossed')
        if crossed is not None:
            takerOrMaker = 'taker' if crossed else 'maker'
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': self.safe_string(trade, 'oid'),
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': {
                'cost': fee,
                'currency': self.safe_string(trade, 'feeToken'),
                'rate': None,
            },
        }, market)

    async def fetch_position(self, symbol: str, params={}):
        """
        fetch data on an open position

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary

        :param str symbol: unified market symbol of the market the position is held in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :returns dict: a `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        positions = await self.fetch_positions([symbol], params)
        return self.safe_dict(positions, 0, {})

    async def fetch_positions(self, symbols: Strings = None, params={}) -> List[Position]:
        """
        fetch all open positions

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary

        :param str[] [symbols]: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.user]: user address, will default to self.walletAddress if not provided
        :param str [params.subAccountAddress]: sub account user address
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        await self.load_markets()
        userAddress = None
        userAddress, params = self.handle_public_address('fetchPositions', params)
        symbols = self.market_symbols(symbols)
        request: dict = {
            'type': 'clearinghouseState',
            'user': userAddress,
        }
        response = await self.publicPostInfo(self.extend(request, params))
        #
        #     {
        #         "assetPositions": [
        #             {
        #                 "position": {
        #                     "coin": "ETH",
        #                     "cumFunding": {
        #                         "allTime": "0.0",
        #                         "sinceChange": "0.0",
        #                         "sinceOpen": "0.0"
        #                     },
        #                     "entryPx": "2213.9",
        #                     "leverage": {
        #                         "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": {
        #             "accountValue": "100.0",
        #             "totalMarginUsed": "0.0",
        #             "totalNtlPos": "0.0",
        #             "totalRawUsd": "100.0"
        #         },
        #         "marginSummary": {
        #             "accountValue": "100.0",
        #             "totalMarginUsed": "0.0",
        #             "totalNtlPos": "0.0",
        #             "totalRawUsd": "100.0"
        #         },
        #         "time": "1704261007014",
        #         "withdrawable": "100.0"
        #     }
        #
        data = self.safe_list(response, 'assetPositions', [])
        result = []
        for i in range(0, len(data)):
            result.append(self.parse_position(data[i], None))
        return self.filter_by_array_positions(result, 'symbol', symbols, False)

    def parse_position(self, position: dict, market: Market = None):
        #
        #     {
        #         "position": {
        #             "coin": "ETH",
        #             "cumFunding": {
        #                 "allTime": "0.0",
        #                 "sinceChange": "0.0",
        #                 "sinceOpen": "0.0"
        #             },
        #             "entryPx": "2213.9",
        #             "leverage": {
        #                 "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 = self.safe_dict(position, 'position', {})
        coin = self.safe_string(entry, 'coin')
        marketId = self.coin_to_market_id(coin)
        market = self.safe_market(marketId, None)
        symbol = market['symbol']
        leverage = self.safe_dict(entry, 'leverage', {})
        marginMode = self.safe_string(leverage, 'type')
        isIsolated = (marginMode == 'isolated')
        rawSize = self.safe_string(entry, 'szi')
        size = rawSize
        side = None
        if size is not None:
            side = 'long' if Precise.string_gt(rawSize, '0') else 'short'
            size = Precise.string_abs(size)
        rawUnrealizedPnl = self.safe_string(entry, 'unrealizedPnl')
        absRawUnrealizedPnl = Precise.string_abs(rawUnrealizedPnl)
        marginUsed = self.safe_string(entry, 'marginUsed')
        initialMargin = None
        if isIsolated:
            initialMargin = Precise.string_sub(marginUsed, rawUnrealizedPnl)
        else:
            initialMargin = marginUsed
        percentage = Precise.string_mul(Precise.string_div(absRawUnrealizedPnl, marginUsed), '100')
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'isolated': isIsolated,
            'hedged': None,
            'side': side,
            'contracts': self.parse_number(size),
            'contractSize': None,
            'entryPrice': self.safe_number(entry, 'entryPx'),
            'markPrice': None,
            'notional': self.safe_number(entry, 'positionValue'),
            'leverage': self.safe_number(leverage, 'value'),
            'collateral': self.parse_number(marginUsed),
            'initialMargin': self.parse_number(initialMargin),
            'maintenanceMargin': None,
            'initialMarginPercentage': None,
            'maintenanceMarginPercentage': None,
            'unrealizedPnl': self.parse_number(rawUnrealizedPnl),
            'liquidationPrice': self.safe_number(entry, 'liquidationPx'),
            'marginMode': marginMode,
            'percentage': self.parse_number(percentage),
        })

    async def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
        """
        set margin mode(symbol)
        :param str marginMode: margin mode must be either [isolated, cross]
        :param str symbol: unified market symbol of the market the position is held in, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.leverage]: the rate of leverage, is required if setting trade mode(symbol)
        :param str [params.vaultAddress]: the vault address
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: response from the exchange
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        leverage = self.safe_integer(params, 'leverage')
        if leverage is None:
            raise ArgumentsRequired(self.id + ' setMarginMode() requires a leverage parameter')
        asset = self.parse_to_int(market['baseId'])
        isCross = (marginMode == 'cross')
        nonce = self.milliseconds()
        params = self.omit(params, ['leverage'])
        updateAction: dict = {
            'type': 'updateLeverage',
            'asset': asset,
            'isCross': isCross,
            'leverage': leverage,
        }
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'setMarginMode', 'vaultAddress', 'subAccountAddress')
        if vaultAddress is not None:
            if vaultAddress.startswith('0x'):
                vaultAddress = vaultAddress.replace('0x', '')
        signature = self.sign_l1_action(updateAction, nonce, vaultAddress)
        request: dict = {
            'action': updateAction,
            'nonce': nonce,
            'signature': signature,
            # 'vaultAddress': vaultAddress,
        }
        if vaultAddress is not None:
            request['vaultAddress'] = vaultAddress
        response = await self.privatePostExchange(request)
        #
        #     {
        #         'response': {
        #             'type': 'default'
        #         },
        #         'status': 'ok'
        #     }
        #
        return response

    async def set_leverage(self, leverage: int, symbol: Str = None, params={}):
        """
        set the level of leverage for a market
        :param float leverage: the rate of leverage
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.marginMode]: margin mode must be either [isolated, cross], default is cross
        :returns dict: response from the exchange
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setLeverage() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        marginMode = self.safe_string(params, 'marginMode', 'cross')
        isCross = (marginMode == 'cross')
        asset = self.parse_to_int(market['baseId'])
        nonce = self.milliseconds()
        params = self.omit(params, 'marginMode')
        updateAction: dict = {
            'type': 'updateLeverage',
            'asset': asset,
            'isCross': isCross,
            'leverage': leverage,
        }
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'setLeverage', 'vaultAddress', 'subAccountAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        signature = self.sign_l1_action(updateAction, nonce, vaultAddress)
        request: dict = {
            'action': updateAction,
            'nonce': nonce,
            'signature': signature,
            # 'vaultAddress': vaultAddress,
        }
        if vaultAddress is not None:
            params = self.omit(params, 'vaultAddress')
            request['vaultAddress'] = vaultAddress
        response = await self.privatePostExchange(request)
        #
        #     {
        #         'response': {
        #             'type': 'default'
        #         },
        #         'status': 'ok'
        #     }
        #
        return response

    async def add_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """
        add margin

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin

        :param str symbol: unified market symbol
        :param float amount: amount of margin to add
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.vaultAddress]: the vault address
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: a `margin structure <https://docs.ccxt.com/#/?id=add-margin-structure>`
        """
        return await self.modify_margin_helper(symbol, amount, 'add', params)

    async def reduce_margin(self, symbol: str, amount: float, params={}) -> MarginModification:
        """

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin

        remove margin from a position
        :param str symbol: unified market symbol
        :param float amount: the amount of margin to remove
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.vaultAddress]: the vault address
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: a `margin structure <https://docs.ccxt.com/#/?id=reduce-margin-structure>`
        """
        return await self.modify_margin_helper(symbol, amount, 'reduce', params)

    async def modify_margin_helper(self, symbol: str, amount, type, params={}) -> MarginModification:
        await self.load_markets()
        market = self.market(symbol)
        asset = self.parse_to_int(market['baseId'])
        sz = self.parse_to_int(Precise.string_mul(self.amount_to_precision(symbol, amount), '1000000'))
        if type == 'reduce':
            sz = -sz
        nonce = self.milliseconds()
        updateAction: dict = {
            'type': 'updateIsolatedMargin',
            'asset': asset,
            'isBuy': True,
            'ntli': sz,
        }
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'modifyMargin', 'vaultAddress', 'subAccountAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        signature = self.sign_l1_action(updateAction, nonce, vaultAddress)
        request: dict = {
            'action': updateAction,
            'nonce': nonce,
            'signature': signature,
            # 'vaultAddress': vaultAddress,
        }
        if vaultAddress is not None:
            request['vaultAddress'] = vaultAddress
        response = await self.privatePostExchange(request)
        #
        #     {
        #         'response': {
        #             'type': 'default'
        #         },
        #         'status': 'ok'
        #     }
        #
        return self.extend(self.parse_margin_modification(response, market), {
            'code': self.safe_string(response, 'status'),
        })

    def parse_margin_modification(self, data: dict, market: Market = None) -> MarginModification:
        #
        #    {
        #        'type': 'default'
        #    }
        #
        return {
            'info': data,
            'symbol': self.safe_symbol(None, market),
            'type': None,
            'marginMode': 'isolated',
            'amount': None,
            'total': None,
            'code': self.safe_string(market, 'settle'),
            'status': None,
            'timestamp': None,
            'datetime': None,
        }

    async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#l1-usdc-transfer

        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from *spot, swap*
        :param str toAccount: account to transfer to *swap, spot or address*
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.vaultAddress]: the vault address for order
        :returns dict: a `transfer structure <https://docs.ccxt.com/#/?id=transfer-structure>`
        """
        self.check_required_credentials()
        await self.load_markets()
        isSandboxMode = self.safe_bool(self.options, 'sandboxMode')
        nonce = self.milliseconds()
        if self.in_array(fromAccount, ['spot', 'swap', 'perp']):
            # handle swap <> spot account transfer
            if not self.in_array(toAccount, ['spot', 'swap', 'perp']):
                raise NotSupported(self.id + ' transfer() only support spot <> swap transfer')
            strAmount = self.number_to_string(amount)
            vaultAddress = self.safe_string_2(params, 'vaultAddress', 'subAccountAddress')
            if vaultAddress is not None:
                vaultAddress = self.format_vault_address(vaultAddress)
                strAmount = strAmount + ' subaccount:' + vaultAddress
            toPerp = (toAccount == 'perp') or (toAccount == 'swap')
            transferPayload: dict = {
                'hyperliquidChain': 'Testnet' if isSandboxMode else 'Mainnet',
                'amount': strAmount,
                'toPerp': toPerp,
                'nonce': nonce,
            }
            transferSig = self.build_usd_class_send_sig(transferPayload)
            transferRequest: dict = {
                'action': {
                    'hyperliquidChain': transferPayload['hyperliquidChain'],
                    'signatureChainId': '0x66eee',
                    'type': 'usdClassTransfer',
                    'amount': strAmount,
                    'toPerp': toPerp,
                    'nonce': nonce,
                },
                'nonce': nonce,
                'signature': transferSig,
            }
            transferResponse = await self.privatePostExchange(transferRequest)
            return transferResponse
        # transfer between main account and subaccount
        isDeposit = False
        subAccountAddress = None
        if fromAccount == 'main':
            subAccountAddress = toAccount
            isDeposit = True
        elif toAccount == 'main':
            subAccountAddress = fromAccount
        else:
            raise NotSupported(self.id + ' transfer() only support main <> subaccount transfer')
        self.check_address(subAccountAddress)
        if code is None or code.upper() == 'USDC':
            # Transfer USDC with subAccountTransfer
            usd = self.parse_to_int(Precise.string_mul(self.number_to_string(amount), '1000000'))
            action = {
                'type': 'subAccountTransfer',
                'subAccountUser': subAccountAddress,
                'isDeposit': isDeposit,
                'usd': usd,
            }
            sig = self.sign_l1_action(action, nonce)
            request: dict = {
                'action': action,
                'nonce': nonce,
                'signature': sig,
            }
            response = await self.privatePostExchange(request)
            #
            # {'response': {'type': 'default'}, 'status': 'ok'}
            #
            return self.parse_transfer(response)
        else:
            # Transfer non-USDC with subAccountSpotTransfer
            symbol = self.symbol(code)
            action = {
                'type': 'subAccountSpotTransfer',
                'subAccountUser': subAccountAddress,
                'isDeposit': isDeposit,
                'token': symbol,
                'amount': self.number_to_string(amount),
            }
            sig = self.sign_l1_action(action, nonce)
            request: dict = {
                'action': action,
                'nonce': nonce,
                'signature': sig,
            }
            response = await self.privatePostExchange(request)
            return self.parse_transfer(response)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        # {'response': {'type': 'default'}, 'status': 'ok'}
        #
        return {
            'info': transfer,
            'id': None,
            'timestamp': None,
            'datetime': None,
            'currency': None,
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': 'ok',
        }

    async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
        """
        make a withdrawal(only support USDC)

        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#initiate-a-withdrawal-request
        https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#deposit-or-withdraw-from-a-vault

        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.vaultAddress]: vault address withdraw from
        :returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        self.check_required_credentials()
        await self.load_markets()
        self.check_address(address)
        if code is not None:
            code = code.upper()
            if code != 'USDC':
                raise NotSupported(self.id + ' withdraw() only support USDC')
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params(params, 'withdraw', 'vaultAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        params = self.omit(params, 'vaultAddress')
        nonce = self.milliseconds()
        action: dict = {}
        sig = None
        if vaultAddress is not None:
            action = {
                'type': 'vaultTransfer',
                'vaultAddress': '0x' + vaultAddress,
                'isDeposit': False,
                'usd': amount,
            }
            sig = self.sign_l1_action(action, nonce)
        else:
            isSandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
            payload: dict = {
                'hyperliquidChain': 'Testnet' if isSandboxMode else 'Mainnet',
                'destination': address,
                'amount': str(amount),
                'time': nonce,
            }
            sig = self.build_withdraw_sig(payload)
            action = {
                'hyperliquidChain': payload['hyperliquidChain'],
                'signatureChainId': '0x66eee',  # check self out
                'destination': address,
                'amount': str(amount),
                'time': nonce,
                'type': 'withdraw3',
            }
        request: dict = {
            'action': action,
            'nonce': nonce,
            'signature': sig,
        }
        response = await self.privatePostExchange(request)
        return self.parse_transaction(response)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # {status: 'ok', response: {type: 'default'}}
        #
        # fetchDeposits / fetchWithdrawals
        # {
        #     "time":1724762307531,
        #     "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        #     "delta":{
        #         "type":"accountClassTransfer",
        #         "usdc":"50.0",
        #         "toPerp":false
        #     }
        # }
        #
        timestamp = self.safe_integer(transaction, 'time')
        delta = self.safe_dict(transaction, 'delta', {})
        fee = None
        feeCost = self.safe_integer(delta, 'fee')
        if feeCost is not None:
            fee = {
                'currency': 'USDC',
                'cost': feeCost,
            }
        internal = None
        type = self.safe_string(delta, 'type')
        if type is not None:
            internal = (type == 'internalTransfer')
        return {
            'info': transaction,
            'id': None,
            'txid': self.safe_string(transaction, 'hash'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'address': None,
            'addressTo': self.safe_string(delta, 'destination'),
            'addressFrom': self.safe_string(delta, 'user'),
            'tag': None,
            'tagTo': None,
            'tagFrom': None,
            'type': None,
            'amount': self.safe_number(delta, 'usdc'),
            'currency': None,
            'status': self.safe_string(transaction, 'status'),
            'updated': None,
            'comment': None,
            'internal': internal,
            'fee': fee,
        }

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

    def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
        #
        #     {
        #         "dailyUserVlm": [
        #             {
        #                 "date": "2024-07-08",
        #                 "userCross": "0.0",
        #                 "userAdd": "0.0",
        #                 "exchange": "90597185.23639999"
        #             }
        #         ],
        #         "feeSchedule": {
        #             "cross": "0.00035",
        #             "add": "0.0001",
        #             "tiers": {
        #                 "vip": [
        #                     {
        #                         "ntlCutoff": "5000000.0",
        #                         "cross": "0.0003",
        #                         "add": "0.00005"
        #                     }
        #                 ],
        #                 "mm": [
        #                     {
        #                         "makerFractionCutoff": "0.005",
        #                         "add": "-0.00001"
        #                     }
        #                 ]
        #             },
        #             "referralDiscount": "0.04"
        #         },
        #         "userCrossRate": "0.00035",
        #         "userAddRate": "0.0001",
        #         "activeReferralDiscount": "0.0"
        #     }
        #
        symbol = self.safe_symbol(None, market)
        return {
            'info': fee,
            'symbol': symbol,
            'maker': self.safe_number(fee, 'userAddRate'),
            'taker': self.safe_number(fee, 'userCrossRate'),
            'percentage': None,
            'tierBased': None,
        }

    async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[LedgerEntry]:
        """
        fetch the history of changes, actions done by the user or operations that altered the balance of the user
        :param str [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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: timestamp in ms of the latest ledger entry
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: a `ledger structure <https://docs.ccxt.com/#/?id=ledger>`
        """
        await self.load_markets()
        userAddress = None
        userAddress, params = self.handle_public_address('fetchLedger', params)
        request: dict = {
            'type': 'userNonFundingLedgerUpdates',
            'user': userAddress,
        }
        if since is not None:
            request['startTime'] = since
        until = self.safe_integer(params, 'until')
        if until is not None:
            request['endTime'] = until
            params = self.omit(params, ['until'])
        response = await self.publicPostInfo(self.extend(request, params))
        #
        # [
        #     {
        #         "time":1724762307531,
        #         "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        #         "delta":{
        #             "type":"accountClassTransfer",
        #             "usdc":"50.0",
        #             "toPerp":false
        #         }
        #     }
        # ]
        #
        return self.parse_ledger(response, None, since, limit)

    def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
        #
        # {
        #     "time":1724762307531,
        #     "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        #     "delta":{
        #         "type":"accountClassTransfer",
        #         "usdc":"50.0",
        #         "toPerp":false
        #     }
        # }
        #
        timestamp = self.safe_integer(item, 'time')
        delta = self.safe_dict(item, 'delta', {})
        fee = None
        feeCost = self.safe_integer(delta, 'fee')
        if feeCost is not None:
            fee = {
                'currency': 'USDC',
                'cost': feeCost,
            }
        type = self.safe_string(delta, 'type')
        amount = self.safe_string(delta, 'usdc')
        return self.safe_ledger_entry({
            'info': item,
            'id': self.safe_string(item, 'hash'),
            'direction': None,
            'account': None,
            'referenceAccount': self.safe_string(delta, 'user'),
            'referenceId': self.safe_string(item, 'hash'),
            'type': self.parse_ledger_entry_type(type),
            'currency': None,
            'amount': self.parse_number(amount),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'before': None,
            'after': None,
            'status': None,
            'fee': fee,
        }, currency)

    def parse_ledger_entry_type(self, type):
        ledgerType: dict = {
            'internalTransfer': 'transfer',
            'accountClassTransfer': 'transfer',
        }
        return self.safe_string(ledgerType, type, type)

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all deposits made to an account
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch withdrawals for
        :param str [params.subAccountAddress]: sub account user address
        :param str [params.vaultAddress]: vault address
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        userAddress = None
        userAddress, params = self.handle_public_address('fetchDepositsWithdrawals', params)
        request: dict = {
            'type': 'userNonFundingLedgerUpdates',
            'user': userAddress,
        }
        if since is not None:
            request['startTime'] = since
        until = self.safe_integer(params, 'until')
        if until is not None:
            if since is None:
                raise ArgumentsRequired(self.id + ' fetchDeposits requires since while until is set')
            request['endTime'] = until
            params = self.omit(params, ['until'])
        response = await self.publicPostInfo(self.extend(request, params))
        #
        # [
        #     {
        #         "time":1724762307531,
        #         "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        #         "delta":{
        #             "type":"accountClassTransfer",
        #             "usdc":"50.0",
        #             "toPerp":false
        #         }
        #     }
        # ]
        #
        records = self.extract_type_from_delta(response)
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'vaultAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        deposits = []
        if vaultAddress is not None:
            for i in range(0, len(records)):
                record = records[i]
                if record['type'] == 'vaultDeposit':
                    delta = self.safe_dict(record, 'delta')
                    if delta['vault'] == '0x' + vaultAddress:
                        deposits.append(record)
        else:
            deposits = self.filter_by_array(records, 'type', ['deposit'], False)
        return self.parse_transactions(deposits, None, since, limit)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :param str 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 dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch withdrawals for
        :param str [params.subAccountAddress]: sub account user address
        :param str [params.vaultAddress]: vault address
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        userAddress = None
        userAddress, params = self.handle_public_address('fetchDepositsWithdrawals', params)
        request: dict = {
            'type': 'userNonFundingLedgerUpdates',
            'user': userAddress,
        }
        if since is not None:
            request['startTime'] = since
        until = self.safe_integer(params, 'until')
        if until is not None:
            request['endTime'] = until
            params = self.omit(params, ['until'])
        response = await self.publicPostInfo(self.extend(request, params))
        #
        # [
        #     {
        #         "time":1724762307531,
        #         "hash":"0x620a234a7e0eb7930575040f59482a01050058b0802163b4767bfd9033e77781",
        #         "delta":{
        #             "type":"accountClassTransfer",
        #             "usdc":"50.0",
        #             "toPerp":false
        #         }
        #     }
        # ]
        #
        records = self.extract_type_from_delta(response)
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params(params, 'fetchDepositsWithdrawals', 'vaultAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        withdrawals = []
        if vaultAddress is not None:
            for i in range(0, len(records)):
                record = records[i]
                if record['type'] == 'vaultWithdraw':
                    delta = self.safe_dict(record, 'delta')
                    if delta['vault'] == '0x' + vaultAddress:
                        withdrawals.append(record)
        else:
            withdrawals = self.filter_by_array(records, 'type', ['withdraw'], False)
        return self.parse_transactions(withdrawals, None, since, limit)

    async def fetch_open_interests(self, symbols: Strings = None, params={}):
        """
        Retrieves the open interest for a list of symbols
        :param str[] [symbols]: Unified CCXT market symbol
        :param dict [params]: exchange specific parameters
        :returns dict} an open interest structure{@link https://docs.ccxt.com/#/?id=open-interest-structure:
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        swapMarkets = await self.fetch_swap_markets()
        return self.parse_open_interests(swapMarkets, symbols)

    async def fetch_open_interest(self, symbol: str, params={}):
        """
        retrieves the open interest of a contract trading pair
        :param str symbol: unified CCXT market symbol
        :param dict [params]: exchange specific parameters
        :returns dict: an `open interest structure <https://docs.ccxt.com/#/?id=open-interest-structure>`
        """
        symbol = self.symbol(symbol)
        await self.load_markets()
        ois = await self.fetch_open_interests([symbol], params)
        return ois[symbol]

    def parse_open_interest(self, interest, market: Market = None):
        #
        #  {
        #      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: ['27.5915', '27.6319'],
        #      dayBaseVlm: '10790652.83',
        #      baseId: 159
        #  }
        #
        interest = self.safe_dict(interest, 'info', {})
        coin = self.safe_string(interest, 'name')
        marketId = None
        if coin is not None:
            marketId = self.coin_to_market_id(coin)
        return self.safe_open_interest({
            'symbol': self.safe_symbol(marketId),
            'openInterestAmount': self.safe_number(interest, 'openInterest'),
            'openInterestValue': None,
            'timestamp': None,
            'datetime': None,
            'info': interest,
        }, market)

    async def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch the history of funding payments paid and received on self account
        :param str [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 dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subAccountAddress]: sub account user address
        :returns dict: a `funding history structure <https://docs.ccxt.com/#/?id=funding-history-structure>`
        """
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        userAddress = None
        userAddress, params = self.handle_public_address('fetchFundingHistory', params)
        request: dict = {
            'user': userAddress,
            'type': 'userFunding',
        }
        if since is not None:
            request['startTime'] = since
        until = self.safe_integer(params, 'until')
        params = self.omit(params, 'until')
        if until is not None:
            request['endTime'] = until
        response = await self.publicPostInfo(self.extend(request, params))
        #
        # [
        #     {
        #         "time": 1734026400057,
        #         "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        #         "delta": {
        #             "type": "funding",
        #             "coin": "SOL",
        #             "usdc": "75.635093",
        #             "szi": "-7375.9",
        #             "fundingRate": "0.00004381",
        #             "nSamples": null
        #         }
        #     }
        # ]
        #
        return self.parse_incomes(response, market, since, limit)

    def parse_income(self, income, market: Market = None):
        #
        # {
        #     "time": 1734026400057,
        #     "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        #     "delta": {
        #         "type": "funding",
        #         "coin": "SOL",
        #         "usdc": "75.635093",
        #         "szi": "-7375.9",
        #         "fundingRate": "0.00004381",
        #         "nSamples": null
        #     }
        # }
        #
        id = self.safe_string(income, 'hash')
        timestamp = self.safe_integer(income, 'time')
        delta = self.safe_dict(income, 'delta')
        baseId = self.safe_string(delta, 'coin')
        marketSymbol = baseId + '/USDC:USDC'
        market = self.safe_market(marketSymbol)
        symbol = market['symbol']
        amount = self.safe_string(delta, 'usdc')
        code = self.safe_currency_code('USDC')
        rate = self.safe_number(delta, 'fundingRate')
        return {
            'info': income,
            'symbol': symbol,
            'code': code,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'id': id,
            'amount': self.parse_number(amount),
            'rate': rate,
        }

    async def reserve_request_weight(self, weight: Num, params={}) -> dict:
        """
        Instead of trading to increase the address based rate limits, self 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 dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a response object
        """
        nonce = self.milliseconds()
        request: dict = {
            'nonce': nonce,
        }
        action: dict = {
            'type': 'reserveRequestWeight',
            'weight': weight,
        }
        signature = self.sign_l1_action(action, nonce)
        request['action'] = action
        request['signature'] = signature
        response = await self.privatePostExchange(self.extend(request, params))
        return response

    def extract_type_from_delta(self, data=[]):
        records = []
        for i in range(0, len(data)):
            record = data[i]
            record['type'] = record['delta']['type']
            records.append(record)
        return records

    def format_vault_address(self, address: Str = None):
        if address is None:
            return None
        if address.startswith('0x'):
            return address.replace('0x', '')
        return address

    def handle_public_address(self, methodName: str, params: dict):
        userAux = None
        userAux, params = self.handle_option_and_params_2(params, methodName, 'user', 'subAccountAddress')
        user = userAux
        user, params = self.handle_option_and_params(params, methodName, 'address', userAux)
        if (user is not None) and (user != ''):
            return [user, params]
        if (self.walletAddress is not None) and (self.walletAddress != ''):
            return [self.walletAddress, params]
        raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a user parameter inside \'params\' or the wallet address set')

    def coin_to_market_id(self, coin: Str):
        if coin.find('/') > -1 or coin.find('@') > -1:
            return coin  # spot
        return self.safe_currency_code(coin) + '/USDC:USDC'

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if not response:
            return None  # fallback to default error handler
        # {"status":"err","response":"User or API Wallet 0xb8a6f8b26223de27c31938d56e470a5b832703a5 does not exist."}
        #
        #     {
        #         status: 'ok',
        #         response: {type: 'order', data: {statuses: [{error: 'Insufficient margin to place order. asset=4'}]}}
        #     }
        # {"status":"ok","response":{"type":"order","data":{"statuses":[{"error":"Insufficient margin to place order. asset=84"}]}}}
        #
        # {"status":"unknownOid"}
        #
        status = self.safe_string(response, 'status', '')
        error = self.safe_string(response, 'error')
        message = None
        if status == 'err':
            message = self.safe_string(response, 'response')
        elif status == 'unknownOid':
            raise OrderNotFound(self.id + ' ' + body)  # {"status":"unknownOid"}
        elif error is not None:
            message = error
        else:
            responsePayload = self.safe_dict(response, 'response', {})
            data = self.safe_dict(responsePayload, 'data', {})
            statuses = self.safe_list(data, 'statuses', [])
            for i in range(0, len(statuses)):
                message = self.safe_string(statuses[i], 'error')
                if message is not None:
                    break
        feedback = self.id + ' ' + body
        nonEmptyMessage = ((message is not None) and (message != ''))
        if nonEmptyMessage:
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
        if nonEmptyMessage:
            raise ExchangeError(feedback)  # unknown message
        return None

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.implode_hostname(self.urls['api'][api]) + '/' + path
        if method == 'POST':
            headers = {
                'Content-Type': 'application/json',
            }
            body = self.json(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
        if ('byType' in config) and ('type' in params):
            type = params['type']
            byType = config['byType']
            if type in byType:
                return byType[type]
        return self.safe_value(config, 'cost', 1)

    def parse_create_edit_order_args(self, id: Str, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        market = self.market(symbol)
        vaultAddress = None
        vaultAddress, params = self.handle_option_and_params_2(params, 'createOrder', 'vaultAddress', 'subAccountAddress')
        vaultAddress = self.format_vault_address(vaultAddress)
        symbol = market['symbol']
        order = {
            'symbol': symbol,
            'type': type,
            'side': side,
            'amount': amount,
            'price': price,
            'params': params,
        }
        globalParams = {}
        if vaultAddress is not None:
            globalParams['vaultAddress'] = vaultAddress
        if id is not None:
            order['id'] = id
        return [order, globalParams]
