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

class timex extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'timex',
            'name' => 'TimeX',
            'countries' => array( 'AU' ),
            'version' => 'v1',
            'rateLimit' => 1500,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createOrder' => true,
                'createReduceOnlyOrder' => false,
                'createStopLimitOrder' => false,
                'createStopMarketOrder' => false,
                'createStopOrder' => false,
                'editOrder' => true,
                'fetchAllGreeks' => false,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRate' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchBorrowRates' => false,
                'fetchBorrowRatesPerSymbol' => false,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDeposit' => false,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchFundingHistory' => false,
                'fetchFundingInterval' => false,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchIsolatedPositions' => false,
                'fetchLeverage' => false,
                'fetchLeverages' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchLongShortRatio' => false,
                'fetchLongShortRatioHistory' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarginModes' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMarkPrice' => false,
                'fetchMarkPrices' => false,
                'fetchMyLiquidations' => false,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => false,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true, // maker fee only
                'fetchUnderlyingAssets' => false,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
            ),
            'timeframes' => array(
                '1m' => 'I1',
                '5m' => 'I5',
                '15m' => 'I15',
                '30m' => 'I30',
                '1h' => 'H1',
                '2h' => 'H2',
                '4h' => 'H4',
                '6h' => 'H6',
                '12h' => 'H12',
                '1d' => 'D1',
                '1w' => 'W1',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/70423869-6839ab00-1a7f-11ea-8f94-13ae72c31115.jpg',
                'api' => array(
                    'rest' => 'https://plasma-relay-backend.timex.io',
                ),
                'www' => 'https://timex.io',
                'doc' => 'https://plasma-relay-backend.timex.io/swagger-ui/index.html',
                'referral' => 'https://timex.io/?refcode=1x27vNkTbP1uwkCck',
            ),
            'api' => array(
                'addressbook' => array(
                    'get' => array(
                        'me',
                    ),
                    'post' => array(
                        '',
                        'id/{id}',
                        'id/{id}/remove',
                    ),
                ),
                'custody' => array(
                    'get' => array(
                        'credentials', // Get api key for address
                        'credentials/h/{hash}', // Get api key by hash
                        'credentials/k/{key}', // Get api key by key
                        'credentials/me',
                        'credentials/me/address', // Get api key by hash
                        'deposit-addresses', // Get deposit addresses list
                        'deposit-addresses/h/{hash}', // Get deposit address by hash
                    ),
                ),
                'history' => array(
                    'get' => array(
                        'orders', // Gets historical orders
                        'orders/details', // Gets order details
                        'orders/export/csv', // Export orders to csv
                        'trades', // Gets historical trades
                        'trades/export/csv', // Export trades to csv
                    ),
                ),
                'currencies' => array(
                    'get' => array(
                        'a/{address}', // Gets currency by address
                        'i/{id}', // Gets currency by id
                        's/{symbol}', // Gets currency by symbol
                    ),
                    'post' => array(
                        'perform', // Creates new currency
                        'prepare', // Prepare creates new currency
                        'remove/perform', // Removes currency by symbol
                        's/{symbol}/remove/prepare', // Prepare remove currency by symbol
                        's/{symbol}/update/perform', // Prepare update currency by symbol
                        's/{symbol}/update/prepare', // Prepare update currency by symbol
                    ),
                ),
                'manager' => array(
                    'get' => array(
                        'deposits',
                        'transfers',
                        'withdrawals',
                    ),
                ),
                'markets' => array(
                    'get' => array(
                        'i/{id}', // Gets market by id
                        's/{symbol}', // Gets market by symbol
                    ),
                    'post' => array(
                        'perform', // Creates new market
                        'prepare', // Prepare creates new market
                        'remove/perform', // Removes market by symbol
                        's/{symbol}/remove/prepare', // Prepare remove market by symbol
                        's/{symbol}/update/perform', // Prepare update market by symbol
                        's/{symbol}/update/prepare', // Prepare update market by symbol
                    ),
                ),
                'public' => array(
                    'get' => array(
                        'candles', // Gets candles
                        'currencies', // Gets all the currencies
                        'markets', // Gets all the markets
                        'orderbook', // Gets orderbook
                        'orderbook/raw', // Gets raw orderbook
                        'orderbook/v2', // Gets orderbook v2
                        'tickers', // Gets all the tickers
                        'trades', // Gets trades
                    ),
                ),
                'statistics' => array(
                    'get' => array(
                        'address', // calculateAddressStatistics
                    ),
                ),
                'trading' => array(
                    'get' => array(
                        'balances', // Get trading balances for all (or selected) currencies
                        'fees', // Get trading fee rates for all (or selected) markets
                        'orders', // Gets open orders
                    ),
                    'post' => array(
                        'orders', // Create new order
                        'orders/json', // Create orders
                    ),
                    'put' => array(
                        'orders', // Cancel or update orders
                        'orders/json', // Update orders
                    ),
                    'delete' => array(
                        'orders', // Delete orders
                        'orders/json', // Delete orders
                    ),
                ),
                'tradingview' => array(
                    'get' => array(
                        'config', // Gets config
                        'history', // Gets history
                        'symbol_info', // Gets symbol info
                        'time', // Gets time
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    '0' => '\\ccxt\\ExchangeError',
                    '1' => '\\ccxt\\NotSupported',
                    '4000' => '\\ccxt\\BadRequest',
                    '4001' => '\\ccxt\\BadRequest',
                    '4002' => '\\ccxt\\InsufficientFunds',
                    '4003' => '\\ccxt\\AuthenticationError',
                    '4004' => '\\ccxt\\AuthenticationError',
                    '4005' => '\\ccxt\\BadRequest',
                    '4006' => '\\ccxt\\BadRequest',
                    '4007' => '\\ccxt\\BadRequest',
                    '4300' => '\\ccxt\\PermissionDenied',
                    '4100' => '\\ccxt\\AuthenticationError',
                    '4400' => '\\ccxt\\OrderNotFound',
                    '5001' => '\\ccxt\\InvalidOrder',
                    '5002' => '\\ccxt\\ExchangeError',
                    '400' => '\\ccxt\\BadRequest',
                    '401' => '\\ccxt\\AuthenticationError',
                    '403' => '\\ccxt\\PermissionDenied',
                    '404' => '\\ccxt\\OrderNotFound',
                    '429' => '\\ccxt\\RateLimitExceeded',
                    '500' => '\\ccxt\\ExchangeError',
                    '503' => '\\ccxt\\ExchangeNotAvailable',
                ),
                'broad' => array(
                    'Insufficient' => '\\ccxt\\InsufficientFunds',
                ),
            ),
            'options' => array(
                'expireIn' => 31536000, // 365 × 24 × 60 × 60
                'fetchTickers' => array(
                    'period' => '1d',
                ),
                'fetchTrades' => array(
                    'sort' => 'timestamp,asc',
                ),
                'fetchMyTrades' => array(
                    'sort' => 'timestamp,asc',
                ),
                'fetchOpenOrders' => array(
                    'sort' => 'createdAt,asc',
                ),
                'fetchClosedOrders' => array(
                    'sort' => 'createdAt,asc',
                ),
                'defaultSort' => 'timestamp,asc',
                'defaultSortOrders' => 'createdAt,asc',
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => false,
                        'triggerDirection' => false,
                        'triggerPriceType' => null,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        // todo
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => false,
                            'GTD' => true,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 100, // todo
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null, // todo
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 100, // todo
                        'daysBack' => 100000, // todo
                        'daysBackCanceled' => 1, // todo
                        'untilDays' => 100000, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => null,
                    ),
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        $response = $this->tradingviewGetTime ($params);
        //
        //     1708682617
        //
        return $this->parse_to_int($response) * 1000;
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on all markets for timex
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/listMarkets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market data
         */
        $response = $this->publicGetMarkets ($params);
        //
        //     array(
        //         {
        //             "symbol" => "ETHBTC",
        //             "name" => "ETH/BTC",
        //             "baseCurrency" => "ETH",
        //             "baseTokenAddress" => "0x45932db54b38af1f5a57136302eeba66a5975c15",
        //             "quoteCurrency" => "BTC",
        //             "quoteTokenAddress" => "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        //             "feeCurrency" => "BTC",
        //             "feeTokenAddress" => "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        //             "quantityIncrement" => "0.0000001",
        //             "takerFee" => "0.005",
        //             "makerFee" => "0.0025",
        //             "tickSize" => "0.00000001",
        //             "baseMinSize" => "0.0001",
        //             "quoteMinSize" => "0.00001",
        //             "locked" => false
        //         }
        //     )
        //
        return $this->parse_markets($response);
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/listCurrencies
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        $response = $this->publicGetCurrencies ($params);
        //
        //     array(
        //         array(
        //             "symbol" => "BTC",
        //             "name" => "Bitcoin",
        //             "address" => "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        //             "icon" => "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggb3BhY2l0eT0iMC41IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMwIDUzQzQyLjcwMjUgNTMgNTMgNDIuNzAyNSA1MyAzMEM1MyAxNy4yOTc1IDQyLjcwMjUgNyAzMCA3QzE3LjI5NzUgNyA3IDE3LjI5NzUgNyAzMEM3IDQyLjcwMjUgMTcuMjk3NSA1MyAzMCA1M1pNMzAgNTVDNDMuODA3MSA1NSA1NSA0My44MDcxIDU1IDMwQzU1IDE2LjE5MjkgNDMuODA3MSA1IDMwIDVDMTYuMTkyOSA1IDUgMTYuMTkyOSA1IDMwQzUgNDMuODA3MSAxNi4xOTI5IDU1IDMwIDU1WiIvPgo8cGF0aCBkPSJNNDAuOTQyNSAyNi42NTg1QzQxLjQwMDMgMjMuNjExMyAzOS4wNzA1IDIxLjk3MzIgMzUuODg0OCAyMC44ODA0TDM2LjkxODIgMTYuNzUyNkwzNC4zOTUxIDE2LjEyNjRMMzMuMzg5IDIwLjE0NTVDMzIuNzI1OCAxOS45ODA5IDMyLjA0NDUgMTkuODI1NiAzMS4zNjc1IDE5LjY3MTdMMzIuMzgwOCAxNS42MjYyTDI5Ljg1OTEgMTVMMjguODI1IDE5LjEyNjRDMjguMjc2IDE5LjAwMTkgMjcuNzM3IDE4Ljg3ODggMjcuMjEzOSAxOC43NDkzTDI3LjIxNjggMTguNzM2NEwyMy43MzcyIDE3Ljg3MTJMMjMuMDY2IDIwLjU1NDhDMjMuMDY2IDIwLjU1NDggMjQuOTM4IDIwLjk4MjEgMjQuODk4NSAyMS4wMDg1QzI1LjkyMDQgMjEuMjYyNiAyNi4xMDUgMjEuOTM2IDI2LjA3NDEgMjIuNDY5OUwyNC44OTcgMjcuMTcyNEMyNC45Njc1IDI3LjE5MDMgMjUuMDU4NyAyNy4yMTYgMjUuMTU5MyAyNy4yNTYxQzI1LjA3NTMgMjcuMjM1NCAyNC45ODU0IDI3LjIxMjQgMjQuODkyNyAyNy4xOTAzTDIzLjI0MjggMzMuNzc3OEMyMy4xMTc3IDM0LjA4NjkgMjIuODAwOCAzNC41NTA2IDIyLjA4NjUgMzQuMzc0NkMyMi4xMTE3IDM0LjQxMTEgMjAuMjUyNiAzMy45MTg3IDIwLjI1MjYgMzMuOTE4N0wxOSAzNi43OTQ5TDIyLjI4MzQgMzcuNjFDMjIuODk0MiAzNy43NjI0IDIzLjQ5MjggMzcuOTIyIDI0LjA4MjEgMzguMDcyM0wyMy4wMzggNDIuMjQ3NEwyNS41NTgyIDQyLjg3MzZMMjYuNTkyMyAzOC43NDI5QzI3LjI4MDcgMzguOTI5IDI3Ljk0OSAzOS4xMDA3IDI4LjYwMyAzOS4yNjI0TDI3LjU3MjUgNDMuMzczOEwzMC4wOTU2IDQ0TDMxLjEzOTcgMzkuODMyOEMzNS40NDIyIDQwLjY0MzYgMzguNjc3NCA0MC4zMTY2IDQwLjAzOTIgMzYuNDQxNEM0MS4xMzY1IDMzLjMyMTIgMzkuOTg0NiAzMS41MjEzIDM3LjcyMDkgMzAuMzQ3N0MzOS4zNjk0IDI5Ljk2OTEgNDAuNjExMiAyOC44ODkyIDQwLjk0MjUgMjYuNjU4NVYyNi42NTg1Wk0zNS4xNzc3IDM0LjcwODhDMzQuMzk4IDM3LjgyOSAyOS4xMjI2IDM2LjE0MjIgMjcuNDEyMiAzNS43MTkzTDI4Ljc5NzcgMzAuMTg4MUMzMC41MDgxIDMwLjYxMzIgMzUuOTkyNiAzMS40NTQ4IDM1LjE3NzcgMzQuNzA4OFpNMzUuOTU4MSAyNi42MTM0QzM1LjI0NjcgMjkuNDUxNyAzMC44NTU5IDI4LjAwOTcgMjkuNDMxNiAyNy42NTYxTDMwLjY4NzcgMjIuNjM5NUMzMi4xMTIgMjIuOTkzIDM2LjY5OSAyMy42NTI4IDM1Ljk1ODEgMjYuNjEzNFoiLz4KPC9zdmc+Cg==",
        //             "background" => "transparent",
        //             "fiatSymbol" => "BTC",
        //             "decimals" => 8,
        //             "tradeDecimals" => 20,
        //             "displayDecimals" => 4,
        //             "crypto" => true,
        //             "depositEnabled" => true,
        //             "withdrawalEnabled" => true,
        //             "transferEnabled" => true,
        //             "buyEnabled" => false,
        //             "purchaseEnabled" => false,
        //             "redeemEnabled" => false,
        //             "active" => true,
        //             "withdrawalFee" => "50000000000000000",
        //             "purchaseCommissions" => array()
        //         ),
        //     )
        //
        return $this->parse_currencies($response);
    }

    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://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Manager/getDeposits
         *
         * @param {string} $code unified $currency $code
         * @param {int} [$since] the earliest time in ms to fetch deposits for
         * @param {int} [$limit] the maximum number of deposits structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $address = $this->safe_string($params, 'address');
        $params = $this->omit($params, 'address');
        if ($address === null) {
            throw new ArgumentsRequired($this->id . ' fetchDeposits() requires an $address parameter');
        }
        $request = array(
            'address' => $address,
        );
        $response = $this->managerGetDeposits ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "from" => "0x1134cc86b45039cc211c6d1d2e4b3c77f60207ed",
        //             "timestamp" => "2022-01-01T00:00:00Z",
        //             "to" => "0x1134cc86b45039cc211c6d1d2e4b3c77f60207ed",
        //             "token" => "0x6baad3fe5d0fd4be604420e728adbd68d67e119e",
        //             "transferHash" => "0x5464cdff35448314e178b8677ea41e670ea0f2533f4e52bfbd4e4a6cfcdef4c2",
        //             "value" => "100"
        //         }
        //     )
        //
        $currency = $this->safe_currency($code);
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made to an account
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Manager/getWithdraws
         *
         * @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 transaction 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~
         */
        $address = $this->safe_string($params, 'address');
        $params = $this->omit($params, 'address');
        if ($address === null) {
            throw new ArgumentsRequired($this->id . ' fetchDeposits() requires an $address parameter');
        }
        $request = array(
            'address' => $address,
        );
        $response = $this->managerGetWithdrawals ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "from" => "0x1134cc86b45039cc211c6d1d2e4b3c77f60207ed",
        //             "timestamp" => "2022-01-01T00:00:00Z",
        //             "to" => "0x1134cc86b45039cc211c6d1d2e4b3c77f60207ed",
        //             "token" => "0x6baad3fe5d0fd4be604420e728adbd68d67e119e",
        //             "transferHash" => "0x5464cdff35448314e178b8677ea41e670ea0f2533f4e52bfbd4e4a6cfcdef4c2",
        //             "value" => "100"
        //         }
        //     )
        //
        $currency = $this->safe_currency($code);
        return $this->parse_transactions($response, $currency, $since, $limit);
    }

    public function get_currency_by_address($address) {
        $currencies = $this->currencies;
        for ($i = 0; $i < count($currencies); $i++) {
            $currency = $currencies[$i];
            $info = $this->safe_value($currency, 'info', array());
            $a = $this->safe_string($info, 'address');
            if ($a === $address) {
                return $currency;
            }
        }
        return null;
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        //     {
        //         "from" => "0x1134cc86b45039cc211c6d1d2e4b3c77f60207ed",
        //         "timestamp" => "2022-01-01T00:00:00Z",
        //         "to" => "0x1134cc86b45039cc211c6d1d2e4b3c77f60207ed",
        //         "token" => "0x6baad3fe5d0fd4be604420e728adbd68d67e119e",
        //         "transferHash" => "0x5464cdff35448314e178b8677ea41e670ea0f2533f4e52bfbd4e4a6cfcdef4c2",
        //         "value" => "100"
        //     }
        //
        $datetime = $this->safe_string($transaction, 'timestamp');
        $currencyAddresss = $this->safe_string($transaction, 'token', '');
        $currency = $this->get_currency_by_address($currencyAddresss);
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'transferHash'),
            'txid' => $this->safe_string($transaction, 'txid'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'network' => null,
            'address' => null,
            'addressTo' => $this->safe_string($transaction, 'to'),
            'addressFrom' => $this->safe_string($transaction, 'from'),
            'tag' => null,
            'tagTo' => null,
            'tagFrom' => null,
            'type' => null,
            'amount' => $this->safe_number($transaction, 'value'),
            'currency' => $this->safe_currency_code(null, $currency),
            'status' => 'ok',
            'updated' => null,
            'internal' => null,
            'comment' => null,
            'fee' => null,
        );
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/listTickers
         *
         * @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();
        $period = $this->safe_string($this->options['fetchTickers'], 'period', '1d');
        $request = array(
            'period' => $this->timeframes[$period], // I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1
        );
        $response = $this->publicGetTickers ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "ask" => 0.017,
        //             "bid" => 0.016,
        //             "high" => 0.019,
        //             "last" => 0.017,
        //             "low" => 0.015,
        //             "market" => "TIME/ETH",
        //             "open" => 0.016,
        //             "period" => "H1",
        //             "timestamp" => "2018-12-14T20:50:36.134Z",
        //             "volume" => 4.57,
        //             "volumeQuote" => 0.07312
        //         }
        //     )
        //
        return $this->parse_tickers($response, $symbols);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price $ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/listTickers
         *
         * @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);
        $period = $this->safe_string($this->options['fetchTickers'], 'period', '1d');
        $request = array(
            'market' => $market['id'],
            'period' => $this->timeframes[$period], // I1, I5, I15, I30, H1, H2, H4, H6, H12, D1, W1
        );
        $response = $this->publicGetTickers ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "ask" => 0.017,
        //             "bid" => 0.016,
        //             "high" => 0.019,
        //             "last" => 0.017,
        //             "low" => 0.015,
        //             "market" => "TIME/ETH",
        //             "open" => 0.016,
        //             "period" => "H1",
        //             "timestamp" => "2018-12-14T20:50:36.134Z",
        //             "volume" => 4.57,
        //             "volumeQuote" => 0.07312
        //         }
        //     )
        //
        $ticker = $this->safe_dict($response, 0);
        return $this->parse_ticker($ticker, $market);
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/orderbookV2
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the order book for
         * @param {int} [$limit] the maximum amount of order book entries to return
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetOrderbookV2 ($this->extend($request, $params));
        //
        //     {
        //         "timestamp":"2019-12-05T00:21:09.538",
        //         "bid":array(
        //             array(
        //                 "index":"2",
        //                 "price":"0.02024007",
        //                 "baseTokenAmount":"0.0096894",
        //                 "baseTokenCumulativeAmount":"0.0096894",
        //                 "quoteTokenAmount":"0.000196114134258",
        //                 "quoteTokenCumulativeAmount":"0.000196114134258"
        //             ),
        //         "ask":[
        //             array(
        //                 "index":"-3",
        //                 "price":"0.02024012",
        //                 "baseTokenAmount":"0.005",
        //                 "baseTokenCumulativeAmount":"0.005",
        //                 "quoteTokenAmount":"0.0001012006",
        //                 "quoteTokenCumulativeAmount":"0.0001012006"
        //             ),
        //         )
        //     }
        //
        $timestamp = $this->parse8601($this->safe_string($response, 'timestamp'));
        return $this->parse_order_book($response, $symbol, $timestamp, 'bid', 'ask', 'price', 'baseTokenAmount');
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent trades for a particular $symbol
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/listTrades
         *
         * @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);
        $options = $this->safe_value($this->options, 'fetchTrades', array());
        $defaultSort = $this->safe_value($options, 'sort', 'timestamp,asc');
        $sort = $this->safe_string($params, 'sort', $defaultSort);
        $query = $this->omit($params, 'sort');
        $request = array(
            // 'address' => 'string', // trade’s member account (?)
            // 'cursor' => 1234, // int64 (?)
            // 'from' => $this->iso8601($since),
            'market' => $market['id'],
            // 'page' => 0, // results page you want to retrieve 0 .. N
            // 'size' => $limit, // number of records per page, 100 by default
            'sort' => $sort, // array[string], sorting criteria in the format "property,asc" or "property,desc", default is ascending
            // 'till' => $this->iso8601($this->milliseconds()),
        );
        if ($since !== null) {
            $request['from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['size'] = $limit; // default is 100
        }
        $response = $this->publicGetTrades ($this->extend($request, $query));
        //
        //     array(
        //         {
        //             "id":1,
        //             "timestamp":"2019-06-25T17:01:50.309",
        //             "direction":"BUY",
        //             "price":"0.027",
        //             "quantity":"0.001"
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Public/listCandles
         *
         * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
         * @param {string} $timeframe the length of time each candle represents
         * @param {int} [$since] timestamp in ms of the earliest candle to fetch
         * @param {int} [$limit] the maximum amount of candles to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest candle to fetch
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
            'period' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
        );
        // if $since and $limit are not specified
        $duration = $this->parse_timeframe($timeframe);
        $until = $this->safe_integer($params, 'until');
        if ($limit === null) {
            $limit = 1000; // exchange provides tens of thousands of data, but we set generous default value
        }
        if ($since !== null) {
            $request['from'] = $this->iso8601($since);
            if ($until === null) {
                $request['till'] = $this->iso8601($this->sum($since, $this->sum($limit, 1) * $duration * 1000));
            } else {
                $request['till'] = $this->iso8601($until);
            }
        } elseif ($until !== null) {
            $request['till'] = $this->iso8601($until);
            $fromTimestamp = $until - $this->sum($limit, 1) * $duration * 1000;
            $request['from'] = $this->iso8601($fromTimestamp);
        } else {
            $now = $this->milliseconds();
            $request['till'] = $this->iso8601($now);
            $request['from'] = $this->iso8601($now - $this->sum($limit, 1) * $duration * 1000 - 1);
        }
        $params = $this->omit($params, 'until');
        $response = $this->publicGetCandles ($this->extend($request, $params));
        //
        //     array(
        //         array(
        //             "timestamp":"2019-12-04T23:00:00",
        //             "open":"0.02024009",
        //             "high":"0.02024009",
        //             "low":"0.02024009",
        //             "close":"0.02024009",
        //             "volume":"0.00008096036",
        //             "volumeQuote":"0.004",
        //         ),
        //     )
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function parse_balance($response): array {
        $result = array(
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
        );
        for ($i = 0; $i < count($response); $i++) {
            $balance = $response[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['total'] = $this->safe_string($balance, 'totalBalance');
            $account['used'] = $this->safe_string($balance, 'lockedBalance');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    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://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Trading/getBalances
         *
         * @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->tradingGetBalances ($params);
        //
        //     array(
        //         array("currency":"BTC","totalBalance":"0","lockedBalance":"0"),
        //         array("currency":"AUDT","totalBalance":"0","lockedBalance":"0"),
        //         array("currency":"ETH","totalBalance":"0","lockedBalance":"0"),
        //         array("currency":"TIME","totalBalance":"0","lockedBalance":"0"),
        //         array("currency":"USDT","totalBalance":"0","lockedBalance":"0")
        //     )
        //
        return $this->parse_balance($response);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade $order
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Trading/createOrder
         *
         * @param {string} $symbol unified $symbol of the $market to create an $order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in $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~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $uppercaseSide = strtoupper($side);
        $uppercaseType = strtoupper($type);
        $postOnly = $this->safe_bool($params, 'postOnly', false);
        if ($postOnly) {
            $uppercaseType = 'POST_ONLY';
            $params = $this->omit($params, array( 'postOnly' ));
        }
        $request = array(
            'symbol' => $market['id'],
            'quantity' => $this->amount_to_precision($symbol, $amount),
            'side' => $uppercaseSide,
            'orderTypes' => $uppercaseType,
            // 'clientOrderId' => '123',
            // 'expireIn' => 1575523308, // in seconds
            // 'expireTime' => 1575523308, // unix timestamp
        );
        $query = $params;
        if (($uppercaseType === 'LIMIT') || ($uppercaseType === 'POST_ONLY')) {
            $request['price'] = $this->price_to_precision($symbol, $price);
            $defaultExpireIn = $this->safe_integer($this->options, 'expireIn');
            $expireTime = $this->safe_value($params, 'expireTime');
            $expireIn = $this->safe_value($params, 'expireIn', $defaultExpireIn);
            if ($expireTime !== null) {
                $request['expireTime'] = $expireTime;
            } elseif ($expireIn !== null) {
                $request['expireIn'] = $expireIn;
            } else {
                throw new InvalidOrder($this->id . ' createOrder() method requires a $expireTime or $expireIn param for a ' . $type . ' $order, you can also set the $expireIn exchange-wide option');
            }
            $query = $this->omit($params, array( 'expireTime', 'expireIn' ));
        } else {
            $request['price'] = 0;
        }
        $response = $this->tradingPostOrders ($this->extend($request, $query));
        //
        //     {
        //         "orders" => array(
        //             {
        //                 "cancelledQuantity" => "0.3",
        //                 "clientOrderId" => "my-$order-1",
        //                 "createdAt" => "1970-01-01T00:00:00",
        //                 "cursorId" => 50,
        //                 "expireTime" => "1970-01-01T00:00:00",
        //                 "filledQuantity" => "0.3",
        //                 "id" => "string",
        //                 "price" => "0.017",
        //                 "quantity" => "0.3",
        //                 "side" => "BUY",
        //                 "symbol" => "TIMEETH",
        //                 "type" => "LIMIT",
        //                 "updatedAt" => "1970-01-01T00:00:00"
        //             }
        //         )
        //     }
        //
        $orders = $this->safe_value($response, 'orders', array());
        $order = $this->safe_dict($orders, 0, array());
        return $this->parse_order($order, $market);
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'id' => $id,
        );
        if ($amount !== null) {
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $response = $this->tradingPutOrders ($this->extend($request, $params));
        //
        //     {
        //         "changedOrders" => array(
        //             array(
        //                 "newOrder" => array(
        //                 "cancelledQuantity" => "0.3",
        //                 "clientOrderId" => "my-$order-1",
        //                 "createdAt" => "1970-01-01T00:00:00",
        //                 "cursorId" => 50,
        //                 "expireTime" => "1970-01-01T00:00:00",
        //                 "filledQuantity" => "0.3",
        //                 "id" => "string",
        //                 "price" => "0.017",
        //                 "quantity" => "0.3",
        //                 "side" => "BUY",
        //                 "symbol" => "TIMEETH",
        //                 "type" => "LIMIT",
        //                 "updatedAt" => "1970-01-01T00:00:00"
        //                 ),
        //                 "oldId" => "string",
        //             ),
        //         ),
        //         "unchangedOrders" => array( "string" ),
        //     }
        //
        if (is_array($response) && array_key_exists('unchangedOrders', $response)) {
            $orderIds = $this->safe_value($response, 'unchangedOrders', array());
            $orderId = $this->safe_string($orderIds, 0);
            return $this->safe_order(array(
                'id' => $orderId,
                'info' => $response,
            ));
        }
        $orders = $this->safe_value($response, 'changedOrders', array());
        $firstOrder = $this->safe_value($orders, 0, array());
        $order = $this->safe_dict($firstOrder, 'newOrder', array());
        return $this->parse_order($order, $market);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Trading/deleteOrders
         *
         * @param {string} $id order $id
         * @param {string} $symbol not used by timex cancelOrder ()
         * @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();
        $orders = $this->cancel_orders(array( $id ), $symbol, $params);
        return $this->safe_dict($orders, 0);
    }

    public function cancel_orders($ids, ?string $symbol = null, $params = array ()) {
        /**
         * cancel multiple $orders
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Trading/deleteOrders
         *
         * @param {string[]} $ids order $ids
         * @param {string} $symbol unified market $symbol, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $request = array(
            'id' => $ids,
        );
        $response = $this->tradingDeleteOrders ($this->extend($request, $params));
        //
        //     {
        //         "changedOrders" => array(
        //             array(
        //                 "newOrder" => array(
        //                     "cancelledQuantity" => "0.3",
        //                     "clientOrderId" => "my-order-1",
        //                     "createdAt" => "1970-01-01T00:00:00",
        //                     "cursorId" => 50,
        //                     "expireTime" => "1970-01-01T00:00:00",
        //                     "filledQuantity" => "0.3",
        //                     "id" => "string",
        //                     "price" => "0.017",
        //                     "quantity" => "0.3",
        //                     "side" => "BUY",
        //                     "symbol" => "TIMEETH",
        //                     "type" => "LIMIT",
        //                     "updatedAt" => "1970-01-01T00:00:00"
        //                 ),
        //                 "oldId" => "string",
        //             ),
        //         ),
        //         "unchangedOrders" => array( "string" ),
        //     }
        //
        $changedOrders = $this->safe_list($response, 'changedOrders', array());
        $unchangedOrders = $this->safe_list($response, 'unchangedOrders', array());
        $orders = array();
        for ($i = 0; $i < count($changedOrders); $i++) {
            $newOrder = $this->safe_dict($changedOrders[$i], 'newOrder');
            $orders[] = $this->parse_order($newOrder);
        }
        for ($i = 0; $i < count($unchangedOrders); $i++) {
            $orders[] = $this->safe_order(array(
                'info' => $unchangedOrders[$i],
                'id' => $unchangedOrders[$i],
                'status' => 'unchanged',
            ));
        }
        return $orders;
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an $order made by the user
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/History/getOrderDetails
         *
         * @param {string} $id $order $id
         * @param {string} $symbol not used by timex fetchOrder
         * @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(
            'orderHash' => $id,
        );
        $response = $this->historyGetOrdersDetails ($request);
        //
        //     {
        //         "order" => array(
        //             "cancelledQuantity" => "0.3",
        //             "clientOrderId" => "my-$order-1",
        //             "createdAt" => "1970-01-01T00:00:00",
        //             "cursorId" => 50,
        //             "expireTime" => "1970-01-01T00:00:00",
        //             "filledQuantity" => "0.3",
        //             "id" => "string",
        //             "price" => "0.017",
        //             "quantity" => "0.3",
        //             "side" => "BUY",
        //             "symbol" => "TIMEETH",
        //             "type" => "LIMIT",
        //             "updatedAt" => "1970-01-01T00:00:00"
        //         ),
        //         "trades" => array(
        //             {
        //                 "fee" => "0.3",
        //                 "id" => 100,
        //                 "makerOrTaker" => "MAKER",
        //                 "makerOrderId" => "string",
        //                 "price" => "0.017",
        //                 "quantity" => "0.3",
        //                 "side" => "BUY",
        //                 "symbol" => "TIMEETH",
        //                 "takerOrderId" => "string",
        //                 "timestamp" => "2019-12-05T07:48:26.310Z"
        //             }
        //         )
        //     }
        //
        $order = $this->safe_value($response, 'order', array());
        $trades = $this->safe_list($response, 'trades', array());
        return $this->parse_order($this->extend($order, array( 'trades' => $trades )));
    }

    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://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Trading/getOpenOrders
         *
         * @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 $orders structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $options = $this->safe_value($this->options, 'fetchOpenOrders', array());
        $defaultSort = $this->safe_value($options, 'sort', 'createdAt,asc');
        $sort = $this->safe_string($params, 'sort', $defaultSort);
        $query = $this->omit($params, 'sort');
        $request = array(
            // 'clientOrderId' => '123', // order’s client id list for filter
            // page => 0, // results page you want to retrieve (0 .. N)
            'sort' => $sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple $sort criteria are supported
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['size'] = $limit;
        }
        $response = $this->tradingGetOrders ($this->extend($request, $query));
        //
        //     {
        //         "orders" => array(
        //             {
        //                 "cancelledQuantity" => "0.3",
        //                 "clientOrderId" => "my-order-1",
        //                 "createdAt" => "1970-01-01T00:00:00",
        //                 "cursorId" => 50,
        //                 "expireTime" => "1970-01-01T00:00:00",
        //                 "filledQuantity" => "0.3",
        //                 "id" => "string",
        //                 "price" => "0.017",
        //                 "quantity" => "0.3",
        //                 "side" => "BUY",
        //                 "symbol" => "TIMEETH",
        //                 "type" => "LIMIT",
        //                 "updatedAt" => "1970-01-01T00:00:00"
        //             }
        //         )
        //     }
        //
        $orders = $this->safe_list($response, 'orders', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple closed $orders made by the user
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/History/getOrders
         *
         * @param {string} $symbol unified $market $symbol of the $market $orders were made in
         * @param {int} [$since] the earliest time in ms to fetch $orders for
         * @param {int} [$limit] the maximum number of order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $options = $this->safe_value($this->options, 'fetchClosedOrders', array());
        $defaultSort = $this->safe_value($options, 'sort', 'createdAt,asc');
        $sort = $this->safe_string($params, 'sort', $defaultSort);
        $query = $this->omit($params, 'sort');
        $request = array(
            // 'clientOrderId' => '123', // order’s client id list for filter
            // page => 0, // results page you want to retrieve (0 .. N)
            'sort' => $sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple $sort criteria are supported
            'side' => 'BUY', // or 'SELL'
            // 'till' => $this->iso8601($this->milliseconds()),
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['size'] = $limit;
        }
        $response = $this->historyGetOrders ($this->extend($request, $query));
        //
        //     {
        //         "orders" => array(
        //             {
        //                 "cancelledQuantity" => "0.3",
        //                 "clientOrderId" => "my-order-1",
        //                 "createdAt" => "1970-01-01T00:00:00",
        //                 "cursorId" => 50,
        //                 "expireTime" => "1970-01-01T00:00:00",
        //                 "filledQuantity" => "0.3",
        //                 "id" => "string",
        //                 "price" => "0.017",
        //                 "quantity" => "0.3",
        //                 "side" => "BUY",
        //                 "symbol" => "TIMEETH",
        //                 "type" => "LIMIT",
        //                 "updatedAt" => "1970-01-01T00:00:00"
        //             }
        //         )
        //     }
        //
        $orders = $this->safe_list($response, 'orders', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all $trades made by the user
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/History/getTrades_1
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch $trades for
         * @param {int} [$limit] the maximum number of $trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->load_markets();
        $options = $this->safe_value($this->options, 'fetchMyTrades', array());
        $defaultSort = $this->safe_value($options, 'sort', 'timestamp,asc');
        $sort = $this->safe_string($params, 'sort', $defaultSort);
        $query = $this->omit($params, 'sort');
        $request = array(
            // 'cursorId' => 123, // int64 (?)
            // 'from' => $this->iso8601($since),
            // 'makerOrderId' => '1234', // maker order hash
            // 'owner' => '...', // owner address (?)
            // 'page' => 0, // results page you want to retrieve (0 .. N)
            // 'side' => 'BUY', // or 'SELL'
            // 'size' => $limit,
            'sort' => $sort, // sorting criteria in the format "property,asc" or "property,desc", default order is ascending, multiple $sort criteria are supported
            // 'symbol' => $market['id'],
            // 'takerOrderId' => '1234',
            // 'till' => $this->iso8601($this->milliseconds()),
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['from'] = $this->iso8601($since);
        }
        if ($limit !== null) {
            $request['size'] = $limit;
        }
        $response = $this->historyGetTrades ($this->extend($request, $query));
        //
        //     {
        //         "trades" => array(
        //             {
        //                 "fee" => "0.3",
        //                 "id" => 100,
        //                 "makerOrTaker" => "MAKER",
        //                 "makerOrderId" => "string",
        //                 "price" => "0.017",
        //                 "quantity" => "0.3",
        //                 "side" => "BUY",
        //                 "symbol" => "TIMEETH",
        //                 "takerOrderId" => "string",
        //                 "timestamp" => "2019-12-08T04:54:11.171Z"
        //             }
        //         )
        //     }
        //
        $trades = $this->safe_list($response, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        //
        //     {
        //         "fee" => 0.0075,
        //         "market" => "ETHBTC"
        //     }
        //
        $marketId = $this->safe_string($fee, 'market');
        $rate = $this->safe_number($fee, 'fee');
        return array(
            'info' => $fee,
            'symbol' => $this->safe_symbol($marketId, $market),
            'maker' => $rate,
            'taker' => $rate,
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): array {
        /**
         * fetch the trading fees for a $market
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Trading/getFees
         *
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'markets' => $market['id'],
        );
        $response = $this->tradingGetFees ($this->extend($request, $params));
        //
        //     array(
        //         {
        //             "fee" => 0.0075,
        //             "market" => "ETHBTC"
        //         }
        //     )
        //
        $result = $this->safe_value($response, 0, array());
        return $this->parse_trading_fee($result, $market);
    }

    public function parse_market(array $market): array {
        //
        //     {
        //         "symbol" => "ETHBTC",
        //         "name" => "ETH/BTC",
        //         "baseCurrency" => "ETH",
        //         "baseTokenAddress" => "0x45932db54b38af1f5a57136302eeba66a5975c15",
        //         "quoteCurrency" => "BTC",
        //         "quoteTokenAddress" => "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        //         "feeCurrency" => "BTC",
        //         "feeTokenAddress" => "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        //         "quantityIncrement" => "0.0000001",
        //         "takerFee" => "0.005",
        //         "makerFee" => "0.0025",
        //         "tickSize" => "0.00000001",
        //         "baseMinSize" => "0.0001",
        //         "quoteMinSize" => "0.00001",
        //         "locked" => false
        //     }
        //
        $locked = $this->safe_value($market, 'locked');
        $id = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'baseCurrency');
        $quoteId = $this->safe_string($market, 'quoteCurrency');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $amountIncrement = $this->safe_string($market, 'quantityIncrement');
        $minBase = $this->safe_string($market, 'baseMinSize');
        $minAmount = Precise::string_max($amountIncrement, $minBase);
        $priceIncrement = $this->safe_string($market, 'tickSize');
        $minCost = $this->safe_number($market, 'quoteMinSize');
        return array(
            'id' => $id,
            'symbol' => $base . '/' . $quote,
            'base' => $base,
            'quote' => $quote,
            'settle' => null,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => null,
            'type' => 'spot',
            'spot' => true,
            'margin' => false,
            'swap' => false,
            'future' => false,
            'option' => false,
            'active' => !$locked,
            'contract' => false,
            'linear' => null,
            'inverse' => null,
            'taker' => $this->safe_number($market, 'takerFee'),
            'maker' => $this->safe_number($market, 'makerFee'),
            'contractSize' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($market, 'quantityIncrement'),
                'price' => $this->safe_number($market, 'tickSize'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->parse_number($minAmount),
                    'max' => null,
                ),
                'price' => array(
                    'min' => $this->parse_number($priceIncrement),
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $minCost,
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        );
    }

    public function parse_currency(array $currency): array {
        //
        //     {
        //         "symbol" => "BTC",
        //         "name" => "Bitcoin",
        //         "address" => "0x8370fbc6ddec1e18b4e41e72ed943e238458487c",
        //         "icon" => "data:image/svg+xml;base64,PHN2ZyB3aWR...mc+Cg==",
        //         "background" => "transparent",
        //         "fiatSymbol" => "BTC",
        //         "decimals" => 8,
        //         "tradeDecimals" => 20,
        //         "displayDecimals" => 4,
        //         "crypto" => true,
        //         "depositEnabled" => true,
        //         "withdrawalEnabled" => true,
        //         "transferEnabled" => true,
        //         "buyEnabled" => false,
        //         "purchaseEnabled" => false,
        //         "redeemEnabled" => false,
        //         "active" => true,
        //         "withdrawalFee" => "50000000000000000",
        //         "purchaseCommissions" => array()
        //     }
        //
        // https://github.com/ccxt/ccxt/issues/6878
        //
        //     {
        //         "symbol":"XRP",
        //         "name":"Ripple",
        //         "address":"0x0dc8882914f3ddeebf4cec6dc20edb99df3def6c",
        //         "decimals":6,
        //         "tradeDecimals":16,
        //         "depositEnabled":true,
        //         "withdrawalEnabled":true,
        //         "transferEnabled":true,
        //         "active":true
        //     }
        //
        $id = $this->safe_string($currency, 'symbol');
        $code = $this->safe_currency_code($id);
        // $fee = $this->safe_number($currency, 'withdrawalFee');
        $feeString = $this->safe_string($currency, 'withdrawalFee');
        $tradeDecimals = $this->safe_integer($currency, 'tradeDecimals');
        $fee = null;
        if (($feeString !== null) && ($tradeDecimals !== null)) {
            $feeStringLen = count($feeString);
            $dotIndex = $feeStringLen - $tradeDecimals;
            if ($dotIndex > 0) {
                $whole = mb_substr($feeString, 0, $dotIndex - 0);
                $fraction = mb_substr($feeString, -$dotIndex);
                $fee = $this->parse_number($whole . '.' . $fraction);
            } else {
                $fraction = '.';
                for ($i = 0; $i < -$dotIndex; $i++) {
                    $fraction .= '0';
                }
                $fee = $this->parse_number($fraction . $feeString);
            }
        }
        return $this->safe_currency_structure(array(
            'id' => $code,
            'code' => $code,
            'info' => $currency,
            'type' => null,
            'name' => $this->safe_string($currency, 'name'),
            'active' => $this->safe_bool($currency, 'active'),
            'deposit' => $this->safe_bool($currency, 'depositEnabled'),
            'withdraw' => $this->safe_bool($currency, 'withdrawalEnabled'),
            'fee' => $fee,
            'precision' => $this->parse_number($this->parse_precision($this->safe_string($currency, 'decimals'))),
            'limits' => array(
                'withdraw' => array( 'min' => null, 'max' => null ),
                'amount' => array( 'min' => null, 'max' => null ),
            ),
            'networks' => array(),
        ));
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "ask" => 0.017,
        //         "bid" => 0.016,
        //         "high" => 0.019,
        //         "last" => 0.017,
        //         "low" => 0.015,
        //         "market" => "TIME/ETH",
        //         "open" => 0.016,
        //         "period" => "H1",
        //         "timestamp" => "2018-12-14T20:50:36.134Z",
        //         "volume" => 4.57,
        //         "volumeQuote" => 0.07312
        //     }
        //
        $marketId = $this->safe_string($ticker, 'market');
        $symbol = $this->safe_symbol($marketId, $market, '/');
        $timestamp = $this->parse8601($this->safe_string($ticker, 'timestamp'));
        $last = $this->safe_string($ticker, 'last');
        $open = $this->safe_string($ticker, 'open');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'info' => $ticker,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => $this->safe_string($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_string($ticker, 'ask'),
            'askVolume' => null,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'volume'),
            'quoteVolume' => $this->safe_string($ticker, 'volumeQuote'),
        ), $market);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //     {
        //         "id":1,
        //         "timestamp":"2019-06-25T17:01:50.309",
        //         "direction":"BUY",
        //         "price":"0.027",
        //         "quantity":"0.001"
        //     }
        //
        // fetchMyTrades, fetchOrder (private)
        //
        //     {
        //         "id" => "7613414",
        //         "makerOrderId" => "0x8420af060722f560098f786a2894d4358079b6ea5d14b395969ed77bc87a623a",
        //         "takerOrderId" => "0x1235ef158a361815b54c9988b6241c85aedcbc1fe81caf8df8587d5ab0373d1a",
        //         "symbol" => "LTCUSDT",
        //         "side" => "BUY",
        //         "quantity" => "0.2",
        //         "fee" => "0.22685",
        //         "feeToken" => "USDT",
        //         "price" => "226.85",
        //         "makerOrTaker" => "TAKER",
        //         "timestamp" => "2021-04-09T15:39:45.608"
        //    }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->parse8601($this->safe_string($trade, 'timestamp'));
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'quantity');
        $price = $this->parse_number($priceString);
        $amount = $this->parse_number($amountString);
        $cost = $this->parse_number(Precise::string_mul($priceString, $amountString));
        $id = $this->safe_string($trade, 'id');
        $side = $this->safe_string_lower_2($trade, 'direction', 'side');
        $takerOrMaker = $this->safe_string_lower($trade, 'makerOrTaker');
        $orderId = null;
        if ($takerOrMaker !== null) {
            $orderId = $this->safe_string($trade, $takerOrMaker . 'OrderId');
        }
        $fee = null;
        $feeCost = $this->safe_number($trade, 'fee');
        $feeCurrency = $this->safe_currency_code($this->safe_string($trade, 'feeToken'));
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrency,
            );
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'takerOrMaker' => $takerOrMaker,
            'fee' => $fee,
        ));
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "timestamp":"2019-12-04T23:00:00",
        //         "open":"0.02024009",
        //         "high":"0.02024009",
        //         "low":"0.02024009",
        //         "close":"0.02024009",
        //         "volume":"0.00008096036",
        //         "volumeQuote":"0.004",
        //     }
        //
        return array(
            $this->parse8601($this->safe_string($ohlcv, 'timestamp')),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $this->safe_number($ohlcv, 'volume'),
        );
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // fetchOrder, createOrder, cancelOrder, cancelOrders, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "cancelledQuantity" => "0.3",
        //         "clientOrderId" => "my-$order-1",
        //         "createdAt" => "1970-01-01T00:00:00",
        //         "cursorId" => 50,
        //         "expireTime" => "1970-01-01T00:00:00",
        //         "filledQuantity" => "0.3",
        //         "id" => "string",
        //         "price" => "0.017",
        //         "quantity" => "0.3",
        //         "side" => "BUY",
        //         "symbol" => "TIMEETH",
        //         "type" => "LIMIT",
        //         "updatedAt" => "1970-01-01T00:00:00"
        //         "trades" => array(), // injected from the outside
        //     }
        //
        $id = $this->safe_string($order, 'id');
        $type = $this->safe_string_lower($order, 'type');
        $side = $this->safe_string_lower($order, 'side');
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->parse8601($this->safe_string($order, 'createdAt'));
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'quantity');
        $filled = $this->safe_string($order, 'filledQuantity');
        $canceledQuantity = $this->omit_zero($this->safe_string($order, 'cancelledQuantity'));
        if (Precise::string_equals($filled, $amount)) {
            $status = 'closed';
        } elseif ($canceledQuantity !== null) {
            $status = 'canceled';
        } else {
            $status = 'open';
        }
        $rawTrades = $this->safe_value($order, 'trades', array());
        $clientOrderId = $this->safe_string($order, 'clientOrderId');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => null,
            'amount' => $amount,
            'cost' => null,
            'average' => null,
            'filled' => $filled,
            'remaining' => null,
            'status' => $status,
            'fee' => null,
            'trades' => $rawTrades,
        ), $market);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit address for a $currency associated with this account, does not accept $params["network"]
         *
         * @see https://plasma-relay-backend.timex.io/swagger-ui/index.html?urls.primaryName=Relay#/Currency/selectCurrencyBySymbol
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=address-structure address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'symbol' => $currency['code'],
        );
        $response = $this->currenciesGetSSymbol ($this->extend($request, $params));
        //
        //    {
        //        id => '1',
        //        $currency => {
        //            symbol => 'BTC',
        //            name => 'Bitcoin',
        //            address => '0x8370fbc6ddec1e18b4e41e72ed943e238458487c',
        //            decimals => '8',
        //            tradeDecimals => '20',
        //            fiatSymbol => 'BTC',
        //            depositEnabled => true,
        //            withdrawalEnabled => true,
        //            transferEnabled => true,
        //            active => true
        //        }
        //    }
        //
        $data = $this->safe_dict($response, 'currency', array());
        return $this->parse_deposit_address($data, $currency);
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        //
        //    {
        //        symbol => 'BTC',
        //        name => 'Bitcoin',
        //        address => '0x8370fbc6ddec1e18b4e41e72ed943e238458487c',
        //        decimals => '8',
        //        tradeDecimals => '20',
        //        fiatSymbol => 'BTC',
        //        depositEnabled => true,
        //        withdrawalEnabled => true,
        //        transferEnabled => true,
        //        active => true
        //    }
        //
        $currencyId = $this->safe_string($depositAddress, 'symbol');
        return array(
            'info' => $depositAddress,
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'network' => null,
            'address' => $this->safe_string($depositAddress, 'address'),
            'tag' => null,
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $paramsToExtract = $this->extract_params($path);
        $path = $this->implode_params($path, $params);
        $params = $this->omit($params, $paramsToExtract);
        $url = $this->urls['api']['rest'] . '/' . $api . '/' . $path;
        if ($params) {
            $url .= '?' . $this->urlencode_with_array_repeat($params);
        }
        if ($api !== 'public' && $api !== 'tradingview') {
            $this->check_required_credentials();
            $auth = base64_encode($this->apiKey . ':' . $this->secret);
            $secret = 'Basic ' . $auth;
            $headers = array( 'authorization' => $secret );
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $statusCode, string $statusText, string $url, string $method, array $responseHeaders, $responseBody, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null;
        }
        if ($statusCode >= 400) {
            //
            //     array("error":array("timestamp":"05.12.2019T05:25:43.584+0000","status":"BAD_REQUEST","message":"Insufficient ETH balance. Required => 1, actual => 0.","code":4001))
            //     array("error":array("timestamp":"05.12.2019T04:03:25.419+0000","status":"FORBIDDEN","message":"Access denied","code":4300))
            //
            $feedback = $this->id . ' ' . $responseBody;
            $error = $this->safe_value($response, 'error');
            if ($error === null) {
                $error = $response;
            }
            $code = $this->safe_string_2($error, 'code', 'status');
            $message = $this->safe_string_2($error, 'message', 'debugMessage');
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
