# -*- 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.base.exchange import Exchange
from ccxt.abstract.pacifica import ImplicitAPI
import math
from ccxt.base.types import Any, Balances, Currency, Int, LedgerEntry, Leverage, MarginMode, Market, Num, Order, OrderBook, OrderRequest, OrderSide, OrderType, Position, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade, TradingFeeInterface, Transaction, TransferEntry
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import PermissionDenied
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.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class pacifica(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(pacifica, self).describe(), {
            'id': 'pacifica',
            'name': 'Pacifica',
            'countries': [],
            'version': 'v1',
            'isSandboxModeEnabled': False,  # is testnet api
            'rateLimit': 50,  # 125 requests per minute without api-key(300 with api-key) ~ 2 req/sec = 1 req/500 ms.
            'certified': False,
            'pro': True,
            'dex': True,
            'has': {
                'CORS': None,
                'spot': False,
                'margin': False,
                'swap': True,
                'future': True,
                'option': False,
                'addMargin': False,
                'borrowCrossMargin': False,
                'borrowIsolatedMargin': False,
                'cancelAllOrders': True,
                'cancelAllOrdersAfter': False,
                'cancelOrder': True,
                'cancelOrders': True,
                'cancelOrdersForSymbols': None,
                'closeAllPositions': False,
                'closePosition': False,
                'createMarketBuyOrderWithCost': False,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createOrders': True,
                'createOrderWithTakeProfitAndStopLoss': True,
                'createReduceOnlyOrder': True,
                'createStopOrder': True,
                'editOrder': True,
                'editOrders': False,
                'fetchAccounts': True,
                'fetchBalance': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCanceledAndClosedOrders': True,
                'fetchCanceledOrders': True,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': False,
                'fetchDepositAddress': False,
                'fetchDepositAddresses': False,
                'fetchDeposits': False,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': False,
                'fetchFundingHistory': True,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': True,
                'fetchFundingRates': True,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLedger': True,
                'fetchLeverage': True,
                'fetchLeverageTiers': False,
                'fetchLiquidations': False,
                'fetchMarginMode': True,
                '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,
                'fetchStatus': None,
                'fetchTicker': 'emulated',
                'fetchTickers': True,
                'fetchTime': None,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': False,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': False,
                'reduceMargin': False,
                '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',
            },
            'hostname': 'pacifica.fi',
            'urls': {
                'logo': 'https://github.com/user-attachments/assets/f795515a-828e-4a04-8fca-bf19fcf17ea4',
                'api': {
                    'public': 'https://api.{hostname}',
                    'private': 'https://api.{hostname}',
                },
                'test': {
                    'public': 'https://test-api.{hostname}',
                    'private': 'https://test-api.{hostname}',
                },
                'www': 'https://www.pacifica.fi',
                'doc': 'https://docs.pacifica.fi/api-documentation/api/rest-api',
                'fees': 'https://docs.pacifica.fi/trading-on-pacifica/trading-fees',
                'referral': 'https://app.pacifica.fi?referral=ccxt',
            },
            'api': {
                'public': {
                    'get': {
                        # ~12 weight depends on the limit 3 max for api-key, but min without api-key
                        'info': 1,
                        'info/prices': 1,
                        'kline': 12,
                        'kline/mark': 12,
                        'book': 1,
                        'trades': 1,  # Recent
                        'funding_rate/history': 1,
                        'account': 1,
                        'account/settings': 1,
                        'positions': 1,
                        'trades/history': 12,
                        'funding/history': 1,
                        'portfolio': 1,
                        'account/balance/history': 12,
                        'orders': 1,
                        'orders/history': 12,
                        'orders/history_by_id': 1,
                        'account/builder_codes/approvals': 1,
                    },
                },
                'private': {
                    'post': {
                        'account/leverage': 1,
                        'account/margin': 1,
                        'account/withdraw': 1,
                        'account/subaccount/create': 1,
                        'account/subaccount/list': 1,
                        'account/subaccount/transfer': 1,
                        'orders/create': 1,
                        'orders/create_market': 1,
                        'orders/stop/create': 1,
                        'positions/tpsl': 1,
                        'orders/cancel': 0.5,
                        'orders/cancel_all': 0.5,
                        'orders/stop/cancel': 0.5,
                        'orders/edit': 1,
                        'orders/batch': 1,
                        'account/builder_codes/approve': 1,
                        'account/builder_codes/revoke': 1,
                        'agent/bind': 1,
                        'account/api_keys/create': 1,
                        'account/api_keys/revoke': 1,
                        'account/api_keys': 1,
                    },
                },
            },
            'fees': {
                'swap': {
                    'taker': self.parse_number('0.0004'),
                    'maker': self.parse_number('0.00015'),
                },
            },
            #
            # Reminder:
            # If you're using an agent wallet, its private key must also be in the privateKey field in requiredCredentials.
            # However, walletAddress must ALWAYS be equal to the main address. For the agent wallet address, there's a field in options: agentAddress
            #
            'requiredCredentials': {
                'apiKey': False,
                'secret': False,
                'walletAddress': False,  # agentAddress, apiKey only in options.
                'privateKey': True,  # base58 solana private key
            },
            'exceptions': {
                'exact': {
                    '400': BadRequest,
                    '403': PermissionDenied,
                    '404': BadRequest,
                    '409': ExchangeError,
                    '422': ExchangeError,
                    '429': RateLimitExceeded,
                    '500': ExchangeError,
                    '503': ExchangeNotAvailable,
                    '504': RequestTimeout,
                },
                'broad': {
                    'UNKNOWN': ExchangeError,
                    'ACCOUNT_NOT_FOUND': ExchangeError,
                    'BOOK_NOT_FOUND': ExchangeError,
                    'INVALID_TICK_LEVEL': InvalidOrder,
                    'INSUFFICIENT_BALANCE': InsufficientFunds,
                    'ORDER_NOT_FOUND': OrderNotFound,
                    'OVER_WITHDRAWAL': InsufficientFunds,
                    'INVALID_LEVERAGE': ExchangeError,
                    'CANNOT_UPDATE_MARGIN': ExchangeError,
                    'POSITION_NOT_FOUND': ExchangeError,
                    'POSITION_TPSL_LIMIT_EXCEEDED': InvalidOrder,
                },
            },
            'precisionMode': TICK_SIZE,
            'commonCurrencies': {},
            'options': {
                'agentAddress': None,
                'apiKey': None,
                'builderCode': 'CCXT',  # case sensitive
                'feeRate': '0.01',  # default rate for builder fee approval 0.01%
                'builderFee': True,
                'batchOrdersMax': 10,
                'defaultType': 'swap',
                'defaultSlippage': '0.5',
                'expiryWindow': 5000,
                'maxCostHugeWithApiKey': 3,
                'marketHelperProps': [],
                'defaultMarginMode': 'cross',
                'builderSupportOperations': {
                    'create_market_order': True,
                    'create_limit_order': True,
                    'create_stop_order': True,
                    'set_position_tpsl': True,
                },
            },
            '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': 10,
                    },
                    'editOrder': {
                        'side': False,
                        'type': False,
                    },
                    'fetchMyTrades': {
                        'marginMode': False,
                        'limit': 100,
                        'daysBack': None,
                        'untilDays': None,
                        'symbolRequired': False,
                    },
                    'fetchOrder': {
                        'marginMode': False,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': 100,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOrders': {
                        'marginMode': False,
                        'limit': 100,
                        'daysBack': None,
                        'untilDays': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchClosedOrders': {
                        'marginMode': False,
                        'limit': 100,
                        'daysBack': None,
                        'daysBackCanceled': None,
                        'untilDays': None,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOHLCV': {
                        'limit': 3950,
                    },
                    'fetchLedger': {
                        'code': False,
                    },
                },
                'forPerps': {
                    'extends': 'default',
                    'createOrder': {
                        'stopLossPrice': True,
                        'takeProfitPrice': True,
                        'attachedStopLossTakeProfit': None,
                    },
                },
                'spot': None,
                'swap': {
                    'linear': {
                        'extends': 'forPerps',
                    },
                    'inverse': {
                        'extends': 'forPerps',
                    },
                },
                'future': {
                    'linear': {
                        'extends': 'forPerps',
                    },
                    'inverse': {
                        'extends': 'forPerps',
                    },
                },
            },
        })

    def initialize_client(self):
        try:
            self.handle_builder_fee_approval()
        except Exception as e:
            return False
        return True

    def handle_builder_fee_approval(self):
        if self.isSandboxModeEnabled:  # At self stage, building codes are mostly only on the mainnet.
            return False
        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, 'builderCode', 'CCXT')  # case sensitive
            maxFeeRate = self.safe_string(self.options, 'feeRate', '0.01')
            self.approve_builder_code(builder, maxFeeRate)
            self.options['approvedBuilderFee'] = True
        except Exception as e:
            self.options['builderFee'] = False  # disable builder fee if an error occurs
        return True

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for pacifica
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        if self.check_required_credentials(False):
            self.initialize_client()
            self.load_account_settings()
        swapMarkets = self.fetch_swap_markets(params)
        return swapMarkets

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-market-info

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.publicGetInfo(params)  # meta
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "symbol": "ETH",
        #       "tick_size": "0.1",
        #       "min_tick": "0",
        #       "max_tick": "1000000",
        #       "lot_size": "0.0001",
        #       "max_leverage": 50,
        #       "isolated_only": False,
        #       "min_order_size": "10",
        #       "max_order_size": "5000000",
        #       "funding_rate": "0.0000125",
        #       "next_funding_rate": "0.0000125",
        #       "created_at": 1748881333944
        #     },
        #     {
        #       "symbol": "BTC",
        #       "tick_size": "1",
        #       "min_tick": "0",
        #       "max_tick": "1000000",
        #       "lot_size": "0.00001",
        #       "max_leverage": 50,
        #       "isolated_only": False,
        #       "min_order_size": "10",
        #       "max_order_size": "5000000",
        #       "funding_rate": "0.0000125",
        #       "next_funding_rate": "0.0000125",
        #       "created_at": 1748881333944
        #     },
        #     ....
        #   ],
        #   "error": null,
        #   "code": null
        # }
        meta = self.safe_list(response, 'data', [])
        results = []
        for i in range(0, len(meta)):
            results.append(meta[i])
        return self.parse_markets(results)

    def parse_market(self, market: dict) -> Market:
        #     {
        #       "symbol": "ETH",
        #       "tick_size": "0.1",
        #       "min_tick": "0",
        #       "max_tick": "1000000",
        #       "lot_size": "0.0001",
        #       "max_leverage": 50,
        #       "isolated_only": False,
        #       "min_order_size": "10",
        #       "max_order_size": "5000000",
        #       "funding_rate": "0.0000125",
        #       "next_funding_rate": "0.0000125",
        #       "created_at": 1748881333944
        #     },
        #     {
        #       "symbol": "BTC",
        #       "tick_size": "1",
        #       "min_tick": "0",
        #       "max_tick": "1000000",
        #       "lot_size": "0.00001",
        #       "max_leverage": 50,
        #       "isolated_only": False,
        #       "min_order_size": "10",
        #       "max_order_size": "5000000",
        #       "funding_rate": "0.0000125",
        #       "next_funding_rate": "0.0000125",
        #       "created_at": 1748881333944
        #     },
        quoteId = 'usdc'
        settleId = 'usdc'
        id = self.safe_string(market, 'symbol')
        baseId = id.lower()
        baseName = id.upper()
        base = self.safe_currency_code(baseName)
        quote = self.safe_currency_code(quoteId)
        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, 'lot_size')
        pricePrecisionStr = self.safe_string(market, 'tick_size')
        active = True  # there is no non-active markets comes from endpoint market info
        return self.safe_market_structure({
            'id': id,
            '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(amountPrecisionStr),
                'price': self.parse_number(pricePrecisionStr),
            },
            'limits': {
                'leverage': {
                    'min': 1,
                    'max': self.safe_integer(market, 'max_leverage'),
                },
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': self.safe_string(market, 'min_tick'),
                    'max': self.safe_string(market, 'max_tick'),
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'created': None,
            'marginModes': {'cross': True, 'isolated': True},
            'info': market,
        })

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-info

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.account]: will default to walletAddress if not provided
        :returns dict: a `balance structure <https://docs.ccxt.com/?id=balance-structure>`
        """
        userAccount = None
        userAccount, params = self.handle_origin_and_single_address('fetchBalance', params)
        request = {
            'account': userAccount,
        }
        response = self.publicGetAccount(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": {
        #     "balance": "2000.000000",
        #     "fee_level": 0,
        #     "maker_fee": "0.00015",
        #     "taker_fee": "0.0004",
        #     "account_equity": "2150.250000",
        #     "available_to_spend": "1800.750000",
        #     "available_to_withdraw": "1500.850000",
        #     "pending_balance": "0.000000",
        #     "total_margin_used": "349.500000",
        #     "cross_mmr": "420.690000",
        #     "positions_count": 2,
        #     "orders_count": 3,
        #     "stop_orders_count": 1,
        #     "updated_at": 1716200000000,
        #     "use_ltp_for_stop_orders": False
        #   },
        #   "error": null,
        #   "code": null
        # }
        data = self.safe_dict(response, 'data', {})
        result = {
            'info': data,
        }
        result['free'] = {}
        result['used'] = {}
        result['total'] = {}
        totalBalance = self.safe_number(data, 'account_equity')
        usedMargin = self.safe_number(data, 'total_margin_used')
        freeBalance = self.safe_number(data, 'available_to_spend')
        result['total']['USDC'] = totalBalance
        result['used']['USDC'] = usedMargin
        result['free']['USDC'] = freeBalance
        timestamp = self.safe_integer(data, 'updated_at')
        result['timestamp'] = timestamp
        result['datetime'] = self.iso8601(timestamp)
        return self.safe_balance(result)

    def fetch_leverage(self, symbol: str, params={}) -> Leverage:
        """
        fetch the set leverage for a market
        :param str symbol:  unified symbol of the market
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.account]: will default to walletAddress if not provided
        :returns dict: a `leverage structure <https://docs.ccxt.com/?id=leverage-structure>`
        """
        self.load_account_settings()
        self.load_markets()
        market = self.market(symbol)
        userAccount = None
        userAccount, params = self.handle_origin_and_single_address('fetchLeverage', params)
        cacheAddress = self.walletAddress
        settings = None
        if userAccount == cacheAddress:
            settings = self.handle_option('fetchLeverage', 'settings', None)
        else:
            request: dict = {
                'account': userAccount,
            }
            settings = self.fetch_account_settings(self.extend(request, params))
        setting = self.safe_dict(settings, symbol, None)
        if setting is None:
            # NOTE: Upon account creation, all markets have margin settings default to cross margin and leverage default to max.
            # When querying self endpoint, all markets with default margin and leverage settings on self account will return blank.
            return self.parse_leverage_from_market(market)
        else:
            return self.parse_leverage_from_setting(symbol, setting)

    def parse_leverage_from_setting(self, symbol: Str, setting: dict) -> Leverage:
        # {
        #   "WLFI/USDC:USDC": {
        #       "symbol": "WLFI",
        #       "isolated": False,
        #       "leverage": 5,
        #       "created_at": 1758085929703,
        #       "updated_at": 1758086074002
        #    },
        # }
        isIsolated = self.safe_bool(setting, 'isolated', False)
        leverage = self.safe_integer(setting, 'leverage')
        marginMode = 'isolated' if isIsolated else 'cross'
        return {
            'info': setting,
            'symbol': symbol,
            'marginMode': marginMode,
            'longLeverage': leverage,
            'shortLeverage': leverage,
        }

    def parse_leverage_from_market(self, market: Market) -> Leverage:
        marketLimits = self.safe_dict(market, 'limits', {})
        leverageLimits = self.safe_dict(marketLimits, 'leverage', {})
        return {
            'info': market,
            'symbol': self.safe_string(market, 'symbol'),
            'marginMode': self.handle_option('fetchLeverage', 'defaultMarginMode', 'cross'),
            'longLeverage': self.safe_integer(leverageLimits, 'max'),
            'shortLeverage': self.safe_integer(leverageLimits, 'max'),
        }

    def fetch_account_settings(self, params={}) -> dict:
        """
        fetch account's market settings. Settings are cached for walletAddress. To refresh the cache, call loadAccountSettings with refresh=true

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-settings

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.account]: will default to walletAddress if not provided
        :returns dict: Dict repacked from list by symbol key
        """
        userAccount = None
        userAccount, params = self.handle_origin_and_single_address('fetchAccountSettings', params)
        request: dict = {
            'account': userAccount,
        }
        response = self.publicGetAccountSettings(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "symbol": "WLFI",
        #       "isolated": False,
        #       "leverage": 5,
        #       "created_at": 1758085929703,
        #       "updated_at": 1758086074002
        #     }
        #   ],
        #   "error": null,
        #   "code": null
        # }
        return self.parse_account_settings(self.safe_list(response, 'data', []))

    def load_account_settings(self, refresh: bool = False, params={}):
        settings = self.handle_option('loadAccountSettings', 'settings', None)
        if (settings is None) or (refresh is True):
            self.options['settings'] = self.create_safe_dictionary()
            settings = self.fetch_account_settings(params)
            self.options['settings'] = settings

    def parse_account_settings(self, settings: List[Any]) -> dict:
        settingsLen = len(settings)
        if settingsLen == 0:
            return {}
        settingsBySymbol = {}
        for i in range(0, len(settings)):
            marketId = settings[i]['symbol']
            market = self.safe_market(marketId)
            symbol = market['symbol']
            settingsBySymbol[symbol] = settings[i]
        return settingsBySymbol

    def fetch_margin_mode(self, symbol: str, params={}) -> MarginMode:
        """
        fetches the margin mode of the trading pair
        :param str symbol: unified symbol of the market to fetch the margin mode for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.account]: will default to walletAddress if not provided
        :returns dict: a `margin mode structure <https://docs.ccxt.com/?id=margin-mode-structure>`
        """
        self.load_account_settings()
        userAccount = None
        userAccount, params = self.handle_origin_and_single_address('fetchMarginMode', params)
        cacheAddress = self.walletAddress
        settings = None
        if userAccount == cacheAddress:
            settings = self.handle_option('fetchMarginMode', 'settings', None)
        else:
            request: dict = {
                'account': userAccount,
            }
            settings = self.fetch_account_settings(self.extend(request, params))
        # {
        #   "WLFI/USDC:USDC": {
        #       "symbol": "WLFI",
        #       "isolated": False,
        #       "leverage": 5,
        #       "created_at": 1758085929703,
        #       "updated_at": 1758086074002
        #    },
        # }
        setting = self.safe_dict(settings, symbol, None)
        if setting is None:
            # NOTE: Upon account creation, all markets have margin settings default to cross margin and leverage default to max.
            # When querying self endpoint, all markets with default margin and leverage settings on self account will return blank.
            return {
                'symbol': symbol,
                'marginMode': self.handle_option('fetchMarginMode', 'defaultMarginMode', 'cross'),
            }
        else:
            return self.parse_margin_mode_from_setting(symbol, setting)

    def parse_margin_mode_from_setting(self, symbol: Str, setting: dict) -> MarginMode:
        # {
        #       "symbol": "WLFI",
        #       "isolated": False,
        #       "leverage": 5,
        #       "created_at": 1758085929703,
        #       "updated_at": 1758086074002
        #
        # }
        isIsolated = self.safe_bool(setting, 'isolated', False)
        marginMode = 'isolated' if isIsolated else 'cross'
        return {
            'symbol': symbol,
            'marginMode': marginMode,
            'info': setting,
        }

    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://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-orderbook

        :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
        :param int [params.aggLevel]: aggregation level for price grouping. Defaults to 1. Can be 1, 10, 100, 1000, 10000
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        aggLevel = None
        aggLevel, params = self.handle_option_and_params(params, 'fetchOrderBook', 'aggLevel', 1)
        request: dict = {
            'symbol': market['id'],
            'agg_level': aggLevel,
        }
        response = self.publicGetBook(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": {
        #     "s": "BTC",
        #     "l": [
        #       [
        #         {
        #           "p": "106504",
        #           "a": "0.26203",
        #           "n": 1
        #         },
        #         {
        #           "p": "106498",
        #           "a": "0.29281",
        #           "n": 1
        #         }
        #       ],
        #       [
        #         {
        #           "p": "106559",
        #           "a": "0.26802",
        #           "n": 1
        #         },
        #         {
        #           "p": "106564",
        #           "a": "0.3002",
        #           "n": 1
        #         },
        #       ]
        #     ],
        #     "t": 1751370536325
        #   },
        #   "error": null,
        #   "code": null
        # }
        data = self.safe_dict(response, 'data', {})
        levels = self.safe_list(data, 'l', [])
        result: dict = {
            'bids': self.safe_list(levels, 0, []),
            'asks': self.safe_list(levels, 1, []),
        }
        timestamp = self.safe_integer(data, 't')
        return self.parse_order_book(result, self.safe_symbol(None, market), timestamp, 'bids', 'asks', 'p', 'a')

    def fetch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
        """
        retrieves data on all swap markets for pacifica
        :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
        """
        response = self.publicGetInfoPrices(params)
        #
        #  {
        #     "success": True,
        #     "data": [
        #         {
        #         "funding": "0.00010529",
        #         "mark": "1.084819",
        #         "mid": "1.08615",
        #         "next_funding": "0.00011096",
        #         "open_interest": "3634796",
        #         "oracle": "1.084524",
        #         "symbol": "XPL",
        #         "timestamp": 1759222967974,
        #         "volume_24h": "20896698.0672",
        #         "yesterday_price": "1.3412"
        #         }
        #     ],
        #     "error": null,
        #     "code": null
        #   }
        #
        result = self.safe_list(response, 'data', [])
        return self.parse_funding_rates(result, symbols)

    def parse_funding_rate(self, info, market: Market = None) -> FundingRate:
        #
        #      {
        #         "funding": "0.00010529",
        #         "mark": "1.084819",
        #         "mid": "1.08615",
        #         "next_funding": "0.00011096",
        #         "open_interest": "3634796",
        #         "oracle": "1.084524",
        #         "symbol": "XPL",
        #         "timestamp": 1759222967974,
        #         "volume_24h": "20896698.0672",
        #         "yesterday_price": "1.3412"
        #       }
        #
        marketId = self.safe_string(info, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        funding = self.safe_number(info, 'funding')
        markPx = self.safe_number(info, 'mark')
        oraclePx = self.safe_number(info, 'oracle')
        nextFundingRate = self.safe_number(info, 'next_funding')
        timestamp = self.safe_integer(info, 'timestamp')
        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': timestamp,
            'datetime': self.iso8601(timestamp),
            'fundingRate': funding,
            'fundingTimestamp': fundingTimestamp,
            'fundingDatetime': self.iso8601(fundingTimestamp),
            'nextFundingRate': nextFundingRate,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
            'interval': '1h',
        }

    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://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-candle-data

        :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', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '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. 'limit' is priority
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        if since is None:
            raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a "since" argument')
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOHLCV() requires a "symbol" argument')
        defaultMaxLimit = 3950  # 4000 by docs, but in fact >~3960 returns error
        self.load_markets()
        market = self.market(symbol)
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False)
        if paginate:
            return self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, defaultMaxLimit)
        tf = self.safe_string(self.timeframes, timeframe, timeframe)
        request: dict = {
            'symbol': market['id'],
            'interval': tf,
            'start_time': since,
        }
        request, params = self.handle_until_option('end_time', request, params)
        nowMillis = self.milliseconds()
        until = self.safe_integer(request, 'end_time')
        if until is None:
            if limit is not None:
                until = since + (limit * (self.parse_timeframe(tf) * 1000)) - 1
            if until is None:
                until = since + (defaultMaxLimit * (self.parse_timeframe(tf) * 1000)) - 1
            if until > nowMillis:
                until = nowMillis
            request['end_time'] = until
        response = self.publicGetKline(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "t": 1748954160000,
        #       "T": 1748954220000,
        #       "s": "BTC",
        #       "i": "1m",
        #       "o": "105376",
        #       "c": "105376",
        #       "h": "105376",
        #       "l": "105376",
        #       "v": "0.00022",
        #       "n": 2
        #     }
        #   ],
        #   "error": null,
        #   "code": null
        # }
        #
        candles = self.safe_list(response, 'data', [])
        return self.parse_ohlcvs(candles, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     {
        #       "t": 1748954160000,
        #       "T": 1748954220000,
        #       "s": "BTC",
        #       "i": "1m",
        #       "o": "105376",
        #       "c": "105376",
        #       "h": "105376",
        #       "l": "105376",
        #       "v": "0.00022",
        #       "n": 2
        #     }
        #
        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'),
        ]

    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://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-recent-trades

        :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
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.publicGetTrades(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "event_type": "fulfill_taker",
        #       "price": "104721",
        #       "amount": "0.0001",
        #       "side": "close_long",
        #       "cause": "normal",
        #       "created_at": 1765006315306
        #     }
        #   ],
        #   "error": null,
        #   "code": null,
        #   "last_order_id": 1557404170
        # }
        #
        recentTrades = self.safe_list(response, 'data', [])
        return self.parse_trades(recentTrades, market, since, limit)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        fetch all trades made by the user

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-trade-history

        :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.account]: will default to walletAddress if not provided
        :param str [params.cursor]: pagination cursor from prev request(manual use)
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/?id=trade-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate', False)
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchMyTrades', params)
        defaultLimit = 100  # Default max limit
        if paginate:
            return self.fetch_paginated_call_cursor('fetchMyTrades', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
        request: dict = {}
        request, params = self.handle_until_option('end_time', request, params)
        request['account'] = userAddress
        if symbol is not None:
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['start_time'] = since
        response = self.publicGetTradesHistory(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "history_id": 19329801,
        #       "order_id": 315293920,
        #       "client_order_id": "acf...",
        #       "symbol": "LDO",
        #       "amount": "0.1",
        #       "price": "1.1904",
        #       "entry_price": "1.176247",
        #       "fee": "0",
        #       "pnl": "-0.001415",
        #       "event_type": "fulfill_maker",
        #       "side": "close_short",
        #       "created_at": 1759215599188,
        #       "cause": "normal"
        #     },
        #     ...
        #   ],
        #   "next_cursor": "11111Z5RK",  # not included to info!
        #   "has_more": True   # not included to info!
        # }
        #
        data = self.add_pagination_cursor_to_result(response)
        return self.parse_trades(data, market, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # user trades:
        #     {
        #       "history_id": 19329801,
        #       "order_id": 315293920,
        #       "client_order_id": "acf...",
        #       "symbol": "LDO",
        #       "amount": "0.1",
        #       "price": "1.1904",
        #       "entry_price": "1.176247",
        #       "fee": "0",
        #       "pnl": "-0.001415",
        #       "event_type": "fulfill_maker",
        #       "side": "close_short",
        #       "created_at": 1759215599188,
        #       "cause": "normal"
        #     },
        # recent public trades:
        #     {
        #       "event_type": "fulfill_taker",
        #       "price": "104721",
        #       "amount": "0.0001",
        #       "side": "close_long",
        #       "cause": "normal",
        #       "created_at": 1765006315306
        #     }
        #
        eventType = self.safe_string(trade, 'event_type')
        timestamp = self.safe_integer(trade, 'created_at')
        price = self.safe_string(trade, 'price')
        amount = self.safe_string(trade, 'amount')
        symbol = self.safe_symbol(None, market)
        id = self.safe_string(trade, 'history_id')
        side = self.safe_string(trade, 'side')
        if side == 'open_long':
            side = 'buy'
        elif side == 'close_long':
            side = 'sell'
        elif side == 'open_short':
            side = 'sell'
        elif side == 'close_short':
            side = 'buy'
        fee = self.safe_string(trade, 'fee')
        orderId = self.safe_string(trade, 'order_id')
        takerOrMaker = None
        if eventType is not None:
            takerOrMaker = 'maker' if (eventType == 'fulfill_maker') else 'taker'
        # public trades have no orderId
        if orderId is None:
            takerOrMaker = None
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': {
                'cost': fee,
                'currency': 'USDC',
                'rate': None,
            },
        }, market)

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-limit-order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-market-order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-stop-order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-position-tp-sl

        :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. Not used for set tpsl order!
        :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: The price a trigger order is triggered at
        :param float [params.stopLossPrice]: the price that a stop loss order is triggered at(optional provide stopLossCloid)
        :param float [params.takeProfitPrice]: the price that a take profit order is triggered at(optional provide takeProfitCloid)
        :param str [params.timeInForce]: "GTC", "IOC", or "PO" or "ALO" or "PO_TOB"(or "TOB" - PO by top of book)
        :param boolean [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
        :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
        :param int [params.expiryWindow]: time to live in milliseconds
        :returns dict: an `order structure <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        self.initialize_client()
        request, operationType = self.create_order_request(symbol, type, side, amount, price, params)
        params = self.omit(params, [
            'reduceOnly', 'clientOrderId', 'stopLimitPrice', 'timeInForce', 'triggerPrice', 'stopLossCloid',
            'stopLossPrice', 'stopLossLimitPrice', 'takeProfitCloid', 'takeProfitPrice', 'takeProfitLimitPrice', 'expiryWindow',
        ])
        response = None
        if operationType == 'create_market_order':
            response = self.privatePostOrdersCreateMarket(self.extend(request, params))
        elif operationType == 'create_stop_order':
            response = self.privatePostOrdersStopCreate(self.extend(request, params))
        elif operationType == 'set_position_tpsl':
            response = self.privatePostPositionsTpsl(self.extend(request, params))
        else:  # create_order
            response = self.privatePostOrdersCreate(self.extend(request, params))
        #
        # {
        #   'success': True,
        #   'data': {
        #    "order_id": 12345
        #    },
        # }
        #
        success = self.safe_bool(response, 'success', False)
        status = None
        if not success:
            status = 'rejected'
        else:
            status = 'open'
        order = self.safe_dict(response, 'data', {})
        orderId = self.safe_string(order, 'order_id')
        return self.safe_order({'id': orderId, 'status': status, 'info': response, 'symbol': symbol})

    def create_order_request(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
 @ignore
        create a trade order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-limit-order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-market-order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-stop-order
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/create-position-tp-sl
        :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 fullfilled, in units of the quote currency, ignored in market orders, but can be used of Trigger Order.
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: The price a trigger order is triggered at
        :param float [params.stopLossPrice]: the price that a stop loss order is triggered at(optional provide stopLossCloid)
        :param float [params.takeProfitPrice]: the price that a take profit order is triggered at(optional provide takeProfitCloid)
        :param str [params.timeInForce]: "GTC", "IOC", or "PO" or "ALO" or "PO_TOB"(or "TOB" - PO by top of book)
        :param boolean [params.reduceOnly]: Ensures that the executed order does not flip the opened position.
        :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
        :param int [params.expiryWindow]: time to live in milliseconds
        :returns dict: an [order structure]
        """
        market = self.market(symbol)
        sigPayload: dict = {
            'symbol': market['id'],
            'side': self.map_side(side),
        }
        operationType = None
        reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only', False)
        orderType = type.upper()
        triggerPrice = self.safe_string(params, 'triggerPrice')
        stopLossPrice = self.safe_string(params, 'stopLossPrice')
        takeProfitPrice = self.safe_string(params, 'takeProfitPrice')
        tifRaw = self.safe_string_upper(params, 'timeInForce')
        isMarket = orderType == 'MARKET'
        isTakeProfitOrder = (takeProfitPrice is not None)
        isStopLossOrder = (stopLossPrice is not None)
        isStopOrder = (triggerPrice is not None)
        timeInForce = self.map_time_in_force(tifRaw)
        if isMarket:
            operationType = 'create_market_order'
            sigPayload['reduce_only'] = reduceOnly
            defaultSlippage = self.handle_option('createOrder', 'defaultSlippage', '0.5')
            slippage = self.safe_string_2(params, 'slippage', 'slippage_percent', defaultSlippage)
            sigPayload['slippage_percent'] = slippage
        elif (isTakeProfitOrder or isStopLossOrder) and (price is None):  # the tpsl endpoint does not accept a 'price' parameter
            operationType = 'set_position_tpsl'
        elif isStopOrder:
            operationType = 'create_stop_order'
            sigPayload['reduce_only'] = reduceOnly
            stopClientOrderId = self.safe_string(params, 'clientOrderId')
            params = self.omit(params, ['clientOrderId'])
            stopPayload = {
                'amount': self.amount_to_precision(symbol, amount),
                'stop_price': self.price_to_precision(symbol, triggerPrice),
            }
            if stopClientOrderId is not None:
                stopPayload['client_order_id'] = stopClientOrderId
            if price is not None:
                stopPayload['limit_price'] = self.price_to_precision(symbol, price)
            sigPayload['stop_order'] = stopPayload
        else:
            operationType = 'create_order'
            sigPayload['reduce_only'] = reduceOnly
            if timeInForce is None:
                sigPayload['tif'] = 'GTC'
            else:
                sigPayload['tif'] = timeInForce
        if isTakeProfitOrder:
            tpPayload: dict = {
                'stop_price': self.price_to_precision(symbol, takeProfitPrice),
            }
            if price is not None:
                tpPayload['limit_price'] = self.price_to_precision(symbol, price)
            sigPayload['take_profit'] = tpPayload
        if isStopLossOrder:
            slPayload: dict = {
                'stop_price': self.price_to_precision(symbol, stopLossPrice),
            }
            if price is not None:
                slPayload['limit_price'] = self.price_to_precision(symbol, price)
            sigPayload['stop_loss'] = slPayload
        if price is not None and operationType == 'create_order':
            sigPayload['price'] = self.price_to_precision(symbol, price)
        if amount is not None and (operationType != 'create_stop_order' and operationType != 'set_position_tpsl'):
            sigPayload['amount'] = self.amount_to_precision(symbol, amount)
        clientOrderId = self.safe_string_n(params, ['clientOrderId'])
        if clientOrderId is not None:
            sigPayload['client_order_id'] = clientOrderId
        request = self.post_action_request(operationType, sigPayload, params)
        return [request, operationType]

    def batch_orders_request(self, actions: List[Any]):
        #
        # [
        #     {
        #         "type":"Create",
        #         "data":{
        #             "account":"42trU9A5...",
        #             "signature":"5UpRZ14Q...",
        #             "timestamp":1749190500355,
        #             "expiry_window":5000,
        #             "symbol":"BTC",
        #             "price":"100000",
        #             "reduce_only":false,
        #             "amount":"0.1",
        #             "side":"bid",
        #             "tif":"GTC",
        #             "client_order_id":"57a5efb1-bb96-49a5-8bfd-f25d5f22bc7e"
        #         }
        #     },
        #     {
        #         "type":"Cancel",
        #         "data":{
        #             "account":"42trU9A5...",
        #             "signature":"4NDFHyTG...",
        #             "timestamp":1749190500355,
        #             "expiry_window":5000,
        #             "symbol":"BTC",
        #             "order_id":42069
        #         }
        #     }
        # ]
        #
        #  Create(Only Limit or Market, never stop order or tpsl order)
        #  Cancel(Only common(limit) orders)
        #
        lenActions = len(actions)
        maxLen = self.handle_option('batchOrdersRequest', 'batchOrdersMax')
        if maxLen is not None:
            if lenActions > maxLen:
                raise ExchangeError(self.id + ' batchOrdersRequest() too many orders to create/cancel. Limit is ' + maxLen)
        return {
            'actions': actions,
        }

    def create_orders_request(self, orders: List[OrderRequest], params={}):
        actions = []
        timestamp = self.milliseconds()  # unified sequence
        for i in range(0, len(orders)):
            order = orders[i]
            symbol = self.safe_string(order, 'symbol')
            side = self.safe_string(order, 'side')
            price = self.safe_string(order, 'price')
            type = self.safe_string(order, 'type', 'limit')
            orderParams = self.safe_dict(order, 'params', {})
            orderParams['timestamp'] = timestamp
            amount = self.safe_string(order, 'amount')
            amountNumber = self.parse_number(amount)
            priceNumber = self.parse_number(price)
            if type != 'limit':
                raise NotSupported(self.id + ' createOrders() supports only type = "limit"! Your value type=' + type)
            requestList = self.create_order_request(symbol, type, side, amountNumber, priceNumber, orderParams)
            action = {
                'type': 'Create',
                'data': requestList[0],
            }
            actions.append(action)
        return self.batch_orders_request(actions)

    def create_orders(self, orders: List[OrderRequest], params={}):
        """
        create a list of trade orders. It is supports only limit orders and have a random jitter ~100-300ms!

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/batch-order

        :param Array orders: list of orders to create, each object should contain the parameters required by createOrder, namely symbol, type(optional or 'limit'), 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>`
        """
        self.load_markets()
        self.initialize_client()
        request = self.create_orders_request(orders)
        response = self.privatePostOrdersBatch(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": {
        #     "results": [
        #       {
        #         "success": True,
        #         "order_id": 470506,
        #         "error": null
        #       },
        #       {
        #         "success": True,
        #       }
        #     ]
        #   },
        #     "error": null,
        #     "code": null
        # }
        #
        data = self.safe_dict(response, 'data', {})
        results = self.safe_list(data, 'results', [])
        ordersToReturn = []
        for i in range(0, len(results)):
            order = results[i]
            error = self.safe_string(order, 'error', None)
            success = self.safe_bool(order, 'success', False)
            status = None
            if (error is not None) or (not success):
                status = 'rejected'
            else:
                status = 'open'
            orderId = self.safe_string(order, 'order_id')
            ordersToReturn.append(self.safe_order({'info': order, 'id': orderId, 'status': status}))
        return ordersToReturn

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/batch-order

        :param str[] ids: order ids. An ids list is always required(can be empty). Both ids and clientOrderIds can be passed simultaneously.
        :param str [symbol]: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param string|str[] [params.clientOrderIds]: client order ids,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
        :param int [params.expiryWindow]: time to live in milliseconds
        :returns dict: an list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        self.initialize_client()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrders() requires a "symbol" argument!')
        request = self.cancel_orders_request(ids, symbol, params)
        params = self.omit(params, ['expiryWindow', 'clientOrderIds'])
        response = self.privatePostOrdersBatch(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": {
        #     "results": [
        #       {
        #         "success": True,
        #         "order_id": 470506,
        #         "error": null
        #       },
        #       {
        #         "success": True,
        #       }
        #     ]
        #   },
        #     "error": null,
        #     "code": null
        # }
        #
        data = self.safe_dict(response, 'data', {})
        results = self.safe_list(data, 'results', [])
        ordersToReturn = []
        for i in range(0, len(results)):
            order = results[i]
            error = self.safe_string(order, 'error', None)
            success = self.safe_bool(order, 'success', False)
            status = None
            if (error is not None) or (not success):
                status = 'closed'
            else:
                status = 'canceled'
            ordersToReturn.append(self.safe_order({'info': order, 'status': status, 'symbol': symbol}))
        return ordersToReturn

    def cancel_orders_request(self, ids: List[Str], symbol: Str = None, params={}):
        actions = []
        for i in range(0, len(ids)):
            id = ids[i]
            request = self.cancel_order_request(id, symbol, params)
            action = {
                'type': 'Cancel',
                'data': request,
            }
            actions.append(action)
        clientOrderIds = self.safe_list(params, 'clientOrderIds', [])
        params = self.omit(params, 'clientOrderIds')
        for i in range(0, len(clientOrderIds)):
            cloid = clientOrderIds[i]
            cloidParams = {
                'clientOrderId': cloid,
            }
            request = self.cancel_order_request(cloid, symbol, self.extend(cloidParams, params))
            action = {
                'type': 'Cancel',
                'data': request,
            }
            actions.append(action)
        return self.batch_orders_request(actions)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders in a market

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-all-orders

        :param str symbol:(optional) unified market symbol of the market to cancel orders in.
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.excludeReduceOnly]: whether to exclude reduce-only orders
        :param int [params.expiryWindow]: time to live in milliseconds
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        self.initialize_client()
        request = self.cancel_all_orders_request(symbol, params)
        params = self.omit(params, ['excludeReduceOnly', 'expiryWindow'])
        response = self.privatePostOrdersCancelAll(self.extend(request, params))
        #
        # {
        #   success: True,
        #   data: {
        #     "cancelled_count": 5,
        #   },
        #   code: null,
        #   error: null
        # }
        #
        return [
            self.safe_order({
                'info': response,
            }),
        ]

    def cancel_all_orders_request(self, symbol: Str, params={}):
        operationType = 'cancel_all_orders'
        sigPayload: dict = {}
        excludeReduceOnly = self.safe_bool(params, 'excludeReduceOnly', False)
        sigPayload['exclude_reduce_only'] = excludeReduceOnly
        if symbol is not None:
            market = self.market(symbol)
            sigPayload['all_symbols'] = False
            sigPayload['symbol'] = market['id']
        else:
            sigPayload['all_symbols'] = True
        request = self.post_action_request(operationType, sigPayload, params)
        return request

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-stop-order#response
        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/cancel-order

        :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 boolean [params.stop]: necessary if self is to cancel a stop order.
        :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
        :param int [params.expiryWindow]: time to live in milliseconds
        :returns dict: An `order structure <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        self.initialize_client()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        request = self.cancel_order_request(id, symbol, params)
        isStopOrder = self.safe_bool_2(params, 'trigger', 'stop', False)
        params = self.omit(params, ['expiryWindow', 'trigger', 'stop', 'clientOrderId'])
        response = None
        if isStopOrder:
            response = self.privatePostOrdersStopCancel(self.extend(request, params))
        else:
            response = self.privatePostOrdersCancel(self.extend(request, params))
        #
        # response:
        # {
        #   "success": True,
        #   "data": null
        # }
        #
        success = self.safe_bool(response, 'success', False)
        status = 'canceled' if success else 'closed'
        return self.safe_order({'id': id, 'status': status, 'info': response, 'symbol': symbol})

    def cancel_order_request(self, id: Str, symbol: Str = None, params={}):
        market = self.market(symbol)
        isStopOrder = self.safe_bool_2(params, 'trigger', 'stop', False)
        operationType = None
        if isStopOrder:
            operationType = 'cancel_stop_order'
        else:
            operationType = 'cancel_order'
        clientOrderId = self.safe_string(params, 'clientOrderId')
        sigPayload: dict = {
            'symbol': market['id'],
        }
        if clientOrderId is not None:
            sigPayload['client_order_id'] = clientOrderId
        else:
            sigPayload['order_id'] = self.parse_to_int(id)
        request = self.post_action_request(operationType, sigPayload, params)
        return request

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/edit-order

        :param str id: edit order id
        :param str symbol: unified symbol of the market to edit an order in
        :param str type: 'market' or 'limit' WARN is not usable!
        :param str side: 'buy' or 'sell' WARN is not usable!
        :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
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.clientOrderId]: client order id,(optional uuid v4 e.g.: f47ac10b-58cc-4372-a567-0e02b2c3d479)
        :param int [params.expiryWindow]: time to live in milliseconds
        :returns dict: an `order structure <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        self.initialize_client()
        market = self.market(symbol)
        request = self.edit_order_request(id, symbol, type, side, amount, price, market, params)
        params = self.omit(params, ['expiryWindow', 'clientOrderId'])
        response = self.privatePostOrdersEdit(self.extend(request, params))
        #
        # {
        #     'data': {
        #         "order_id": 123498765
        #     }
        # }
        #
        data = self.safe_dict(response, 'data', {})
        orderId = self.safe_string(data, 'order_id')
        return self.safe_order({'id': orderId, 'info': response, 'symbol': symbol})

    def edit_order_request(self, id: str, symbol: str, type: str, side: str, amount: Num, price: Num, market: Market, params={}):
        if amount is None:
            raise ArgumentsRequired(self.id + ' editOrder() requires an amount!')
        if price is None:
            raise ArgumentsRequired(self.id + ' editOrder() requires a price')
        operationType = 'edit_order'
        clientOrderId = self.safe_string(params, 'clientOrderId')
        priceNormalized = self.price_to_precision(symbol, price)
        amountNormalized = self.amount_to_precision(symbol, amount)
        sigPayload: dict = {
            'symbol': market['id'],
            'price': priceNormalized,
            'amount': amountNormalized,
        }
        if (clientOrderId is None) and (id is None):
            raise ArgumentsRequired('self.id' + 'editOrder() requires either "id" or "clientOrderId"')
        if clientOrderId is not None:
            sigPayload['client_order_id'] = clientOrderId
        else:
            sigPayload['order_id'] = self.parse_to_int(id)
        request = self.post_action_request(operationType, sigPayload, params)
        return request

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-historical-funding

        :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 str [params.cursor]: pagination cursor from prev request(manual use)
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/?id=funding-rate-history-structure>`
        """
        self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchFundingRateHistory() requires a symbol argument')
        market = self.market(symbol)
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchFundingRateHistory', 'paginate', False)
        defaultLimit = 100  # Default max limit
        if paginate:
            return self.fetch_paginated_call_cursor('fetchFundingRateHistory', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
        request: dict = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetFundingRateHistory(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "oracle_price": "117170.410304",
        #       "bid_impact_price": "117126",
        #       "ask_impact_price": "117142",
        #       "funding_rate": "0.0000125",
        #       "next_funding_rate": "0.0000125",
        #       "created_at": 1753806934249
        #     },
        #     ...
        #   ],
        #   "next_cursor": "11114Lz77",
        #   "has_more": True
        # }
        #
        data = self.add_pagination_cursor_to_result(response)
        result = []
        for i in range(0, len(data)):
            entry = data[i]
            timestamp = self.safe_integer(entry, 'created_at')
            result.append({
                'info': entry,
                'symbol': market['symbol'],
                'fundingRate': self.safe_number(entry, 'funding_rate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(result, 'timestamp')
        return self.filter_by_since_limit(sorted, since, limit, 'timestamp')

    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://docs.pacifica.fi/api-documentation/api/rest-api/markets/get-prices

        :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
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/?id=ticker-structure>`
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        response = self.publicGetInfoPrices(params)
        #
        #  {
        #   "success": True,
        #   "data": [
        #     {
        #       "funding": "0.00010529",
        #       "mark": "1.084819",
        #       "mid": "1.08615",
        #       "next_funding": "0.00011096",
        #       "open_interest": "3634796",
        #       "oracle": "1.084524",
        #       "symbol": "XPL",
        #       "timestamp": 1759222967974,
        #       "volume_24h": "20896698.0672",
        #       "yesterday_price": "1.3412"
        #     }
        #   ],
        #   "error": null,
        #   "code": null
        # }
        #
        data = self.safe_list(response, 'data', [])
        result: dict = {}
        for i in range(0, len(data)):
            info = data[i]
            ticker = self.parse_ticker(info)
            symbol = self.safe_string(ticker, 'symbol')
            result[symbol] = ticker
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #       "funding": "0.00010529",
        #       "mark": "1.084819",
        #       "mid": "1.08615",
        #       "next_funding": "0.00011096",
        #       "open_interest": "3634796",
        #       "oracle": "1.084524",
        #       "symbol": "XPL",
        #       "timestamp": 1759222967974,
        #       "volume_24h": "20896698.0672",
        #       "yesterday_price": "1.3412"
        #     }
        #
        marketId = self.safe_string(ticker, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        timestamp = self.safe_integer(ticker, 'timestamp')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'previousClose': self.safe_number(ticker, 'yesterday_price'),
            'close': self.safe_number(ticker, 'mid'),
            'bid': None,
            'ask': None,
            'quoteVolume': self.safe_number(ticker, 'volume_24h'),
            'info': ticker,
        }, market)

    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.account]: will default to walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        orders = 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)

    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.account]: will default to walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        orders = 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)

    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.account]: will default to walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        orders = 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)

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-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.account]: will default to walletAddress if not provided
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchOpenOrders', params)
        request: dict = {
            'account': userAddress,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = self.publicGetOrders(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "order_id": 315979358,
        #       "client_order_id": "add9a4b5-c7f7-4124-b57f-86982d86d479",
        #       "symbol": "ASTER",
        #       "side": "ask",
        #       "price": "1.836",
        #       "initial_amount": "85.33",
        #       "filled_amount": "0",
        #       "cancelled_amount": "0",
        #       "stop_price": null,
        #       "order_type": "limit",
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "created_at": 1759224706737,
        #       "updated_at": 1759224706737
        #     }
        #   ],
        #   "error": null,
        #   "code": null,
        #   "last_order_id": 1557370337
        # }
        #
        data = self.safe_list(response, 'data', [])
        return self.parse_orders(data, market, since, limit)

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-order-history

        :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.account]: will default to walletAddress if not provided
        :param str [params.cursor]: pagination cursor from prev request(manual use)
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate', False)
        defaultLimit = 100  # max default 100
        if paginate:
            return self.fetch_paginated_call_cursor('fetchOrders', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchOrders', params)
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {
            'account': userAddress,
        }
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetOrdersHistory(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "order_id": 315992721,
        #       "client_order_id": "ade",
        #       "symbol": "XPL",
        #       "side": "ask",
        #       "initial_price": "1.0865",
        #       "average_filled_price": "0",
        #       "amount": "984",
        #       "filled_amount": "0",
        #       "order_status": "open",
        #       "order_type": "limit",
        #       "stop_price": null,
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "reason": null,
        #       "created_at": 1759224893638,
        #       "updated_at": 1759224893638
        #     },
        #     ...
        #   ],
        #   "next_cursor": "1111Hyd74",
        #   "has_more": True
        # }
        #
        data = self.add_pagination_cursor_to_result(response)
        orders = self.parse_orders(data, market, since, limit)
        return orders

    def add_pagination_cursor_to_result(self, response):
        data = self.safe_list(response, 'data', [])
        paginationCursor = self.safe_string(response, 'next_cursor')
        hasMore = self.safe_bool(response, 'has_more', False)
        dataLength = len(data)
        if hasMore:
            if (paginationCursor is not None) and (dataLength > 0):
                first = data[0]
                first['next_cursor'] = paginationCursor
                first['has_more'] = hasMore
                data[0] = first
        return data

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/orders/get-order-history-by-id

        :param str id: order id
        :param str symbol:(optional) unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt.com/?id=order-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {
            'order_id': id,
        }
        response = self.publicGetOrdersHistoryById(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "history_id": 641452639,
        #       "order_id": 315992721,
        #       "client_order_id": "ade1aa6...",
        #       "symbol": "XPL",
        #       "side": "ask",
        #       "price": "1.0865",
        #       "initial_amount": "984",
        #       "filled_amount": "0",
        #       "cancelled_amount": "984",
        #       "event_type": "cancel",
        #       "order_type": "limit",
        #       "order_status": "cancelled",
        #       "stop_price": null,
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "created_at": 1759224895038
        #     },
        #     {
        #       "history_id": 641452513,
        #       "order_id": 315992721,
        #       "client_order_id": "ade1aa6...",
        #       "symbol": "XPL",
        #       "side": "ask",
        #       "price": "1.0865",
        #       "initial_amount": "984",
        #       "filled_amount": "0",
        #       "cancelled_amount": "0",
        #       "event_type": "make",
        #       "order_type": "limit",
        #       "order_status": "open",
        #       "stop_price": null,
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "created_at": 1759224893638
        #     }
        #   ],
        #   "error": null,
        #   "code": null
        # }
        #
        data = self.safe_list(response, 'data', [])
        # return last state
        sorted = self.sort_by(data, 'created_at')
        lastIdx = len(sorted)
        lastInfo = {}
        if lastIdx > 0:
            lastInfo = sorted[0]
        return self.parse_order(lastInfo, market)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'open': 'open',
            'partially_filled': 'open',
            'filled': 'closed',
            'cancelled': 'canceled',
            'rejected': 'failed',
        }
        return self.safe_string(statuses, status, status)

    def map_time_in_force(self, tifRaw: Str):
        tifMap: dict = {
            'GTC': 'GTC',
            'IOC': 'IOC',
            'PO': 'ALO',
            'POST_ONLY': 'ALO',
            'PO_TOB': 'TOB',
            'TOB': 'TOB',
            'ALO': 'ALO',
        }
        tif = None
        if tifRaw is not None:
            tif = tifRaw.upper()
        return self.safe_string(tifMap, tif, None)

    def map_side(self, sideRaw: str):
        sideMap: dict = {
            'sell': 'ask',
            'buy': 'bid',
        }
        return self.safe_string(sideMap, sideRaw, sideRaw)

    def parse_order_type(self, status: str):
        statuses: dict = {
            'stop_limit': 'limit',
            'stop_market': 'market',
            'take_profit_limit': 'limit',
            'stop_loss_limit': 'limit',
            'take_profit_market': 'market',
            'stop_loss_market': 'market',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # fetchOpenOrders
        #   [
        #     {
        #       "order_id": 315979358,
        #       "client_order_id": "add9a4b5-c7f7-4124-b57f-86982d86d479",
        #       "symbol": "ASTER",
        #       "side": "ask",
        #       "price": "1.836",
        #       "initial_amount": "85.33",
        #       "filled_amount": "0",
        #       "cancelled_amount": "0",
        #       "stop_price": null,
        #       "order_type": "limit",
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "created_at": 1759224706737,
        #       "updated_at": 1759224706737
        #     }
        #   ],
        #
        # fetchOrders
        #  [
        #     {
        #       "order_id": 315992721,
        #       "client_order_id": "ade",
        #       "symbol": "XPL",
        #       "side": "ask",
        #       "initial_price": "1.0865",
        #       "average_filled_price": "0",
        #       "amount": "984",
        #       "filled_amount": "0",
        #       "order_status": "open",
        #       "order_type": "limit",
        #       "stop_price": null,
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "reason": null,
        #       "created_at": 1759224893638,
        #       "updated_at": 1759224893638
        #     },
        #  ]
        #
        # fetchOrder
        #     {
        #       "history_id": 641452639,
        #       "order_id": 315992721,
        #       "client_order_id": "ade1aa6...",
        #       "symbol": "XPL",
        #       "side": "ask",
        #       "price": "1.0865",
        #       "initial_amount": "984",
        #       "filled_amount": "0",
        #       "cancelled_amount": "984",
        #       "event_type": "cancel",
        #       "order_type": "limit",
        #       "order_status": "cancelled",
        #       "stop_price": null,
        #       "stop_parent_order_id": null,
        #       "reduce_only": False,
        #       "created_at": 1759224895038
        #     }
        #
        # websocket watchOrders
        #     {
        #       "i": 1559665358,
        #       "I": null,
        #       "u": "BrZp5bidJ3WUvceSq7X78bhjTfZXeezzGvGEV4hAYKTa",
        #       "s": "BTC",
        #       "d": "bid",
        #       "p": "89501",
        #       "ip": "89501",
        #       "lp": "89501",
        #       "a": "0.00012",
        #       "f": "0.00012",
        #       "oe": "fulfill_limit",
        #       "os": "filled",
        #       "ot": "limit",
        #       "sp": null,
        #       "si": null,
        #       "r": False,
        #       "ct": 1765017049008,
        #       "ut": 1765017219639,
        #       "li": 1559696133
        #     }
        #
        marketId = self.safe_string_2(order, 'symbol', 's')
        symbol = None
        if symbol is not None:
            market = self.safe_market(marketId, market)
            symbol = market['symbol']
        timestamp = self.safe_integer_2(order, 'created_at', 'ct')
        status = self.safe_string_2(order, 'order_status', 'os', 'open')  # open if method is fetchOpenOrders
        side = self.safe_string(order, 'side', 'd')
        if side is not None:
            side = 'buy' if (side == 'bid') else 'sell'
        totalAmount = self.safe_string_2(order, 'initial_amount', 'a')
        filledAmount = self.safe_string_2(order, 'filled_amount', 'f')
        remaining = Precise.string_sub(totalAmount, filledAmount)
        return self.safe_order({
            'info': order,
            'id': self.safe_string_2(order, 'order_id', 'i'),
            'clientOrderId': self.safe_string_2(order, 'client_order_id', 'I'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'lastUpdateTimestamp': self.safe_integer_2(order, 'updated_at', 'ut'),
            'symbol': symbol,
            'type': self.parse_order_type(self.safe_string_lower_2(order, 'order_type', 'ot')),
            'timeInForce': None,
            'postOnly': None,
            'reduceOnly': self.safe_bool_2(order, 'reduce_only', 'r'),
            'side': side,
            'price': self.safe_string_2(order, 'price', 'lp'),
            'triggerPrice': self.safe_number_2(order, 'stop_price', 'sp'),
            'amount': totalAmount,
            'cost': None,
            'average': self.safe_string_2(order, 'average_filled_price', 'p'),
            'filled': filledAmount,
            'remaining': remaining,
            'status': self.parse_order_status(status),
            'fee': None,
            'trades': None,
        }, market)

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-positions

        :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.account]: will default to walletAddress if not provided
        :returns dict: a `position structure <https://docs.ccxt.com/?id=position-structure>`
        """
        positions = self.fetch_positions([symbol], params)
        return self.safe_dict(positions, 0, {})

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-positions

        :param str[] [symbols]: list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.account]: will default to walletAddress if not provided
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/?id=position-structure>`
        """
        self.load_markets()
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchPositions', params)
        symbols = self.market_symbols(symbols)
        request: dict = {
            'account': userAddress,
        }
        response = self.publicGetPositions(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "symbol": "AAVE",
        #       "side": "ask",
        #       "amount": "223.72",
        #       "entry_price": "279.283134",
        #       "margin": "0",  # only shown for isolated margin
        #       "funding": "13.159593",
        #       "isolated": False,
        #       "created_at": 1754928414996,
        #       "updated_at": 1759223365538
        #     }
        #   ],
        #   "error": null,
        #   "code": null,
        #   "last_order_id": 1557431179
        # }
        data = self.safe_list(response, 'data', [])
        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):
        #
        #     {
        #       "symbol": "AAVE",
        #       "side": "ask",
        #       "amount": "223.72",
        #       "entry_price": "279.283134",
        #       "margin": "0",  # only shown for isolated margin
        #       "funding": "13.159593",
        #       "isolated": False,
        #       "created_at": 1754928414996,
        #       "updated_at": 1759223365538
        #     }
        #
        marketId = self.safe_string(position, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        margin = self.safe_string(position, 'margin')
        marginMode = 'isolated' if (margin is not None and margin != '0') else 'cross'
        isIsolated = (marginMode == 'isolated')
        side = self.safe_string(position, 'side')
        if side is not None:
            side = 'long' if (side == 'bid') else 'short'
        createdAt = self.safe_integer(position, 'created_at')
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': symbol,
            'timestamp': createdAt,
            'datetime': self.iso8601(createdAt),
            'isolated': isIsolated,
            'hedged': None,
            'side': side,
            'contracts': self.safe_number(position, 'amount'),
            'contractSize': None,
            'entryPrice': self.safe_number(position, 'entry_price'),
            'markPrice': None,
            'notional': None,
            'leverage': None,
            'collateral': margin,
            'initialMargin': None,
            'maintenanceMargin': None,
            'initialMarginPercentage': None,
            'maintenanceMarginPercentage': None,
            'unrealizedPnl': None,
            'liquidationPrice': None,
            'marginMode': marginMode,
            'percentage': None,
        })

    def set_margin_mode(self, marginMode: str, symbol: Str = None, params={}):
        """
        set margin mode(symbol)

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/update-margin-mode

        :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 int [params.expiryWindow]: time to live in milliseconds
        :returns dict: response from the exchange
        """
        operationType = 'update_margin_mode'
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        isIsolated = (marginMode == 'isolated')
        sigPayload: dict = {
            'symbol': market['id'],
            'is_isolated': isIsolated,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        params = self.omit(params, ['expiryWindow'])
        response = self.privatePostAccountMargin(request)
        # {
        #     "success": True
        # }
        return response

    def set_leverage(self, leverage: int, symbol: Str = None, params={}):
        """
        set the level of leverage for a market

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/update-leverage

        :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 int [params.expiryWindow]: time to live in milliseconds
        :returns dict: response from the exchange
        """
        operationType = 'update_leverage'
        if symbol is None:
            raise ArgumentsRequired(self.id + ' setMarginMode() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        sigPayload: dict = {
            'symbol': market['id'],
            'leverage': leverage,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        params = self.omit(params, ['expiryWindow'])
        response = self.privatePostAccountLeverage(request)
        # {
        #     "success": True
        # }
        return response

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/request-withdrawal

        :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 int [params.expiryWindow]: time to live in milliseconds
        :returns dict: a `transaction structure <https://docs.ccxt.com/?id=transaction-structure>`
        """
        operationType = 'withdraw'
        self.load_markets()
        self.check_address(address)
        sigPayload: dict = {
            'amount': str(amount),
        }
        request = self.post_action_request(operationType, sigPayload, params)
        params = self.omit(params, ['expiryWindow'])
        response = self.privatePostAccountWithdraw(self.extend(request, params))
        return {'info': response}

    def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        fetch the trading fees for a market

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-info

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.account]: will default to walletAddress if not provided
        :returns dict: a `fee structure <https://docs.ccxt.com/?id=fee-structure>`
        """
        self.load_markets()
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchTradingFee', params)
        market = self.market(symbol)
        request: dict = {
            'account': userAddress,
        }
        response = self.publicGetAccount(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": {
        #     "balance": "2000.000000",
        #     "fee_level": 0,
        #     "maker_fee": "0.00015",
        #     "taker_fee": "0.0004",
        #     "account_equity": "2150.250000",
        #     "available_to_spend": "1800.750000",
        #     "available_to_withdraw": "1500.850000",
        #     "pending_balance": "0.000000",
        #     "total_margin_used": "349.500000",
        #     "cross_mmr": "420.690000",
        #     "positions_count": 2,
        #     "orders_count": 3,
        #     "stop_orders_count": 1,
        #     "updated_at": 1716200000000,
        #     "use_ltp_for_stop_orders": False
        #   },
        #   "error": null,
        #   "code": null
        # }
        data: dict = self.safe_dict(response, 'data', {})
        return self.parse_trading_fee(data, market)

    def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
        #
        #   {
        #     "balance": "2000.000000",
        #     "fee_level": 0,
        #     "maker_fee": "0.00015",
        #     "taker_fee": "0.0004",
        #     "account_equity": "2150.250000",
        #     "available_to_spend": "1800.750000",
        #     "available_to_withdraw": "1500.850000",
        #     "pending_balance": "0.000000",
        #     "total_margin_used": "349.500000",
        #     "cross_mmr": "420.690000",
        #     "positions_count": 2,
        #     "orders_count": 3,
        #     "stop_orders_count": 1,
        #     "updated_at": 1716200000000,
        #     "use_ltp_for_stop_orders": False
        #   }
        #
        #
        symbol = self.safe_symbol(None, market)
        return {
            'info': fee,
            'symbol': symbol,
            'maker': self.safe_number(fee, 'maker_fee'),
            'taker': self.safe_number(fee, 'taker_fee'),
            'percentage': None,
            'tierBased': None,
        }

    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:
        """
        self.load_markets()
        symbols = self.market_symbols(symbols)
        swapMarkets = self.fetch_swap_markets()
        return self.parse_open_interests(swapMarkets, symbols)

    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)
        self.load_markets()
        ois = self.fetch_open_interests([symbol], params)
        return ois[symbol]

    def parse_open_interest(self, interest, market: Market = None):
        #
        #     {
        #       "funding": "0.00010529",
        #       "mark": "1.084819",
        #       "mid": "1.08615",
        #       "next_funding": "0.00011096",
        #       "open_interest": "3634796",
        #       "oracle": "1.084524",
        #       "symbol": "XPL",
        #       "timestamp": 1759222967974,
        #       "volume_24h": "20896698.0672",
        #       "yesterday_price": "1.3412"
        #     }
        #
        marketId = self.safe_string(interest, 'symbol')
        symbol = None
        if marketId is not None:
            market = self.safe_market(marketId, market)
            symbol = market['symbol']
        interestValue = None
        markPrice = self.safe_string(interest, 'mark')
        openInterest = self.safe_string(interest, 'open_interest')
        if (openInterest is not None) and (markPrice is not None):
            interestValue = Precise.string_mul(openInterest, markPrice)
        timestamp = self.safe_integer(interest, 'timestamp')
        return self.safe_open_interest({
            'symbol': self.safe_symbol(symbol),
            'openInterestAmount': self.parse_number(openInterest),
            'openInterestValue': self.parse_number(interestValue),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'info': interest,
        }, market)

    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

        https://docs.pacifica.fi/api-documentation/api/rest-api/account/get-account-balance-history

        :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 str [params.account]: will default to walletAddress if not provided
        :param str [params.cursor]: pagination cursor from prev request(manual use)
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict: a `ledger structure <https://docs.ccxt.com/?id=ledger-entry-structure>`
        """
        self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchLedger', 'paginate', False)
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchLedger', params)
        defaultLimit = 100  # Default max limit
        if paginate:
            return self.fetch_paginated_call_cursor('fetchLedger', code, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
        request: dict = {
            'account': userAddress,
        }
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetAccountBalanceHistory(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "amount": "100.000000",
        #       "balance": "1200.000000",
        #       "pending_balance": "0.000000",
        #       "event_type": "deposit",
        #       "created_at": 1716200000000
        #     }
        #     ...
        #   ],
        #   "next_cursor": "11114Lz77",
        #   "has_more": True
        # }
        data = self.add_pagination_cursor_to_result(response)
        return self.parse_ledger(data, None, since, limit)

    def parse_ledger_entry(self, item: dict, currency: Currency = None) -> LedgerEntry:
        #
        #     {
        #       "amount": "100.000000",
        #       "balance": "1200.000000",
        #       "pending_balance": "0.000000",
        #       "event_type": "deposit",
        #       "created_at": 1716200000000
        #     }
        #
        timestamp = self.safe_integer(item, 'created_at')
        type = self.safe_string(item, 'event_type')
        amount = self.safe_string(item, 'amount')
        balance = self.safe_string(item, 'balance')
        return self.safe_ledger_entry({
            'info': item,
            'id': None,
            'direction': None,
            'account': None,
            'referenceAccount': None,
            'referenceId': None,
            'type': self.parse_ledger_entry_type(type),
            'currency': None,
            'amount': self.parse_number(amount),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'before': None,
            'after': self.parse_number(balance),
            'status': None,
            'fee': None,
        }, currency)

    def parse_ledger_entry_type(self, type):
        ledgerType: dict = {
            'subaccount_transfer': 'transfer',
            'deposit': 'transaction',
            'deposit_release': 'transaction',
            'withdraw': 'transaction',
            'trade': 'trade',
            'market_liquidation': 'trade',
            'backstop_liquidation': 'trade',
            'adl_liquidation': 'trade',
            'funding': 'funding',
            'fee': 'fee',
            'rebate': 'rebate',
            'cashback': 'cashback',
            'referral': 'referral',
            'airdrop': 'airdrop',
            'payout': 'payout',
        }
        return self.safe_string(ledgerType, type, type)

    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.account]: will default to walletAddress if not provided
        :param str [params.cursor]: pagination cursor from prev request
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict: a `funding history structure <https://docs.ccxt.com/?id=funding-history-structure>`
        """
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate', False)
        userAddress = None
        userAddress, params = self.handle_origin_and_single_address('fetchFundingHistory', params)
        request: dict = {
            'account': userAddress,
        }
        if limit is not None:
            request['limit'] = limit
        defaultLimit = 100
        if paginate:
            return self.fetch_paginated_call_cursor('fetchFundingHistory', symbol, since, limit, params, 'next_cursor', 'cursor', None, defaultLimit)
        response = self.publicGetFundingHistory(self.extend(request, params))
        # {
        #   "success": True,
        #   "data": [
        #     {
        #       "history_id": 2287920,
        #       "symbol": "PUMP",
        #       "side": "ask",
        #       "amount": "39033804",
        #       "payout": "2.617479",
        #       "rate": "0.0000125",
        #       "created_at": 1759222804122
        #     },
        #     ...
        #   ],
        #   "next_cursor": "11114Lz77",
        #   "has_more": True
        # }
        data = self.add_pagination_cursor_to_result(response)
        return self.parse_incomes(data, market, since, limit)

    def parse_income(self, income, market: Market = None):
        #
        #     {
        #       "history_id": 2287920,
        #       "symbol": "PUMP",
        #       "side": "ask",
        #       "amount": "39033804",
        #       "payout": "2.617479",
        #       "rate": "0.0000125",
        #       "created_at": 1759222804122
        #     }
        #
        id = self.safe_string(income, 'history_id')
        timestamp = self.safe_integer(income, 'created_at')
        marketId = self.safe_string(income, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        amount = self.safe_string(income, 'amount')
        code = self.safe_currency_code('USDC')
        rate = self.safe_number(income, 'rate')
        return {
            'info': income,
            'symbol': symbol,
            'code': code,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'id': id,
            'amount': self.parse_number(amount),
            'rate': rate,
        }

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

        https://docs.pacifica.fi/api-documentation/api/rest-api/subaccounts/subaccount-fund-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 int [params.expiryWindow]: time to live in milliseconds
        :returns dict: a `transfer structure <https://docs.ccxt.com/?id=transfer-structure>`
        """
        operationType = 'transfer_funds'
        sigPayload = {
            'to_account': toAccount,
            'amount': amount,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        params = self.omit(params, ['expiryWindow'])
        response = self.privatePostAccountSubaccountTransfer(self.extend(request, params))
        #
        # {
        #   "success": True,
        #   "data": {
        #     "success": True,
        #     "error": null
        #   },
        #   "error": null,
        #   "code": null
        # }
        #
        data = self.safe_dict(response, 'data', {})
        return self.parse_transfer(data)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        # {
        #   "success": True,
        #   "data": {
        #     "success": True,
        #     "error": null
        #   },
        #   "error": null,
        #   "code": null
        # }
        #
        return {
            'info': transfer,
            'id': None,
            'timestamp': None,
            'datetime': None,
            'currency': None,
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': 'ok',
        }

    def create_sub_account(self, name: str, params={}):
        """
        creates a sub-account under the main account
        :param str name: unused argument
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.expiryWindow]: time to live in milliseconds
        :param str [params.subAccountAddress]: - The public key(address) of the sub-account to use for creation
        :param str [params.subAccountPrivateKey]: - The private key of the sub-account to use for creation
        :returns dict: a response object
        """
        finalHeaders = {}
        agentAddress = None
        agentAddress, params = self.handle_option('createSubAccount', 'agentAddress', None)
        originAddress = None
        originAddress, params = self.handle_origin_and_single_address('createSubAccount', params)
        if originAddress is None:
            raise ArgumentsRequired(self.id + ' createSubAccount() requires "originAddress" in params or "walletAddress" in requiredCredentials')
        if agentAddress is not None:
            finalHeaders['agent_wallet'] = agentAddress
        subAccountAddress = None
        subAccountAddress, params = self.handle_option_and_params(params, 'createSubAccount', 'subAccountAddress')
        subAccountPrivateKey = None
        subAccountPrivateKey, params = self.handle_option_and_params(params, 'createSubAccount', 'subAccountPrivateKey')
        if subAccountAddress is None:
            raise ArgumentsRequired(self.id + ' createSubAccount() requires a "subAccountAddress"!')
        if subAccountPrivateKey is None:
            raise ArgumentsRequired(self.id + ' createSubAccount() requires a "subAccountPrivateKey"!')
        timestamp = self.milliseconds()
        expiryWindow = None
        expiryWindow, params = self.handle_option_and_params_2(params, 'createSubAccount', 'expiryWindow', 'expiry_window', 5000)
        subaccountSignatureHeader = {
            'timestamp': timestamp,
            'expiry_window': expiryWindow,
            'type': 'subaccount_initiate',
        }
        subSigPayload = {
            'account': originAddress,
        }
        subaccountSignature = self.sign_message(subaccountSignatureHeader, subSigPayload, subAccountPrivateKey)
        mainSignatureHeader = {
            'timestamp': timestamp,
            'expiry_window': expiryWindow,
            'type': 'subaccount_confirm',
        }
        mainSigPayload = {
            'signature': subaccountSignature,
        }
        main_signature = self.sign_message(mainSignatureHeader, mainSigPayload, self.privateKey)
        finalHeaders['main_account'] = originAddress
        finalHeaders['subaccount'] = subAccountAddress
        finalHeaders['sub_signature'] = subaccountSignature
        finalHeaders['main_signature'] = main_signature
        finalHeaders['timestamp'] = timestamp
        finalHeaders['expiry_window'] = expiryWindow
        request = finalHeaders
        response = self.privatePostAccountSubaccountCreate(request)
        #
        # {
        #   "success": True,
        #   "data": null,
        #   "error": null,
        #   "code": null,
        # }
        #
        return response

    def bind_agent_wallet(self, agentAddress: str, params={}):
        operationType = 'bind_agent_wallet'
        sigPayload = {
            'agent_wallet': agentAddress,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        return self.privatePostAgentBind(self.extend(request, params))

    def create_api_key(self, params={}):
        operationType = 'create_api_key'
        sigPayload = {}
        request = self.post_action_request(operationType, sigPayload, params)
        return self.privatePostAccountApiKeysCreate(self.extend(request, params))

    def revoke_api_key(self, apiKey: str, params={}):
        operationType = 'revoke_api_key'
        sigPayload = {
            'api_key': apiKey,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        return self.privatePostAccountApiKeysRevoke(self.extend(request, params))

    def fetch_api_keys(self, params={}):
        operationType = 'list_api_keys'
        sigPayload = {}
        request = self.post_action_request(operationType, sigPayload, params)
        return self.privatePostAccountApiKeys(self.extend(request, params))

    def approve_builder_code(self, builderCode: str, maxFeeRate: str, params={}):
        operationType = 'approve_builder_code'
        sigPayload = {
            'builder_code': builderCode,
            'max_fee_rate': maxFeeRate,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        return self.privatePostAccountBuilderCodesApprove(self.extend(request, params))

    def fetch_builder_approvals(self, address: str):
        request = {
            'account': address,
        }
        return self.publicGetAccountBuilderCodesApprovals(self.extend(request))

    def revoke_builder_code(self, builderCode: str, params={}):
        operationType = 'revoke_builder_code'
        sigPayload = {
            'builder_code': builderCode,
        }
        request = self.post_action_request(operationType, sigPayload, params)
        return self.privatePostAccountBuilderCodesRevoke(self.extend(request, params))

    def handle_origin_and_single_address(self, methodName: str, params: dict):
        address = None
        address, params = self.handle_param_string_2(params, 'account', 'address', None)  # self is for get endpoints that accept account or address
        if address is not None:
            return [address, params]
        address1 = self.walletAddress
        if address1 is not None:
            return [address1, params]
        raise ArgumentsRequired(self.id + ' ' + methodName + '() requires address either as "exchange.walletAddress = ..." or or "address" in params')

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # fallback to default error handler
        #
        #     {"success":false,"data":null,"error":"Beta access required. Signer must redeem a valid beta code.","code":403}
        #     {"success":false,"data":null,"error":"Agent not authorized for account","code":400}
        #     {"success":false,"data":null,"error":"Internal server error","code":500}
        #
        inCode = self.safe_integer(response, 'code')  # actually if all ok -> code = None or code = 200
        message = self.safe_string(response, 'error')
        error = None
        if inCode is None or inCode == 200:
            error = False
        else:
            error = True
        nonEmptyMessage = ((message is not None) and (message != ''))
        if error or nonEmptyMessage:
            feedback = self.id + ' ' + body
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)  # Try deeper catch first
            self.throw_exactly_matched_exception(self.exceptions['exact'], inCode, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
        return None

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        isTestnet = self.isSandboxModeEnabled
        urlKey = 'test' if (isTestnet) else 'api'
        host = self.implode_hostname(self.urls[urlKey][api])
        url = host + '/api/' + self.version + '/' + self.implode_params(path, params)
        params = self.omit(params, self.extract_params(path))
        paramsLen = params
        headers = {
            'Content-Type': 'application/json',
        }
        if method == 'GET' and paramsLen:
            url += '?' + self.urlencode(params)
            headers['Accept'] = '*/*'
        if method == 'POST':
            body = self.json(params)
        if self.handle_option('sign', 'apiKey', None) is not None:
            headers['PF-API-KEY'] = self.options['apiKey']
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
        cost = self.safe_string(config, 'cost', '1')
        costNumber = self.parse_number(cost)
        # 1 is normal POST/GET, 0.5 is cancels, 3-12 is heavy GET
        if costNumber > 1:
            if self.handle_option(method, 'apiKey', None) is not None:
                costWithKey = self.handle_option(
                    method,
                    'maxCostHugeWithApiKey',
                    3
                )
                return costWithKey
        return costNumber

    def sort_json_keys(self, value: Any) -> Any:
        if isinstance(value, dict):
            result = {}
            keys = list(value.keys())
            sortedKeys = self.sort(keys)
            for i in range(0, len(sortedKeys)):
                key = sortedKeys[i]
                result[key] = self.sort_json_keys(value[key])
            return result
        elif isinstance(value, list):
            result = []
            for i in range(0, len(value)):
                result.append(self.sort_json_keys(value[i]))
            return result
        else:
            return value

    def prepare_message(self, header: dict, payload: dict) -> str:
        if header['type'] is None or header['timestamp'] is None or header['expiry_window'] is None:
            raise ArgumentsRequired(self.id + ' prepareMessage() requires type, timestamp, expiry_window in header')
        data = self.extend(header, {'data': payload})
        sorted = self.sort_json_keys(data)
        return self.json(sorted)

    def sign_message(self, header: dict, payload: dict, privateKey: str) -> str:
        message = self.prepare_message(header, payload)
        messageBytes = self.encode(message)
        secretBytes = self.base58_to_binary(privateKey)
        seed = self.array_slice(secretBytes, 0, 32)
        signatureBase64 = self.eddsa(messageBytes, seed, 'ed25519')
        signatureBinary = self.base64_to_binary(signatureBase64)
        signatureBase58 = self.binary_to_base58(signatureBinary)
        return signatureBase58

    def post_action_request(self, operationType: Str, sigPayload: dict, params: dict) -> dict:
        self.check_required_credentials()  # check credentials every post action
        if operationType == 'None':
            raise ArgumentsRequired(self.id + ' action: ' + operationType + ' postActionRequest() requires "operationType"')
        if not self.isSandboxModeEnabled:  # At self stage, building codes are mostly only on the mainnet.
            useBuilder = self.handle_option('postActionRequest', 'builderFee', True)
            builderCode = None
            if useBuilder:
                builderCode = self.handle_option('postActionRequest', 'builderCode')
            if builderCode is not None:
                isOperationSupportBuilder = self.safe_bool(self.options['builderSupportOperations'], operationType, False)
                if isOperationSupportBuilder:
                    sigPayload['builder_code'] = builderCode
        expiryWindow = None
        expiryWindow, params = self.handle_option_and_params_2(params, 'postActionRequest', 'expiryWindow', 'expiry_window', 5000)
        timestamp = self.safe_integer(params, 'timestamp', self.milliseconds())
        signatureHeader = {
            'timestamp': timestamp,
            'expiry_window': expiryWindow,
            'type': operationType,
        }
        signature = self.sign_message(signatureHeader, sigPayload, self.privateKey)
        finalHeaders = {}
        agentAddress = None
        agentAddress, params = self.handle_option_and_params(params, 'postActionRequest', 'agentAddress')
        originAddress = None
        originAddress, params = self.handle_origin_and_single_address('postActionRequest', params)
        if originAddress is None:
            raise ArgumentsRequired(self.id + ' action: ' + operationType + ' postActionRequest() requires "originAddress" in params or "walletAddress" in requiredCredentials')
        finalHeaders['account'] = originAddress
        if agentAddress is not None:
            finalHeaders['agent_wallet'] = agentAddress
        finalHeaders['signature'] = signature
        finalHeaders['timestamp'] = self.safe_integer(signatureHeader, 'timestamp')
        finalHeaders['expiry_window'] = self.safe_integer(signatureHeader, 'expiry_window')
        request = self.extend(finalHeaders, sigPayload)
        return request
