<?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\ExchangeError;
use \ccxt\InvalidOrder;

class timex extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'timex',
            'name' => 'TimeX',
            'countries' => array( 'AU' ),
            'version' => 'v1',
            'rateLimit' => 1500,
            'has' => array(
                'cancelOrder' => true,
                'cancelOrders' => true,
                'CORS' => false,
                'createOrder' => true,
                'editOrder' => true,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true, // maker fee only
            ),
            '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' => 'https://plasma-relay-backend.timex.io',
                'www' => 'https://timex.io',
                'doc' => 'https://docs.timex.io',
                'referral' => 'https://timex.io/?refcode=1x27vNkTbP1uwkCck',
            ),
            'api' => array(
                '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/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
                    ),
                ),
                '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
                    ),
                ),
            ),
            '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(
                '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',
            ),
        ));
    }

    public function fetch_markets($params = array ()) {
        $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
        //         }
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $result[] = $this->parse_market($response[$i]);
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $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()
        //         ),
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $currency = $response[$i];
            $result[] = $this->parse_currency($currency);
        }
        return $this->index_by($result, 'code');
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        $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 (array_merge($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($symbol, $params = array ()) {
        $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 (array_merge($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_value($response, 0);
        return $this->parse_ticker($ticker, $market);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetOrderbookV2 (array_merge($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, $timestamp, 'bid', 'ask', 'price', 'baseTokenAmount');
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        $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 (array_merge($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($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market' => $market['id'],
            'period' => $this->timeframes[$timeframe],
        );
        // if $since and $limit are not specified
        $duration = $this->parse_timeframe($timeframe);
        if ($since !== null) {
            $request['from'] = $this->iso8601($since);
            if ($limit !== null) {
                $request['till'] = $this->iso8601($this->sum($since, $this->sum($limit, 1) * $duration * 1000));
            }
        } else if ($limit !== null) {
            $now = $this->milliseconds();
            $request['till'] = $this->iso8601($now);
            $request['from'] = $this->iso8601($now - $limit * $duration * 1000 - 1);
        } else {
            $request['till'] = $this->iso8601($this->milliseconds());
        }
        $response = $this->publicGetCandles (array_merge($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 fetch_balance($params = array ()) {
        $this->load_markets();
        $balances = $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")
        //     )
        //
        $result = array( 'info' => $balances );
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['total'] = $this->safe_float($balance, 'totalBalance');
            $account['used'] = $this->safe_float($balance, 'lockedBalance');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'quantity' => $this->amount_to_precision($symbol, $amount),
            'side' => strtoupper($side),
            // 'clientOrderId' => '123',
            // 'expireIn' => 1575523308, // in seconds
            // 'expireTime' => 1575523308, // unix timestamp
        );
        $query = $params;
        if ($type === 'limit') {
            $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;
            } else if ($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 (array_merge($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_value($orders, 0, array());
        return $this->parse_order($order, $market);
    }

    public function edit_order($id, $symbol, $type, $side, $amount = null, $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 (array_merge($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 array(
                'id' => $orderId,
                'info' => $response,
            );
        }
        $orders = $this->safe_value($response, 'changedOrders', array());
        $firstOrder = $this->safe_value($orders, 0, array());
        $order = $this->safe_value($firstOrder, 'newOrder', array());
        return $this->parse_order($order, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        return $this->cancel_orders(array( $id ), $symbol, $params);
    }

    public function cancel_orders($ids, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'id' => $ids,
        );
        $response = $this->tradingDeleteOrders (array_merge($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" ),
        //     }
        return $response;
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        $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_value($response, 'trades', array());
        return $this->parse_order(array_merge($order, array( 'trades' => $trades )));
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $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 (array_merge($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());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $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 (array_merge($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());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $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 (array_merge($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_value($response, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_trading_fee($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'markets' => $market['id'],
        );
        $response = $this->tradingGetFees (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "fee" => 0.0075,
        //             "$market" => "ETHBTC"
        //         }
        //     )
        //
        $result = $this->safe_value($response, 0, array());
        return array(
            'info' => $response,
            'maker' => $this->safe_float($result, 'fee'),
            'taker' => null,
        );
    }

    public function parse_market($market) {
        //
        //     {
        //         "$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');
        $active = !$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);
        $symbol = $base . '/' . $quote;
        $precision = array(
            'amount' => $this->precision_from_string($this->safe_string($market, 'quantityIncrement')),
            'price' => $this->precision_from_string($this->safe_string($market, 'tickSize')),
        );
        $amountIncrement = $this->safe_float($market, 'quantityIncrement');
        $minBase = $this->safe_float($market, 'baseMinSize');
        $minAmount = max ($amountIncrement, $minBase);
        $priceIncrement = $this->safe_float($market, 'tickSize');
        $minCost = $this->safe_float($market, 'quoteMinSize');
        $limits = array(
            'amount' => array( 'min' => $minAmount, 'max' => null ),
            'price' => array( 'min' => $priceIncrement, 'max' => null ),
            'cost' => array( 'min' => max ($minCost, $minAmount * $priceIncrement), 'max' => null ),
        );
        $taker = $this->safe_float($market, 'takerFee');
        $maker = $this->safe_float($market, 'makerFee');
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'type' => 'spot',
            'active' => $active,
            'precision' => $precision,
            'limits' => $limits,
            'taker' => $taker,
            'maker' => $maker,
            'info' => $market,
        );
    }

    public function parse_currency($currency) {
        //
        //     {
        //         "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);
        $name = $this->safe_string($currency, 'name');
        $precision = $this->safe_integer($currency, 'decimals');
        $active = $this->safe_value($currency, 'active');
        // $fee = $this->safe_float($currency, 'withdrawalFee');
        $feeString = $this->safe_string($currency, 'withdrawalFee');
        $tradeDecimals = $this->safe_integer($currency, 'tradeDecimals');
        $fee = null;
        if (($feeString !== null) && ($tradeDecimals !== null)) {
            $feeStringLen = is_array($feeString) ? count($feeString) : 0;
            $dotIndex = $feeStringLen - $tradeDecimals;
            if ($dotIndex > 0) {
                $whole = mb_substr($feeString, 0, $dotIndex - 0);
                $fraction = mb_substr($feeString, -$dotIndex);
                $fee = floatval($whole . '.' . $fraction);
            } else {
                $fraction = '.';
                for ($i = 0; $i < -$dotIndex; $i++) {
                    $fraction .= '0';
                }
                $fee = floatval($fraction . $feeString);
            }
        }
        return array(
            'id' => $code,
            'code' => $code,
            'info' => $currency,
            'type' => null,
            'name' => $name,
            'active' => $active,
            'fee' => $fee,
            'precision' => $precision,
            'limits' => array(
                'withdraw' => array( 'min' => $fee, 'max' => null ),
                'amount' => array( 'min' => null, 'max' => null ),
                'price' => array( 'min' => null, 'max' => null ),
                'cost' => array( 'min' => null, 'max' => null ),
            ),
        );
    }

    public function parse_tickers($rawTickers, $symbols = null) {
        $tickers = array();
        for ($i = 0; $i < count($rawTickers); $i++) {
            $tickers[] = $this->parse_ticker($rawTickers[$i]);
        }
        return $this->filter_by_array($tickers, 'symbol', $symbols);
    }

    public function parse_ticker($ticker, $market = null) {
        //
        //     {
        //         "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_float($ticker, 'last');
        $open = $this->safe_float($ticker, 'open');
        $change = null;
        $average = null;
        if ($last !== null && $open !== null) {
            $change = $last - $open;
            $average = $this->sum($last, $open) / 2;
        }
        $percentage = null;
        if ($change !== null && $open) {
            $percentage = ($change / $open) * 100;
        }
        return array(
            'symbol' => $symbol,
            'info' => $ticker,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'high'),
            'low' => $this->safe_float($ticker, 'low'),
            'bid' => $this->safe_float($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_float($ticker, 'ask'),
            'askVolume' => null,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $this->safe_float($ticker, 'volume'),
            'quoteVolume' => $this->safe_float($ticker, 'volumeQuote'),
        );
    }

    public function parse_trade($trade, $market = null) {
        //
        // fetchTrades (public)
        //
        //     {
        //         "$id":1,
        //         "$timestamp":"2019-06-25T17:01:50.309",
        //         "direction":"BUY",
        //         "$price":"0.027",
        //         "quantity":"0.001"
        //     }
        //
        // fetchMyTrades, fetchOrder (private)
        //
        //     {
        //         "$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"
        //     }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->parse8601($this->safe_string($trade, 'timestamp'));
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float($trade, 'quantity');
        $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_float($trade, 'fee');
        if ($feeCost !== null) {
            $feeCurrency = ($market === null) ? null : $market['quote'];
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrency,
            );
        }
        $cost = null;
        if (($price !== null) && ($amount !== null)) {
            $cost = $this->cost_to_precision($symbol, $amount * $price);
        }
        return 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, $market = null) {
        //
        //     {
        //         "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_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float($ohlcv, 'volume'),
        );
    }

    public function parse_order($order, $market = null) {
        //
        // 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_float($order, 'price');
        $amount = $this->safe_float($order, 'quantity');
        $filled = $this->safe_float($order, 'filledQuantity');
        $canceledQuantity = $this->safe_float($order, 'cancelledQuantity');
        $remaining = null;
        $status = null;
        if (($amount !== null) && ($filled !== null)) {
            $remaining = max ($amount - $filled, 0.0);
            if ($filled >= $amount) {
                $status = 'closed';
            } else if (($canceledQuantity !== null) && ($canceledQuantity > 0)) {
                $status = 'canceled';
            } else {
                $status = 'open';
            }
        }
        $cost = floatval($this->cost_to_precision($symbol, $price * $filled));
        $fee = null;
        $lastTradeTimestamp = null;
        $trades = null;
        $rawTrades = $this->safe_value($order, 'trades');
        if ($rawTrades !== null) {
            $trades = $this->parse_trades($rawTrades, $market, null, null, array(
                'order' => $id,
            ));
        }
        if ($trades !== null) {
            $numTrades = is_array($trades) ? count($trades) : 0;
            if ($numTrades > 0) {
                $lastTradeTimestamp = $trades[$numTrades - 1]['timestamp'];
            }
        }
        $clientOrderId = $this->safe_string($order, 'clientOrderId');
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => null,
            'amount' => $amount,
            'cost' => $cost,
            'average' => null,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => $fee,
            'trades' => $trades,
        );
    }

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

    public function handle_errors($statusCode, $statusText, $url, $method, $responseHeaders, $responseBody, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return;
        }
        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);
        }
    }
}
