<?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\foxbit as Exchange;

class foxbit extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'foxbit',
            'name' => 'Foxbit',
            'countries' => array( 'pt-BR' ),
            // 300 requests per 10 seconds = 30 requests per second
            // rateLimit = 1000 ms / 30 requests ~= 33.334
            'rateLimit' => 33.334,
            'version' => '1',
            'comment' => 'Foxbit Exchange',
            'certified' => false,
            'pro' => false,
            'has' => array(
                'CORS' => true,
                'spot' => true,
                'margin' => null,
                'swap' => null,
                'future' => null,
                'option' => null,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'createLimitBuyOrder' => true,
                'createLimitSellOrder' => true,
                'createMarketBuyOrder' => true,
                'createMarketSellOrder' => true,
                'createOrder' => true,
                'fecthOrderBook' => true,
                'fetchBalance' => true,
                'fetchCanceledOrders' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchL2OrderBook' => true,
                'fetchLedger' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrders' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => true,
                'fetchTransactions' => true,
                'fetchWithdrawals' => true,
                'loadMarkets' => true,
                'sandbox' => false,
                'withdraw' => true,
                'ws' => false,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '5m' => '5m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '2h' => '2h',
                '4h' => '4h',
                '6h' => '6h',
                '12h' => '12h',
                '1d' => '1d',
                '1w' => '1w',
                '2w' => '2w',
                '1M' => '1M',
            ),
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/1f8faca2-ae2f-4222-b33e-5671e7d873dd',
                'api' => array(
                    'public' => 'https://api.foxbit.com.br',
                    'private' => 'https://api.foxbit.com.br',
                    'status' => 'https://metadata-v2.foxbit.com.br/api',
                ),
                'www' => 'https://app.foxbit.com.br',
                'doc' => array(
                    'https://docs.foxbit.com.br',
                ),
            ),
            'precisionMode' => DECIMAL_PLACES,
            'exceptions' => array(
                'exact' => array(
                    // https://docs.foxbit.com.br/rest/v3/#tag/API-Codes/Errors
                    '400' => '\\ccxt\\BadRequest', // Bad request. An unknown error occurred while processing request parameters.
                    '429' => '\\ccxt\\RateLimitExceeded', // Too many requests. Request limit exceeded. Try again later.
                    '404' => '\\ccxt\\BadRequest', // Resource not found. A resource was not found while processing the request.
                    '500' => '\\ccxt\\ExchangeError', // Internal server error. An unknown error occurred while processing the request.
                    '2001' => '\\ccxt\\AuthenticationError', // Authentication error. Error authenticating request.
                    '2002' => '\\ccxt\\AuthenticationError', // Invalid signature. The signature for this request is not valid.
                    '2003' => '\\ccxt\\AuthenticationError', // Invalid access key. Access key missing, invalid or not found.
                    '2004' => '\\ccxt\\BadRequest', // Invalid timestamp. Invalid or missing timestamp.
                    '2005' => '\\ccxt\\PermissionDenied', // IP not allowed. The IP address {IP_ADDR} isn't on the trusted list for this API key.
                    '3001' => '\\ccxt\\PermissionDenied', // Permission denied. Permission denied for this request.
                    '3002' => '\\ccxt\\PermissionDenied', // KYC required. A greater level of KYC verification is required to proceed with this request.
                    '3003' => '\\ccxt\\AccountSuspended', // Member disabled. This member is disabled. Please get in touch with our support for more information.
                    '4001' => '\\ccxt\\BadRequest', // Validation error. A validation error occurred.
                    '4002' => '\\ccxt\\InsufficientFunds', // Insufficient funds. Insufficient funds to proceed with this request.
                    '4003' => '\\ccxt\\InvalidOrder', // Quantity below the minimum allowed. Quantity below the minimum allowed to proceed with this request.
                    '4004' => '\\ccxt\\BadSymbol', // Invalid symbol. The market or asset symbol is invalid or was not found.
                    '4005' => '\\ccxt\\BadRequest', // Invalid idempotent. Characters allowed are "a-z", "0-9", "_" or "-", and 36 at max. We recommend UUID v4 in lowercase.
                    '4007' => '\\ccxt\\ExchangeError', // Locked error. There was an error in your allocated balance, please contact us.
                    '4008' => '\\ccxt\\InvalidOrder', // Cannot submit order. The order cannot be created.
                    '4009' => '\\ccxt\\PermissionDenied', // Invalid level. The sub-member does not have the required level to create the transaction.
                    '4011' => '\\ccxt\\RateLimitExceeded', // Too many open orders. You have reached the limit of open orders per market/side.
                    '4012' => '\\ccxt\\ExchangeError', // Too many simultaneous account operations. We are currently unable to process your balance change due to simultaneous operations on your account. Please retry shortly.
                    '5001' => '\\ccxt\\ExchangeNotAvailable', // Service unavailable. The requested resource is currently unavailable. Try again later.
                    '5002' => '\\ccxt\\OnMaintenance', // Service under maintenance. The requested resource is currently under maintenance. Try again later.
                    '5003' => '\\ccxt\\OnMaintenance', // Market under maintenance. The market is under maintenance. Try again later.
                    '5004' => '\\ccxt\\InvalidOrder', // Market is not deep enough. The market is not deep enough to complete your request.
                    '5005' => '\\ccxt\\InvalidOrder', // Price out of range from market. The order price is out of range from market to complete your request.
                    '5006' => '\\ccxt\\InvalidOrder', // Significant price deviation detected, exceeding acceptable limits. The order price is exceeding acceptable limits from market to complete your request.
                ),
                'broad' => array(
                    // todo => add details messages that can be usefull here, like when market is not found
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'api' => array(
                'v3' => array(
                    'public' => array(
                        'get' => array(
                            'currencies' => 5, // 6 requests per second
                            'markets' => 5, // 6 requests per second
                            'markets/ticker/24hr' => 60, // 1 request per 2 seconds
                            'markets/{market}/orderbook' => 6, // 10 requests per 2 seconds
                            'markets/{market}/candlesticks' => 12, // 5 requests per 2 seconds
                            'markets/{market}/trades/history' => 12, // 5 requests per 2 seconds
                            'markets/{market}/ticker/24hr' => 15, // 4 requests per 2 seconds
                        ),
                    ),
                    'private' => array(
                        'get' => array(
                            'accounts' => 2, // 15 requests per second
                            'accounts/{symbol}/transactions' => 60, // 1 requests per 2 seconds
                            'orders' => 2, // 30 requests per 2 seconds
                            'orders/by-order-id/{id}' => 2, // 30 requests per 2 seconds
                            'trades' => 6, // 5 orders per second
                            'deposits/address' => 10, // 3 requests per second
                            'deposits' => 10, // 3 requests per second
                            'withdrawals' => 10, // 3 requests per second
                            'me/fees/trading' => 60, // 1 requests per 2 seconds
                        ),
                        'post' => array(
                            'orders' => 2, // 30 requests per 2 seconds
                            'orders/batch' => 7.5, // 8 requests per 2 seconds
                            'orders/cancel-replace' => 3, // 20 requests per 2 seconds
                            'withdrawals' => 10, // 3 requests per second
                        ),
                        'put' => array(
                            'orders/cancel' => 2, // 30 requests per 2 seconds
                        ),
                    ),
                ),
                'status' => array(
                    'public' => array(
                        'get' => array(
                            'status' => 30, // 1 request per second
                        ),
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'feeSide' => 'get',
                    'tierBased' => false,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.005'),
                    'maker' => $this->parse_number('0.0025'),
                ),
            ),
            'options' => array(
                'sandboxMode' => false,
                'networksById' => array(
                    'algorand' => 'ALGO',
                    'arbitrum' => 'ARBITRUM',
                    'avalanchecchain' => 'AVAX',
                    'bitcoin' => 'BTC',
                    'bitcoincash' => 'BCH',
                    'bsc' => 'BEP20',
                    'cardano' => 'ADA',
                    'cosmos' => 'ATOM',
                    'dogecoin' => 'DOGE',
                    'erc20' => 'ETH',
                    'hedera' => 'HBAR',
                    'litecoin' => 'LTC',
                    'near' => 'NEAR',
                    'optimism' => 'OPTIMISM',
                    'polkadot' => 'DOT',
                    'polygon' => 'MATIC',
                    'ripple' => 'XRP',
                    'solana' => 'SOL',
                    'stacks' => 'STX',
                    'stellar' => 'XLM',
                    'tezos' => 'XTZ',
                    'trc20' => 'TRC20',
                ),
                'networks' => array(
                    'ALGO' => 'algorand',
                    'ARBITRUM' => 'arbitrum',
                    'AVAX' => 'avalanchecchain',
                    'BTC' => 'bitcoin',
                    'BCH' => 'bitcoincash',
                    'BEP20' => 'bsc',
                    'ADA' => 'cardano',
                    'ATOM' => 'cosmos',
                    'DOGE' => 'dogecoin',
                    'ETH' => 'erc20',
                    'HBAR' => 'hedera',
                    'LTC' => 'litecoin',
                    'NEAR' => 'near',
                    'OPTIMISM' => 'optimism',
                    'DOT' => 'polkadot',
                    'MATIC' => 'polygon',
                    'XRP' => 'ripple',
                    'SOL' => 'solana',
                    'STX' => 'stacks',
                    'XLM' => 'stellar',
                    'XTZ' => 'tezos',
                    'TRC20' => 'trc20',
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true, // foxbit default trigger price type is last, no params will change it
                            'mark' => false,
                            'index' => false,
                        ),
                        'triggerDirection' => false,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'GTC' => true,
                            'FOK' => true,
                            'IOC' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => array(
                            'expire_maker' => true, // foxbit prevents self trading by default, no params can change this
                            'expire_taker' => true, // foxbit prevents self trading by default, no params can change this
                            'expire_both' => true, // foxbit prevents self trading by default, no params can change this
                            'none' => true, // foxbit prevents self trading by default, no params can change this
                        ),
                        'trailing' => false,
                        'icebergAmount' => false,
                    ),
                    'createOrders' => array(
                        'max' => 5,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 90,
                        'untilDays' => 10000, // high value just to keep clear that there is no range limit, just the limit of the page size
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'limit' => 1,
                        'daysBack' => 90,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'daysBack' => 90,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => true,
                        'limit' => 100,
                        'daysBack' => 90,
                        'untilDays' => 10000, // high value just to keep clear that there is no range limit, just the limit of the page size
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => true,
                        'limit' => 100,
                        'daysBack' => 90,
                        'daysBackCanceled' => 90,
                        'untilDays' => 10000, // high value just to keep clear that there is no range limit, just the limit of the page size
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 500,
                    ),
                ),
            ),
        ));
    }

    public function fetch_currencies($params = array ()): ?array {
        $response = $this->v3PublicGetCurrencies ($params);
        // {
        //   "data" => array(
        //     {
        //       "symbol" => "btc",
        //       "name" => "Bitcoin",
        //       "type" => "CRYPTO",
        //       "precision" => 8,
        //       "deposit_info" => array(
        //         "min_to_confirm" => "1",
        //         "min_amount" => "0.0001"
        //       ),
        //       "withdraw_info" => array(
        //         "enabled" => true,
        //         "min_amount" => "0.0001",
        //         "fee" => "0.0001"
        //       ),
        //       "category" => array(
        //           "code" => "cripto",
        //         "name" => "Cripto"
        //       ),
        //       "networks" => array(
        //           {
        //               "name" => "Bitcoin",
        //               "code" => "btc",
        //               "deposit_info" => array(
        //                  status => "ENABLED",
        //               ),
        //               "withdraw_info" => array(
        //                  "status" => "ENABLED",
        //                  "fee" => "0.0001",
        //               ),
        //               "has_destination_tag" => false
        //           }
        //       )
        //     }
        //   )
        // }
        $data = $this->safe_list($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $currency = $data[$i];
            $precision = $this->safe_integer($currency, 'precision');
            $currencyId = $this->safe_string($currency, 'symbol');
            $name = $this->safe_string($currency, 'name');
            $code = $this->safe_currency_code($currencyId);
            $depositInfo = $this->safe_dict($currency, 'deposit_info');
            $withdrawInfo = $this->safe_dict($currency, 'withdraw_info');
            $networks = $this->safe_list($currency, 'networks', array());
            $type = $this->safe_string_lower($currency, 'type');
            $parsedNetworks = array();
            for ($j = 0; $j < count($networks); $j++) {
                $network = $networks[$j];
                $networkId = $this->safe_string($network, 'code');
                $networkCode = $this->network_id_to_code($networkId, $code);
                $networkWithdrawInfo = $this->safe_dict($network, 'withdraw_info');
                $networkDepositInfo = $this->safe_dict($network, 'deposit_info');
                $isWithdrawEnabled = $this->safe_string($networkWithdrawInfo, 'status') === 'ENABLED';
                $isDepositEnabled = $this->safe_string($networkDepositInfo, 'status') === 'ENABLED';
                $parsedNetworks[$networkCode] = array(
                    'info' => $currency,
                    'id' => $networkId,
                    'network' => $networkCode,
                    'name' => $this->safe_string($network, 'name'),
                    'deposit' => $isDepositEnabled,
                    'withdraw' => $isWithdrawEnabled,
                    'active' => true,
                    'precision' => $precision,
                    'fee' => $this->safe_number($networkWithdrawInfo, 'fee'),
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => $this->safe_number($depositInfo, 'min_amount'),
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => $this->safe_number($withdrawInfo, 'min_amount'),
                            'max' => null,
                        ),
                    ),
                );
            }
            if ($this->safe_dict($result, $code) === null) {
                $result[$code] = $this->safe_currency_structure(array(
                    'id' => $currencyId,
                    'code' => $code,
                    'info' => $currency,
                    'name' => $name,
                    'active' => true,
                    'type' => $type,
                    'deposit' => $this->safe_bool($depositInfo, 'enabled', false),
                    'withdraw' => $this->safe_bool($withdrawInfo, 'enabled', false),
                    'fee' => $this->safe_number($withdrawInfo, 'fee'),
                    'precision' => $precision,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => $this->safe_number($depositInfo, 'min_amount'),
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => $this->safe_number($withdrawInfo, 'min_amount'),
                            'max' => null,
                        ),
                    ),
                    'networks' => $parsedNetworks,
                ));
            }
        }
        return $result;
    }

    public function fetch_markets($params = array ()): array {
        /**
         * Retrieves data on all $markets for foxbit.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_index
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        $response = $this->v3PublicGetMarkets ($params);
        // {
        //     "data" => array(
        //       {
        //         "symbol" => "btcbrl",
        //         "quantity_min" => "0.00000236",
        //         "quantity_increment" => "0.00000001",
        //         "quantity_precision" => 8,
        //         "price_min" => "0.0001",
        //         "price_increment" => "0.0001",
        //         "price_precision" => 4,
        //         "default_fees" => array(
        //           "maker" => "0.001",
        //           "taker" => "0.001"
        //         ),
        //         "base" => {
        //           "symbol" => "btc",
        //           "name" => "Bitcoin",
        //           "type" => "CRYPTO",
        //           "precision" => 8,
        //           "category" => array(
        //             "code" => "cripto",
        //             "name" => "Cripto"
        //           ),
        //           "deposit_info" => array(
        //             "min_to_confirm" => "1",
        //             "min_amount" => "0.0001",
        //             "enabled" => true
        //           ),
        //           "withdraw_info" => array(
        //             "enabled" => true,
        //             "min_amount" => "0.0001",
        //             "fee" => "0.0001"
        //           ),
        //           "networks" => array(
        //             array(
        //               "name" => "Bitcoin",
        //               "code" => "bitcoin",
        //               "deposit_info" => array(
        //                 "status" => "ENABLED"
        //               ),
        //               "withdraw_info" => array(
        //                 "status" => "ENABLED",
        //                 "fee" => "0.0001"
        //               ),
        //               "has_destination_tag" => false
        //             }
        //           ),
        //           "default_network_code" => "bitcoin"
        //         ),
        //         "quote" => {
        //           "symbol" => "btc",
        //           "name" => "Bitcoin",
        //           "type" => "CRYPTO",
        //           "precision" => 8,
        //           "category" => array(
        //             "code" => "cripto",
        //             "name" => "Cripto"
        //           ),
        //           "deposit_info" => array(
        //             "min_to_confirm" => "1",
        //             "min_amount" => "0.0001",
        //             "enabled" => true
        //           ),
        //           "withdraw_info" => array(
        //             "enabled" => true,
        //             "min_amount" => "0.0001",
        //             "fee" => "0.0001"
        //           ),
        //           "networks" => array(
        //             array(
        //               "name" => "Bitcoin",
        //               "code" => "bitcoin",
        //               "deposit_info" => array(
        //                 "status" => "ENABLED"
        //               ),
        //               "withdraw_info" => array(
        //                 "status" => "ENABLED",
        //                 "fee" => "0.0001"
        //               ),
        //               "has_destination_tag" => false
        //             }
        //           ),
        //           "default_network_code" => "bitcoin"
        //         ),
        //         "order_type" => array(
        //           "LIMIT",
        //           "MARKET",
        //           "INSTANT",
        //           "STOP_LIMIT",
        //           "STOP_MARKET"
        //         )
        //       }
        //     )
        //   }
        $markets = $this->safe_list($response, 'data', array());
        return $this->parse_markets($markets);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * Get last 24 hours ticker information, in real-time, for given $market->
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_ticker
         *
         * @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->v3PublicGetMarketsMarketTicker24hr ($this->extend($request, $params));
        //  {
        //    "data" => array(
        //      {
        //        "market_symbol" => "btcbrl",
        //        "last_trade" => array(
        //          "price" => "358504.69340000",
        //          "volume" => "0.00027893",
        //          "date" => "2024-01-01T00:00:00.000Z"
        //        ),
        //        "rolling_24h" => array(
        //          "price_change" => "3211.87290000",
        //          "price_change_percent" => "0.90400726",
        //          "volume" => "20.03206866",
        //          "trades_count" => "4376",
        //          "open" => "355292.82050000",
        //          "high" => "362999.99990000",
        //          "low" => "355002.88880000"
        //        ),
        //        "best" => {
        //          "ask" => array(
        //            "price" => "358504.69340000",
        //            "volume" => "0.00027893"
        //          ),
        //          "bid" => {
        //            "price" => "358504.69340000",
        //            "volume" => "0.00027893"
        //          }
        //        }
        //      }
        //    )
        //  }
        $data = $this->safe_list($response, 'data', array());
        $result = $this->safe_dict($data, 0, array());
        return $this->parse_ticker($result, $market);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * Retrieve the ticker $data of all markets.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_tickers
         *
         * @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);
        $response = $this->v3PublicGetMarketsTicker24hr ($params);
        //  {
        //    "data" => array(
        //      {
        //        "market_symbol" => "btcbrl",
        //        "last_trade" => array(
        //          "price" => "358504.69340000",
        //          "volume" => "0.00027893",
        //          "date" => "2024-01-01T00:00:00.000Z"
        //        ),
        //        "rolling_24h" => array(
        //          "price_change" => "3211.87290000",
        //          "price_change_percent" => "0.90400726",
        //          "volume" => "20.03206866",
        //          "trades_count" => "4376",
        //          "open" => "355292.82050000",
        //          "high" => "362999.99990000",
        //          "low" => "355002.88880000"
        //        ),
        //      }
        //    )
        //  }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_tickers($data, $symbols);
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         * fetch the trading fees for multiple markets
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Member-Info/operation/MembersController_listTradingFees
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by $market symbols
         */
        $this->load_markets();
        $response = $this->v3PrivateGetMeFeesTrading ($params);
        // array(
        //     {
        //         "market_symbol" => "btcbrl",
        //         "maker" => "0.0025",
        //         "taker" => "0.005"
        //     }
        // )
        $data = $this->safe_list($response, 'data', array());
        $result = array();
        for ($i = 0; $i < count($data); $i++) {
            $entry = $data[$i];
            $marketId = $this->safe_string($entry, 'market_symbol');
            $market = $this->safe_market($marketId);
            $symbol = $market['symbol'];
            $result[$symbol] = $this->parse_trading_fee($entry, $market);
        }
        return $result;
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * Exports a copy of the order book of a specific $market->
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_findOrderbook
         *
         * @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, the maximum is 100
         * @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);
        $defaultLimit = 20;
        $request = array(
            'market' => $market['id'],
            'depth' => ($limit === null) ? $defaultLimit : $limit,
        );
        $response = $this->v3PublicGetMarketsMarketOrderbook ($this->extend($request, $params));
        //  {
        //    "sequence_id" => 1234567890,
        //    "timestamp" => 1713187921336,
        //    "bids" => array(
        //      array(
        //        "3.00000000",
        //        "300.00000000"
        //      ),
        //      array(
        //        "1.70000000",
        //        "310.00000000"
        //      )
        //    ),
        //    "asks" => array(
        //      array(
        //        "3.00000000",
        //        "300.00000000"
        //      ),
        //      array(
        //        "2.00000000",
        //        "321.00000000"
        //      )
        //    )
        //  }
        $timestamp = $this->safe_integer($response, 'timestamp');
        return $this->parse_order_book($response, $symbol, $timestamp);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * Retrieve the trades of a specific $market->
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_publicTrades
         *
         * @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
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 200) {
                $request['page_size'] = 200;
            }
        }
        // array(
        //     {
        //         "id" => 1,
        //         "price" => "329248.74700000",
        //         "volume" => "0.00100000",
        //         "taker_side" => "BUY",
        //         "created_at" => "2024-01-01T00:00:00Z"
        //     }
        // )
        $response = $this->v3PublicGetMarketsMarketTradesHistory ($this->extend($request, $params));
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * Fetch historical candlestick data containing the open, high, low, and close price, and the volume of a $market->
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Market-Data/operation/MarketsController_findCandlesticks
         *
         * @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
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $interval = $this->safe_string($this->timeframes, $timeframe, $timeframe);
        $request = array(
            'market' => $market['id'],
            'interval' => $interval,
        );
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
            if ($limit > 500) {
                $request['limit'] = 500;
            }
        }
        $response = $this->v3PublicGetMarketsMarketCandlesticks ($this->extend($request, $params));
        // array(
        //     array(
        //         "1692918000000", // timestamp
        //         "127772.05150000", // open
        //         "128467.99980000", // high
        //         "127750.01000000", // low
        //         "128353.99990000", // close
        //         "1692918060000", // close timestamp
        //         "0.17080431", // base volume
        //         "21866.35948786", // quote volume
        //         66, // number of trades
        //         "0.12073605", // taker buy base volume
        //         "15466.34096391" // taker buy quote volume
        //     )
        // )
        return $this->parse_ohlcvs($response, $market, $interval, $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.foxbit.com.br/rest/v3/#tag/Account/operation/AccountsController_all
         *
         * @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->load_markets();
        $response = $this->v3PrivateGetAccounts ($params);
        // {
        //     "data" => array(
        //         {
        //         "currency_symbol" => "btc",
        //         "balance" => "10000.0",
        //         "balance_available" => "9000.0",
        //         "balance_locked" => "1000.0"
        //         }
        //     )
        // }
        $accounts = $this->safe_list($response, 'data', array());
        $result = array(
            'info' => $response,
        );
        for ($i = 0; $i < count($accounts); $i++) {
            $account = $accounts[$i];
            $currencyId = $this->safe_string($account, 'currency_symbol');
            $currencyCode = $this->safe_currency_code($currencyId);
            $total = $this->safe_string($account, 'balance');
            $used = $this->safe_string($account, 'balance_locked');
            $free = $this->safe_string($account, 'balance_available');
            $balanceObj = array(
                'free' => $free,
                'used' => $used,
                'total' => $total,
            );
            $result[$currencyCode] = $balanceObj;
        }
        return $this->safe_balance($result);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * Fetch all unfilled currently open orders.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_listOrders
         *
         * @param {string} $symbol unified market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of open 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~
         */
        return $this->fetch_orders_by_status('ACTIVE', $symbol, $since, $limit, $params);
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * Fetch all currently closed orders.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_listOrders
         *
         * @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~
         */
        return $this->fetch_orders_by_status('FILLED', $symbol, $since, $limit, $params);
    }

    public function fetch_canceled_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        return $this->fetch_orders_by_status('CANCELED', $symbol, $since, $limit, $params);
    }

    public function fetch_orders_by_status(?string $status, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        $this->load_markets();
        $market = null;
        $request = array(
            'state' => $status,
        );
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market_symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 100) {
                $request['page_size'] = 100;
            }
        }
        $response = $this->v3PrivateGetOrders ($this->extend($request, $params));
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_orders($data);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()): array {
        /**
         * Create an order with the specified characteristics
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_create
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market', 'limit', 'stop_market', 'stop_limit', 'instant'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much you want to trade in units of the 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 {string} [$params->timeInForce] "GTC", "FOK", "IOC", "PO"
         * @param {float} [$params->triggerPrice] The time in force for the order. One of GTC, FOK, IOC, PO. See .features or foxbit's doc to see more details.
         * @param {bool} [$params->postOnly] true or false whether the order is post-only
         * @param {string} [$params->clientOrderId] a unique identifier for the order
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $type = strtoupper($type);
        if ($type !== 'LIMIT' && $type !== 'MARKET' && $type !== 'STOP_MARKET' && $type !== 'STOP_LIMIT' && $type !== 'INSTANT') {
            throw new InvalidOrder('Invalid order $type => ' . $type . '. Must be one of => limit, $market, stop_market, stop_limit, instant.');
        }
        $timeInForce = $this->safe_string_upper($params, 'timeInForce');
        $postOnly = $this->safe_bool($params, 'postOnly', false);
        $triggerPrice = $this->safe_number($params, 'triggerPrice');
        $request = array(
            'market_symbol' => $market['id'],
            'side' => strtoupper($side),
            'type' => $type,
        );
        if ($type === 'STOP_MARKET' || $type === 'STOP_LIMIT') {
            if ($triggerPrice === null) {
                throw new InvalidOrder('Invalid order $type => ' . $type . '. Must have $triggerPrice->');
            }
        }
        if ($timeInForce !== null) {
            if ($timeInForce === 'PO') {
                $request['post_only'] = true;
            } else {
                $request['time_in_force'] = $timeInForce;
            }
        }
        if ($postOnly) {
            $request['post_only'] = true;
        }
        if ($triggerPrice !== null) {
            $request['stop_price'] = $this->price_to_precision($symbol, $triggerPrice);
        }
        if ($type === 'INSTANT') {
            $request['amount'] = $this->price_to_precision($symbol, $amount);
        } else {
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($type === 'LIMIT' || $type === 'STOP_LIMIT') {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['client_order_id'] = $clientOrderId;
        }
        $params = $this->omit($params, array( 'timeInForce', 'postOnly', 'triggerPrice', 'clientOrderId' ));
        $response = $this->v3PrivatePostOrders ($this->extend($request, $params));
        // {
        //     "id" => 1234567890,
        //     "sn" => "OKMAKSDHRVVREK",
        //     "client_order_id" => "451637946501"
        // }
        return $this->parse_order($response, $market);
    }

    public function create_orders(array $orders, $params = array ()) {
        /**
         * create a list of trade $orders
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/createBatch
         *
         * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely $symbol, $type, side, amount, price and $params
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
         */
        $this->load_markets();
        $ordersRequests = array();
        for ($i = 0; $i < count($orders); $i++) {
            $order = $this->safe_dict($orders, $i);
            $symbol = $this->safe_string($order, 'symbol');
            $market = $this->market($symbol);
            $type = $this->safe_string_upper($order, 'type');
            $orderParams = $this->safe_dict($order, 'params', array());
            if ($type !== 'LIMIT' && $type !== 'MARKET' && $type !== 'STOP_MARKET' && $type !== 'STOP_LIMIT' && $type !== 'INSTANT') {
                throw new InvalidOrder('Invalid $order $type => ' . $type . '. Must be one of => limit, $market, stop_market, stop_limit, instant.');
            }
            $timeInForce = $this->safe_string_upper($orderParams, 'timeInForce');
            $postOnly = $this->safe_bool($orderParams, 'postOnly', false);
            $triggerPrice = $this->safe_number($orderParams, 'triggerPrice');
            $request = array(
                'market_symbol' => $market['id'],
                'side' => $this->safe_string_upper($order, 'side'),
                'type' => $type,
            );
            if ($type === 'STOP_MARKET' || $type === 'STOP_LIMIT') {
                if ($triggerPrice === null) {
                    throw new InvalidOrder('Invalid $order $type => ' . $type . '. Must have $triggerPrice->');
                }
            }
            if ($timeInForce !== null) {
                if ($timeInForce === 'PO') {
                    $request['post_only'] = true;
                } else {
                    $request['time_in_force'] = $timeInForce;
                }
                unset($orderParams['timeInForce']);
            }
            if ($postOnly) {
                $request['post_only'] = true;
                unset($orderParams['postOnly']);
            }
            if ($triggerPrice !== null) {
                $request['stop_price'] = $this->price_to_precision($symbol, $triggerPrice);
                unset($orderParams['triggerPrice']);
            }
            if ($type === 'INSTANT') {
                $request['amount'] = $this->price_to_precision($symbol, $this->safe_string($order, 'amount'));
            } else {
                $request['quantity'] = $this->amount_to_precision($symbol, $this->safe_string($order, 'amount'));
            }
            if ($type === 'LIMIT' || $type === 'STOP_LIMIT') {
                $request['price'] = $this->price_to_precision($symbol, $this->safe_string($order, 'price'));
            }
            $ordersRequests[] = $this->extend($request, $orderParams);
        }
        $createOrdersRequest = array( 'data' => $ordersRequests );
        $response = $this->v3PrivatePostOrdersBatch ($this->extend($createOrdersRequest, $params));
        // {
        //     "data" => array(
        //         {
        //         "side" => "BUY",
        //         "type" => "LIMIT",
        //         "market_symbol" => "btcbrl",
        //         "client_order_id" => "451637946501",
        //         "remark" => "A remarkable note for the $order->",
        //         "quantity" => "0.42",
        //         "price" => "250000.0",
        //         "post_only" => true,
        //         "time_in_force" => "GTC"
        //         }
        //     )
        // }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_orders($data);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * Cancel open orders.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_cancel
         *
         * @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
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $request = array(
            'id' => $this->parse_number($id),
            'type' => 'ID',
        );
        $response = $this->v3PrivatePutOrdersCancel ($this->extend($request, $params));
        // {
        //     "data" => array(
        //         {
        //         "sn" => "OKMAKSDHRVVREK",
        //         "id" => 123456789
        //         }
        //     )
        // }
        $data = $this->safe_list($response, 'data', array());
        $result = $this->safe_dict($data, 0, array());
        return $this->parse_order($result);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * Cancel all open orders or all open orders for a specific $market->
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_cancel
         *
         * @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~
         */
        $this->load_markets();
        $request = array(
            'type' => 'ALL',
        );
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['type'] = 'MARKET';
            $request['market_symbol'] = $market['id'];
        }
        $response = $this->v3PrivatePutOrdersCancel ($this->extend($request, $params));
        // {
        //     "data" => array(
        //         {
        //           "sn" => "OKMAKSDHRVVREK",
        //           "id" => 123456789
        //         }
        //     )
        // }
        return array( $this->safe_order(array(
            'info' => $response,
        )) );
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()): array {
        /**
         * Get an order by ID.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_findByOrderId
         *
         * @param $id
         * @param {string} $symbol it is not used in the foxbit API
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $this->load_markets();
        $request = array(
            'id' => $id,
        );
        $response = $this->v3PrivateGetOrdersByOrderIdId ($this->extend($request, $params));
        // {
        //     "id" => "1234567890",
        //     "sn" => "OKMAKSDHRVVREK",
        //     "client_order_id" => "451637946501",
        //     "market_symbol" => "btcbrl",
        //     "side" => "BUY",
        //     "type" => "LIMIT",
        //     "state" => "ACTIVE",
        //     "price" => "290000.0",
        //     "price_avg" => "295333.3333",
        //     "quantity" => "0.42",
        //     "quantity_executed" => "0.41",
        //     "instant_amount" => "290.0",
        //     "instant_amount_executed" => "290.0",
        //     "created_at" => "2021-02-15T22:06:32.999Z",
        //     "trades_count" => "2",
        //     "remark" => "A remarkable note for the order.",
        //     "funds_received" => "290.0"
        // }
        return $this->parse_order($response, null);
    }

    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.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_listOrders
         *
         * @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->state] Enum => ACTIVE, CANCELED, FILLED, PARTIALLY_CANCELED, PARTIALLY_FILLED
         * @param {string} [$params->side] Enum => BUY, SELL
         * @return {Order[]} a $list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $market = null;
        $request = array();
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market_symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 100) {
                $request['page_size'] = 100;
            }
        }
        $response = $this->v3PrivateGetOrders ($this->extend($request, $params));
        // {
        //     "data" => array(
        //         {
        //         "id" => "1234567890",
        //         "sn" => "OKMAKSDHRVVREK",
        //         "client_order_id" => "451637946501",
        //         "market_symbol" => "btcbrl",
        //         "side" => "BUY",
        //         "type" => "LIMIT",
        //         "state" => "ACTIVE",
        //         "price" => "290000.0",
        //         "price_avg" => "295333.3333",
        //         "quantity" => "0.42",
        //         "quantity_executed" => "0.41",
        //         "instant_amount" => "290.0",
        //         "instant_amount_executed" => "290.0",
        //         "created_at" => "2021-02-15T22:06:32.999Z",
        //         "trades_count" => "2",
        //         "remark" => "A remarkable note for the order.",
        //         "funds_received" => "290.0"
        //         }
        //     )
        // }
        $list = $this->safe_list($response, 'data', array());
        return $this->parse_orders($list, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * Trade history queries will only have $data available for the last 3 months, in descending order (most recents trades first).
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/TradesController_all
         *
         * @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 trade structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_symbol' => $market['id'],
        );
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 100) {
                $request['page_size'] = 100;
            }
        }
        $response = $this->v3PrivateGetTrades ($this->extend($request, $params));
        // {
        //     "data" => array(
        //         "id" => 1234567890,
        //         "sn" => "TC5JZVW2LLJ3IW",
        //         "order_id" => 1234567890,
        //         "market_symbol" => "btcbrl",
        //         "side" => "BUY",
        //         "price" => "290000.0",
        //         "quantity" => "1.0",
        //         "fee" => "0.01",
        //         "fee_currency_symbol" => "btc",
        //         "created_at" => "2021-02-15T22:06:32.999Z"
        //     )
        // }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * Fetch the deposit address for a $currency associated with this account.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Deposit/operation/DepositsController_depositAddress
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->networkCode] the blockchain network to create a deposit address on
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency_symbol' => $currency['id'],
        );
        list($networkCode, $paramsOmited) = $this->handle_network_code_and_params($params);
        if ($networkCode !== null) {
            $request['network_code'] = $this->network_code_to_id($networkCode, $code);
        }
        $response = $this->v3PrivateGetDepositsAddress ($this->extend($request, $paramsOmited));
        // {
        //     "currency_symbol" => "btc",
        //     "address" => "2N9sS8LgrY19rvcCWDmE1ou1tTVmqk4KQAB",
        //     "message" => "Address was retrieved successfully",
        //     "destination_tag" => "string",
        //     "network" => {
        //         "name" => "Bitcoin Network",
        //         "code" => "btc"
        //     }
        // }
        return $this->parse_deposit_address($response, $currency);
    }

    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.foxbit.com.br/rest/v3/#tag/Deposit/operation/DepositsController_listOrders
         *
         * @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 deposit structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 100) {
                $request['page_size'] = 100;
            }
        }
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        $response = $this->v3PrivateGetDeposits ($this->extend($request, $params));
        // {
        //     "data" => array(
        //         {
        //             "sn" => "OKMAKSDHRVVREK",
        //             "state" => "ACCEPTED",
        //             "currency_symbol" => "btc",
        //             "amount" => "1.0",
        //             "fee" => "0.1",
        //             "created_at" => "2022-02-18T22:06:32.999Z",
        //             "details_crypto" => {
        //                 "transaction_id" => "e20f035387020c5d5ea18ad53244f09f3",
        //                 "receiving_address" => "2N2rTrnKEFcyJjEJqvVjgWZ3bKvKT7Aij61"
        //             }
        //         }
        //     )
        // }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_transactions($data, $currency, $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.foxbit.com.br/rest/v3/#tag/Withdrawal/operation/WithdrawalsController_listWithdrawals
         *
         * @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 withdrawal structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 100) {
                $request['page_size'] = 100;
            }
        }
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        $response = $this->v3PrivateGetWithdrawals ($this->extend($request, $params));
        // {
        //     "data" => array(
        //         {
        //             "sn" => "OKMAKSDHRVVREK",
        //             "state" => "ACCEPTED",
        //             "rejection_reason" => "monthly_limit_exceeded",
        //             "currency_symbol" => "btc",
        //             "amount" => "1.0",
        //             "fee" => "0.1",
        //             "created_at" => "2022-02-18T22:06:32.999Z",
        //             "details_crypto" => array(
        //                 "transaction_id" => "e20f035387020c5d5ea18ad53244f09f3",
        //                 "destination_address" => "2N2rTrnKEFcyJjEJqvVjgWZ3bKvKT7Aij61"
        //             ),
        //             "details_fiat" => {
        //                 "bank" => {
        //                     "code" => "1",
        //                     "branch" => array(
        //                         "number" => "1234567890",
        //                         "digit" => "1"
        //                     ),
        //                     "account" => {
        //                         "number" => "1234567890",
        //                         "digit" => "1",
        //                         "type" => "CHECK"
        //                     }
        //                 }
        //             }
        //         }
        //     )
        // }
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function fetch_transactions(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * Fetch all transactions ($deposits and $withdrawals) made from an account.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Withdrawal/operation/WithdrawalsController_listWithdrawals
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Deposit/operation/DepositsController_listOrders
         *
         * @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 withdrawal structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $withdrawals = $this->fetch_withdrawals($code, $since, $limit, $params);
        $deposits = $this->fetch_deposits($code, $since, $limit, $params);
        $allTransactions = $this->array_concat($withdrawals, $deposits);
        $result = $this->sort_by($allTransactions, 'timestamp');
        return $result;
    }

    public function fetch_status($params = array ()) {
        /**
         * The latest known information on the availability of the exchange API.
         *
         * @see https://status.foxbit.com/
         *
         * @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->statusPublicGetStatus ($params);
        // {
        //     "data" => {
        //       "id" => 1,
        //       "attributes" => array(
        //         "status" => "NORMAL",
        //         "createdAt" => "2023-05-17T18:37:05.934Z",
        //         "updatedAt" => "2024-04-17T02:33:50.945Z",
        //         "publishedAt" => "2023-05-17T18:37:07.653Z",
        //         "locale" => "pt-BR"
        //       }
        //     ),
        //     "meta" => {
        //     }
        // }
        $data = $this->safe_dict($response, 'data', array());
        $attributes = $this->safe_dict($data, 'attributes', array());
        $statusRaw = $this->safe_string($attributes, 'status');
        $statusMap = array(
            'NORMAL' => 'ok',
            'UNDER_MAINTENANCE' => 'maintenance',
        );
        return array(
            'status' => $this->safe_string($statusMap, $statusRaw, $statusRaw),
            'updated' => $this->safe_string($attributes, 'updatedAt'),
            'eta' => null,
            'url' => null,
            'info' => $response,
        );
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()): array {
        /**
         * Simultaneously cancel an existing order and create a new one.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Trading/operation/OrdersController_cancelReplace
         *
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of the currency you want to trade in units of the 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, used on stop $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' editOrder() requires a $symbol argument');
        }
        $type = strtoupper($type);
        if ($type !== 'LIMIT' && $type !== 'MARKET' && $type !== 'STOP_MARKET' && $type !== 'INSTANT') {
            throw new InvalidOrder('Invalid order $type => ' . $type . '. Must be one of => LIMIT, MARKET, STOP_MARKET, INSTANT.');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'mode' => 'ALLOW_FAILURE',
            'cancel' => array(
                'type' => 'ID',
                'id' => $this->parse_number($id),
            ),
            'create' => array(
                'type' => $type,
                'side' => strtoupper($side),
                'market_symbol' => $market['id'],
            ),
        );
        if ($type === 'LIMIT' || $type === 'MARKET') {
            $request['create']['quantity'] = $this->amount_to_precision($symbol, $amount);
            if ($type === 'LIMIT') {
                $request['create']['price'] = $this->price_to_precision($symbol, $price);
            }
        }
        if ($type === 'STOP_MARKET') {
            $request['create']['stop_price'] = $this->price_to_precision($symbol, $price);
            $request['create']['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($type === 'INSTANT') {
            $request['create']['amount'] = $this->price_to_precision($symbol, $amount);
        }
        $response = $this->v3PrivatePostOrdersCancelReplace ($this->extend($request, $params));
        // {
        //     "cancel" => array(
        //         "id" => 123456789
        //     ),
        //     "create" => {
        //         "id" => 1234567890,
        //         "client_order_id" => "451637946501"
        //     }
        // }
        return $this->parse_order($response['create'], $market);
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * Make a withdrawal.
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Withdrawal/operation/WithdrawalsController_createWithdrawal
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency_symbol' => $currency['id'],
            'amount' => $this->number_to_string($amount),
            'destination_address' => $address,
        );
        if ($tag !== null) {
            $request['destination_tag'] = $tag;
        }
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        if ($networkCode !== null) {
            $request['network_code'] = $this->network_code_to_id($networkCode);
        }
        $response = $this->v3PrivatePostWithdrawals ($this->extend($request, $params));
        // {
        //     "amount" => "1",
        //     "currency_symbol" => "xrp",
        //     "network_code" => "ripple",
        //     "destination_address" => "0x1234567890123456789012345678",
        //     "destination_tag" => "123456"
        // }
        return $this->parse_transaction($response);
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch the history of changes, actions done by the user or operations that altered balance of the user
         *
         * @see https://docs.foxbit.com.br/rest/v3/#tag/Account/operation/AccountsController_getTransactions
         *
         * @param {string} $code unified $currency $code, default is null
         * @param {int} [$since] timestamp in ms of the earliest ledger entry, default is null
         * @param {int} [$limit] max number of ledger entrys to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger-structure ledger structure~
         */
        $this->load_markets();
        $request = array();
        if ($code === null) {
            throw new ArgumentsRequired($this->id . ' fetchLedger() requires a $code argument');
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
            if ($limit > 100) {
                $request['page_size'] = 100;
            }
        }
        if ($since !== null) {
            $request['start_time'] = $this->iso8601($since);
        }
        $currency = $this->currency($code);
        $request['symbol'] = $currency['id'];
        $response = $this->v3PrivateGetAccountsSymbolTransactions ($this->extend($request, $params));
        $data = $this->safe_list($response, 'data', array());
        return $this->parse_ledger($data, $currency, $since, $limit);
    }

    public function parse_market(array $market): array {
        $id = $this->safe_string($market, 'symbol');
        $baseAssets = $this->safe_dict($market, 'base');
        $baseId = $this->safe_string($baseAssets, 'symbol');
        $quoteAssets = $this->safe_dict($market, 'quote');
        $quoteId = $this->safe_string($quoteAssets, 'symbol');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $fees = $this->safe_dict($market, 'default_fees');
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'active' => true,
            'type' => 'spot',
            'spot' => true,
            'margin' => false,
            'future' => false,
            'swap' => false,
            'option' => false,
            'contract' => false,
            'settle' => null,
            'settleId' => null,
            'contractSize' => null,
            'linear' => null,
            'inverse' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'taker' => $this->safe_number($fees, 'taker'),
            'maker' => $this->safe_number($fees, 'maker'),
            'percentage' => true,
            'tierBased' => false,
            'feeSide' => 'get',
            'precision' => array(
                'price' => $this->safe_integer($quoteAssets, 'precision'),
                'amount' => $this->safe_integer($baseAssets, 'precision'),
                'cost' => $this->safe_integer($quoteAssets, 'precision'),
            ),
            'limits' => array(
                'amount' => array(
                    'min' => $this->safe_number($market, 'quantity_min'),
                    'max' => null,
                ),
                'price' => array(
                    'min' => $this->safe_number($market, 'price_min'),
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'info' => $market,
        ));
    }

    public function parse_trading_fee(array $entry, ?array $market = null): array {
        return array(
            'info' => $entry,
            'symbol' => $market['symbol'],
            'maker' => $this->safe_number($entry, 'maker'),
            'taker' => $this->safe_number($entry, 'taker'),
            'percentage' => true,
            'tierBased' => true,
        );
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        $marketId = $this->safe_string($ticker, 'market_symbol');
        $symbol = $this->safe_symbol($marketId, $market, null, 'spot');
        $rolling_24h = $ticker['rolling_24h'];
        $best = $this->safe_dict($ticker, 'best');
        $bestAsk = $this->safe_dict($best, 'ask');
        $bestBid = $this->safe_dict($best, 'bid');
        $lastTrade = $ticker['last_trade'];
        $lastPrice = $this->safe_string($lastTrade, 'price');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $this->parse_date($this->safe_string($lastTrade, 'date')),
            'datetime' => $this->iso8601($this->parse_date($this->safe_string($lastTrade, 'date'))),
            'high' => $this->safe_number($rolling_24h, 'high'),
            'low' => $this->safe_number($rolling_24h, 'low'),
            'bid' => $this->safe_number($bestBid, 'price'),
            'bidVolume' => $this->safe_number($bestBid, 'volume'),
            'ask' => $this->safe_number($bestAsk, 'price'),
            'askVolume' => $this->safe_number($bestAsk, 'volume'),
            'vwap' => null,
            'open' => $this->safe_number($rolling_24h, 'open'),
            'close' => $lastPrice,
            'last' => $lastPrice,
            'previousClose' => null,
            'change' => $this->safe_string($rolling_24h, 'price_change'),
            'percentage' => $this->safe_string($rolling_24h, 'price_change_percent'),
            'average' => null,
            'baseVolume' => $this->safe_string($rolling_24h, 'volume'),
            'quoteVolume' => $this->safe_string($rolling_24h, 'quote_volume'),
            'info' => $ticker,
        ), $market);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        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, 6),
        );
    }

    public function parse_trade($trade, $market = null): array {
        $timestamp = $this->parse_date($this->safe_string($trade, 'created_at'));
        $price = $this->safe_string($trade, 'price');
        $amount = $this->safe_string($trade, 'volume', $this->safe_string($trade, 'quantity'));
        $privateSideField = $this->safe_string_lower($trade, 'side');
        $side = $this->safe_string_lower($trade, 'taker_side', $privateSideField);
        $cost = Precise::string_mul($price, $amount);
        $fee = array(
            'currency' => $this->safe_symbol($this->safe_string($trade, 'fee_currency_symbol')),
            'cost' => $this->safe_number($trade, 'fee'),
            'rate' => null,
        );
        return $this->safe_trade(array(
            'id' => $this->safe_string($trade, 'id'),
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'order' => null,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'PARTIALLY_CANCELED' => 'open',
            'ACTIVE' => 'open',
            'PARTIALLY_FILLED' => 'open',
            'FILLED' => 'closed',
            'PENDING_CANCEL' => 'canceled',
            'CANCELED' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null): array {
        $symbol = $this->safe_string($order, 'market_symbol');
        if ($market === null && $symbol !== null) {
            $market = $this->market($symbol);
        }
        if ($market !== null) {
            $symbol = $market['symbol'];
        }
        $timestamp = $this->parse_date($this->safe_string($order, 'created_at'));
        $price = $this->safe_string($order, 'price');
        $filled = $this->safe_string($order, 'quantity_executed');
        $remaining = $this->safe_string($order, 'quantity');
        // TODO => validate logic of $amount here, should this be calculated?
        $amount = null;
        if ($remaining !== null && $filled !== null) {
            $amount = Precise::string_add($remaining, $filled);
        }
        $cost = $this->safe_string($order, 'funds_received');
        if (!$cost) {
            $priceAverage = $this->safe_string($order, 'price_avg');
            $priceToCalculate = $this->safe_string($order, 'price', $priceAverage);
            $cost = Precise::string_mul($priceToCalculate, $amount);
        }
        $side = $this->safe_string_lower($order, 'side');
        $feeCurrency = $this->safe_string_upper($market, 'quoteId');
        if ($side === 'buy') {
            $feeCurrency = $this->safe_string_upper($market, 'baseId');
        }
        return $this->safe_order(array(
            'id' => $this->safe_string($order, 'id'),
            'info' => $order,
            'clientOrderId' => $this->safe_string($order, 'client_order_id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'status' => $this->parse_order_status($this->safe_string($order, 'state')),
            'symbol' => $this->safe_string($market, 'symbol'),
            'type' => $this->safe_string($order, 'type'),
            'timeInForce' => $this->safe_string($order, 'time_in_force'),
            'postOnly' => $this->safe_bool($order, 'post_only'),
            'reduceOnly' => null,
            'side' => $side,
            'price' => $this->parse_number($price),
            'triggerPrice' => $this->safe_number($order, 'stop_price'),
            'takeProfitPrice' => null,
            'stopLossPrice' => null,
            'cost' => $this->parse_number($cost),
            'average' => $this->safe_number($order, 'price_avg'),
            'amount' => $this->parse_number($amount),
            'filled' => $this->parse_number($filled),
            'remaining' => $this->parse_number($remaining),
            'trades' => null,
            'fee' => array(
                'currency' => $feeCurrency,
                'cost' => $this->safe_number($order, 'fee_paid'),
            ),
        ));
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null) {
        $network = $this->safe_dict($depositAddress, 'network');
        $networkId = $this->safe_string($network, 'code');
        $currencyCode = $this->safe_currency_code(null, $currency);
        $unifiedNetwork = $this->network_id_to_code($networkId, $currencyCode);
        return array(
            'address' => $this->safe_string($depositAddress, 'address'),
            'tag' => $this->safe_string($depositAddress, 'tag'),
            'currency' => $currencyCode,
            'network' => $unifiedNetwork,
            'info' => $depositAddress,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            // BOTH
            'SUBMITTING' => 'pending',
            'SUBMITTED' => 'pending',
            'REJECTED' => 'failed',
            // DEPOSIT-SPECIFIC
            'CANCELLED' => 'canceled',
            'ACCEPTED' => 'ok',
            'WARNING' => 'pending',
            'UNBLOCKED' => 'pending',
            'BLOCKED' => 'pending',
            // WITHDRAWAL-SPECIFIC
            'PROCESSING' => 'pending',
            'CANCELED' => 'canceled',
            'FAILED' => 'failed',
            'DONE' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, ?array $currency = null, ?int $since = null, ?int $limit = null): array {
        $cryptoDetails = $this->safe_dict($transaction, 'details_crypto');
        $address = $this->safe_string_2($cryptoDetails, 'receiving_address', 'destination_address');
        $sn = $this->safe_string($transaction, 'sn');
        $type = 'withdrawal';
        if ($sn !== null && $sn[0] === 'D') {
            $type = 'deposit';
        }
        $fee = $this->safe_string($transaction, 'fee', '0');
        $amount = $this->safe_string($transaction, 'amount');
        $currencySymbol = $this->safe_string($transaction, 'currency_symbol');
        $actualAmount = $amount;
        $currencyCode = $this->safe_currency_code($currencySymbol);
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'state'));
        $created_at = $this->safe_string($transaction, 'created_at');
        $timestamp = $this->parse_date($created_at);
        $datetime = $this->iso8601($timestamp);
        if ($fee !== null && $amount !== null) {
            // $actualAmount = $amount - $fee;
            $actualAmount = Precise::string_sub($amount, $fee);
        }
        $feeRate = Precise::string_div($fee, $actualAmount);
        $feeObj = array(
            'cost' => $this->parse_number($fee),
            'currency' => $currencyCode,
            'rate' => $this->parse_number($feeRate),
        );
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'sn'),
            'txid' => $this->safe_string($cryptoDetails, 'transaction_id'),
            'timestamp' => $timestamp,
            'datetime' => $datetime,
            'network' => $this->safe_string($transaction, 'network_code'),
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => $this->safe_string($transaction, 'destination_tag'),
            'tagTo' => $this->safe_string($transaction, 'destination_tag'),
            'tagFrom' => null,
            'type' => $type,
            'amount' => $this->parse_number($amount),
            'currency' => $currencyCode,
            'status' => $status,
            'updated' => null,
            'fee' => $feeObj,
            'comment' => null,
            'internal' => null,
        );
    }

    public function parse_ledger_entry_type($type) {
        $types = array(
            'DEPOSITING' => 'transaction',
            'WITHDRAWING' => 'transaction',
            'TRADING' => 'trade',
            'INTERNAL_TRANSFERING' => 'transfer',
            'OTHERS' => 'transaction',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null) {
        // {
        //     "uuid" => "f8e9f2d6-3c1e-4f2d-8f8e-9f2d6c1e4f2d",
        //     "amount" => "0.0001",
        //     "balance" => "0.0002",
        //     "created_at" => "2021-07-01T12:00:00Z",
        //     "currency_symbol" => "btc",
        //     "fee" => "0.0001",
        //     "locked" => "0.0001",
        //     "locked_amount" => "0.0001",
        //     "reason_type" => "DEPOSITING"
        // }
        $id = $this->safe_string($item, 'uuid');
        $createdAt = $this->safe_string($item, 'created_at');
        $timestamp = $this->parse8601($createdAt);
        $reasonType = $this->safe_string($item, 'reason_type');
        $type = $this->parse_ledger_entry_type($reasonType);
        $exchangeSymbol = $this->safe_string($item, 'currency_symbol');
        $currencySymbol = $this->safe_currency_code($exchangeSymbol);
        $direction = 'in';
        $amount = $this->safe_number($item, 'amount');
        $realAmount = $amount;
        $balance = $this->safe_number($item, 'balance');
        $fee = array(
            'cost' => $this->safe_number($item, 'fee'),
            'currency' => $currencySymbol,
        );
        if ($amount < 0) {
            $direction = 'out';
            $realAmount = $amount * -1;
        }
        return array(
            'id' => $id,
            'info' => $item,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'direction' => $direction,
            'account' => null,
            'referenceId' => null,
            'referenceAccount' => null,
            'type' => $type,
            'currency' => $currencySymbol,
            'amount' => $realAmount,
            'before' => $balance - $amount,
            'after' => $balance,
            'status' => 'ok',
            'fee' => $fee,
        );
    }

    public function sign($path, $api = [], $method = 'GET', $params = array (), $headers = null, $body = null) {
        $version = $api[0];
        $urlPath = $api[1];
        $fullPath = '/rest/' . $version . '/' . $this->implode_params($path, $params);
        if ($version === 'status') {
            $fullPath = '/status';
            $urlPath = 'status';
        }
        $url = $this->urls['api'][$urlPath] . $fullPath;
        $params = $this->omit($params, $this->extract_params($path));
        $timestamp = $this->milliseconds();
        $query = '';
        $signatureQuery = '';
        if ($method === 'GET') {
            $paramKeys = is_array($params) ? array_keys($params) : array();
            $paramKeysLength = count($paramKeys);
            if ($paramKeysLength > 0) {
                $query = $this->urlencode($params);
                $url .= '?' . $query;
            }
            for ($i = 0; $i < count($paramKeys); $i++) {
                $key = $paramKeys[$i];
                $value = $this->safe_string($params, $key);
                if ($value !== null) {
                    $signatureQuery .= $key . '=' . $value;
                }
                if ($i < $paramKeysLength - 1) {
                    $signatureQuery .= '&';
                }
            }
        }
        if ($method === 'POST' || $method === 'PUT') {
            $body = $this->json($params);
        }
        $bodyToSignature = '';
        if ($body !== null) {
            $bodyToSignature = $body;
        }
        $headers = array(
            'Content-Type' => 'application/json',
        );
        if ($urlPath === 'private') {
            $this->check_required_credentials();
            $preHash = $this->number_to_string($timestamp) . $method . $fullPath . $signatureQuery . $bodyToSignature;
            $signature = $this->hmac($this->encode($preHash), $this->encode($this->secret), 'sha256', 'hex');
            $headers['X-FB-ACCESS-KEY'] = $this->apiKey;
            $headers['X-FB-ACCESS-TIMESTAMP'] = $this->number_to_string($timestamp);
            $headers['X-FB-ACCESS-SIGNATURE'] = $signature;
        }
        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 === null) {
            return null;
        }
        $error = $this->safe_dict($response, 'error');
        $code = $this->safe_string($error, 'code');
        $details = $this->safe_list($error, 'details');
        $message = $this->safe_string($error, 'message');
        $detailsString = '';
        if ($details) {
            for ($i = 0; $i < count($details); $i++) {
                $detailsString = $detailsString . $details[$i] . ' ';
            }
        }
        if ($error !== null) {
            $feedback = $this->id . ' ' . $message . ' $details => ' . $detailsString;
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $detailsString, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
