<?php

namespace ccxt;

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

use Exception; // a common import
use ccxt\abstract\paradex as Exchange;

class paradex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'paradex',
            'name' => 'Paradex',
            'countries' => array(),
            'version' => 'v1',
            'rateLimit' => 50,
            'certified' => false,
            'pro' => true,
            'dex' => true,
            'has' => array(
                'CORS' => null,
                'spot' => false,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'cancelAllOrders' => true,
                'cancelAllOrdersAfter' => false,
                'cancelOrder' => false,
                'cancelOrders' => false,
                'cancelOrdersForSymbols' => false,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createMarketBuyOrderWithCost' => false,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => false,
                'createReduceOnlyOrder' => false,
                'createStopOrder' => true,
                'createTriggerOrder' => true,
                'editOrder' => false,
                'fetchAccounts' => false,
                'fetchAllGreeks' => true,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledOrders' => false,
                'fetchClosedOrders' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => false,
                'fetchDepositAddress' => false,
                'fetchDepositAddresses' => false,
                'fetchDeposits' => true,
                'fetchDepositWithdrawFee' => false,
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchGreeks' => true,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchLedger' => false,
                'fetchLeverage' => true,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => true,
                'fetchMarginMode' => true,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyLiquidations' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchOrderTrades' => false,
                'fetchPosition' => true,
                'fetchPositionMode' => false,
                'fetchPositions' => true,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                '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' => true,
                'setMarginMode' => true,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => false,
            ),
            'timeframes' => array(
                '1m' => 1,
                '3m' => 3,
                '5m' => 5,
                '15m' => 15,
                '30m' => 30,
                '1h' => 60,
            ),
            'hostname' => 'paradex.trade',
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/84628770-784e-4ec4-a759-ec2fbb2244ea',
                'api' => array(
                    'v1' => 'https://api.prod.{hostname}/v1',
                ),
                'test' => array(
                    'v1' => 'https://api.testnet.{hostname}/v1',
                ),
                'www' => 'https://www.paradex.trade/',
                'doc' => 'https://docs.api.testnet.paradex.trade/',
                'fees' => 'https://docs.paradex.trade/getting-started/trading-fees',
                'referral' => 'https://app.paradex.trade/r/ccxt24',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'bbo/{market}' => 1,
                        'funding/data' => 1,
                        'markets' => 1,
                        'markets/klines' => 1,
                        'markets/summary' => 1,
                        'orderbook/{market}' => 1,
                        'insurance' => 1,
                        'referrals/config' => 1,
                        'system/config' => 1,
                        'system/state' => 1,
                        'system/time' => 1,
                        'trades' => 1,
                        'vaults' => 1,
                        'vaults/balance' => 1,
                        'vaults/config' => 1,
                        'vaults/history' => 1,
                        'vaults/positions' => 1,
                        'vaults/summary' => 1,
                        'vaults/transfers' => 1,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'account' => 1,
                        'account/info' => 1,
                        'account/history' => 1,
                        'account/margin' => 1,
                        'account/profile' => 1,
                        'account/subaccounts' => 1,
                        'balance' => 1,
                        'fills' => 1,
                        'funding/payments' => 1,
                        'positions' => 1,
                        'tradebusts' => 1,
                        'transactions' => 1,
                        'liquidations' => 1,
                        'orders' => 1,
                        'orders-history' => 1,
                        'orders/by_client_id/{client_id}' => 1,
                        'orders/{order_id}' => 1,
                        'points_data/{market}/{program}' => 1,
                        'referrals/qr-code' => 1,
                        'referrals/summary' => 1,
                        'transfers' => 1,
                        'algo/orders' => 1,
                        'algo/orders-history' => 1,
                        'algo/orders/{algo_id}' => 1,
                        'vaults/account-summary' => 1,
                    ),
                    'post' => array(
                        'account/margin/{market}' => 1,
                        'account/profile/max_slippage' => 1,
                        'account/profile/referral_code' => 1,
                        'account/profile/username' => 1,
                        'auth' => 1,
                        'onboarding' => 1,
                        'orders' => 1,
                        'orders/batch' => 1,
                        'algo/orders' => 1,
                        'vaults' => 1,
                    ),
                    'put' => array(
                        'orders/{order_id}' => 1,
                    ),
                    'delete' => array(
                        'orders' => 1,
                        'orders/by_client_id/{client_id}' => 1,
                        'orders/{order_id}' => 1,
                        'algo/orders/{algo_id}' => 1,
                    ),
                ),
            ),
            'fees' => array(
                'swap' => array(
                    'taker' => $this->parse_number('0.0002'),
                    'maker' => $this->parse_number('0.0002'),
                ),
                'spot' => array(
                    'taker' => $this->parse_number('0.0002'),
                    'maker' => $this->parse_number('0.0002'),
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => false,
                'secret' => false,
                'walletAddress' => true,
                'privateKey' => true,
            ),
            'exceptions' => array(
                'exact' => array(
                    'VALIDATION_ERROR' => '\\ccxt\\AuthenticationError',
                    'BINDING_ERROR' => '\\ccxt\\OperationRejected',
                    'INTERNAL_ERROR' => '\\ccxt\\ExchangeError',
                    'NOT_FOUND' => '\\ccxt\\BadRequest',
                    'SERVICE_UNAVAILABLE' => '\\ccxt\\ExchangeError',
                    'INVALID_REQUEST_PARAMETER' => '\\ccxt\\BadRequest',
                    'ORDER_ID_NOT_FOUND' => '\\ccxt\\InvalidOrder',
                    'ORDER_IS_CLOSED' => '\\ccxt\\InvalidOrder',
                    'ORDER_IS_NOT_OPEN_YET' => '\\ccxt\\InvalidOrder',
                    'CLIENT_ORDER_ID_NOT_FOUND' => '\\ccxt\\InvalidOrder',
                    'DUPLICATED_CLIENT_ID' => '\\ccxt\\InvalidOrder',
                    'INVALID_PRICE_PRECISION' => '\\ccxt\\OperationRejected',
                    'INVALID_SYMBOL' => '\\ccxt\\OperationRejected',
                    'INVALID_TOKEN' => '\\ccxt\\OperationRejected',
                    'INVALID_ETHEREUM_ADDRESS' => '\\ccxt\\OperationRejected',
                    'INVALID_ETHEREUM_SIGNATURE' => '\\ccxt\\OperationRejected',
                    'INVALID_STARKNET_ADDRESS' => '\\ccxt\\OperationRejected',
                    'INVALID_STARKNET_SIGNATURE' => '\\ccxt\\OperationRejected',
                    'STARKNET_SIGNATURE_VERIFICATION_FAILED' => '\\ccxt\\AuthenticationError',
                    'BAD_STARKNET_REQUEST' => '\\ccxt\\BadRequest',
                    'ETHEREUM_SIGNER_MISMATCH' => '\\ccxt\\BadRequest',
                    'ETHEREUM_HASH_MISMATCH' => '\\ccxt\\BadRequest',
                    'NOT_ONBOARDED' => '\\ccxt\\BadRequest',
                    'INVALID_TIMESTAMP' => '\\ccxt\\BadRequest',
                    'INVALID_SIGNATURE_EXPIRATION' => '\\ccxt\\AuthenticationError',
                    'ACCOUNT_NOT_FOUND' => '\\ccxt\\AuthenticationError',
                    'INVALID_ORDER_SIGNATURE' => '\\ccxt\\AuthenticationError',
                    'PUBLIC_KEY_INVALID' => '\\ccxt\\BadRequest',
                    'UNAUTHORIZED_ETHEREUM_ADDRESS' => '\\ccxt\\BadRequest',
                    'ETHEREUM_ADDRESS_ALREADY_ONBOARDED' => '\\ccxt\\BadRequest',
                    'MARKET_NOT_FOUND' => '\\ccxt\\BadRequest',
                    'ALLOWLIST_ENTRY_NOT_FOUND' => '\\ccxt\\BadRequest',
                    'USERNAME_IN_USE' => '\\ccxt\\AuthenticationError',
                    'GEO_IP_BLOCK' => '\\ccxt\\PermissionDenied',
                    'ETHEREUM_ADDRESS_BLOCKED' => '\\ccxt\\PermissionDenied',
                    'PROGRAM_NOT_FOUND' => '\\ccxt\\BadRequest',
                    'INVALID_DASHBOARD' => '\\ccxt\\OperationRejected',
                    'MARKET_NOT_OPEN' => '\\ccxt\\BadRequest',
                    'INVALID_REFERRAL_CODE' => '\\ccxt\\OperationRejected',
                    'PARENT_ADDRESS_ALREADY_ONBOARDED' => '\\ccxt\\BadRequest',
                    'INVALID_PARENT_ACCOUNT' => '\\ccxt\\OperationRejected',
                    'INVALID_VAULT_OPERATOR_CHAIN' => '\\ccxt\\OperationRejected',
                    'VAULT_OPERATOR_ALREADY_ONBOARDED' => '\\ccxt\\OperationRejected',
                    'VAULT_NAME_IN_USE' => '\\ccxt\\OperationRejected',
                    'BATCH_SIZE_OUT_OF_RANGE' => '\\ccxt\\OperationRejected',
                    'ISOLATED_MARKET_ACCOUNT_MISMATCH' => '\\ccxt\\OperationRejected',
                    'POINTS_SUMMARY_NOT_FOUND' => '\\ccxt\\OperationRejected',
                    '-32700' => '\\ccxt\\BadRequest', // Parse error
                    '-32600' => '\\ccxt\\BadRequest', // Invalid request
                    '-32601' => '\\ccxt\\BadRequest', // Method not found
                    '-32602' => '\\ccxt\\BadRequest', // Invalid parameterss
                    '-32603' => '\\ccxt\\ExchangeError', // Internal error
                    '100' => '\\ccxt\\BadRequest', // Method error
                    '40110' => '\\ccxt\\AuthenticationError', // Malformed Bearer Token
                    '40111' => '\\ccxt\\AuthenticationError', // Invalid Bearer Token
                    '40112' => '\\ccxt\\PermissionDenied', // Geo IP blocked
                ),
                'broad' => array(
                    'missing or malformed jwt' => '\\ccxt\\AuthenticationError',
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'commonCurrencies' => array(
            ),
            'options' => array(
                'paradexAccount' => null, // add array("privateKey" => "copy Paradex Private Key from UI", "publicKey" => "used when onboard (optional)", "address" => "copy Paradex Address from UI")
                'broker' => 'CCXT',
            ),
            'features' => array(
                'spot' => null,
                'forSwap' => array(
                    'sandbox' => true,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerDirection' => true, // todo
                        'triggerPriceType' => null,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => false,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => true, // todo
                        'iceberg' => false,
                    ),
                    'createOrders' => null, // todo
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 100, // todo
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => null, // todo
                    'fetchOHLCV' => array(
                        'limit' => null, // todo by from/to
                    ),
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forSwap',
                    ),
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://docs.api.testnet.paradex.trade/#get-system-time-unix-milliseconds
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        $response = $this->publicGetSystemTime ($params);
        //
        //     {
        //         "server_time" => "1681493415023"
        //     }
        //
        return $this->safe_integer($response, 'server_time');
    }

    public function fetch_status($params = array ()) {
        /**
         * the latest known information on the availability of the exchange API
         *
         * @see https://docs.api.testnet.paradex.trade/#get-system-state
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-$status-structure $status structure~
         */
        $response = $this->publicGetSystemState ($params);
        //
        //     {
        //         "status" => "ok"
        //     }
        //
        $status = $this->safe_string($response, 'status');
        return array(
            'status' => ($status === 'ok') ? 'ok' : 'maintenance',
            'updated' => null,
            'eta' => null,
            'url' => null,
            'info' => $response,
        );
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves $data on all markets for bitget
         *
         * @see https://docs.api.testnet.paradex.trade/#list-available-markets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market $data
         */
        $response = $this->publicGetMarkets ($params);
        //
        //     {
        //         "results" => array(
        //             {
        //                 "symbol" => "BODEN-USD-PERP",
        //                 "base_currency" => "BODEN",
        //                 "quote_currency" => "USD",
        //                 "settlement_currency" => "USDC",
        //                 "order_size_increment" => "1",
        //                 "price_tick_size" => "0.00001",
        //                 "min_notional" => "200",
        //                 "open_at" => 1717065600000,
        //                 "expiry_at" => 0,
        //                 "asset_kind" => "PERP",
        //                 "position_limit" => "2000000",
        //                 "price_bands_width" => "0.2",
        //                 "max_open_orders" => 50,
        //                 "max_funding_rate" => "0.05",
        //                 "delta1_cross_margin_params" => array(
        //                     "imf_base" => "0.2",
        //                     "imf_shift" => "180000",
        //                     "imf_factor" => "0.00071",
        //                     "mmf_factor" => "0.5"
        //                 ),
        //                 "price_feed_id" => "9LScEHse1ioZt2rUuhwiN6bmYnqpMqvZkQJDNUpxVHN5",
        //                 "oracle_ewma_factor" => "0.14999987905913592",
        //                 "max_order_size" => "520000",
        //                 "max_funding_rate_change" => "0.0005",
        //                 "max_tob_spread" => "0.2"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results');
        return $this->parse_markets($data);
    }

    public function parse_market(array $market): array {
        //
        //     {
        //         "symbol" => "BODEN-USD-PERP",
        //         "base_currency" => "BODEN",
        //         "quote_currency" => "USD",
        //         "settlement_currency" => "USDC",
        //         "order_size_increment" => "1",
        //         "price_tick_size" => "0.00001",
        //         "min_notional" => "200",
        //         "open_at" => 1717065600000,
        //         "expiry_at" => 0,
        //         "asset_kind" => "PERP",
        //         "position_limit" => "2000000",
        //         "price_bands_width" => "0.2",
        //         "max_open_orders" => 50,
        //         "max_funding_rate" => "0.05",
        //         "delta1_cross_margin_params" => array(
        //             "imf_base" => "0.2",
        //             "imf_shift" => "180000",
        //             "imf_factor" => "0.00071",
        //             "mmf_factor" => "0.5"
        //         ),
        //         "price_feed_id" => "9LScEHse1ioZt2rUuhwiN6bmYnqpMqvZkQJDNUpxVHN5",
        //         "oracle_ewma_factor" => "0.14999987905913592",
        //         "max_order_size" => "520000",
        //         "max_funding_rate_change" => "0.0005",
        //         "max_tob_spread" => "0.2"
        //     }
        //
        // {
        //     "symbol":"BTC-USD-96000-C",
        //     "base_currency":"BTC",
        //     "quote_currency":"USD",
        //     "settlement_currency":"USDC",
        //     "order_size_increment":"0.001",
        //     "price_tick_size":"0.01",
        //     "min_notional":"100",
        //     "open_at":"1736764200000",
        //     "expiry_at":"0",
        //     "asset_kind":"PERP_OPTION",
        //     "market_kind":"cross",
        //     "position_limit":"10",
        //     "price_bands_width":"0.05",
        //     "iv_bands_width":"0.05",
        //     "max_open_orders":"100",
        //     "max_funding_rate":"0.02",
        //     "option_cross_margin_params":{
        //        "imf":array(
        //           "long_itm":"0.2",
        //           "short_itm":"0.15",
        //           "short_otm":"0.1",
        //           "short_put_cap":"0.5",
        //           "premium_multiplier":"1"
        //        ),
        //        "mmf":array(
        //           "long_itm":"0.1",
        //           "short_itm":"0.075",
        //           "short_otm":"0.05",
        //           "short_put_cap":"0.5",
        //           "premium_multiplier":"0.5"
        //        }
        //     ),
        //     "price_feed_id":"GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU",
        //     "oracle_ewma_factor":"0.20000046249626113",
        //     "max_order_size":"2",
        //     "max_funding_rate_change":"0.02",
        //     "max_tob_spread":"0.2",
        //     "interest_rate":"0.0001",
        //     "clamp_rate":"0.02",
        //     "option_type":"CALL",
        //     "strike_price":"96000",
        //     "funding_period_hours":"24",
        //     "tags":array(
        //     )
        //  }
        //
        $assetKind = $this->safe_string($market, 'asset_kind');
        $isOption = ($assetKind === 'PERP_OPTION');
        $type = ($isOption) ? 'option' : 'swap';
        $isSwap = ($type === 'swap');
        $marketId = $this->safe_string($market, 'symbol');
        $quoteId = $this->safe_string($market, 'quote_currency');
        $baseId = $this->safe_string($market, 'base_currency');
        $quote = $this->safe_currency_code($quoteId);
        $base = $this->safe_currency_code($baseId);
        $settleId = $this->safe_string($market, 'settlement_currency');
        $settle = $this->safe_currency_code($settleId);
        $symbol = $base . '/' . $quote . ':' . $settle;
        $expiry = $this->safe_integer($market, 'expiry_at');
        $optionType = $this->safe_string($market, 'option_type');
        $strikePrice = $this->safe_string($market, 'strike_price');
        $takerFee = $this->parse_number('0.0003');
        $makerFee = $this->parse_number('-0.00005');
        if ($isOption) {
            $optionTypeSuffix = ($optionType === 'CALL') ? 'C' : 'P';
            $symbol = $symbol . '-' . $strikePrice . '-' . $optionTypeSuffix;
            $makerFee = $this->parse_number('0.0003');
        } else {
            $expiry = null;
        }
        return $this->safe_market_structure(array(
            'id' => $marketId,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $type,
            'spot' => false,
            'margin' => null,
            'swap' => $isSwap,
            'future' => false,
            'option' => $isOption,
            'active' => $this->safe_bool($market, 'enableTrading'),
            'contract' => true,
            'linear' => true,
            'inverse' => false,
            'taker' => $takerFee,
            'maker' => $makerFee,
            'contractSize' => $this->parse_number('1'),
            'expiry' => $expiry,
            'expiryDatetime' => ($expiry === 0) ? null : $this->iso8601($expiry),
            'strike' => $this->parse_number($strikePrice),
            'optionType' => $this->safe_string_lower($market, 'option_type'),
            'precision' => array(
                'amount' => $this->safe_number($market, 'order_size_increment'),
                'price' => $this->safe_number($market, 'price_tick_size'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => null,
                    'max' => $this->safe_number($market, 'max_order_size'),
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'min_notional'),
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick $data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://docs.api.testnet.paradex.trade/#ohlcv-for-a-$symbol
         *
         * @param {string} $symbol unified $symbol of the $market to fetch OHLCV $data for
         * @param {string} $timeframe the length of time each candle represents
         * @param {int} [$since] timestamp in ms of the earliest candle to fetch
         * @param {int} [$limit] the maximum amount of candles to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'resolution' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
            'symbol' => $market['id'],
        );
        $now = $this->milliseconds();
        $duration = $this->parse_timeframe($timeframe);
        $until = $this->safe_integer_2($params, 'until', 'till', $now);
        $params = $this->omit($params, array( 'until', 'till' ));
        if ($since !== null) {
            $request['start_at'] = $since;
            if ($limit !== null) {
                $request['end_at'] = $this->sum($since, $duration * ($limit + 1) * 1000) - 1;
            } else {
                $request['end_at'] = $until;
            }
        } else {
            $request['end_at'] = $until;
            if ($limit !== null) {
                $request['start_at'] = $until - $duration * ($limit + 1) * 1000 + 1;
            } else {
                $request['start_at'] = $until - $duration * 101 * 1000 + 1;
            }
        }
        $response = $this->publicGetMarketsKlines ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             array(
        //                 1720071900000,
        //                 58961.3,
        //                 58961.3,
        //                 58961.3,
        //                 58961.3,
        //                 1591
        //             )
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     array(
        //         1720071900000,
        //         58961.3,
        //         58961.3,
        //         58961.3,
        //         58961.3,
        //         1591
        //     )
        //
        return array(
            $this->safe_integer($ohlcv, 0),
            $this->safe_number($ohlcv, 1),
            $this->safe_number($ohlcv, 2),
            $this->safe_number($ohlcv, 3),
            $this->safe_number($ohlcv, 4),
            $this->safe_number($ohlcv, 5),
        );
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
         *
         * @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
         *
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $request = array(
            'market' => 'ALL',
        );
        $response = $this->publicGetMarketsSummary ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             {
        //                 "symbol" => "BTC-USD-PERP",
        //                 "oracle_price" => "68465.17449906",
        //                 "mark_price" => "68465.17449906",
        //                 "last_traded_price" => "68495.1",
        //                 "bid" => "68477.6",
        //                 "ask" => "69578.2",
        //                 "volume_24h" => "5815541.397939004",
        //                 "total_volume" => "584031465.525259686",
        //                 "created_at" => 1718170156580,
        //                 "underlying_price" => "67367.37268422",
        //                 "open_interest" => "162.272",
        //                 "funding_rate" => "0.01629574927887",
        //                 "price_change_rate_24h" => "0.009032"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        return $this->parse_tickers($data, $symbols);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price $ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the $ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        $response = $this->publicGetMarketsSummary ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             {
        //                 "symbol" => "BTC-USD-PERP",
        //                 "oracle_price" => "68465.17449906",
        //                 "mark_price" => "68465.17449906",
        //                 "last_traded_price" => "68495.1",
        //                 "bid" => "68477.6",
        //                 "ask" => "69578.2",
        //                 "volume_24h" => "5815541.397939004",
        //                 "total_volume" => "584031465.525259686",
        //                 "created_at" => 1718170156580,
        //                 "underlying_price" => "67367.37268422",
        //                 "open_interest" => "162.272",
        //                 "funding_rate" => "0.01629574927887",
        //                 "price_change_rate_24h" => "0.009032"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        $ticker = $this->safe_dict($data, 0, array());
        return $this->parse_ticker($ticker, $market);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "symbol" => "BTC-USD-PERP",
        //         "oracle_price" => "68465.17449906",
        //         "mark_price" => "68465.17449906",
        //         "last_traded_price" => "68495.1",
        //         "bid" => "68477.6",
        //         "ask" => "69578.2",
        //         "volume_24h" => "5815541.397939004",
        //         "total_volume" => "584031465.525259686",
        //         "created_at" => 1718170156581,
        //         "underlying_price" => "67367.37268422",
        //         "open_interest" => "162.272",
        //         "funding_rate" => "0.01629574927887",
        //         "price_change_rate_24h" => "0.009032"
        //     }
        //
        $percentage = $this->safe_string($ticker, 'price_change_rate_24h');
        if ($percentage !== null) {
            $percentage = Precise::string_mul($percentage, '100');
        }
        $last = $this->safe_string($ticker, 'last_traded_price');
        $marketId = $this->safe_string($ticker, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($ticker, 'created_at');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => null,
            'low' => null,
            'bid' => $this->safe_string($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_string($ticker, 'ask'),
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => $percentage,
            'average' => null,
            'baseVolume' => null,
            'quoteVolume' => $this->safe_string($ticker, 'volume_24h'),
            'markPrice' => $this->safe_string($ticker, 'mark_price'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://docs.api.testnet.paradex.trade/#get-$market-$orderbook
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the order book for
         * @param {int} [$limit] the maximum amount of order book entries to return
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array( 'market' => $market['id'] );
        $response = $this->publicGetOrderbookMarket ($this->extend($request, $params));
        //
        //     {
        //         "market" => "BTC-USD-PERP",
        //         "seq_no" => 14115975,
        //         "last_updated_at" => 1718172538340,
        //         "asks" => array(
        //             array(
        //                 "69578.2",
        //                 "3.019"
        //             )
        //         ),
        //         "bids" => array(
        //             array(
        //                 "68477.6",
        //                 "0.1"
        //             )
        //         )
        //     }
        //
        if ($limit !== null) {
            $request['depth'] = $limit;
        }
        $timestamp = $this->safe_integer($response, 'last_updated_at');
        $orderbook = $this->parse_order_book($response, $market['symbol'], $timestamp);
        $orderbook['nonce'] = $this->safe_integer($response, 'seq_no');
        return $orderbook;
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent $trades for a particular $symbol
         *
         * @see https://docs.api.testnet.paradex.trade/#trade-tape
         *
         * @param {string} $symbol unified $symbol of the $market to fetch $trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of $trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch $trades for
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
         */
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchTrades', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_cursor('fetchTrades', $symbol, $since, $limit, $params, 'next', 'cursor', null, 100);
        }
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        if ($since !== null) {
            $request['start_at'] = $since;
        }
        list($request, $params) = $this->handle_until_option('end_at', $request, $params);
        $response = $this->publicGetTrades ($this->extend($request, $params));
        //
        //     {
        //         "next" => "...",
        //         "prev" => "...",
        //         "results" => array(
        //             {
        //                 "id" => "1718154353750201703989430001",
        //                 "market" => "BTC-USD-PERP",
        //                 "side" => "BUY",
        //                 "size" => "0.026",
        //                 "price" => "69578.2",
        //                 "created_at" => 1718154353750,
        //                 "trade_type" => "FILL"
        //             }
        //         )
        //     }
        //
        $trades = $this->safe_list($response, 'results', array());
        for ($i = 0; $i < count($trades); $i++) {
            $trades[$i]['next'] = $this->safe_string($response, 'next');
        }
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //     {
        //         "id" => "1718154353750201703989430001",
        //         "market" => "BTC-USD-PERP",
        //         "side" => "BUY",
        //         "size" => "0.026",
        //         "price" => "69578.2",
        //         "created_at" => 1718154353750,
        //         "trade_type" => "FILL"
        //     }
        //
        // fetchMyTrades (private)
        //
        //     {
        //         "id" => "1718947571560201703986670001",
        //         "side" => "BUY",
        //         "liquidity" => "TAKER",
        //         "market" => "BTC-USD-PERP",
        //         "order_id" => "1718947571540201703992340000",
        //         "price" => "64852.9",
        //         "size" => "0.01",
        //         "fee" => "0.1945587",
        //         "fee_currency" => "USDC",
        //         "created_at" => 1718947571569,
        //         "remaining_size" => "0",
        //         "client_id" => "",
        //         "fill_type" => "FILL"
        //     }
        //
        $marketId = $this->safe_string($trade, 'market');
        $market = $this->safe_market($marketId, $market);
        $id = $this->safe_string($trade, 'id');
        $timestamp = $this->safe_integer($trade, 'created_at');
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'size');
        $side = $this->safe_string_lower($trade, 'side');
        $liability = $this->safe_string_lower($trade, 'liquidity', 'taker');
        $isTaker = $liability === 'taker';
        $takerOrMaker = ($isTaker) ? 'taker' : 'maker';
        $currencyId = $this->safe_string($trade, 'fee_currency');
        $code = $this->safe_currency_code($currencyId);
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $id,
            'order' => $this->safe_string($trade, 'order_id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'type' => null,
            'takerOrMaker' => $takerOrMaker,
            'side' => $side,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => array(
                'cost' => $this->safe_string($trade, 'fee'),
                'currency' => $code,
                'rate' => null,
            ),
        ), $market);
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        /**
         * retrieves the open $interest of a contract trading pair
         *
         * @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
         *
         * @param {string} $symbol unified CCXT $market $symbol
         * @param {array} [$params] exchange specific parameters
         * @return {array} an open $interest structurearray(@link https://docs.ccxt.com/#/?id=open-$interest-structure)
         */
        $this->load_markets();
        $market = $this->market($symbol);
        if (!$market['contract']) {
            throw new BadRequest($this->id . ' fetchOpenInterest() supports contract markets only');
        }
        $request = array(
            'market' => $market['id'],
        );
        $response = $this->publicGetMarketsSummary ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             {
        //                 "symbol" => "BTC-USD-PERP",
        //                 "oracle_price" => "68465.17449906",
        //                 "mark_price" => "68465.17449906",
        //                 "last_traded_price" => "68495.1",
        //                 "bid" => "68477.6",
        //                 "ask" => "69578.2",
        //                 "volume_24h" => "5815541.397939004",
        //                 "total_volume" => "584031465.525259686",
        //                 "created_at" => 1718170156580,
        //                 "underlying_price" => "67367.37268422",
        //                 "open_interest" => "162.272",
        //                 "funding_rate" => "0.01629574927887",
        //                 "price_change_rate_24h" => "0.009032"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        $interest = $this->safe_dict($data, 0, array());
        return $this->parse_open_interest($interest, $market);
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        //     {
        //         "symbol" => "BTC-USD-PERP",
        //         "oracle_price" => "68465.17449904",
        //         "mark_price" => "68465.17449906",
        //         "last_traded_price" => "68495.1",
        //         "bid" => "68477.6",
        //         "ask" => "69578.2",
        //         "volume_24h" => "5815541.397939004",
        //         "total_volume" => "584031465.525259686",
        //         "created_at" => 1718170156580,
        //         "underlying_price" => "67367.37268422",
        //         "open_interest" => "162.272",
        //         "funding_rate" => "0.01629574927887",
        //         "price_change_rate_24h" => "0.009032"
        //     }
        //
        $timestamp = $this->safe_integer($interest, 'created_at');
        $marketId = $this->safe_string($interest, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        return $this->safe_open_interest(array(
            'symbol' => $symbol,
            'openInterestAmount' => $this->safe_string($interest, 'open_interest'),
            'openInterestValue' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'info' => $interest,
        ), $market);
    }

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

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

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

    public function get_system_config() {
        $cachedConfig = $this->safe_dict($this->options, 'systemConfig');
        if ($cachedConfig !== null) {
            return $cachedConfig;
        }
        $response = $this->publicGetSystemConfig ();
        //
        // {
        //     "starknet_gateway_url" => "https://potc-testnet-sepolia.starknet.io",
        //     "starknet_fullnode_rpc_url" => "https://pathfinder.api.testnet.paradex.trade/rpc/v0_7",
        //     "starknet_chain_id" => "PRIVATE_SN_POTC_SEPOLIA",
        //     "block_explorer_url" => "https://voyager.testnet.paradex.trade/",
        //     "paraclear_address" => "0x286003f7c7bfc3f94e8f0af48b48302e7aee2fb13c23b141479ba00832ef2c6",
        //     "paraclear_decimals" => 8,
        //     "paraclear_account_proxy_hash" => "0x3530cc4759d78042f1b543bf797f5f3d647cde0388c33734cf91b7f7b9314a9",
        //     "paraclear_account_hash" => "0x41cb0280ebadaa75f996d8d92c6f265f6d040bb3ba442e5f86a554f1765244e",
        //     "oracle_address" => "0x2c6a867917ef858d6b193a0ff9e62b46d0dc760366920d631715d58baeaca1f",
        //     "bridged_tokens" => array(
        //         {
        //             "name" => "TEST USDC",
        //             "symbol" => "USDC",
        //             "decimals" => 6,
        //             "l1_token_address" => "0x29A873159D5e14AcBd63913D4A7E2df04570c666",
        //             "l1_bridge_address" => "0x8586e05adc0C35aa11609023d4Ae6075Cb813b4C",
        //             "l2_token_address" => "0x6f373b346561036d98ea10fb3e60d2f459c872b1933b50b21fe6ef4fda3b75e",
        //             "l2_bridge_address" => "0x46e9237f5408b5f899e72125dd69bd55485a287aaf24663d3ebe00d237fc7ef"
        //         }
        //     ),
        //     "l1_core_contract_address" => "0x582CC5d9b509391232cd544cDF9da036e55833Af",
        //     "l1_operator_address" => "0x11bACdFbBcd3Febe5e8CEAa75E0Ef6444d9B45FB",
        //     "l1_chain_id" => "11155111",
        //     "liquidation_fee" => "0.2"
        // }
        //
        $this->options['systemConfig'] = $response;
        return $response;
    }

    public function prepare_paradex_domain($l1 = false) {
        $systemConfig = $this->get_system_config();
        if ($l1 === true) {
            $l1D = array(
                'name' => 'Paradex',
                'chainId' => $systemConfig['l1_chain_id'],
                'version' => '1',
            );
            return $l1D;
        }
        $domain = array(
            'name' => 'Paradex',
            'chainId' => $systemConfig['starknet_chain_id'],
            'version' => 1,
        );
        return $domain;
    }

    public function retrieve_account() {
        $cachedAccount = $this->safe_dict($this->options, 'paradexAccount');
        if ($cachedAccount !== null) {
            return $cachedAccount;
        }
        $this->check_required_credentials();
        $systemConfig = $this->get_system_config();
        $domain = $this->prepare_paradex_domain(true);
        $messageTypes = array(
            'Constant' => array(
                array( 'name' => 'action', 'type' => 'string' ),
            ),
        );
        $message = array(
            'action' => 'STARK Key',
        );
        $msg = $this->eth_encode_structured_data($domain, $messageTypes, $message);
        $signature = $this->sign_message($msg, $this->privateKey);
        $account = $this->retrieve_stark_account(
            $signature,
            $systemConfig['paraclear_account_hash'],
            $systemConfig['paraclear_account_proxy_hash']
        );
        $this->options['paradexAccount'] = $account;
        return $account;
    }

    public function onboarding($params = array ()) {
        $account = $this->retrieve_account();
        $req = array(
            'action' => 'Onboarding',
        );
        $domain = $this->prepare_paradex_domain();
        $messageTypes = array(
            'Constant' => array(
                array( 'name' => 'action', 'type' => 'felt' ),
            ),
        );
        $msg = $this->starknet_encode_structured_data($domain, $messageTypes, $req, $account['address']);
        $signature = $this->starknet_sign($msg, $account['privateKey']);
        $params['signature'] = $signature;
        $params['account'] = $account['address'];
        $params['public_key'] = $account['publicKey'];
        $response = $this->privatePostOnboarding ($params);
        return $response;
    }

    public function authenticate_rest($params = array ()) {
        $cachedToken = $this->safe_string($this->options, 'authToken');
        $now = $this->nonce();
        if ($cachedToken !== null) {
            $cachedExpires = $this->safe_integer($this->options, 'expires');
            if ($now < $cachedExpires) {
                return $cachedToken;
            }
        }
        $account = $this->retrieve_account();
        // https://docs.paradex.trade/api-reference/general-information/authentication
        $expires = $now + 180;
        $req = array(
            'method' => 'POST',
            'path' => '/v1/auth',
            'body' => '',
            'timestamp' => $now,
            'expiration' => $expires,
        );
        $domain = $this->prepare_paradex_domain();
        $messageTypes = array(
            'Request' => array(
                array( 'name' => 'method', 'type' => 'felt' ),
                array( 'name' => 'path', 'type' => 'felt' ),
                array( 'name' => 'body', 'type' => 'felt' ),
                array( 'name' => 'timestamp', 'type' => 'felt' ),
                array( 'name' => 'expiration', 'type' => 'felt' ),
            ),
        );
        $msg = $this->starknet_encode_structured_data($domain, $messageTypes, $req, $account['address']);
        $signature = $this->starknet_sign($msg, $account['privateKey']);
        $params['signature'] = $signature;
        $params['account'] = $account['address'];
        $params['timestamp'] = $req['timestamp'];
        $params['expiration'] = $req['expiration'];
        $response = $this->privatePostAuth ($params);
        //
        // {
        //     jwt_token => "ooooccxtooootoooootheoooomoonooooo"
        // }
        //
        $token = $this->safe_string($response, 'jwt_token');
        $this->options['authToken'] = $token;
        $this->options['expires'] = $expires;
        return $token;
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // {
        //     "account" => "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
        //     "avg_fill_price" => "26000",
        //     "client_id" => "x1234",
        //     "cancel_reason" => "NOT_ENOUGH_MARGIN",
        //     "created_at" => 1681493746016,
        //     "flags" => array(
        //         "REDUCE_ONLY"
        //     ),
        //     "id" => "123456",
        //     "instruction" => "GTC",
        //     "last_updated_at" => 1681493746016,
        //     "market" => "BTC-USD-PERP",
        //     "price" => "26000",
        //     "published_at" => 1681493746016,
        //     "received_at" => 1681493746016,
        //     "remaining_size" => "0",
        //     "seq_no" => 1681471234972000000,
        //     "side" => "BUY",
        //     "size" => "0.05",
        //     "status" => "NEW",
        //     "stp" => "EXPIRE_MAKER",
        //     "timestamp" => 1681493746016,
        //     "trigger_price" => "26000",
        //     "type" => "MARKET"
        // }
        //
        $timestamp = $this->safe_integer($order, 'created_at');
        $orderId = $this->safe_string($order, 'id');
        $clientOrderId = $this->omit_zero($this->safe_string($order, 'client_id'));
        $marketId = $this->safe_string($order, 'market');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'size');
        $orderType = $this->safe_string($order, 'type');
        $cancelReason = $this->safe_string($order, 'cancel_reason');
        $status = $this->safe_string($order, 'status');
        if ($cancelReason !== null) {
            if ($cancelReason === 'NOT_ENOUGH_MARGIN' || $cancelReason === 'ORDER_EXCEEDS_POSITION_LIMIT') {
                $status = 'rejected';
            } else {
                $status = 'canceled';
            }
        }
        $side = $this->safe_string_lower($order, 'side');
        $average = $this->omit_zero($this->safe_string($order, 'avg_fill_price'));
        $remaining = $this->omit_zero($this->safe_string($order, 'remaining_size'));
        $lastUpdateTimestamp = $this->safe_integer($order, 'last_updated_at');
        $flags = $this->safe_list($order, 'flags', array());
        $reduceOnly = null;
        if (is_array($flags) && array_key_exists('REDUCE_ONLY', $flags)) {
            $reduceOnly = true;
        }
        return $this->safe_order(array(
            'id' => $orderId,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $lastUpdateTimestamp,
            'status' => $this->parse_order_status($status),
            'symbol' => $symbol,
            'type' => $this->parse_order_type($orderType),
            'timeInForce' => $this->parse_time_in_force($this->safe_string($order, 'instruction')),
            'postOnly' => null,
            'reduceOnly' => $reduceOnly,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $this->safe_string($order, 'trigger_price'),
            'takeProfitPrice' => null,
            'stopLossPrice' => null,
            'average' => $average,
            'amount' => $amount,
            'filled' => null,
            'remaining' => $remaining,
            'cost' => null,
            'trades' => null,
            'fee' => array(
                'cost' => null,
                'currency' => null,
            ),
            'info' => $order,
        ), $market);
    }

    public function parse_time_in_force(?string $timeInForce) {
        $timeInForces = array(
            'IOC' => 'IOC',
            'GTC' => 'GTC',
            'POST_ONLY' => 'PO',
        );
        return $this->safe_string($timeInForces, $timeInForce, null);
    }

    public function parse_order_status(?string $status) {
        if ($status !== null) {
            $statuses = array(
                'NEW' => 'open',
                'UNTRIGGERED' => 'open',
                'OPEN' => 'open',
                'CLOSED' => 'closed',
            );
            return $this->safe_string($statuses, $status, $status);
        }
        return $status;
    }

    public function parse_order_type(?string $type) {
        $types = array(
            'LIMIT' => 'limit',
            'MARKET' => 'market',
            'STOP_LIMIT' => 'limit',
            'STOP_MARKET' => 'market',
        );
        return $this->safe_string_lower($types, $type, $type);
    }

    public function convert_short_string(string $str) {
        // TODO => add stringToBase16 in exchange
        return '0x' . bin2hex(base64_decode(base64_encode($str)));
    }

    public function scale_number(string $num) {
        return Precise::string_mul($num, '100000000');
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade $order
         *
         * @see https://docs.api.prod.paradex.trade/#create-$order
         *
         * @param {string} $symbol unified $symbol of the $market to create an $order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the $order is to be fullfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {float} [$params->stopPrice] alias for $triggerPrice
         * @param {float} [$params->triggerPrice] The $price a trigger $order is triggered at
         * @param {float} [$params->stopLossPrice] the $price that a stop loss $order is triggered at
         * @param {float} [$params->takeProfitPrice] the $price that a take profit $order is triggered at
         * @param {string} [$params->timeInForce] "GTC", "IOC", or "POST_ONLY"
         * @param {bool} [$params->postOnly] true or false
         * @param {bool} [$params->reduceOnly] Ensures that the executed $order does not flip the opened position.
         * @param {string} [$params->clientOrderId] a unique id for the $order
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $reduceOnly = $this->safe_bool_2($params, 'reduceOnly', 'reduce_only');
        $orderType = strtoupper($type);
        $orderSide = strtoupper($side);
        $request = array(
            'market' => $market['id'],
            'side' => $orderSide,
            'type' => $orderType, // LIMIT/MARKET/STOP_LIMIT/STOP_MARKET,STOP_LOSS_MARKET,STOP_LOSS_LIMIT,TAKE_PROFIT_MARKET,TAKE_PROFIT_LIMIT
        );
        $triggerPrice = $this->safe_string_2($params, 'triggerPrice', 'stopPrice');
        $stopLossPrice = $this->safe_string($params, 'stopLossPrice');
        $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
        $isMarket = $orderType === 'MARKET';
        $isTakeProfitOrder = ($takeProfitPrice !== null);
        $isStopLossOrder = ($stopLossPrice !== null);
        $isStopOrder = ($triggerPrice !== null) || $isTakeProfitOrder || $isStopLossOrder;
        $timeInForce = $this->safe_string_upper($params, 'timeInForce');
        $postOnly = $this->is_post_only($isMarket, null, $params);
        if (!$isMarket) {
            if ($postOnly) {
                $request['instruction'] = 'POST_ONLY';
            } elseif ($timeInForce === 'ioc') {
                $request['instruction'] = 'IOC';
            }
        }
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
        if ($clientOrderId !== null) {
            $request['client_id'] = $clientOrderId;
        }
        $sizeString = '0';
        $stopPrice = null;
        if ($isStopOrder) {
            // flags => Reduce_Only must be provided for TPSL orders.
            if ($isMarket) {
                if ($isStopLossOrder) {
                    $stopPrice = $this->price_to_precision($symbol, $stopLossPrice);
                    $reduceOnly = true;
                    $request['type'] = 'STOP_LOSS_MARKET';
                } elseif ($isTakeProfitOrder) {
                    $stopPrice = $this->price_to_precision($symbol, $takeProfitPrice);
                    $reduceOnly = true;
                    $request['type'] = 'TAKE_PROFIT_MARKET';
                } else {
                    $stopPrice = $this->price_to_precision($symbol, $triggerPrice);
                    $sizeString = $this->amount_to_precision($symbol, $amount);
                    $request['type'] = 'STOP_MARKET';
                }
            } else {
                if ($isStopLossOrder) {
                    $stopPrice = $this->price_to_precision($symbol, $stopLossPrice);
                    $reduceOnly = true;
                    $request['type'] = 'STOP_LOSS_LIMIT';
                } elseif ($isTakeProfitOrder) {
                    $stopPrice = $this->price_to_precision($symbol, $takeProfitPrice);
                    $reduceOnly = true;
                    $request['type'] = 'TAKE_PROFIT_LIMIT';
                } else {
                    $stopPrice = $this->price_to_precision($symbol, $triggerPrice);
                    $sizeString = $this->amount_to_precision($symbol, $amount);
                    $request['type'] = 'STOP_LIMIT';
                }
            }
        } else {
            $sizeString = $this->amount_to_precision($symbol, $amount);
        }
        if ($stopPrice !== null) {
            $request['trigger_price'] = $stopPrice;
        }
        $request['size'] = $sizeString;
        if ($reduceOnly) {
            $request['flags'] = array(
                'REDUCE_ONLY',
            );
        }
        $params = $this->omit($params, array( 'reduceOnly', 'reduce_only', 'clOrdID', 'clientOrderId', 'client_order_id', 'postOnly', 'timeInForce', 'stopPrice', 'triggerPrice', 'stopLossPrice', 'takeProfitPrice' ));
        $account = $this->retrieve_account();
        $now = $this->nonce();
        $orderReq = array(
            'timestamp' => $now * 1000,
            'market' => $this->convert_short_string($request['market']),
            'side' => ($orderSide === 'BUY') ? '1' : '2',
            'orderType' => $this->convert_short_string($request['type']),
            'size' => $this->scale_number($request['size']),
            'price' => ($isMarket) ? '0' : $this->scale_number($request['price']),
        );
        $domain = $this->prepare_paradex_domain();
        $messageTypes = array(
            'Order' => array(
                array( 'name' => 'timestamp', 'type' => 'felt' ),
                array( 'name' => 'market', 'type' => 'felt' ),
                array( 'name' => 'side', 'type' => 'felt' ),
                array( 'name' => 'orderType', 'type' => 'felt' ),
                array( 'name' => 'size', 'type' => 'felt' ),
                array( 'name' => 'price', 'type' => 'felt' ),
            ),
        );
        $msg = $this->starknet_encode_structured_data($domain, $messageTypes, $orderReq, $account['address']);
        $signature = $this->starknet_sign($msg, $account['privateKey']);
        $request['signature'] = $signature;
        $request['signature_timestamp'] = $orderReq['timestamp'];
        $response = $this->privatePostOrders ($this->extend($request, $params));
        //
        // {
        //     "account" => "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
        //     "avg_fill_price" => "26000",
        //     "cancel_reason" => "NOT_ENOUGH_MARGIN",
        //     "client_id" => "x1234",
        //     "created_at" => 1681493746016,
        //     "flags" => array(
        //       "REDUCE_ONLY"
        //     ),
        //     "id" => "123456",
        //     "instruction" => "GTC",
        //     "last_updated_at" => 1681493746016,
        //     "market" => "BTC-USD-PERP",
        //     "price" => "26000",
        //     "published_at" => 1681493746016,
        //     "received_at" => 1681493746016,
        //     "remaining_size" => "0",
        //     "seq_no" => 1681471234972000000,
        //     "side" => "BUY",
        //     "size" => "0.05",
        //     "status" => "NEW",
        //     "stp" => "EXPIRE_MAKER",
        //     "timestamp" => 1681493746016,
        //     "trigger_price" => "26000",
        //     "type" => "MARKET"
        // }
        //
        $order = $this->parse_order($response, $market);
        return $order;
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://docs.api.prod.paradex.trade/#cancel-order
         * @see https://docs.api.prod.paradex.trade/#cancel-open-order-by-client-order-$id
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] a unique $id for the order
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $request = array();
        $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
        $response = null;
        if ($clientOrderId !== null) {
            $request['client_id'] = $clientOrderId;
            $response = $this->privateDeleteOrdersByClientIdClientId ($this->extend($request, $params));
        } else {
            $request['order_id'] = $id;
            $response = $this->privateDeleteOrdersOrderId ($this->extend($request, $params));
        }
        //
        // if success, no $response->..
        //
        return $this->parse_order($response);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders in a $market
         *
         * @see https://docs.api.prod.paradex.trade/#cancel-all-open-orders
         *
         * @param {string} $symbol unified $market $symbol of the $market to cancel orders in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
        }
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        $response = $this->privateDeleteOrders ($this->extend($request, $params));
        //
        // if success, no $response->..
        //
        return array( $this->safe_order(array( 'info' => $response )) );
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://docs.api.prod.paradex.trade/#get-order
         * @see https://docs.api.prod.paradex.trade/#get-order-by-client-$id
         *
         * @param {string} $id the order $id
         * @param {string} $symbol unified $symbol of the market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->clientOrderId] a unique $id for the order
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $request = array();
        $clientOrderId = $this->safe_string_n($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
        $params = $this->omit($params, array( 'clOrdID', 'clientOrderId', 'client_order_id' ));
        $response = null;
        if ($clientOrderId !== null) {
            $request['client_id'] = $clientOrderId;
            $response = $this->privateGetOrdersByClientIdClientId ($this->extend($request, $params));
        } else {
            $request['order_id'] = $id;
            $response = $this->privateGetOrdersOrderId ($this->extend($request, $params));
        }
        //
        //     {
        //         "id" => "1718941725080201704028870000",
        //         "account" => "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
        //         "market" => "BTC-USD-PERP",
        //         "side" => "SELL",
        //         "type" => "LIMIT",
        //         "size" => "10.153",
        //         "remaining_size" => "10.153",
        //         "price" => "70784.5",
        //         "status" => "CLOSED",
        //         "created_at" => 1718941725082,
        //         "last_updated_at" => 1718958002991,
        //         "timestamp" => 1718941724678,
        //         "cancel_reason" => "USER_CANCELED",
        //         "client_id" => "",
        //         "seq_no" => 1718958002991595738,
        //         "instruction" => "GTC",
        //         "avg_fill_price" => "",
        //         "stp" => "EXPIRE_TAKER",
        //         "received_at" => 1718958510959,
        //         "published_at" => 1718958510960,
        //         "flags" => array(),
        //         "trigger_price" => "0"
        //     }
        //
        return $this->parse_order($response);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple $orders made by the user
         *
         * @see https://docs.api.prod.paradex.trade/#get-$orders
         *
         * @param {string} $symbol unified $market $symbol of the $market $orders were made in
         * @param {int} [$since] the earliest time in ms to fetch $orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->side] 'buy' or 'sell'
         * @param {boolean} [$params->paginate] set to true if you want to fetch $orders with pagination
         * @param {int} $params->until timestamp in ms of the latest order to fetch
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOrders', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_cursor('fetchOrders', $symbol, $since, $limit, $params, 'next', 'cursor', null, 50);
        }
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market'] = $market['id'];
        }
        if ($since !== null) {
            $request['start_at'] = $since;
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        list($request, $params) = $this->handle_until_option('end_at', $request, $params);
        $response = $this->privateGetOrdersHistory ($this->extend($request, $params));
        //
        // {
        //     "next" => "eyJmaWx0ZXIiMsIm1hcmtlciI6eyJtYXJrZXIiOiIxNjc1NjUwMDE3NDMxMTAxNjk5N=",
        //     "prev" => "eyJmaWx0ZXIiOnsiTGltaXQiOjkwfSwidGltZSI6MTY4MTY3OTgzNzk3MTMwOTk1MywibWFya2VyIjp7Im1zMjExMD==",
        //     "results" => array(
        //       {
        //         "account" => "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
        //         "avg_fill_price" => "26000",
        //         "cancel_reason" => "NOT_ENOUGH_MARGIN",
        //         "client_id" => "x1234",
        //         "created_at" => 1681493746016,
        //         "flags" => array(
        //           "REDUCE_ONLY"
        //         ),
        //         "id" => "123456",
        //         "instruction" => "GTC",
        //         "last_updated_at" => 1681493746016,
        //         "market" => "BTC-USD-PERP",
        //         "price" => "26000",
        //         "published_at" => 1681493746016,
        //         "received_at" => 1681493746016,
        //         "remaining_size" => "0",
        //         "seq_no" => 1681471234972000000,
        //         "side" => "BUY",
        //         "size" => "0.05",
        //         "status" => "NEW",
        //         "stp" => "EXPIRE_MAKER",
        //         "timestamp" => 1681493746016,
        //         "trigger_price" => "26000",
        //         "type" => "MARKET"
        //       }
        //     )
        //   }
        //
        $orders = $this->safe_list($response, 'results', array());
        $paginationCursor = $this->safe_string($response, 'next');
        $ordersLength = count($orders);
        if (($paginationCursor !== null) && ($ordersLength > 0)) {
            $first = $orders[0];
            $first['next'] = $paginationCursor;
            $orders[0] = $first;
        }
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple $orders made by the user
         *
         * @see https://docs.api.prod.paradex.trade/#paradex-rest-api-$orders
         *
         * @param {string} $symbol unified $market $symbol of the $market $orders were made in
         * @param {int} [$since] the earliest time in ms to fetch $orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market'] = $market['id'];
        }
        $response = $this->privateGetOrders ($this->extend($request, $params));
        //
        //  {
        //     "results" => array(
        //       {
        //         "account" => "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
        //         "avg_fill_price" => "26000",
        //         "client_id" => "x1234",
        //         "cancel_reason" => "NOT_ENOUGH_MARGIN",
        //         "created_at" => 1681493746016,
        //         "flags" => array(
        //           "REDUCE_ONLY"
        //         ),
        //         "id" => "123456",
        //         "instruction" => "GTC",
        //         "last_updated_at" => 1681493746016,
        //         "market" => "BTC-USD-PERP",
        //         "price" => "26000",
        //         "published_at" => 1681493746016,
        //         "received_at" => 1681493746016,
        //         "remaining_size" => "0",
        //         "seq_no" => 1681471234972000000,
        //         "side" => "BUY",
        //         "size" => "0.05",
        //         "status" => "NEW",
        //         "stp" => "EXPIRE_MAKER",
        //         "timestamp" => 1681493746016,
        //         "trigger_price" => "26000",
        //         "type" => "MARKET"
        //       }
        //     )
        //   }
        //
        $orders = $this->safe_list($response, 'results', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://docs.api.prod.paradex.trade/#list-balances
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $response = $this->privateGetBalance ();
        //
        //     {
        //         "results" => array(
        //             {
        //                 "token" => "USDC",
        //                 "size" => "99980.2382266290601",
        //                 "last_updated_at" => 1718529757240
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        return $this->parse_balance($data);
    }

    public function parse_balance($response): array {
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($response); $i++) {
            $balance = $this->safe_dict($response, $i, array());
            $currencyId = $this->safe_string($balance, 'token');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['total'] = $this->safe_string($balance, 'size');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all $trades made by the user
         *
         * @see https://docs.api.prod.paradex.trade/#list-fills
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch $trades for
         * @param {int} [$limit] the maximum number of $trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [available parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @param {int} [$params->until] the latest time in ms to fetch entries for
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchMyTrades', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_cursor('fetchMyTrades', $symbol, $since, $limit, $params, 'next', 'cursor', null, 100);
        }
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market'] = $market['id'];
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        if ($since !== null) {
            $request['start_at'] = $since;
        }
        list($request, $params) = $this->handle_until_option('end_at', $request, $params);
        $response = $this->privateGetFills ($this->extend($request, $params));
        //
        //     {
        //         "next" => null,
        //         "prev" => null,
        //         "results" => array(
        //             {
        //                 "id" => "1718947571560201703986670001",
        //                 "side" => "BUY",
        //                 "liquidity" => "TAKER",
        //                 "market" => "BTC-USD-PERP",
        //                 "order_id" => "1718947571540201703992340000",
        //                 "price" => "64852.9",
        //                 "size" => "0.01",
        //                 "fee" => "0.1945587",
        //                 "fee_currency" => "USDC",
        //                 "created_at" => 1718947571569,
        //                 "remaining_size" => "0",
        //                 "client_id" => "",
        //                 "fill_type" => "FILL"
        //             }
        //         )
        //     }
        //
        $trades = $this->safe_list($response, 'results', array());
        for ($i = 0; $i < count($trades); $i++) {
            $trades[$i]['next'] = $this->safe_string($response, 'next');
        }
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_position(string $symbol, $params = array ()) {
        /**
         * fetch data on an open position
         *
         * @see https://docs.api.prod.paradex.trade/#list-open-$positions
         *
         * @param {string} $symbol unified $market $symbol of the $market the position is held in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $positions = $this->fetch_positions([ $market['symbol'] ], $params);
        return $this->safe_dict($positions, 0, array());
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open positions
         *
         * @see https://docs.api.prod.paradex.trade/#list-open-positions
         *
         * @param {string[]} [$symbols] list of unified market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $response = $this->privateGetPositions ();
        //
        //     {
        //         "results" => array(
        //             {
        //                 "id" => "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3-BTC-USD-PERP",
        //                 "market" => "BTC-USD-PERP",
        //                 "status" => "OPEN",
        //                 "side" => "LONG",
        //                 "size" => "0.01",
        //                 "average_entry_price" => "64839.96053748",
        //                 "average_entry_price_usd" => "64852.9",
        //                 "realized_pnl" => "0",
        //                 "unrealized_pnl" => "-2.39677214",
        //                 "unrealized_funding_pnl" => "-0.11214013",
        //                 "cost" => "648.39960537",
        //                 "cost_usd" => "648.529",
        //                 "cached_funding_index" => "35202.1002351",
        //                 "last_updated_at" => 1718950074249,
        //                 "last_fill_id" => "1718947571560201703986670001",
        //                 "seq_no" => 1718950074249176253,
        //                 "liquidation_price" => ""
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        return $this->parse_positions($data, $symbols);
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //         "id" => "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3-BTC-USD-PERP",
        //         "market" => "BTC-USD-PERP",
        //         "status" => "OPEN",
        //         "side" => "LONG",
        //         "size" => "0.01",
        //         "average_entry_price" => "64839.96053748",
        //         "average_entry_price_usd" => "64852.9",
        //         "realized_pnl" => "0",
        //         "unrealized_pnl" => "-2.39677214",
        //         "unrealized_funding_pnl" => "-0.11214013",
        //         "cost" => "648.39960537",
        //         "cost_usd" => "648.529",
        //         "cached_funding_index" => "35202.1002351",
        //         "last_updated_at" => 1718950074249,
        //         "last_fill_id" => "1718947571560201703986670001",
        //         "seq_no" => 1718950074249176253,
        //         "liquidation_price" => ""
        //     }
        //
        $marketId = $this->safe_string($position, 'market');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $side = $this->safe_string_lower($position, 'side');
        $quantity = $this->safe_string($position, 'size');
        if ($side !== 'long') {
            $quantity = Precise::string_mul('-1', $quantity);
        }
        $timestamp = $this->safe_integer($position, 'time');
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'id'),
            'symbol' => $symbol,
            'entryPrice' => $this->safe_string($position, 'average_entry_price'),
            'markPrice' => null,
            'notional' => null,
            'collateral' => $this->safe_string($position, 'cost'),
            'unrealizedPnl' => $this->safe_string($position, 'unrealized_pnl'),
            'side' => $side,
            'contracts' => $this->parse_number($quantity),
            'contractSize' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'hedged' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'leverage' => null,
            'liquidationPrice' => null,
            'marginRatio' => null,
            'marginMode' => null,
            'percentage' => null,
        ));
    }

    public function fetch_liquidations(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * retrieves the public liquidations of a trading pair
         *
         * @see https://docs.api.prod.paradex.trade/#list-liquidations
         *
         * @param {string} $symbol unified CCXT $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch liquidations for
         * @param {int} [$limit] the maximum number of liquidation structures to retrieve
         * @param {array} [$params] exchange specific parameters for the huobi api endpoint
         * @param {int} [$params->until] timestamp in ms of the latest liquidation
         * @return {array} an array of ~@link https://docs.ccxt.com/#/?id=liquidation-structure liquidation structures~
         */
        $this->authenticate_rest();
        $request = array();
        if ($since !== null) {
            $request['from'] = $since;
        } else {
            $request['from'] = 1;
        }
        $market = $this->market($symbol);
        list($request, $params) = $this->handle_until_option('to', $request, $params);
        $response = $this->privateGetLiquidations ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             {
        //                 "created_at" => 1697213130097,
        //                 "id" => "0x123456789"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        return $this->parse_liquidations($data, $market, $since, $limit);
    }

    public function parse_liquidation($liquidation, ?array $market = null) {
        //
        //     {
        //         "created_at" => 1697213130097,
        //         "id" => "0x123456789"
        //     }
        //
        $timestamp = $this->safe_integer($liquidation, 'created_at');
        return $this->safe_liquidation(array(
            'info' => $liquidation,
            'symbol' => null,
            'contracts' => null,
            'contractSize' => null,
            'price' => null,
            'side' => null,
            'baseValue' => null,
            'quoteValue' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        ));
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all $deposits made to an account
         *
         * @see https://docs.api.prod.paradex.trade/#paradex-rest-api-transfers
         *
         * @param {string} $code unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch $deposits for
         * @param {int} [$limit] the maximum number of $deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch entries for
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchDeposits', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_cursor('fetchDeposits', $code, $since, $limit, $params, 'next', 'cursor', null, 100);
        }
        $request = array();
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        if ($since !== null) {
            $request['start_at'] = $since;
        }
        list($request, $params) = $this->handle_until_option('end_at', $request, $params);
        $response = $this->privateGetTransfers ($this->extend($request, $params));
        //
        //     {
        //         "next" => null,
        //         "prev" => null,
        //         "results" => array(
        //             {
        //                 "id" => "1718940471200201703989430000",
        //                 "account" => "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
        //                 "kind" => "DEPOSIT",
        //                 "status" => "COMPLETED",
        //                 "amount" => "100000",
        //                 "token" => "USDC",
        //                 "created_at" => 1718940471208,
        //                 "last_updated_at" => 1718941455546,
        //                 "txn_hash" => "0x73a415ca558a97bbdcd1c43e52b45f1e0486a0a84b3bb4958035ad6c59cb866",
        //                 "external_txn_hash" => "",
        //                 "socialized_loss_factor" => ""
        //             }
        //         )
        //     }
        //
        $rows = $this->safe_list($response, 'results', array());
        $deposits = array();
        for ($i = 0; $i < count($rows); $i++) {
            $row = $rows[$i];
            if ($row['kind'] === 'DEPOSIT') {
                $deposits[] = $row;
            }
        }
        return $this->parse_transactions($deposits, null, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         *
         * @see https://docs.api.prod.paradex.trade/#paradex-rest-api-transfers
         *
         * @param {string} $code unified currency $code
         * @param {int} [$since] the earliest time in ms to fetch withdrawals for
         * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch withdrawals for
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchWithdrawals', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_cursor('fetchWithdrawals', $code, $since, $limit, $params, 'next', 'cursor', null, 100);
        }
        $request = array();
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        if ($since !== null) {
            $request['start_at'] = $since;
        }
        list($request, $params) = $this->handle_until_option('end_at', $request, $params);
        $response = $this->privateGetTransfers ($this->extend($request, $params));
        //
        //     {
        //         "next" => null,
        //         "prev" => null,
        //         "results" => array(
        //             {
        //                 "id" => "1718940471200201703989430000",
        //                 "account" => "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
        //                 "kind" => "DEPOSIT",
        //                 "status" => "COMPLETED",
        //                 "amount" => "100000",
        //                 "token" => "USDC",
        //                 "created_at" => 1718940471208,
        //                 "last_updated_at" => 1718941455546,
        //                 "txn_hash" => "0x73a415ca558a97bbdcd1c43e52b45f1e0486a0a84b3bb4958035ad6c59cb866",
        //                 "external_txn_hash" => "",
        //                 "socialized_loss_factor" => ""
        //             }
        //         )
        //     }
        //
        $rows = $this->safe_list($response, 'results', array());
        $deposits = array();
        for ($i = 0; $i < count($rows); $i++) {
            $row = $rows[$i];
            if ($row['kind'] === 'WITHDRAWAL') {
                $deposits[] = $row;
            }
        }
        return $this->parse_transactions($deposits, null, $since, $limit);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // fetchDeposits & fetchWithdrawals
        //
        //     {
        //         "id" => "1718940471200201703989430000",
        //         "account" => "0x49ddd7a564c978f6e4089ff8355b56a42b7e2d48ba282cb5aad60f04bea0ec3",
        //         "kind" => "DEPOSIT",
        //         "status" => "COMPLETED",
        //         "amount" => "100000",
        //         "token" => "USDC",
        //         "created_at" => 1718940471208,
        //         "last_updated_at" => 1718941455546,
        //         "txn_hash" => "0x73a415ca558a97bbdcd1c43e52b45f1e0486a0a84b3bb4958035ad6c59cb866",
        //         "external_txn_hash" => "",
        //         "socialized_loss_factor" => ""
        //     }
        //
        $id = $this->safe_string($transaction, 'id');
        $address = $this->safe_string($transaction, 'account');
        $txid = $this->safe_string($transaction, 'txn_hash');
        $currencyId = $this->safe_string($transaction, 'token');
        $code = $this->safe_currency_code($currencyId, $currency);
        $timestamp = $this->safe_integer($transaction, 'created_at');
        $updated = $this->safe_integer($transaction, 'last_updated_at');
        $type = $this->safe_string($transaction, 'kind');
        $type = ($type === 'DEPOSIT') ? 'deposit' : 'withdrawal';
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $amount = $this->safe_number($transaction, 'amount');
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => null,
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => $updated,
            'internal' => null,
            'comment' => null,
            'fee' => null,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'PENDING' => 'pending',
            'AVAILABLE' => 'pending',
            'COMPLETED' => 'ok',
            'FAILED' => 'failed',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_margin_mode(string $symbol, $params = array ()): array {
        /**
         * fetches the margin mode of a specific $symbol
         *
         * @see https://docs.api.testnet.paradex.trade/#get-account-margin-configuration
         *
         * @param {string} $symbol unified $symbol of the $market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=margin-mode-structure margin mode structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        $response = $this->privateGetAccountMargin ($this->extend($request, $params));
        //
        // {
        //     "account" => "0x6343248026a845b39a8a73fbe9c7ef0a841db31ed5c61ec1446aa9d25e54dbc",
        //     "configs" => array(
        //         {
        //             "market" => "SOL-USD-PERP",
        //             "leverage" => 50,
        //             "margin_type" => "CROSS"
        //         }
        //     )
        // }
        //
        $configs = $this->safe_list($response, 'configs');
        return $this->parse_margin_mode($this->safe_dict($configs, 0), $market);
    }

    public function parse_margin_mode(array $rawMarginMode, $market = null): array {
        $marketId = $this->safe_string($rawMarginMode, 'market');
        $market = $this->safe_market($marketId, $market);
        $marginMode = $this->safe_string_lower($rawMarginMode, 'margin_type');
        return array(
            'info' => $rawMarginMode,
            'symbol' => $market['symbol'],
            'marginMode' => $marginMode,
        );
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        /**
         * set margin mode to 'cross' or 'isolated'
         *
         * @see https://docs.api.testnet.paradex.trade/#set-margin-configuration
         *
         * @param {string} $marginMode 'cross' or 'isolated'
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {float} [$params->leverage] the rate of $leverage
         * @return {array} response from the exchange
         */
        $this->check_required_argument('setMarginMode', $symbol, 'symbol');
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $leverage = null;
        list($leverage, $params) = $this->handle_option_and_params($params, 'setMarginMode', 'leverage', 1);
        $request = array(
            'market' => $market['id'],
            'leverage' => $leverage,
            'margin_type' => $this->encode_margin_mode($marginMode),
        );
        return $this->privatePostAccountMarginMarket ($this->extend($request, $params));
    }

    public function fetch_leverage(string $symbol, $params = array ()): array {
        /**
         * fetch the set leverage for a $market
         *
         * @see https://docs.api.testnet.paradex.trade/#get-account-margin-configuration
         *
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
         */
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        $response = $this->privateGetAccountMargin ($this->extend($request, $params));
        //
        // {
        //     "account" => "0x6343248026a845b39a8a73fbe9c7ef0a841db31ed5c61ec1446aa9d25e54dbc",
        //     "configs" => array(
        //         {
        //             "market" => "SOL-USD-PERP",
        //             "leverage" => 50,
        //             "margin_type" => "CROSS"
        //         }
        //     )
        // }
        //
        $configs = $this->safe_list($response, 'configs');
        return $this->parse_leverage($this->safe_dict($configs, 0), $market);
    }

    public function parse_leverage(array $leverage, ?array $market = null): array {
        $marketId = $this->safe_string($leverage, 'market');
        $market = $this->safe_market($marketId, $market);
        $marginMode = $this->safe_string_lower($leverage, 'margin_type');
        return array(
            'info' => $leverage,
            'symbol' => $this->safe_symbol($marketId, $market),
            'marginMode' => $marginMode,
            'longLeverage' => $this->safe_integer($leverage, 'leverage'),
            'shortLeverage' => $this->safe_integer($leverage, 'leverage'),
        );
    }

    public function encode_margin_mode($mode) {
        $modes = array(
            'cross' => 'CROSS',
            'isolated' => 'ISOLATED',
        );
        return $this->safe_string($modes, $mode, $mode);
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        /**
         * set the level of $leverage for a $market
         *
         * @see https://docs.api.testnet.paradex.trade/#set-margin-configuration
         *
         * @param {float} $leverage the rate of $leverage
         * @param {string} [$symbol] unified $market $symbol (is mandatory for swap markets)
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->marginMode] 'cross' or 'isolated'
         * @return {array} response from the exchange
         */
        $this->check_required_argument('setLeverage', $symbol, 'symbol');
        $this->authenticate_rest();
        $this->load_markets();
        $market = $this->market($symbol);
        $marginMode = null;
        list($marginMode, $params) = $this->handle_margin_mode_and_params('setLeverage', $params, 'cross');
        $request = array(
            'market' => $market['id'],
            'leverage' => $leverage,
            'margin_type' => $this->encode_margin_mode($marginMode),
        );
        return $this->privatePostAccountMarginMarket ($this->extend($request, $params));
    }

    public function fetch_greeks(string $symbol, $params = array ()): array {
        /**
         * fetches an option contracts $greeks, financial metrics used to measure the factors that affect the price of an options contract
         *
         * @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
         *
         * @param {string} $symbol unified $symbol of the $market to fetch $greeks for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=$greeks-structure $greeks structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        $response = $this->publicGetMarketsSummary ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             {
        //                 "symbol" => "BTC-USD-114000-P",
        //                 "mark_price" => "10835.66892602",
        //                 "mark_iv" => "0.71781855",
        //                 "delta" => "-0.98726024",
        //                 "greeks" => array(
        //                     "delta" => "-0.9872602390817709",
        //                     "gamma" => "0.000004560958862297231",
        //                     "vega" => "227.11344863639806",
        //                     "rho" => "-302.0617972461581",
        //                     "vanna" => "0.06609830491614832",
        //                     "volga" => "925.9501532805552"
        //                 ),
        //                 "last_traded_price" => "10551.5",
        //                 "bid" => "10794.9",
        //                 "bid_iv" => "0.05",
        //                 "ask" => "10887.3",
        //                 "ask_iv" => "0.8783283",
        //                 "last_iv" => "0.05",
        //                 "volume_24h" => "0",
        //                 "total_volume" => "195240.72672261014",
        //                 "created_at" => 1747644009995,
        //                 "underlying_price" => "103164.79162649",
        //                 "open_interest" => "0",
        //                 "funding_rate" => "0.000004464241170536191",
        //                 "price_change_rate_24h" => "0.074915",
        //                 "future_funding_rate" => "0.0001"
        //             }
        //         )
        //     }
        //
        $data = $this->safe_list($response, 'results', array());
        $greeks = $this->safe_dict($data, 0, array());
        return $this->parse_greeks($greeks, $market);
    }

    public function fetch_all_greeks(?array $symbols = null, $params = array ()): array {
        /**
         * fetches all option contracts greeks, financial metrics used to measure the factors that affect the price of an options contract
         *
         * @see https://docs.api.testnet.paradex.trade/#list-available-markets-summary
         *
         * @param {string[]} [$symbols] unified $symbols of the markets to fetch greeks for, all markets are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=greeks-structure greeks structure~
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols, null, true, true, true);
        $request = array(
            'market' => 'ALL',
        );
        $response = $this->publicGetMarketsSummary ($this->extend($request, $params));
        //
        //     {
        //         "results" => array(
        //             {
        //                 "symbol" => "BTC-USD-114000-P",
        //                 "mark_price" => "10835.66892602",
        //                 "mark_iv" => "0.71781855",
        //                 "delta" => "-0.98726024",
        //                 "greeks" => array(
        //                     "delta" => "-0.9872602390817709",
        //                     "gamma" => "0.000004560958862297231",
        //                     "vega" => "227.11344863639806",
        //                     "rho" => "-302.0617972461581",
        //                     "vanna" => "0.06609830491614832",
        //                     "volga" => "925.9501532805552"
        //                 ),
        //                 "last_traded_price" => "10551.5",
        //                 "bid" => "10794.9",
        //                 "bid_iv" => "0.05",
        //                 "ask" => "10887.3",
        //                 "ask_iv" => "0.8783283",
        //                 "last_iv" => "0.05",
        //                 "volume_24h" => "0",
        //                 "total_volume" => "195240.72672261014",
        //                 "created_at" => 1747644009995,
        //                 "underlying_price" => "103164.79162649",
        //                 "open_interest" => "0",
        //                 "funding_rate" => "0.000004464241170536191",
        //                 "price_change_rate_24h" => "0.074915",
        //                 "future_funding_rate" => "0.0001"
        //             }
        //         )
        //     }
        //
        $results = $this->safe_list($response, 'results', array());
        return $this->parse_all_greeks($results, $symbols);
    }

    public function parse_greeks(array $greeks, ?array $market = null): array {
        //
        //     {
        //         "symbol" => "BTC-USD-114000-P",
        //         "mark_price" => "10835.66892602",
        //         "mark_iv" => "0.71781855",
        //         "delta" => "-0.98726024",
        //         "greeks" => array(
        //             "delta" => "-0.9872602390817709",
        //             "gamma" => "0.000004560958862297231",
        //             "vega" => "227.11344863639806",
        //             "rho" => "-302.0617972461581",
        //             "vanna" => "0.06609830491614832",
        //             "volga" => "925.9501532805552"
        //         ),
        //         "last_traded_price" => "10551.5",
        //         "bid" => "10794.9",
        //         "bid_iv" => "0.05",
        //         "ask" => "10887.3",
        //         "ask_iv" => "0.8783283",
        //         "last_iv" => "0.05",
        //         "volume_24h" => "0",
        //         "total_volume" => "195240.72672261014",
        //         "created_at" => 1747644009995,
        //         "underlying_price" => "103164.79162649",
        //         "open_interest" => "0",
        //         "funding_rate" => "0.000004464241170536191",
        //         "price_change_rate_24h" => "0.074915",
        //         "future_funding_rate" => "0.0001"
        //     }
        //
        $marketId = $this->safe_string($greeks, 'symbol');
        $market = $this->safe_market($marketId, $market, null, 'option');
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($greeks, 'created_at');
        $greeksData = $this->safe_dict($greeks, 'greeks', array());
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'delta' => $this->safe_number($greeksData, 'delta'),
            'gamma' => $this->safe_number($greeksData, 'gamma'),
            'theta' => null,
            'vega' => $this->safe_number($greeksData, 'vega'),
            'rho' => $this->safe_number($greeksData, 'rho'),
            'vanna' => $this->safe_number($greeksData, 'vanna'),
            'volga' => $this->safe_number($greeksData, 'volga'),
            'bidSize' => null,
            'askSize' => null,
            'bidImpliedVolatility' => $this->safe_number($greeks, 'bid_iv'),
            'askImpliedVolatility' => $this->safe_number($greeks, 'ask_iv'),
            'markImpliedVolatility' => $this->safe_number($greeks, 'mark_iv'),
            'bidPrice' => $this->safe_number($greeks, 'bid'),
            'askPrice' => $this->safe_number($greeks, 'ask'),
            'markPrice' => $this->safe_number($greeks, 'mark_price'),
            'lastPrice' => $this->safe_number($greeks, 'last_traded_price'),
            'underlyingPrice' => $this->safe_number($greeks, 'underlying_price'),
            'info' => $greeks,
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->implode_hostname($this->urls['api'][$this->version]) . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        if ($api === 'public') {
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } elseif ($api === 'private') {
            $headers = array(
                'Accept' => 'application/json',
                'PARADEX-PARTNER' => $this->safe_string($this->options, 'broker', 'CCXT'),
            );
            // TODO => optimize
            if ($path === 'auth') {
                $headers['PARADEX-STARKNET-ACCOUNT'] = $query['account'];
                $headers['PARADEX-STARKNET-SIGNATURE'] = $query['signature'];
                $headers['PARADEX-TIMESTAMP'] = (string) $query['timestamp'];
                $headers['PARADEX-SIGNATURE-EXPIRATION'] = (string) $query['expiration'];
            } elseif ($path === 'onboarding') {
                $headers['PARADEX-ETHEREUM-ACCOUNT'] = $this->walletAddress;
                $headers['PARADEX-STARKNET-ACCOUNT'] = $query['account'];
                $headers['PARADEX-STARKNET-SIGNATURE'] = $query['signature'];
                $headers['PARADEX-TIMESTAMP'] = (string) $this->nonce();
                $headers['Content-Type'] = 'application/json';
                $body = $this->json(array(
                    'public_key' => $query['public_key'],
                ));
            } else {
                $token = $this->options['authToken'];
                $headers['Authorization'] = 'Bearer ' . $token;
                if ($method === 'POST') {
                    $headers['Content-Type'] = 'application/json';
                    $body = $this->json($query);
                } else {
                    $url = $url . '?' . $this->urlencode($query);
                }
            }
            // $headers = array(
            //     'Accept' => 'application/json',
            //     'Authorization' => 'Bearer ' . $this->apiKey,
            // );
            // if ($method === 'POST') {
            //     $body = $this->json($query);
            //     $headers['Content-Type'] = 'application/json';
            // } else {
            //     if ($query) {
            //         $url .= '?' . $this->urlencode($query);
            //     }
            // }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if (!$response) {
            return null; // fallback to default error handler
        }
        //
        //     {
        //         "data" => null,
        //         "error" => "NOT_ONBOARDED",
        //         "message" => "User has never called /onboarding endpoint"
        //     }
        //
        $errorCode = $this->safe_string($response, 'error');
        if ($errorCode !== null) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            throw new ExchangeError($feedback); // unknown message
        }
        return null;
    }
}
