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

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt.async_support.base.exchange import Exchange
from ccxt.abstract.derive import ImplicitAPI
import asyncio
from ccxt.base.types import Any, Balances, Bool, Currencies, Currency, Int, Market, MarketType, Num, Order, OrderSide, OrderType, Position, Str, Strings, Ticker, FundingRate, Trade, Transaction
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
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 RateLimitExceeded
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class derive(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        return self.deep_extend(super(derive, self).describe(), {
            'id': 'derive',
            'name': 'derive',
            'countries': [],
            'version': 'v1',
            'rateLimit': 50,
            'certified': False,
            'pro': True,
            'dex': True,
            'has': {
                'CORS': None,
                'spot': False,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'borrowCrossMargin': False,
                'borrowIsolatedMargin': False,
                'cancelAllOrders': True,
                'cancelAllOrdersAfter': False,
                'cancelOrder': True,
                'cancelOrders': False,
                'cancelOrdersForSymbols': False,
                'closeAllPositions': False,
                'closePosition': False,
                'createMarketBuyOrderWithCost': False,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createOrders': False,
                'createReduceOnlyOrder': False,
                'createStopOrder': False,
                'createTriggerOrder': False,
                'editOrder': True,
                'fetchAccounts': False,
                'fetchBalance': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCanceledAndClosedOrders': False,
                'fetchCanceledOrders': True,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': False,
                'fetchDepositAddresses': False,
                'fetchDeposits': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': False,
                'fetchFundingHistory': True,
                'fetchFundingRate': True,
                'fetchFundingRateHistory': True,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLedger': True,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchLiquidations': False,
                'fetchMarginMode': None,
                'fetchMarketLeverageTiers': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyLiquidations': False,
                'fetchMyTrades': True,
                'fetchOHLCV': False,
                'fetchOpenInterest': False,
                'fetchOpenInterestHistory': False,
                'fetchOpenInterests': False,
                'fetchOpenOrders': True,
                'fetchOrder': False,
                'fetchOrderBook': False,
                'fetchOrders': True,
                'fetchOrderTrades': True,
                'fetchPosition': False,
                'fetchPositionMode': False,
                'fetchPositions': True,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': False,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'repayCrossMargin': False,
                'repayIsolatedMargin': False,
                'sandbox': True,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': False,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '2h': '2h',
                '4h': '4h',
                '8h': '8h',
                '12h': '12h',
                '1d': '1d',
                '3d': '3d',
                '1w': '1w',
                '1M': '1M',
            },
            'hostname': 'derive.xyz',
            'urls': {
                'logo': 'https://github.com/user-attachments/assets/f835b95f-033a-43dd-b6bb-24e698fc498c',
                'api': {
                    'public': 'https://api.lyra.finance/public',
                    'private': 'https://api.lyra.finance/private',
                },
                'test': {
                    'public': 'https://api-demo.lyra.finance/public',
                    'private': 'https://api-demo.lyra.finance/private',
                },
                'www': 'https://www.derive.xyz/',
                'doc': 'https://docs.derive.xyz/docs/',
                'fees': 'https://docs.derive.xyz/reference/fees-1/',
                'referral': 'https://www.derive.xyz/invite/3VB0B',
            },
            'api': {
                'public': {
                    'get': [
                        'get_all_currencies',
                    ],
                    'post': [
                        'build_register_session_key_tx',
                        'register_session_key',
                        'deregister_session_key',
                        'login',
                        'statistics',
                        'get_all_currencies',
                        'get_currency',
                        'get_instrument',
                        'get_all_instruments',
                        'get_instruments',
                        'get_ticker',
                        'get_latest_signed_feeds',
                        'get_option_settlement_prices',
                        'get_spot_feed_history',
                        'get_spot_feed_history_candles',
                        'get_funding_rate_history',
                        'get_trade_history',
                        'get_option_settlement_history',
                        'get_liquidation_history',
                        'get_interest_rate_history',
                        'get_transaction',
                        'get_margin',
                        'margin_watch',
                        'validate_invite_code',
                        'get_points',
                        'get_all_points',
                        'get_points_leaderboard',
                        'get_descendant_tree',
                        'get_tree_roots',
                        'get_swell_percent_points',
                        'get_vault_assets',
                        'get_etherfi_effective_balances',
                        'get_kelp_effective_balances',
                        'get_bridge_balances',
                        'get_ethena_participants',
                        'get_vault_share',
                        'get_vault_statistics',
                        'get_vault_balances',
                        'estimate_integrator_points',
                        'create_subaccount_debug',
                        'deposit_debug',
                        'withdraw_debug',
                        'send_quote_debug',
                        'execute_quote_debug',
                        'get_invite_code',
                        'register_invite',
                        'get_time',
                        'get_live_incidents',
                        'get_maker_programs',
                        'get_maker_program_scores',
                    ],
                },
                'private': {
                    'post': [
                        'get_account',
                        'create_subaccount',
                        'get_subaccount',
                        'get_subaccounts',
                        'get_all_portfolios',
                        'change_subaccount_label',
                        'get_notificationsv',
                        'update_notifications',
                        'deposit',
                        'withdraw',
                        'transfer_erc20',
                        'transfer_position',
                        'transfer_positions',
                        'order',
                        'replace',
                        'order_debug',
                        'get_order',
                        'get_orders',
                        'get_open_orders',
                        'cancel',
                        'cancel_by_label',
                        'cancel_by_nonce',
                        'cancel_by_instrument',
                        'cancel_all',
                        'cancel_trigger_order',
                        'get_order_history',
                        'get_trade_history',
                        'get_deposit_history',
                        'get_withdrawal_history',
                        'send_rfq',
                        'cancel_rfq',
                        'cancel_batch_rfqs',
                        'get_rfqs',
                        'poll_rfqs',
                        'send_quote',
                        'cancel_quote',
                        'cancel_batch_quotes',
                        'get_quotes',
                        'poll_quotes',
                        'execute_quote',
                        'rfq_get_best_quote',
                        'get_margin',
                        'get_collaterals',
                        'get_positions',
                        'get_option_settlement_history',
                        'get_subaccount_value_history',
                        'expired_and_cancelled_history',
                        'get_funding_history',
                        'get_interest_history',
                        'get_erc20_transfer_history',
                        'get_liquidation_history',
                        'liquidate',
                        'get_liquidator_history',
                        'session_keys',
                        'edit_session_key',
                        'register_scoped_session_key',
                        'get_mmp_config',
                        'set_mmp_config',
                        'reset_mmp',
                        'set_cancel_on_disconnect',
                        'get_invite_code',
                        'register_invite',
                    ],
                },
            },
            'fees': {
            },
            'requiredCredentials': {
                'apiKey': False,
                'secret': False,
                'walletAddress': True,
                'privateKey': True,
            },
            'exceptions': {
                'exact': {
                    '-32000': RateLimitExceeded,  # Rate limit exceeded
                    '-32100': RateLimitExceeded,  # Number of concurrent websocket clients limit exceeded
                    '-32700': BadRequest,  # Parse error
                    '-32600': BadRequest,  # Invalid Request
                    '-32601': BadRequest,  # Method not found
                    '-32602': InvalidOrder,  # {"id":"55e66a3d-6a4e-4a36-a23d-5cf8a91ef478","error":{"code":"","message":"Invalid params"}}
                    '-32603': InvalidOrder,  # {"code":"-32603","message":"Internal error","data":"SubAccount matching query does not exist."}
                    '9000': InvalidOrder,  # Order confirmation timeout
                    '10000': BadRequest,  # Manager not found
                    '10001': BadRequest,  # Asset is not an ERC20 token
                    '10002': BadRequest,  # Sender and recipient wallet do not match
                    '10003': BadRequest,  # Sender and recipient subaccount IDs are the same
                    '10004': InvalidOrder,  # Multiple currencies not supported
                    '10005': BadRequest,  # Maximum number of subaccounts per wallet reached
                    '10006': BadRequest,  # Maximum number of session keys per wallet reached
                    '10007': BadRequest,  # Maximum number of assets per subaccount reached
                    '10008': BadRequest,  # Maximum number of expiries per subaccount reached
                    '10009': BadRequest,  # Recipient subaccount ID of the transfer cannot be 0
                    '10010': InvalidOrder,  # PMRM only supports USDC asset collateral. Cannot trade spot markets.
                    '10011': InsufficientFunds,  # ERC20 allowance is insufficient
                    '10012': InsufficientFunds,  # ERC20 balance is less than transfer amount
                    '10013': ExchangeError,  # There is a pending deposit for self asset
                    '10014': ExchangeError,  # There is a pending withdrawal for self asset
                    '11000': InsufficientFunds,  # Insufficient funds
                    '11002': InvalidOrder,  # Order rejected from queue
                    '11003': InvalidOrder,  # Already cancelled
                    '11004': InvalidOrder,  # Already filled
                    '11005': InvalidOrder,  # Already expired
                    '11006': OrderNotFound,  # {"code":"11006","message":"Does not exist","data":"Open order with id: 804018f3-b092-40a3-a933-b29574fa1ff8 does not exist."}
                    '11007': InvalidOrder,  # Self-crossing disallowed
                    '11008': InvalidOrder,  # Post-only reject
                    '11009': InvalidOrder,  # Zero liquidity for market or IOC/FOK order
                    '11010': InvalidOrder,  # Post-only invalid order type
                    '11011': InvalidOrder,  # {"code":11011,"message":"Invalid signature expiry","data":"Order must expire in 300 sec or more"}
                    '11012': InvalidOrder,  # {"code":"11012","message":"Invalid amount","data":"Amount must be a multiple of 0.01"}
                    '11013': InvalidOrder,  # {"code":"11013","message":"Invalid limit price","data":{"limit":"10000","bandwidth":"92530"}}
                    '11014': InvalidOrder,  # Fill-or-kill not filled
                    '11015': InvalidOrder,  # MMP frozen
                    '11016': InvalidOrder,  # Already consumed
                    '11017': InvalidOrder,  # Non unique nonce
                    '11018': InvalidOrder,  # Invalid nonce date
                    '11019': InvalidOrder,  # Open orders limit exceeded
                    '11020': InsufficientFunds,  # Negative ERC20 balance
                    '11021': InvalidOrder,  # Instrument is not live
                    '11022': InvalidOrder,  # Reject timestamp exceeded
                    '11023': InvalidOrder,  # {"code":"11023","message":"Max fee order param is too low","data":"signed max_fee must be >= 194.420835871999983091712000000000000000"}
                    '11024': InvalidOrder,  # {"code":11024,"message":"Reduce only not supported with self time in force"}
                    '11025': InvalidOrder,  # Reduce only reject
                    '11026': BadRequest,  # Transfer reject
                    '11027': InvalidOrder,  # Subaccount undergoing liquidation
                    '11028': InvalidOrder,  # Replaced order filled amount does not match expected state.
                    '11050': InvalidOrder,  # Trigger order was cancelled between the time worker sent order and engine processed order
                    '11051': InvalidOrder,  # {"code":"11051","message":"Trigger price must be higher than the current price for stop orders and vice versa for take orders","data":"Trigger price 9000.0 must be < or > current price 102671.2 depending on trigger type and direction."}
                    '11052': InvalidOrder,  # Trigger order limit exceeded(separate limit from regular orders)
                    '11053': InvalidOrder,  # Index and last-trade trigger price types not supported yet
                    '11054': InvalidOrder,  # {"code":"11054","message":"Trigger orders cannot replace or be replaced"}
                    '11055': InvalidOrder,  # Market order limit_price is unfillable at the given trigger price
                    '11100': InvalidOrder,  # Leg instruments are not unique
                    '11101': InvalidOrder,  # RFQ not found
                    '11102': InvalidOrder,  # Quote not found
                    '11103': InvalidOrder,  # Quote leg does not match RFQ leg
                    '11104': InvalidOrder,  # Requested quote or RFQ is not open
                    '11105': InvalidOrder,  # Requested quote ID references a different RFQ ID
                    '11106': InvalidOrder,  # Invalid RFQ counterparty
                    '11107': InvalidOrder,  # Quote maker total cost too high
                    '11200': InvalidOrder,  # Auction not ongoing
                    '11201': InvalidOrder,  # Open orders not allowed
                    '11202': InvalidOrder,  # Price limit exceeded
                    '11203': InvalidOrder,  # Last trade ID mismatch
                    '12000': InvalidOrder,  # Asset not found
                    '12001': InvalidOrder,  # Instrument not found
                    '12002': BadRequest,  # Currency not found
                    '12003': BadRequest,  # USDC does not have asset caps per manager
                    '13000': BadRequest,  # Invalid channels
                    '14000': BadRequest,  # {"code": 14000, "message": "Account not found"}
                    '14001': InvalidOrder,  # {"code": 14001, "message": "Subaccount not found"}
                    '14002': BadRequest,  # Subaccount was withdrawn
                    '14008': BadRequest,  # Cannot reduce expiry using registerSessionKey RPC route
                    '14009': BadRequest,  # Session key expiry must be > utc_now + 10 min
                    '14010': BadRequest,  # Session key already registered for self account
                    '14011': BadRequest,  # Session key already registered with another account
                    '14012': BadRequest,  # Address must be checksummed
                    '14013': BadRequest,  # str is not a valid ethereum address
                    '14014': InvalidOrder,  # {"code":"14014","message":"Signature invalid for message or transaction","data":"Signature does not match data"}
                    '14015': BadRequest,  # Transaction count for given wallet does not match provided nonce
                    '14016': BadRequest,  # The provided signed raw transaction contains function name that does not match the expected function name
                    '14017': BadRequest,  # The provided signed raw transaction contains contract address that does not match the expected contract address
                    '14018': BadRequest,  # The provided signed raw transaction contains function params that do not match any expected function params
                    '14019': BadRequest,  # The provided signed raw transaction contains function param values that do not match the expected values
                    '14020': BadRequest,  # The X-LyraWallet header does not match the requested subaccount_id or wallet
                    '14021': BadRequest,  # The X-LyraWallet header not provided
                    '14022': AuthenticationError,  # Subscription to a private channel failed
                    '14023': InvalidOrder,  # {"code":"14023","message":"Signer in on-chain related request is not wallet owner or registered session key","data":"Session key does not belong to wallet"}
                    '14024': BadRequest,  # Chain ID must match the current roll up chain id
                    '14025': BadRequest,  # The private request is missing a wallet or subaccount_id param
                    '14026': BadRequest,  # Session key not found
                    '14027': AuthenticationError,  # Unauthorized maker
                    '14028': BadRequest,  # Cross currency RFQ not supported
                    '14029': AuthenticationError,  # Session key IP not whitelisted
                    '14030': BadRequest,  # Session key expired
                    '14031': AuthenticationError,  # Unauthorized key scope
                    '14032': BadRequest,  # Scope should not be changed
                    '16000': AuthenticationError,  # You are in a restricted region that violates our terms of service.
                    '16001': AuthenticationError,  # Account is disabled due to compliance violations, please contact support to enable it.
                    '16100': AuthenticationError,  # Sentinel authorization is invalid
                    '17000': BadRequest,  # This accoount does not have a shareable invite code
                    '17001': BadRequest,  # Invalid invite code
                    '17002': BadRequest,  # Invite code already registered for self account
                    '17003': BadRequest,  # Invite code has no remaining uses
                    '17004': BadRequest,  # Requirement for successful invite registration not met
                    '17005': BadRequest,  # Account must register with a valid invite code to be elligible for points
                    '17006': BadRequest,  # Point program does not exist
                    '17007': BadRequest,  # Invalid leaderboard page number
                    '18000': BadRequest,  # Invalid block number
                    '18001': BadRequest,  # Failed to estimate block number. Please try again later.
                    '18002': BadRequest,  # The provided smart contract owner does not match the wallet in LightAccountFactory.getAddress()
                    '18003': BadRequest,  # Vault ERC20 asset does not exist
                    '18004': BadRequest,  # Vault ERC20 pool does not exist
                    '18005': BadRequest,  # Must add asset to pool before getting balances
                    '18006': BadRequest,  # Invalid Swell season. Swell seasons are in the form 'swell_season_X'.
                    '18007': BadRequest,  # Vault not found
                    '19000': BadRequest,  # Maker program not found
                },
                'broad': {
                },
            },
            'precisionMode': TICK_SIZE,
            'commonCurrencies': {
            },
            'options': {
                'deriveWalletAddress': '',  # a derive wallet address "0x"-prefixed hexstring
                'id': '0x0ad42b8e602c2d3d475ae52d678cf63d84ab2749',
            },
        })

    def set_sandbox_mode(self, enable: bool):
        super(derive, self).set_sandbox_mode(enable)
        self.options['sandboxMode'] = enable

    async def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server

        https://docs.derive.xyz/reference/post_public-get-time

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = await self.publicPostGetTime(params)
        #
        # {
        #     "result": 1735846536758,
        #     "id": "f1c03d21-f886-4c5a-9a9d-33dd06f180f0"
        # }
        #
        return self.safe_integer(response, 'result')

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

        https://docs.derive.xyz/reference/post_public-get-all-currencies

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        result: dict = {}
        tokenResponse = await self.publicGetGetAllCurrencies(params)
        #
        #    {
        #        "result": [
        #            {
        #                "currency": "SEI",
        #                "instrument_types": [
        #                    "perp"
        #                ],
        #                "protocol_asset_addresses": {
        #                    "perp": "0x7225889B75fd34C68eA3098dAE04D50553C09840",
        #                    "option": null,
        #                    "spot": null,
        #                    "underlying_erc20": null
        #                },
        #                "managers": [
        #                    {
        #                        "address": "0x28c9ddF9A3B29c2E6a561c1BC520954e5A33de5D",
        #                        "margin_type": "SM",
        #                        "currency": null
        #                    }
        #                ],
        #                "srm_im_discount": "0",
        #                "srm_mm_discount": "0",
        #                "pm2_collateral_discounts": [],
        #                "borrow_apy": "0",
        #                "supply_apy": "0",
        #                "total_borrow": "0",
        #                "total_supply": "0",
        #                "asset_cap_and_supply_per_manager": {
        #                    "perp": {
        #                        "SM": [
        #                            {
        #                                "current_open_interest": "0",
        #                                "interest_cap": "2000000",
        #                                "manager_currency": null
        #                            }
        #                        ]
        #                    },
        #                    "option": {},
        #                    "erc20": {}
        #                },
        #                "market_type": "SRM_PERP_ONLY",
        #                "spot_price": "0.2193542905042081",
        #                "spot_price_24h": "0.238381655533635830"
        #            },
        #     "id": "7e07fe1d-0ab4-4d2b-9e22-b65ce9e232dc"
        # }
        #
        currencies = self.safe_list(tokenResponse, 'result', [])
        for i in range(0, len(currencies)):
            currency = currencies[i]
            currencyId = self.safe_string(currency, 'currency')
            code = self.safe_currency_code(currencyId)
            result[code] = self.safe_currency_structure({
                'id': currencyId,
                'name': None,
                'code': code,
                'precision': None,
                'active': None,
                'fee': None,
                'networks': None,
                'deposit': None,
                'withdraw': None,
                'limits': {
                    'deposit': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
                'info': currency,
            })
        return result

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

        https://docs.derive.xyz/reference/post_public-get-all-instruments

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        spotMarketsPromise = self.fetch_spot_markets(params)
        swapMarketsPromise = self.fetch_swap_markets(params)
        optionMarketsPromise = self.fetch_option_markets(params)
        spotMarkets, swapMarkets, optionMarkets = await asyncio.gather(*[spotMarketsPromise, swapMarketsPromise, optionMarketsPromise])
        #
        # {
        #     "result": {
        #         "instruments": [
        #             {
        #                 "instrument_type": "perp",
        #                 "instrument_name": "BTC-PERP",
        #                 "scheduled_activation": 1701840228,
        #                 "scheduled_deactivation": 9223372036854776000,
        #                 "is_active": True,
        #                 "tick_size": "0.1",
        #                 "minimum_amount": "0.01",
        #                 "maximum_amount": "10000",
        #                 "amount_step": "0.001",
        #                 "mark_price_fee_rate_cap": "0",
        #                 "maker_fee_rate": "0.00005",
        #                 "taker_fee_rate": "0.0003",
        #                 "base_fee": "0.1",
        #                 "base_currency": "BTC",
        #                 "quote_currency": "USD",
        #                 "option_details": null,
        #                 "perp_details": {
        #                     "index": "BTC-USD",
        #                     "max_rate_per_hour": "0.004",
        #                     "min_rate_per_hour": "-0.004",
        #                     "static_interest_rate": "0.0000125",
        #                     "aggregate_funding": "10538.574363381759146829",
        #                     "funding_rate": "0.0000125"
        #                 },
        #                 "erc20_details": null,
        #                 "base_asset_address": "0xDBa83C0C654DB1cd914FA2710bA743e925B53086",
        #                 "base_asset_sub_id": "0",
        #                 "pro_rata_fraction": "0",
        #                 "fifo_min_allocation": "0",
        #                 "pro_rata_amount_step": "0.1"
        #             }
        #         ],
        #         "pagination": {
        #             "num_pages": 1,
        #             "count": 1
        #         }
        #     },
        #     "id": "a06bc0b2-8e78-4536-a21f-f785f225b5a5"
        # }
        #
        result = self.array_concat(spotMarkets, swapMarkets)
        result = self.array_concat(result, optionMarkets)
        return result

    async def fetch_spot_markets(self, params={}) -> List[Market]:
        request: dict = {
            'expired': False,
            'instrument_type': 'erc20',
        }
        response = await self.publicPostGetAllInstruments(self.extend(request, params))
        result = self.safe_dict(response, 'result', {})
        data = self.safe_list(result, 'instruments', [])
        return self.parse_markets(data)

    async def fetch_swap_markets(self, params={}) -> List[Market]:
        request: dict = {
            'expired': False,
            'instrument_type': 'perp',
        }
        response = await self.publicPostGetAllInstruments(self.extend(request, params))
        result = self.safe_dict(response, 'result', {})
        data = self.safe_list(result, 'instruments', [])
        return self.parse_markets(data)

    async def fetch_option_markets(self, params={}) -> List[Market]:
        request: dict = {
            'expired': False,
            'instrument_type': 'option',
        }
        response = await self.publicPostGetAllInstruments(self.extend(request, params))
        result = self.safe_dict(response, 'result', {})
        data = self.safe_list(result, 'instruments', [])
        return self.parse_markets(data)

    def parse_market(self, market: dict) -> Market:
        type = self.safe_string(market, 'instrument_type')
        marketType: MarketType
        spot = False
        margin = True
        swap = False
        option = False
        linear: Bool = None
        inverse: Bool = None
        baseId = self.safe_string(market, 'base_currency')
        quoteId = self.safe_string(market, 'quote_currency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        marketId = self.safe_string(market, 'instrument_name')
        symbol = base + '/' + quote
        settleId: Str = None
        settle: Str = None
        expiry: Num = None
        strike: Num = None
        optionType: Str = None
        optionLetter: Str = None
        if type == 'erc20':
            spot = True
            marketType = 'spot'
        elif type == 'perp':
            margin = False
            settleId = 'USDC'
            settle = self.safe_currency_code(settleId)
            symbol = base + '/' + quote + ':' + settle
            swap = True
            linear = True
            inverse = False
            marketType = 'swap'
        elif type == 'option':
            settleId = 'USDC'
            settle = self.safe_currency_code(settleId)
            margin = False
            option = True
            marketType = 'option'
            optionDetails = self.safe_dict(market, 'option_details')
            expiry = self.safe_timestamp(optionDetails, 'expiry')
            strike = self.safe_integer(optionDetails, 'strike')
            optionLetter = self.safe_string(optionDetails, 'option_type')
            symbol = base + '/' + quote + ':' + settle + '-' + self.yymmdd(expiry) + '-' + self.number_to_string(strike) + '-' + optionLetter
            if optionLetter == 'P':
                optionType = 'put'
            else:
                optionType = 'call'
            linear = True
            inverse = False
        return self.safe_market_structure({
            'id': marketId,
            'symbol': symbol,
            'base': base,
            'quote': quote,
            'settle': settle,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': settleId,
            'type': marketType,
            'spot': spot,
            'margin': margin,
            'swap': swap,
            'future': False,
            'option': option,
            'active': self.safe_bool(market, 'is_active'),
            'contract': (swap or option),
            'linear': linear,
            'inverse': inverse,
            'contractSize': None if (spot) else 1,
            'expiry': expiry,
            'expiryDatetime': self.iso8601(expiry),
            'taker': self.safe_number(market, 'taker_fee_rate'),
            'maker': self.safe_number(market, 'maker_fee_rate'),
            'strike': strike,
            'optionType': optionType,
            'precision': {
                'amount': self.safe_number(market, 'amount_step'),
                'price': self.safe_number(market, 'tick_size'),
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': self.safe_number(market, 'minimum_amount'),
                    'max': self.safe_number(market, 'maximum_amount'),
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'created': None,
            'info': market,
        })

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market

        https://docs.derive.xyz/reference/post_public-get-ticker

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instrument_name': market['id'],
        }
        response = await self.publicPostGetTicker(self.extend(request, params))
        #
        # spot
        #
        # {
        #     "result": {
        #         "instrument_type": "perp",
        #         "instrument_name": "BTC-PERP",
        #         "scheduled_activation": 1701840228,
        #         "scheduled_deactivation": 9223372036854776000,
        #         "is_active": True,
        #         "tick_size": "0.1",
        #         "minimum_amount": "0.01",
        #         "maximum_amount": "10000",
        #         "amount_step": "0.001",
        #         "mark_price_fee_rate_cap": "0",
        #         "maker_fee_rate": "0.00005",
        #         "taker_fee_rate": "0.0003",
        #         "base_fee": "0.1",
        #         "base_currency": "BTC",
        #         "quote_currency": "USD",
        #         "option_details": null,
        #         "perp_details": {
        #             "index": "BTC-USD",
        #             "max_rate_per_hour": "0.004",
        #             "min_rate_per_hour": "-0.004",
        #             "static_interest_rate": "0.0000125",
        #             "aggregate_funding": "10512.580833189805742522",
        #             "funding_rate": "-0.000022223906766867"
        #         },
        #         "erc20_details": null,
        #         "base_asset_address": "0xDBa83C0C654DB1cd914FA2710bA743e925B53086",
        #         "base_asset_sub_id": "0",
        #         "pro_rata_fraction": "0",
        #         "fifo_min_allocation": "0",
        #         "pro_rata_amount_step": "0.1",
        #         "best_ask_amount": "0.012",
        #         "best_ask_price": "99567.9",
        #         "best_bid_amount": "0.129",
        #         "best_bid_price": "99554.5",
        #         "five_percent_bid_depth": "11.208",
        #         "five_percent_ask_depth": "11.42",
        #         "option_pricing": null,
        #         "index_price": "99577.2",
        #         "mark_price": "99543.642926357933902181684970855712890625",
        #         "stats": {
        #             "contract_volume": "464.712",
        #             "num_trades": "10681",
        #             "open_interest": "72.804739389481989861",
        #             "high": "99519.1",
        #             "low": "97254.1",
        #             "percent_change": "0.0128",
        #             "usd_change": "1258.1"
        #         },
        #         "timestamp": 1736140984000,
        #         "min_price": "97591.2",
        #         "max_price": "101535.1"
        #     },
        #     "id": "bbd7c271-c2be-48f7-b93a-26cf6d4cb79f"
        # }
        #
        data = self.safe_dict(response, 'result', {})
        return self.parse_ticker(data, market)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        # {
        #     "instrument_type": "perp",
        #     "instrument_name": "BTC-PERP",
        #     "scheduled_activation": 1701840228,
        #     "scheduled_deactivation": 9223372036854776000,
        #     "is_active": True,
        #     "tick_size": "0.1",
        #     "minimum_amount": "0.01",
        #     "maximum_amount": "10000",
        #     "amount_step": "0.001",
        #     "mark_price_fee_rate_cap": "0",
        #     "maker_fee_rate": "0.00005",
        #     "taker_fee_rate": "0.0003",
        #     "base_fee": "0.1",
        #     "base_currency": "BTC",
        #     "quote_currency": "USD",
        #     "option_details": null,
        #     "perp_details": {
        #         "index": "BTC-USD",
        #         "max_rate_per_hour": "0.004",
        #         "min_rate_per_hour": "-0.004",
        #         "static_interest_rate": "0.0000125",
        #         "aggregate_funding": "10512.580833189805742522",
        #         "funding_rate": "-0.000022223906766867"
        #     },
        #     "erc20_details": null,
        #     "base_asset_address": "0xDBa83C0C654DB1cd914FA2710bA743e925B53086",
        #     "base_asset_sub_id": "0",
        #     "pro_rata_fraction": "0",
        #     "fifo_min_allocation": "0",
        #     "pro_rata_amount_step": "0.1",
        #     "best_ask_amount": "0.012",
        #     "best_ask_price": "99567.9",
        #     "best_bid_amount": "0.129",
        #     "best_bid_price": "99554.5",
        #     "five_percent_bid_depth": "11.208",
        #     "five_percent_ask_depth": "11.42",
        #     "option_pricing": null,
        #     "index_price": "99577.2",
        #     "mark_price": "99543.642926357933902181684970855712890625",
        #     "stats": {
        #         "contract_volume": "464.712",
        #         "num_trades": "10681",
        #         "open_interest": "72.804739389481989861",
        #         "high": "99519.1",
        #         "low": "97254.1",
        #         "percent_change": "0.0128",
        #         "usd_change": "1258.1"
        #     },
        #     "timestamp": 1736140984000,
        #     "min_price": "97591.2",
        #     "max_price": "101535.1"
        # }
        #
        marketId = self.safe_string(ticker, 'instrument_name')
        timestamp = self.safe_integer_omit_zero(ticker, 'timestamp')
        symbol = self.safe_symbol(marketId, market)
        stats = self.safe_dict(ticker, 'stats')
        change = self.safe_string(stats, 'percent_change')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(stats, 'high'),
            'low': self.safe_string(stats, 'low'),
            'bid': self.safe_string(ticker, 'best_bid_price'),
            'bidVolume': self.safe_string(ticker, 'best_bid_amount'),
            'ask': self.safe_string(ticker, 'best_ask_price'),
            'askVolume': self.safe_string(ticker, 'best_ask_amount'),
            'vwap': None,
            'open': None,
            'close': None,
            'last': None,
            'previousClose': None,
            'change': change,
            'percentage': Precise.string_mul(change, '100'),
            'average': None,
            'baseVolume': None,
            'quoteVolume': None,
            'indexPrice': self.safe_string(ticker, 'index_price'),
            'markPrice': self.safe_string(ticker, 'mark_price'),
            'info': ticker,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol

        https://docs.derive.xyz/reference/post_public-get-trade-history

        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch trades for
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        request: dict = {}
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_name'] = market['id']
        if limit is not None:
            if limit > 1000:
                limit = 1000
            request['page_size'] = limit  # default 100, max 1000
        if since is not None:
            request['from_timestamp'] = since
        until = self.safe_integer(params, 'until')
        params = self.omit(params, ['until'])
        if until is not None:
            request['to_timestamp'] = until
        response = await self.publicPostGetTradeHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "trades": [
        #             {
        #                 "trade_id": "9dbc88b0-f0c4-4439-9cc1-4e6409d4eafb",
        #                 "instrument_name": "BTC-PERP",
        #                 "timestamp": 1736153910930,
        #                 "trade_price": "98995.3",
        #                 "trade_amount": "0.033",
        #                 "mark_price": "98990.875914388161618263",
        #                 "index_price": "99038.050611100001501184",
        #                 "direction": "sell",
        #                 "quote_id": null,
        #                 "wallet": "0x88B6BB87fbFac92a34F8155aaA35c87B5b166fA9",
        #                 "subaccount_id": 8250,
        #                 "tx_status": "settled",
        #                 "tx_hash": "0x020bd735b312f867f17f8cc254946d87cfe9f2c8ff3605035d8129082eb73723",
        #                 "trade_fee": "0.980476701049890015",
        #                 "liquidity_role": "taker",
        #                 "realized_pnl": "-2.92952402688793509",
        #                 "realized_pnl_excl_fees": "-1.949047325838045075"
        #             }
        #         ],
        #         "pagination": {
        #             "num_pages": 598196,
        #             "count": 598196
        #         }
        #     },
        #     "id": "b8539544-6975-4497-8163-5e51a38e4aa7"
        # }
        #
        result = self.safe_dict(response, 'result', {})
        data = self.safe_list(result, 'trades', [])
        return self.parse_trades(data, market, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # {
        #     "subaccount_id": 130837,
        #     "order_id": "30c48194-8d48-43ac-ad00-0d5ba29eddc9",
        #     "instrument_name": "BTC-PERP",
        #     "direction": "sell",
        #     "label": "test1234",
        #     "quote_id": null,
        #     "trade_id": "f8a30740-488c-4c2d-905d-e17057bafde1",
        #     "timestamp": 1738065303708,
        #     "mark_price": "102740.137375457314192317",
        #     "index_price": "102741.553409299981533184",
        #     "trade_price": "102700.6",
        #     "trade_amount": "0.01",
        #     "liquidity_role": "taker",
        #     "realized_pnl": "0",
        #     "realized_pnl_excl_fees": "0",
        #     "is_transfer": False,
        #     "tx_status": "settled",
        #     "trade_fee": "1.127415534092999815",
        #     "tx_hash": "0xc55df1f07330faf86579bd8a6385391fbe9e73089301149d8550e9d29c9ead74",
        #     "transaction_id": "e18b9426-3fa5-41bb-99d3-8b54fb4d51bb"
        # }
        #
        marketId = self.safe_string(trade, 'instrument_name')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer(trade, 'timestamp')
        fee = {
            'currency': 'USDC',
            'cost': self.safe_string(trade, 'trade_fee'),
        }
        return self.safe_trade({
            'info': trade,
            'id': self.safe_string(trade, 'trade_id'),
            'order': self.safe_string(trade, 'order_id'),
            'symbol': symbol,
            'side': self.safe_string_lower(trade, 'direction'),
            'type': None,
            'takerOrMaker': self.safe_string(trade, 'liquidity_role'),
            'price': self.safe_string(trade, 'trade_price'),
            'amount': self.safe_string(trade, 'trade_amount'),
            'cost': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
        }, market)

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

        https://docs.derive.xyz/reference/post_public-get-funding-rate-history

        :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 to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `funding rate structures <https://docs.ccxt.com/#/?id=funding-rate-history-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'instrument_name': market['id'],
        }
        if since is not None:
            request['start_timestamp'] = since
        until = self.safe_integer(params, 'until')
        params = self.omit(params, ['until'])
        if until is not None:
            request['to_timestamp'] = until
        response = await self.publicPostGetFundingRateHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "funding_rate_history": [
        #             {
        #                 "timestamp": 1736215200000,
        #                 "funding_rate": "-0.000020014"
        #             }
        #         ]
        #     },
        #     "id": "3200ab8d-0080-42f0-8517-c13e3d9201d8"
        # }
        #
        result = self.safe_dict(response, 'result', {})
        data = self.safe_list(result, 'funding_rate_history', [])
        rates = []
        for i in range(0, len(data)):
            entry = data[i]
            timestamp = self.safe_integer(entry, 'timestamp')
            rates.append({
                'info': entry,
                'symbol': market['symbol'],
                'fundingRate': self.safe_number(entry, 'funding_rate'),
                'timestamp': timestamp,
                'datetime': self.iso8601(timestamp),
            })
        sorted = self.sort_by(rates, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, market['symbol'], since, limit)

    async def fetch_funding_rate(self, symbol: str, params={}) -> FundingRate:
        """
        fetch the current funding rate

        https://docs.derive.xyz/reference/post_public-get-funding-rate-history

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `funding rate structure <https://docs.ccxt.com/#/?id=funding-rate-structure>`
        """
        response = await self.fetch_funding_rate_history(symbol, None, 1, params)
        #
        # [
        #     {
        #         "info": {
        #             "timestamp": 1736157600000,
        #             "funding_rate": "-0.000008872"
        #         },
        #         "symbol": "BTC/USD:USDC",
        #         "fundingRate": -0.000008872,
        #         "timestamp": 1736157600000,
        #         "datetime": "2025-01-06T10:00:00.000Z"
        #     }
        # ]
        #
        data = self.safe_dict(response, 0)
        return self.parse_funding_rate(data)

    def parse_funding_rate(self, contract, market: Market = None) -> FundingRate:
        symbol = self.safe_string(contract, 'symbol')
        fundingTimestamp = self.safe_integer(contract, 'timestamp')
        return {
            'info': contract,
            'symbol': symbol,
            'markPrice': None,
            'indexPrice': None,
            'interestRate': None,
            'estimatedSettlePrice': None,
            'timestamp': None,
            'datetime': None,
            'fundingRate': self.safe_number(contract, 'fundingRate'),
            'fundingTimestamp': fundingTimestamp,
            'fundingDatetime': self.iso8601(fundingTimestamp),
            'nextFundingRate': None,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
            'interval': None,
        }

    def hash_order_message(self, order):
        accountHash = self.hash(self.eth_abi_encode([
            'bytes32', 'uint256', 'uint256', 'address', 'bytes32', 'uint256', 'address', 'address',
        ], order), 'keccak', 'binary')
        sandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
        DOMAIN_SEPARATOR = '9bcf4dc06df5d8bf23af818d5716491b995020f377d3b7b64c29ed14e3dd1105' if (sandboxMode) else 'd96e5f90797da7ec8dc4e276260c7f3f87fedf68775fbe1ef116e996fc60441b'
        binaryDomainSeparator = self.base16_to_binary(DOMAIN_SEPARATOR)
        prefix = self.base16_to_binary('1901')
        return self.hash(self.binary_concat(prefix, binaryDomainSeparator, accountHash), 'keccak', 'hex')

    def sign_order(self, order, privateKey):
        hashOrder = self.hash_order_message(order)
        return self.sign_hash(hashOrder[-64:], privateKey[-64:])

    def hash_message(self, message):
        binaryMessage = self.encode(message)
        binaryMessageLength = self.binary_length(binaryMessage)
        x19 = self.base16_to_binary('19')
        newline = self.base16_to_binary('0a')
        prefix = self.binary_concat(x19, self.encode('Ethereum Signed Message:'), newline, self.encode(self.number_to_string(binaryMessageLength)))
        return '0x' + self.hash(self.binary_concat(prefix, binaryMessage), 'keccak', 'hex')

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

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

    def parse_units(self, num: str, dec='1000000000000000000'):
        return Precise.string_mul(num, dec)

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

        https://docs.derive.xyz/reference/post_private-order

        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :param float [params.triggerPrice]: The price a trigger order is triggered at
        :param dict [params.takeProfit]: *takeProfit object in params* containing the triggerPrice at which the attached take profit order will be triggered(perpetual swap markets only)
        :param float [params.takeProfit.triggerPrice]: take profit trigger price
        :param dict [params.stopLoss]: *stopLoss object in params* containing the triggerPrice at which the attached stop loss order will be triggered(perpetual swap markets only)
        :param float [params.stopLoss.triggerPrice]: stop loss trigger price
        :param float [params.max_fee]: *required* the maximum fee you are willing to pay for the order
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        if price is None:
            raise ArgumentsRequired(self.id + ' createOrder() requires a price argument')
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('createOrder', params)
        test = self.safe_bool(params, 'test', False)
        reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only')
        timeInForce = self.safe_string_lower_2(params, 'timeInForce', 'time_in_force')
        postOnly = self.safe_bool(params, 'postOnly')
        orderType = type.lower()
        orderSide = side.lower()
        nonce = self.milliseconds()
        # Order signature expiry must be between 2592000 and 7776000 sec from now
        signatureExpiry = self.safe_integer(params, 'signature_expiry_sec', self.seconds() + 7776000)
        ACTION_TYPEHASH = self.base16_to_binary('4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17')
        sandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
        TRADE_MODULE_ADDRESS = '0x87F2863866D85E3192a35A73b388BD625D83f2be' if (sandboxMode) else '0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b'
        priceString = self.number_to_string(price)
        maxFee = None
        maxFee, params = self.handle_option_and_params(params, 'createOrder', 'max_fee')
        if maxFee is None:
            raise ArgumentsRequired(self.id + ' createOrder() requires a max_fee argument in params')
        maxFeeString = self.number_to_string(maxFee)
        amountString = self.number_to_string(amount)
        tradeModuleDataHash = self.hash(self.eth_abi_encode([
            'address', 'uint', 'int', 'int', 'uint', 'uint', 'bool',
        ], [
            market['info']['base_asset_address'],
            self.parse_to_numeric(market['info']['base_asset_sub_id']),
            self.convert_to_big_int(self.parse_units(priceString)),
            self.convert_to_big_int(self.parse_units(self.amount_to_precision(symbol, amountString))),
            self.convert_to_big_int(self.parse_units(maxFeeString)),
            subaccountId,
            orderSide == 'buy',
        ]), 'keccak', 'binary')
        deriveWalletAddress = None
        deriveWalletAddress, params = self.handle_derive_wallet_address('createOrder', params)
        signature = self.sign_order([
            ACTION_TYPEHASH,
            subaccountId,
            nonce,
            TRADE_MODULE_ADDRESS,
            tradeModuleDataHash,
            signatureExpiry,
            deriveWalletAddress,
            self.walletAddress,
        ], self.privateKey)
        request: dict = {
            'instrument_name': market['id'],
            'direction': orderSide,
            'order_type': orderType,
            'nonce': nonce,
            'amount': amountString,
            'limit_price': priceString,
            'max_fee': maxFeeString,
            'subaccount_id': subaccountId,
            'signature_expiry_sec': signatureExpiry,
            'referral_code': self.safe_string(self.options, 'id', '0x0ad42b8e602c2d3d475ae52d678cf63d84ab2749'),
            'signer': self.walletAddress,
        }
        if reduceOnly is not None:
            request['reduce_only'] = reduceOnly
            if reduceOnly and postOnly:
                raise InvalidOrder(self.id + ' cannot use reduce only with post only time in force')
        if postOnly is not None:
            request['time_in_force'] = 'post_only'
        elif timeInForce is not None:
            request['time_in_force'] = timeInForce
        stopLoss = self.safe_value(params, 'stopLoss')
        takeProfit = self.safe_value(params, 'takeProfit')
        triggerPriceType = self.safe_string(params, 'trigger_price_type', 'mark')
        if stopLoss is not None:
            stopLossPrice = self.safe_string(stopLoss, 'triggerPrice', stopLoss)
            request['trigger_price'] = stopLossPrice
            request['trigger_type'] = 'stoploss'
            request['trigger_price_type'] = triggerPriceType
        elif takeProfit is not None:
            takeProfitPrice = self.safe_string(takeProfit, 'triggerPrice', takeProfit)
            request['trigger_price'] = takeProfitPrice
            request['trigger_type'] = 'takeprofit'
            request['trigger_price_type'] = triggerPriceType
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['label'] = clientOrderId
        request['signature'] = signature
        params = self.omit(params, ['reduceOnly', 'reduce_only', 'timeInForce', 'time_in_force', 'postOnly', 'test', 'clientOrderId', 'stopPrice', 'triggerPrice', 'trigger_price', 'stopLoss', 'takeProfit', 'trigger_price_type'])
        response = None
        if test:
            response = await self.privatePostOrderDebug(self.extend(request, params))
        else:
            response = await self.privatePostOrder(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "raw_data": {
        #             "subaccount_id": 130837,
        #             "nonce": 1736923517552,
        #             "module": "0x87F2863866D85E3192a35A73b388BD625D83f2be",
        #             "expiry": 86400,
        #             "owner": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075",
        #             "signer": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075",
        #             "signature": "0xaa4f42b2f3da33c668fa703ea872d4c3a6b55aca66025b5119e3bebb6679fe2e2794638db51dcace21fc39a498047835994f07eb59f311bb956ce057e66793d1c",
        #             "data": {
        #                 "asset": "0xAFB6Bb95cd70D5367e2C39e9dbEb422B9815339D",
        #                 "sub_id": 0,
        #                 "limit_price": "10000",
        #                 "desired_amount": "0.001",
        #                 "worst_fee": "0",
        #                 "recipient_id": 130837,
        #                 "is_bid": True,
        #                 "trade_id": ""
        #             }
        #         },
        #         "encoded_data": "0x000000000000000000000000afb6bb95cd70d5367e2c39e9dbeb422b9815339d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021e19e0c9bab240000000000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ff150000000000000000000000000000000000000000000000000000000000000001",
        #         "encoded_data_hashed": "0xe88fb416bc54dba2d288988f1a82fee40fd792ed555b3471b5f6b4b810d279b4",
        #         "action_hash": "0x273a0befb3751fa991edc7ed73582456c3b50ae964d458c8f472e932fb6a0069",
        #         "typed_data_hash": "0x123e2d2f3d5b2473b4e260f51c6459d6bf904e5db8f042a3ea63be8d55329ce9"
        #     },
        #     "id": "f851c8c4-dddf-4b77-93cf-aeddd0966f29"
        # }
        # {
        #     "result": {
        #         "order": {
        #             "subaccount_id": 130837,
        #             "order_id": "96349ebb-7d46-43ae-81c7-7ab390444293",
        #             "instrument_name": "BTC-PERP",
        #             "direction": "buy",
        #             "label": "",
        #             "quote_id": null,
        #             "creation_timestamp": 1737467576257,
        #             "last_update_timestamp": 1737467576257,
        #             "limit_price": "10000",
        #             "amount": "0.01",
        #             "filled_amount": "0",
        #             "average_price": "0",
        #             "order_fee": "0",
        #             "order_type": "limit",
        #             "time_in_force": "gtc",
        #             "order_status": "open",
        #             "max_fee": "210",
        #             "signature_expiry_sec": 1737468175989,
        #             "nonce": 1737467575989,
        #             "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A",
        #             "signature": "0xd1ca49df1fa06bd805bb59b132ff6c0de29bf973a3e01705abe0a01cc956e4945ed9eb99ab68f3df4c037908113cac5a5bfc3a954a0b7103cdab285962fa6a51c",
        #             "cancel_reason": "",
        #             "mmp": False,
        #             "is_transfer": False,
        #             "replaced_order_id": null,
        #             "trigger_type": null,
        #             "trigger_price_type": null,
        #             "trigger_price": null,
        #             "trigger_reject_message": null
        #         },
        #         "trades": []
        #     },
        #     "id": "397087fa-0125-42af-bfc3-f66166f9fb55"
        # }
        #
        result = self.safe_dict(response, 'result')
        rawOrder = self.safe_dict(result, 'raw_data')
        if rawOrder is None:
            rawOrder = self.safe_dict(result, 'order')
        order = self.parse_order(rawOrder, market)
        order['type'] = type
        return order

    async 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.derive.xyz/reference/post_private-replace

        :param str id: order id
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('editOrder', params)
        reduceOnly = self.safe_bool_2(params, 'reduceOnly', 'reduce_only')
        timeInForce = self.safe_string_lower_2(params, 'timeInForce', 'time_in_force')
        postOnly = self.safe_bool(params, 'postOnly')
        orderType = type.lower()
        orderSide = side.lower()
        nonce = self.milliseconds()
        signatureExpiry = self.safe_number(params, 'signature_expiry_sec', self.seconds() + 7776000)
        # TODO: subaccount id / trade module address
        ACTION_TYPEHASH = self.base16_to_binary('4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17')
        sandboxMode = self.safe_bool(self.options, 'sandboxMode', False)
        TRADE_MODULE_ADDRESS = '0x87F2863866D85E3192a35A73b388BD625D83f2be' if (sandboxMode) else '0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b'
        priceString = self.number_to_string(price)
        maxFeeString = self.safe_string(params, 'max_fee', '0')
        amountString = self.number_to_string(amount)
        tradeModuleDataHash = self.hash(self.eth_abi_encode([
            'address', 'uint', 'int', 'int', 'uint', 'uint', 'bool',
        ], [
            market['info']['base_asset_address'],
            self.parse_to_numeric(market['info']['base_asset_sub_id']),
            self.convert_to_big_int(self.parse_units(priceString)),
            self.convert_to_big_int(self.parse_units(self.amount_to_precision(symbol, amountString))),
            self.convert_to_big_int(self.parse_units(maxFeeString)),
            subaccountId,
            orderSide == 'buy',
        ]), 'keccak', 'binary')
        deriveWalletAddress = None
        deriveWalletAddress, params = self.handle_derive_wallet_address('editOrder', params)
        signature = self.sign_order([
            ACTION_TYPEHASH,
            subaccountId,
            nonce,
            TRADE_MODULE_ADDRESS,
            tradeModuleDataHash,
            signatureExpiry,
            deriveWalletAddress,
            self.walletAddress,
        ], self.privateKey)
        request: dict = {
            'instrument_name': market['id'],
            'order_id_to_cancel': id,
            'direction': orderSide,
            'order_type': orderType,
            'nonce': nonce,
            'amount': amountString,
            'limit_price': priceString,
            'max_fee': maxFeeString,
            'subaccount_id': subaccountId,
            'signature_expiry_sec': signatureExpiry,
            'signer': self.walletAddress,
        }
        if reduceOnly is not None:
            request['reduce_only'] = reduceOnly
            if reduceOnly and postOnly:
                raise InvalidOrder(self.id + ' cannot use reduce only with post only time in force')
        if postOnly is not None:
            request['time_in_force'] = 'post_only'
        elif timeInForce is not None:
            request['time_in_force'] = timeInForce
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['label'] = clientOrderId
        request['signature'] = signature
        params = self.omit(params, ['reduceOnly', 'reduce_only', 'timeInForce', 'time_in_force', 'postOnly', 'clientOrderId'])
        response = await self.privatePostReplace(self.extend(request, params))
        #
        #   {
        #     "result":
        #       {
        #         "cancelled_order":
        #           {
        #             "subaccount_id": 130837,
        #             "order_id": "c2337704-f1af-437d-91c8-dddb9d6bac59",
        #             "instrument_name": "BTC-PERP",
        #             "direction": "buy",
        #             "label": "test1234",
        #             "quote_id": null,
        #             "creation_timestamp": 1737539743959,
        #             "last_update_timestamp": 1737539764234,
        #             "limit_price": "10000",
        #             "amount": "0.01",
        #             "filled_amount": "0",
        #             "average_price": "0",
        #             "order_fee": "0",
        #             "order_type": "limit",
        #             "time_in_force": "post_only",
        #             "order_status": "cancelled",
        #             "max_fee": "211",
        #             "signature_expiry_sec": 1737540343631,
        #             "nonce": 1737539743631,
        #             "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A",
        #             "signature": "0xdb669e18f407a3efa816b79c0dd3bac1c651d4dbf3caad4db67678ce9b81c76378d787a08143a30707eb0827ce4626640767c9f174358df1b90611bd6d1391711b",
        #             "cancel_reason": "user_request",
        #             "mmp": False,
        #             "is_transfer": False,
        #             "replaced_order_id": null,
        #             "trigger_type": null,
        #             "trigger_price_type": null,
        #             "trigger_price": null,
        #             "trigger_reject_message": null,
        #           },
        #         "order":
        #           {
        #             "subaccount_id": 130837,
        #             "order_id": "97af0902-813f-4892-a54b-797e5689db05",
        #             "instrument_name": "BTC-PERP",
        #             "direction": "buy",
        #             "label": "test1234",
        #             "quote_id": null,
        #             "creation_timestamp": 1737539764154,
        #             "last_update_timestamp": 1737539764154,
        #             "limit_price": "10000",
        #             "amount": "0.01",
        #             "filled_amount": "0",
        #             "average_price": "0",
        #             "order_fee": "0",
        #             "order_type": "limit",
        #             "time_in_force": "post_only",
        #             "order_status": "open",
        #             "max_fee": "211",
        #             "signature_expiry_sec": 1737540363890,
        #             "nonce": 1737539763890,
        #             "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A",
        #             "signature": "0xef2c459ab4797cbbd7d97b47678ff172542af009bac912bf53e7879cf92eb1aa6b1a6cf40bf0928684f5394942fb424cc2db71eac0eaf7226a72480034332f291c",
        #             "cancel_reason": "",
        #             "mmp": False,
        #             "is_transfer": False,
        #             "replaced_order_id": "c2337704-f1af-437d-91c8-dddb9d6bac59",
        #             "trigger_type": null,
        #             "trigger_price_type": null,
        #             "trigger_price": null,
        #             "trigger_reject_message": null,
        #           },
        #         "trades": [],
        #         "create_order_error": null,
        #       },
        #     "id": "fb19e991-15f6-4c80-a20c-917e762a1a38",
        #   }
        #
        result = self.safe_dict(response, 'result')
        rawOrder = self.safe_dict(result, 'order')
        order = self.parse_order(rawOrder, market)
        return order

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

        https://docs.derive.xyz/reference/post_private-cancel

        cancels an open 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.trigger]: whether the order is a trigger/algo order
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market: Market = self.market(symbol)
        isTrigger = self.safe_bool_2(params, 'trigger', 'stop', False)
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('cancelOrder', params)
        params = self.omit(params, ['trigger', 'stop'])
        request: dict = {
            'instrument_name': market['id'],
            'subaccount_id': subaccountId,
        }
        clientOrderIdUnified = self.safe_string(params, 'clientOrderId')
        clientOrderIdExchangeSpecific = self.safe_string(params, 'label', clientOrderIdUnified)
        isByClientOrder = clientOrderIdExchangeSpecific is not None
        response = None
        if isByClientOrder:
            request['label'] = clientOrderIdExchangeSpecific
            params = self.omit(params, ['clientOrderId', 'label'])
            response = await self.privatePostCancelByLabel(self.extend(request, params))
        else:
            request['order_id'] = id
            if isTrigger:
                response = await self.privatePostCancelTriggerOrder(self.extend(request, params))
            else:
                response = await self.privatePostCancel(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "subaccount_id": 130837,
        #         "order_id": "de4f30b6-0dcb-4df6-9222-c1a27f1ad80d",
        #         "instrument_name": "BTC-PERP",
        #         "direction": "buy",
        #         "label": "test1234",
        #         "quote_id": null,
        #         "creation_timestamp": 1737540100989,
        #         "last_update_timestamp": 1737540574696,
        #         "limit_price": "10000",
        #         "amount": "0.01",
        #         "filled_amount": "0",
        #         "average_price": "0",
        #         "order_fee": "0",
        #         "order_type": "limit",
        #         "time_in_force": "post_only",
        #         "order_status": "cancelled",
        #         "max_fee": "211",
        #         "signature_expiry_sec": 1737540700726,
        #         "nonce": 1737540100726,
        #         "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A",
        #         "signature": "0x9cd1a6e32a0699929e4e090c08c548366b1353701ec56e02d5cdf37fc89bd19b7b29e00e57e8383bb6336d73019027a7e2a4364f40859e7a949115024c7f199a1b",
        #         "cancel_reason": "user_request",
        #         "mmp": False,
        #         "is_transfer": False,
        #         "replaced_order_id": "4ccc89ba-3c3d-4047-8900-0aa5fb4ef706",
        #         "trigger_type": null,
        #         "trigger_price_type": null,
        #         "trigger_price": null,
        #         "trigger_reject_message": null
        #     },
        #     "id": "cef61e2a-cb13-4779-8e6b-535361981fad"
        # }
        #
        # {
        #     "result": {
        #         "cancelled_orders": 1
        #     },
        #     "id": "674e075e-1e8a-4a47-99ff-75efbdd2370f"
        # }
        #
        extendParams: dict = {'symbol': symbol}
        order = self.safe_dict(response, 'result')
        if isByClientOrder:
            extendParams['client_order_id'] = clientOrderIdExchangeSpecific
        return self.extend(self.parse_order(order, market), extendParams)

    async def cancel_all_orders(self, symbol: Str = None, params={}):
        """

        https://docs.derive.xyz/reference/post_private-cancel-by-instrument
        https://docs.derive.xyz/reference/post_private-cancel-all

        cancel all open orders in a market
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict: an list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('cancelAllOrders', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        response = None
        if market is not None:
            request['instrument_name'] = market['id']
            response = await self.privatePostCancelByInstrument(self.extend(request, params))
        else:
            response = await self.privatePostCancelAll(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "cancelled_orders": 0
        #     },
        #     "id": "9d633799-2098-4559-b547-605bb6f4d8f4"
        # }
        #
        # {
        #     "id": "45548646-c74f-4ca2-9de4-551e6de49afa",
        #     "result": "ok"
        # }
        #
        return response

    async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple orders made by the user

        https://docs.derive.xyz/reference/post_private-get-orders

        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: set to True if you want to fetch orders with pagination
        :param boolean [params.trigger]: whether the order is a trigger/algo order
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOrders', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchOrders', symbol, since, limit, params, 'page', 500)
        isTrigger = self.safe_bool_2(params, 'trigger', 'stop', False)
        params = self.omit(params, ['trigger', 'stop'])
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchOrders', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_name'] = market['id']
        if limit is not None:
            request['page_size'] = limit
        else:
            request['page_size'] = 500
        if isTrigger:
            request['status'] = 'untriggered'
        response = await self.privatePostGetOrders(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "subaccount_id": 130837,
        #         "orders": [
        #             {
        #                 "subaccount_id": 130837,
        #                 "order_id": "63a80cb8-387b-472b-a838-71cd9513c365",
        #                 "instrument_name": "BTC-PERP",
        #                 "direction": "buy",
        #                 "label": "test1234",
        #                 "quote_id": null,
        #                 "creation_timestamp": 1737551053207,
        #                 "last_update_timestamp": 1737551053207,
        #                 "limit_price": "10000",
        #                 "amount": "0.01",
        #                 "filled_amount": "0",
        #                 "average_price": "0",
        #                 "order_fee": "0",
        #                 "order_type": "limit",
        #                 "time_in_force": "post_only",
        #                 "order_status": "open",
        #                 "max_fee": "211",
        #                 "signature_expiry_sec": 1737551652765,
        #                 "nonce": 1737551052765,
        #                 "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A",
        #                 "signature": "0x35535ccb1bcad509ecc435c79e966174db6403fc9aeee1e237d08a941014c57b59279dfe4be39e081f9921a53eaad59cb2a151d9f52f2d05fc47e6280254952e1c",
        #                 "cancel_reason": "",
        #                 "mmp": False,
        #                 "is_transfer": False,
        #                 "replaced_order_id": null,
        #                 "trigger_type": null,
        #                 "trigger_price_type": null,
        #                 "trigger_price": null,
        #                 "trigger_reject_message": null
        #             }
        #         ],
        #         "pagination": {
        #             "num_pages": 1,
        #             "count": 1
        #         }
        #     },
        #     "id": "e5a88d4f-7ac7-40cd-aec9-e0e8152b8b92"
        # }
        #
        data = self.safe_value(response, 'result')
        page = self.safe_integer(params, 'page')
        if page is not None:
            pagination = self.safe_dict(data, 'pagination')
            currentPage = self.safe_integer(pagination, 'num_pages')
            if page > currentPage:
                return []
        orders = self.safe_list(data, 'orders')
        return self.parse_orders(orders, market, since, limit)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple orders made by the user

        https://docs.derive.xyz/reference/post_private-get-orders

        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: set to True if you want to fetch orders with pagination
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        extendedParams = self.extend(params, {'status': 'open'})
        return await self.fetch_orders(symbol, since, limit, extendedParams)

    async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple orders made by the user

        https://docs.derive.xyz/reference/post_private-get-orders

        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: set to True if you want to fetch orders with pagination
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        extendedParams = self.extend(params, {'status': 'filled'})
        return await self.fetch_orders(symbol, since, limit, extendedParams)

    async def fetch_canceled_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetches information on multiple canceled orders made by the user

        https://docs.derive.xyz/reference/post_private-get-orders

        :param str symbol: unified market symbol of the market the orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        extendedParams = self.extend(params, {'status': 'cancelled'})
        return await self.fetch_orders(symbol, since, limit, extendedParams)

    def parse_time_in_force(self, timeInForce: Str):
        timeInForces: dict = {
            'ioc': 'IOC',
            'fok': 'FOK',
            'gtc': 'GTC',
            'post_only': 'PO',
        }
        return self.safe_string(timeInForces, timeInForce, None)

    def parse_order_status(self, status: Str):
        if status is not None:
            statuses: dict = {
                'open': 'open',
                'untriggered': 'open',
                'filled': 'closed',
                'cancelled': 'canceled',
                'expired': 'rejected',
            }
            return self.safe_string(statuses, status, status)
        return status

    def parse_order(self, rawOrder: dict, market: Market = None) -> Order:
        #
        # {
        #     "subaccount_id": 130837,
        #     "nonce": 1736923517552,
        #     "module": "0x87F2863866D85E3192a35A73b388BD625D83f2be",
        #     "expiry": 86400,
        #     "owner": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075",
        #     "signer": "0x108b9aF9279a525b8A8AeAbE7AC2bA925Bc50075",
        #     "signature": "0xaa4f42b2f3da33c668fa703ea872d4c3a6b55aca66025b5119e3bebb6679fe2e2794638db51dcace21fc39a498047835994f07eb59f311bb956ce057e66793d1c",
        #     "data": {
        #         "asset": "0xAFB6Bb95cd70D5367e2C39e9dbEb422B9815339D",
        #         "sub_id": 0,
        #         "limit_price": "10000",
        #         "desired_amount": "0.001",
        #         "worst_fee": "0",
        #         "recipient_id": 130837,
        #         "is_bid": True,
        #         "trade_id": ""
        #     }
        # }
        # {
        #     "subaccount_id": 130837,
        #     "order_id": "96349ebb-7d46-43ae-81c7-7ab390444293",
        #     "instrument_name": "BTC-PERP",
        #     "direction": "buy",
        #     "label": "",
        #     "quote_id": null,
        #     "creation_timestamp": 1737467576257,
        #     "last_update_timestamp": 1737467576257,
        #     "limit_price": "10000",
        #     "amount": "0.01",
        #     "filled_amount": "0",
        #     "average_price": "0",
        #     "order_fee": "0",
        #     "order_type": "limit",
        #     "time_in_force": "gtc",
        #     "order_status": "open",
        #     "max_fee": "210",
        #     "signature_expiry_sec": 1737468175989,
        #     "nonce": 1737467575989,
        #     "signer": "0x30CB7B06AdD6749BbE146A6827502B8f2a79269A",
        #     "signature": "0xd1ca49df1fa06bd805bb59b132ff6c0de29bf973a3e01705abe0a01cc956e4945ed9eb99ab68f3df4c037908113cac5a5bfc3a954a0b7103cdab285962fa6a51c",
        #     "cancel_reason": "",
        #     "mmp": False,
        #     "is_transfer": False,
        #     "replaced_order_id": null,
        #     "trigger_type": null,
        #     "trigger_price_type": null,
        #     "trigger_price": null,
        #     "trigger_reject_message": null
        # }
        order = self.safe_dict(rawOrder, 'data')
        if order is None:
            order = rawOrder
        timestamp = self.safe_integer_2(rawOrder, 'creation_timestamp', 'nonce')
        orderId = self.safe_string(order, 'order_id')
        marketId = self.safe_string(order, 'instrument_name')
        if marketId is not None:
            market = self.safe_market(marketId, market)
        symbol = market['symbol']
        price = self.safe_string(order, 'limit_price')
        average = self.safe_string(order, 'average_price')
        amount = self.safe_string(order, 'desired_amount')
        filled = self.safe_string(order, 'filled_amount')
        fee = self.safe_string(order, 'order_fee')
        orderType = self.safe_string_lower(order, 'order_type')
        isBid = self.safe_bool(order, 'is_bid')
        side = self.safe_string(order, 'direction')
        if side is None:
            if isBid:
                side = 'buy'
            else:
                side = 'sell'
        triggerType = self.safe_string(order, 'trigger_type')
        stopLossPrice = None
        takeProfitPrice = None
        triggerPrice = None
        if triggerType is not None:
            triggerPrice = self.safe_string(order, 'trigger_price')
            if triggerType == 'stoploss':
                stopLossPrice = triggerPrice
            else:
                takeProfitPrice = triggerPrice
        lastUpdateTimestamp = self.safe_integer(rawOrder, 'last_update_timestamp')
        status = self.safe_string(order, 'order_status')
        timeInForce = self.safe_string(order, 'time_in_force')
        return self.safe_order({
            'id': orderId,
            'clientOrderId': self.safe_string(order, 'label'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'lastUpdateTimestamp': lastUpdateTimestamp,
            'status': self.parse_order_status(status),
            'symbol': symbol,
            'type': orderType,
            'timeInForce': self.parse_time_in_force(timeInForce),
            'postOnly': None,  # handled in safeOrder
            'reduceOnly': self.safe_bool(order, 'reduce_only'),
            'side': side,
            'price': price,
            'triggerPrice': triggerPrice,
            'takeProfitPrice': takeProfitPrice,
            'stopLossPrice': stopLossPrice,
            'average': average,
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'cost': None,
            'trades': None,
            'fee': {
                'cost': fee,
                'currency': 'USDC',
            },
            'info': order,
        }, market)

    async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all the trades made from a single order

        https://docs.derive.xyz/reference/post_private-get-trade-history

        :param str id: order id
        :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 to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchOrderTrades', params)
        request: dict = {
            'order_id': id,
            'subaccount_id': subaccountId,
        }
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_name'] = market['id']
        if limit is not None:
            request['page_size'] = limit
        if since is not None:
            request['from_timestamp'] = since
        response = await self.privatePostGetTradeHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "subaccount_id": 130837,
        #         "trades": [
        #             {
        #                 "subaccount_id": 130837,
        #                 "order_id": "30c48194-8d48-43ac-ad00-0d5ba29eddc9",
        #                 "instrument_name": "BTC-PERP",
        #                 "direction": "sell",
        #                 "label": "test1234",
        #                 "quote_id": null,
        #                 "trade_id": "f8a30740-488c-4c2d-905d-e17057bafde1",
        #                 "timestamp": 1738065303708,
        #                 "mark_price": "102740.137375457314192317",
        #                 "index_price": "102741.553409299981533184",
        #                 "trade_price": "102700.6",
        #                 "trade_amount": "0.01",
        #                 "liquidity_role": "taker",
        #                 "realized_pnl": "0",
        #                 "realized_pnl_excl_fees": "0",
        #                 "is_transfer": False,
        #                 "tx_status": "settled",
        #                 "trade_fee": "1.127415534092999815",
        #                 "tx_hash": "0xc55df1f07330faf86579bd8a6385391fbe9e73089301149d8550e9d29c9ead74",
        #                 "transaction_id": "e18b9426-3fa5-41bb-99d3-8b54fb4d51bb"
        #             }
        #         ],
        #         "pagination": {
        #             "num_pages": 1,
        #             "count": 1
        #         }
        #     },
        #     "id": "a16f798c-a121-44e2-b77e-c38a063f8a99"
        # }
        #
        result = self.safe_dict(response, 'result', {})
        trades = self.safe_list(result, 'trades', [])
        return self.parse_trades(trades, market, since, limit, params)

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

        https://docs.derive.xyz/reference/post_private-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 boolean [params.paginate]: set to True if you want to fetch trades with pagination
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchMyTrades', symbol, since, limit, params, 'page', 500)
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchMyTrades', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_name'] = market['id']
        if limit is not None:
            request['page_size'] = limit
        if since is not None:
            request['from_timestamp'] = since
        response = await self.privatePostGetTradeHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "subaccount_id": 130837,
        #         "trades": [
        #             {
        #                 "subaccount_id": 130837,
        #                 "order_id": "30c48194-8d48-43ac-ad00-0d5ba29eddc9",
        #                 "instrument_name": "BTC-PERP",
        #                 "direction": "sell",
        #                 "label": "test1234",
        #                 "quote_id": null,
        #                 "trade_id": "f8a30740-488c-4c2d-905d-e17057bafde1",
        #                 "timestamp": 1738065303708,
        #                 "mark_price": "102740.137375457314192317",
        #                 "index_price": "102741.553409299981533184",
        #                 "trade_price": "102700.6",
        #                 "trade_amount": "0.01",
        #                 "liquidity_role": "taker",
        #                 "realized_pnl": "0",
        #                 "realized_pnl_excl_fees": "0",
        #                 "is_transfer": False,
        #                 "tx_status": "settled",
        #                 "trade_fee": "1.127415534092999815",
        #                 "tx_hash": "0xc55df1f07330faf86579bd8a6385391fbe9e73089301149d8550e9d29c9ead74",
        #                 "transaction_id": "e18b9426-3fa5-41bb-99d3-8b54fb4d51bb"
        #             }
        #         ],
        #         "pagination": {
        #             "num_pages": 1,
        #             "count": 1
        #         }
        #     },
        #     "id": "a16f798c-a121-44e2-b77e-c38a063f8a99"
        # }
        #
        result = self.safe_dict(response, 'result', {})
        page = self.safe_integer(params, 'page')
        if page is not None:
            pagination = self.safe_dict(result, 'pagination')
            currentPage = self.safe_integer(pagination, 'num_pages')
            if page > currentPage:
                return []
        trades = self.safe_list(result, 'trades', [])
        return self.parse_trades(trades, market, since, limit, params)

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

        https://docs.derive.xyz/reference/post_private-get-positions

        :param str[] [symbols]: not used by kraken fetchPositions()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict[]: a list of `position structure <https://docs.ccxt.com/#/?id=position-structure>`
        """
        await self.load_markets()
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchPositions', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        params = self.omit(params, ['subaccount_id'])
        response = await self.privatePostGetPositions(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "subaccount_id": 130837,
        #         "positions": [
        #             {
        #                 "instrument_type": "perp",
        #                 "instrument_name": "BTC-PERP",
        #                 "amount": "-0.02",
        #                 "average_price": "102632.9105389869500088",
        #                 "realized_pnl": "0",
        #                 "unrealized_pnl": "-2.6455959784245548835819950103759765625",
        #                 "total_fees": "2.255789220260999824",
        #                 "average_price_excl_fees": "102745.7",
        #                 "realized_pnl_excl_fees": "0",
        #                 "unrealized_pnl_excl_fees": "-0.3898067581635550595819950103759765625",
        #                 "net_settlements": "-4.032902047219498639",
        #                 "cumulative_funding": "-0.004677736347850093",
        #                 "pending_funding": "0",
        #                 "mark_price": "102765.190337908177752979099750518798828125",
        #                 "index_price": "102767.657193800017641472",
        #                 "delta": "1",
        #                 "gamma": "0",
        #                 "vega": "0",
        #                 "theta": "0",
        #                 "mark_value": "1.38730606879471451975405216217041015625",
        #                 "maintenance_margin": "-101.37788426911356509663164615631103515625",
        #                 "initial_margin": "-132.2074413704858670826070010662078857421875",
        #                 "open_orders_margin": "264.116085900726830004714429378509521484375",
        #                 "leverage": "8.6954476205089299495699106539379941746377322586618",
        #                 "liquidation_price": "109125.705451984322280623018741607666015625",
        #                 "creation_timestamp": 1738065303840
        #             }
        #         ]
        #     },
        #     "id": "167350f1-d9fc-41d4-9797-1c78f83fda8e"
        # }
        #
        result = self.safe_dict(response, 'result', {})
        positions = self.safe_list(result, 'positions', [])
        return self.parse_positions(positions, symbols)

    def parse_position(self, position: dict, market: Market = None):
        #
        # {
        #     "instrument_type": "perp",
        #     "instrument_name": "BTC-PERP",
        #     "amount": "-0.02",
        #     "average_price": "102632.9105389869500088",
        #     "realized_pnl": "0",
        #     "unrealized_pnl": "-2.6455959784245548835819950103759765625",
        #     "total_fees": "2.255789220260999824",
        #     "average_price_excl_fees": "102745.7",
        #     "realized_pnl_excl_fees": "0",
        #     "unrealized_pnl_excl_fees": "-0.3898067581635550595819950103759765625",
        #     "net_settlements": "-4.032902047219498639",
        #     "cumulative_funding": "-0.004677736347850093",
        #     "pending_funding": "0",
        #     "mark_price": "102765.190337908177752979099750518798828125",
        #     "index_price": "102767.657193800017641472",
        #     "delta": "1",
        #     "gamma": "0",
        #     "vega": "0",
        #     "theta": "0",
        #     "mark_value": "1.38730606879471451975405216217041015625",
        #     "maintenance_margin": "-101.37788426911356509663164615631103515625",
        #     "initial_margin": "-132.2074413704858670826070010662078857421875",
        #     "open_orders_margin": "264.116085900726830004714429378509521484375",
        #     "leverage": "8.6954476205089299495699106539379941746377322586618",
        #     "liquidation_price": "109125.705451984322280623018741607666015625",
        #     "creation_timestamp": 1738065303840
        # }
        #
        contract = self.safe_string(position, 'instrument_name')
        market = self.safe_market(contract, market)
        size = self.safe_string(position, 'amount')
        side: Str = None
        if Precise.string_gt(size, '0'):
            side = 'long'
        else:
            side = 'short'
        contractSize = self.safe_string(market, 'contractSize')
        markPrice = self.safe_string(position, 'mark_price')
        timestamp = self.safe_integer(position, 'creation_timestamp')
        unrealisedPnl = self.safe_string(position, 'unrealized_pnl')
        size = Precise.string_abs(size)
        notional = Precise.string_mul(size, markPrice)
        return self.safe_position({
            'info': position,
            'id': None,
            'symbol': self.safe_string(market, 'symbol'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastUpdateTimestamp': None,
            'initialMargin': self.safe_string(position, 'initial_margin'),
            'initialMarginPercentage': None,
            'maintenanceMargin': self.safe_string(position, 'maintenance_margin'),
            'maintenanceMarginPercentage': None,
            'entryPrice': None,
            'notional': self.parse_number(notional),
            'leverage': self.safe_number(position, 'leverage'),
            'unrealizedPnl': self.parse_number(unrealisedPnl),
            'contracts': self.parse_number(size),
            'contractSize': self.parse_number(contractSize),
            'marginRatio': None,
            'liquidationPrice': self.safe_number(position, 'liquidation_price'),
            'markPrice': self.parse_number(markPrice),
            'lastPrice': None,
            'collateral': None,
            'marginMode': None,
            'side': side,
            'percentage': None,
            'hedged': None,
            'stopLossPrice': None,
            'takeProfitPrice': None,
        })

    async def fetch_funding_history(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch the history of funding payments paid and received on self account

        https://docs.derive.xyz/reference/post_private-get-funding-history

        :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 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>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchFundingHistory', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_incremental('fetchFundingHistory', symbol, since, limit, params, 'page', 500)
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchFundingHistory', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrument_name'] = market['id']
        if since is not None:
            request['start_timestamp'] = since
        if limit is not None:
            request['page_size'] = limit
        response = await self.privatePostGetFundingHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "events": [
        #             {
        #                 "instrument_name": "BTC-PERP",
        #                 "timestamp": 1738066618272,
        #                 "funding": "-0.004677736347850093",
        #                 "pnl": "-0.944081615774632967"
        #             },
        #             {
        #                 "instrument_name": "BTC-PERP",
        #                 "timestamp": 1738066617964,
        #                 "funding": "0",
        #                 "pnl": "-0.437556413479249408"
        #             },
        #             {
        #                 "instrument_name": "BTC-PERP",
        #                 "timestamp": 1738065307565,
        #                 "funding": "0",
        #                 "pnl": "-0.39547479770461644"
        #             }
        #         ],
        #         "pagination": {
        #             "num_pages": 1,
        #             "count": 3
        #         }
        #     },
        #     "id": "524b817f-2108-467f-8795-511066f4acec"
        # }
        #
        result = self.safe_dict(response, 'result', {})
        page = self.safe_integer(params, 'page')
        if page is not None:
            pagination = self.safe_dict(result, 'pagination')
            currentPage = self.safe_integer(pagination, 'num_pages')
            if page > currentPage:
                return []
        events = self.safe_list(result, 'events', [])
        return self.parse_incomes(events, market, since, limit)

    def parse_income(self, income, market: Market = None):
        #
        # {
        #     "instrument_name": "BTC-PERP",
        #     "timestamp": 1738065307565,
        #     "funding": "0",
        #     "pnl": "-0.39547479770461644"
        # }
        #
        marketId = self.safe_string(income, 'instrument_name')
        symbol = self.safe_symbol(marketId, market)
        rate = self.safe_string(income, 'funding')
        code = self.safe_currency_code('USDC')
        timestamp = self.safe_integer(income, 'timestamp')
        return {
            'info': income,
            'symbol': symbol,
            'code': code,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'id': None,
            'amount': None,
            'rate': rate,
        }

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

        https://docs.derive.xyz/reference/post_private-get-all-portfolios

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        deriveWalletAddress = None
        deriveWalletAddress, params = self.handle_derive_wallet_address('fetchBalance', params)
        request = {
            'wallet': deriveWalletAddress,
        }
        response = await self.privatePostGetAllPortfolios(self.extend(request, params))
        #
        # {
        #     "result": [{
        #             "subaccount_id": 130837,
        #             "label": "",
        #             "currency": "all",
        #             "margin_type": "SM",
        #             "is_under_liquidation": False,
        #             "positions_value": "0",
        #             "collaterals_value": "318.0760325000001103035174310207366943359375",
        #             "subaccount_value": "318.0760325000001103035174310207366943359375",
        #             "positions_maintenance_margin": "0",
        #             "positions_initial_margin": "0",
        #             "collaterals_maintenance_margin": "238.557024375000082727638073265552520751953125",
        #             "collaterals_initial_margin": "190.845619500000083235136116854846477508544921875",
        #             "maintenance_margin": "238.557024375000082727638073265552520751953125",
        #             "initial_margin": "190.845619500000083235136116854846477508544921875",
        #             "open_orders_margin": "0",
        #             "projected_margin_change": "0",
        #             "open_orders": [],
        #             "positions": [],
        #             "collaterals": [
        #                 {
        #                     "asset_type": "erc20",
        #                     "asset_name": "ETH",
        #                     "currency": "ETH",
        #                     "amount": "0.1",
        #                     "mark_price": "3180.760325000000438272",
        #                     "mark_value": "318.0760325000001103035174310207366943359375",
        #                     "cumulative_interest": "0",
        #                     "pending_interest": "0",
        #                     "initial_margin": "190.845619500000083235136116854846477508544921875",
        #                     "maintenance_margin": "238.557024375000082727638073265552520751953125",
        #                     "realized_pnl": "0",
        #                     "average_price": "3184.891931",
        #                     "unrealized_pnl": "-0.413161",
        #                     "total_fees": "0",
        #                     "average_price_excl_fees": "3184.891931",
        #                     "realized_pnl_excl_fees": "0",
        #                     "unrealized_pnl_excl_fees": "-0.413161",
        #                     "open_orders_margin": "0",
        #                     "creation_timestamp": 1736860533493
        #                 }
        #             ]
        #     }],
        #     "id": "27b9a64e-3379-4ce6-a126-9fb941c4a970"
        # }
        #
        result = self.safe_list(response, 'result')
        return self.parse_balance(result)

    def parse_balance(self, response) -> Balances:
        result: dict = {
            'info': response,
        }
        for i in range(0, len(response)):
            subaccount = response[i]
            collaterals = self.safe_list(subaccount, 'collaterals', [])
            for j in range(0, len(collaterals)):
                balance = collaterals[j]
                code = self.safe_currency_code(self.safe_string(balance, 'currency'))
                account = self.safe_dict(result, code)
                if account is None:
                    account = self.account()
                    account['total'] = self.safe_string(balance, 'amount')
                else:
                    amount = self.safe_string(balance, 'amount')
                    account['total'] = Precise.string_add(account['total'], amount)
                result[code] = account
        return self.safe_balance(result)

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account

        https://docs.derive.xyz/reference/post_private-get-deposit-history

        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchDeposits', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        if since is not None:
            request['start_timestamp'] = since
        response = await self.privatePostGetDepositHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "events": [
        #             {
        #                 "timestamp": 1736860533599,
        #                 "transaction_id": "f2069395-ec00-49f5-925a-87202a5d240f",
        #                 "asset": "ETH",
        #                 "amount": "0.1",
        #                 "tx_status": "settled",
        #                 "tx_hash": "0xeda21a315c59302a19c42049b4cef05a10b685302b6cc3edbaf49102d91166d4",
        #                 "error_log": {}
        #             }
        #         ]
        #     },
        #     "id": "ceebc730-22ab-40cd-9941-33ceb2a74389"
        # }
        #
        currency = self.safe_currency(code)
        result = self.safe_dict(response, 'result', {})
        events = self.safe_list(result, 'events')
        return self.parse_transactions(events, currency, since, limit, params)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account

        https://docs.derive.xyz/reference/post_private-get-withdrawal-history

        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.subaccount_id]: *required* the subaccount id
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        subaccountId = None
        subaccountId, params = self.handle_derive_subaccount_id('fetchWithdrawals', params)
        request: dict = {
            'subaccount_id': subaccountId,
        }
        if since is not None:
            request['start_timestamp'] = since
        response = await self.privatePostGetWithdrawalHistory(self.extend(request, params))
        #
        # {
        #     "result": {
        #         "events": [
        #             {
        #                 "timestamp": 1736860533599,
        #                 "transaction_id": "f2069395-ec00-49f5-925a-87202a5d240f",
        #                 "asset": "ETH",
        #                 "amount": "0.1",
        #                 "tx_status": "settled",
        #                 "tx_hash": "0xeda21a315c59302a19c42049b4cef05a10b685302b6cc3edbaf49102d91166d4",
        #                 "error_log": {}
        #             }
        #         ]
        #     },
        #     "id": "ceebc730-22ab-40cd-9941-33ceb2a74389"
        # }
        #
        currency = self.safe_currency(code)
        result = self.safe_dict(response, 'result', {})
        events = self.safe_list(result, 'events')
        return self.parse_transactions(events, currency, since, limit, params)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # {
        #     "timestamp": 1736860533599,
        #     "transaction_id": "f2069395-ec00-49f5-925a-87202a5d240f",
        #     "asset": "ETH",
        #     "amount": "0.1",
        #     "tx_status": "settled",
        #     "tx_hash": "0xeda21a315c59302a19c42049b4cef05a10b685302b6cc3edbaf49102d91166d4",
        #     "error_log": {}
        # }
        #
        code = self.safe_string(transaction, 'asset')
        timestamp = self.safe_integer(transaction, 'timestamp')
        txId = self.safe_string(transaction, 'tx_hash')
        if txId == '0x0':
            txId = None
        return {
            'info': transaction,
            'id': None,
            'txid': txId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': None,
            'addressFrom': None,
            'addressTo': None,
            'tag': None,
            'tagFrom': None,
            'tagTo': None,
            'type': None,
            'amount': self.safe_number(transaction, 'amount'),
            'currency': code,
            'status': self.parse_transaction_status(self.safe_string(transaction, 'tx_status')),
            'updated': None,
            'comment': None,
            'internal': None,
            'fee': None,
            'network': None,
        }

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'settled': 'ok',
            'reverted': 'failed',
        }
        return self.safe_string(statuses, status, status)

    def handle_derive_subaccount_id(self, methodName: str, params: dict):
        derivesubAccountId = None
        derivesubAccountId, params = self.handle_option_and_params(params, methodName, 'subaccount_id')
        if (derivesubAccountId is not None) and (derivesubAccountId != ''):
            self.options['subaccount_id'] = derivesubAccountId  # saving in options
            return [derivesubAccountId, params]
        optionsWallet = self.safe_string(self.options, 'subaccount_id')
        if optionsWallet is not None:
            return [optionsWallet, params]
        raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a subaccount_id parameter inside \'params\' or exchange.options[\'subaccount_id\']=ID.')

    def handle_derive_wallet_address(self, methodName: str, params: dict):
        deriveWalletAddress = None
        deriveWalletAddress, params = self.handle_option_and_params(params, methodName, 'deriveWalletAddress')
        if (deriveWalletAddress is not None) and (deriveWalletAddress != ''):
            self.options['deriveWalletAddress'] = deriveWalletAddress  # saving in options
            return [deriveWalletAddress, params]
        optionsWallet = self.safe_string(self.options, 'deriveWalletAddress')
        if optionsWallet is not None:
            return [optionsWallet, params]
        raise ArgumentsRequired(self.id + ' ' + methodName + '() requires a deriveWalletAddress parameter inside \'params\' or exchange.options[\'deriveWalletAddress\'] = ADDRESS, the address can find in HOME => Developers tab.')

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if not response:
            return None  # fallback to default error handler
        error = self.safe_dict(response, 'error')
        if error is not None:
            errorCode = self.safe_string(error, 'code')
            feedback = self.id + ' ' + self.json(response)
            self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            raise ExchangeError(feedback)
        return None

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/' + path
        if method == 'POST':
            headers = {
                'Content-Type': 'application/json',
            }
            if api == 'private':
                now = str(self.milliseconds())
                signature = self.sign_message(now, self.privateKey)
                headers['X-LyraWallet'] = self.safe_string(self.options, 'deriveWalletAddress')
                headers['X-LyraTimestamp'] = now
                headers['X-LyraSignature'] = signature
            body = self.json(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
