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

class yobit extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'yobit',
            'name' => 'YoBit',
            'countries' => array( 'RU' ),
            'rateLimit' => 2000, // responses are cached every 2 seconds
            'version' => '3',
            'pro' => false,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelOrder' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createDepositAddress' => true,
                'createMarketOrder' => false,
                'createOrder' => true,
                'createReduceOnlyOrder' => false,
                'createStopLimitOrder' => false,
                'createStopMarketOrder' => false,
                'createStopOrder' => false,
                'fetchAllGreeks' => false,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRate' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchBorrowRates' => false,
                'fetchBorrowRatesPerSymbol' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => false,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => false,
                '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,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => true,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => false,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => true,
                'fetchTransactions' => false,
                'fetchTransfer' => false,
                'fetchTransfers' => false,
                'fetchUnderlyingAssets' => false,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawals' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'repayMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true,
                'ws' => false,
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/27766910-cdcbfdae-5eea-11e7-9859-03fea873272d.jpg',
                'api' => array(
                    'public' => 'https://yobit.net/api',
                    'private' => 'https://yobit.net/tapi',
                ),
                'www' => 'https://www.yobit.net',
                'doc' => 'https://www.yobit.net/en/api/',
                'fees' => 'https://www.yobit.net/en/fees/',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'depth/{pair}' => 1,
                        'info' => 1,
                        'ticker/{pair}' => 1,
                        'trades/{pair}' => 1,
                    ),
                ),
                'private' => array(
                    'post' => array(
                        'ActiveOrders' => 1,
                        'CancelOrder' => 1,
                        'GetDepositAddress' => 1,
                        'getInfo' => 1,
                        'OrderInfo' => 1,
                        'Trade' => 1,
                        'TradeHistory' => 1,
                        'WithdrawCoinsToAddress' => 1,
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'maker' => 0.002,
                    'taker' => 0.002,
                ),
                'funding' => array(
                    'withdraw' => array(),
                ),
            ),
            'commonCurrencies' => array(
                'AIR' => 'AirCoin',
                'ANI' => 'ANICoin',
                'ANT' => 'AntsCoin',  // what is this, a coin for ants?
                'ATMCHA' => 'ATM',
                'ASN' => 'Ascension',
                'AST' => 'Astral',
                'ATM' => 'Autumncoin',
                'AUR' => 'AuroraCoin',
                'BAB' => 'Babel',
                'BAN' => 'BANcoin',
                'BCC' => 'BCH',
                'BCS' => 'BitcoinStake',
                'BITS' => 'Bitstar',
                'BLN' => 'Bulleon',
                'BNS' => 'Benefit Bonus Coin',
                'BOT' => 'BOTcoin',
                'BON' => 'BONES',
                'BPC' => 'BitcoinPremium',
                'BST' => 'BitStone',
                'BTS' => 'Bitshares2',
                'CAT' => 'BitClave',
                'CBC' => 'CryptoBossCoin',
                'CMT' => 'CometCoin',
                'COIN' => 'Coin.com',
                'COV' => 'Coven Coin',
                'COVX' => 'COV',
                'CPC' => 'Capricoin',
                'CREDIT' => 'Creditbit',
                'CS' => 'CryptoSpots',
                'DCT' => 'Discount',
                'DFT' => 'DraftCoin',
                'DGD' => 'DarkGoldCoin',
                'DIRT' => 'DIRTY',
                'DROP' => 'FaucetCoin',
                'DSH' => 'DASH',
                'EGC' => 'EverGreenCoin',
                'EGG' => 'EggCoin',
                'EKO' => 'EkoCoin',
                'ENTER' => 'ENTRC',
                'EPC' => 'ExperienceCoin',
                'ESC' => 'EdwardSnowden',
                'EUROPE' => 'EUROP',
                'EXT' => 'LifeExtension',
                'FUND' => 'FUNDChains',
                'FUNK' => 'FUNKCoin',
                'FX' => 'FCoin',
                'GCC' => 'GlobalCryptocurrency',
                'GEN' => 'Genstake',
                'GENE' => 'Genesiscoin',
                'GMR' => 'Gimmer',
                'GOLD' => 'GoldMint',
                'GOT' => 'Giotto Coin',
                'GSX' => 'GlowShares',
                'GT' => 'GTcoin',
                'HTML5' => 'HTML',
                'HYPERX' => 'HYPER',
                'ICN' => 'iCoin',
                'INSANE' => 'INSN',
                'JNT' => 'JointCoin',
                'JPC' => 'JupiterCoin',
                'JWL' => 'Jewels',
                'KNC' => 'KingN Coin',
                'LBTCX' => 'LiteBitcoin',
                'LIZI' => 'LiZi',
                'LOC' => 'LocoCoin',
                'LOCX' => 'LOC',
                'LUNYR' => 'LUN',
                'LUN' => 'LunarCoin',  // they just change the ticker if it is already taken
                'LUNA' => 'Luna Coin',
                'MASK' => 'Yobit MASK',
                'MDT' => 'Midnight',
                'MEME' => 'Memez Token', // conflict with Meme Inu / Degenerator Meme
                'MIS' => 'MIScoin',
                'MM' => 'MasterMint', // conflict with MilliMeter
                'NAV' => 'NavajoCoin',
                'NBT' => 'NiceBytes',
                'OMG' => 'OMGame',
                'ONX' => 'Onix',
                'PAC' => '$PAC',
                'PLAY' => 'PlayCoin',
                'PIVX' => 'Darknet',
                'PURE' => 'PurePOS',
                'PUTIN' => 'PutinCoin',
                'SPACE' => 'Spacecoin',
                'STK' => 'StakeCoin',
                'SUB' => 'Subscriptio',
                'PAY' => 'EPAY',
                'PLC' => 'Platin Coin',
                'RAI' => 'RaiderCoin',
                'RCN' => 'RCoin',
                'REP' => 'Republicoin',
                'RUR' => 'RUB',
                'SBTC' => 'Super Bitcoin',
                'SMC' => 'SmartCoin',
                'SOLO' => 'SoloCoin',
                'SOUL' => 'SoulCoin',
                'STAR' => 'StarCoin',
                'SUPER' => 'SuperCoin',
                'TNS' => 'Transcodium',
                'TTC' => 'TittieCoin',
                'UNI' => 'Universe',
                'UST' => 'Uservice',
                'VOL' => 'VolumeCoin',
                'XIN' => 'XINCoin',
                'XMT' => 'SummitCoin',
                'XRA' => 'Ratecoin',
                'BCHN' => 'BSV',
            ),
            'options' => array(
                'maxUrlLength' => 2048,
                'fetchOrdersRequiresSymbol' => true,
                'networks' => array(
                    'ETH' => 'ERC20',
                    'TRX' => 'TRC20',
                    'BSC' => 'BEP20',
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    '803' => '\\ccxt\\InvalidOrder', // "Count could not be less than 0.001." (selling below minAmount)
                    '804' => '\\ccxt\\InvalidOrder', // "Count could not be more than 10000." (buying above maxAmount)
                    '805' => '\\ccxt\\InvalidOrder', // "price could not be less than X." (minPrice violation on buy & sell)
                    '806' => '\\ccxt\\InvalidOrder', // "price could not be more than X." (maxPrice violation on buy & sell)
                    '807' => '\\ccxt\\InvalidOrder', // "cost could not be less than X." (minCost violation on buy & sell)
                    '831' => '\\ccxt\\InsufficientFunds', // "Not enougth X to create buy order." (buying with balance.quote < order.cost)
                    '832' => '\\ccxt\\InsufficientFunds', // "Not enougth X to create sell order." (selling with balance.base < order.amount)
                    '833' => '\\ccxt\\OrderNotFound', // "Order with id X was not found." (cancelling non-existent, closed and cancelled order)
                ),
                'broad' => array(
                    'Invalid pair name' => '\\ccxt\\ExchangeError', // array("success":0,"error":"Invalid pair name => btc_eth")
                    'invalid api key' => '\\ccxt\\AuthenticationError',
                    'invalid sign' => '\\ccxt\\AuthenticationError',
                    'api key dont have trade permission' => '\\ccxt\\AuthenticationError',
                    'invalid parameter' => '\\ccxt\\InvalidOrder',
                    'invalid order' => '\\ccxt\\InvalidOrder',
                    'The given order has already been cancelled' => '\\ccxt\\InvalidOrder',
                    'Requests too often' => '\\ccxt\\DDoSProtection',
                    'not available' => '\\ccxt\\ExchangeNotAvailable',
                    'data unavailable' => '\\ccxt\\ExchangeNotAvailable',
                    'external service unavailable' => '\\ccxt\\ExchangeNotAvailable',
                    'Total transaction amount' => '\\ccxt\\InvalidOrder', // array( "success" => 0, "error" => "Total transaction amount is less than minimal total => 0.00010000")
                    'The given order has already been closed and cannot be cancelled' => '\\ccxt\\InvalidOrder',
                    'Insufficient funds' => '\\ccxt\\InsufficientFunds',
                    'invalid key' => '\\ccxt\\AuthenticationError',
                    'invalid nonce' => '\\ccxt\\InvalidNonce', // array("success":0,"error":"invalid nonce (has already been used)")'
                    'Total order amount is less than minimal amount' => '\\ccxt\\InvalidOrder',
                    'Rate Limited' => '\\ccxt\\RateLimitExceeded',
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => false,
                        'triggerDirection' => false,
                        'triggerPriceType' => null,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => false,
                            'FOK' => false,
                            'PO' => false,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => false,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => null,
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'orders' => array(), // orders cache / emulation
        ));
    }

    public function parse_balance($response): array {
        $balances = $this->safe_dict($response, 'return', array());
        $timestamp = $this->safe_integer($balances, 'server_time');
        $result = array(
            'info' => $response,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
        $free = $this->safe_dict($balances, 'funds', array());
        $total = $this->safe_dict($balances, 'funds_incl_orders', array());
        $currencyIds = is_array($this->extend($free, $total)) ? array_keys($this->extend($free, $total)) : array();
        for ($i = 0; $i < count($currencyIds); $i++) {
            $currencyId = $currencyIds[$i];
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_string($free, $currencyId);
            $account['total'] = $this->safe_string($total, $currencyId);
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function fetch_balance($params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * query for balance and get the amount of funds available for trading or funds locked in orders
         * @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->privatePostGetInfo ($params);
        //
        //     {
        //         "success":1,
        //         "return":{
        //             "funds":array(
        //                 "ltc":22,
        //                 "nvc":423.998,
        //                 "ppc":10,
        //             ),
        //             "funds_incl_orders":array(
        //                 "ltc":32,
        //                 "nvc":523.998,
        //                 "ppc":20,
        //             ),
        //             "rights":array(
        //                 "info":1,
        //                 "trade":0,
        //                 "withdraw":0
        //             ),
        //             "transaction_count":0,
        //             "open_orders":1,
        //             "server_time":1418654530
        //         }
        //     }
        //
        return $this->parse_balance($response);
    }

    public function fetch_markets($params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * retrieves data on all $markets for yobit
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $response = $this->publicGetInfo ($params);
        //
        //     {
        //         "server_time":1615856752,
        //         "pairs":array(
        //             "ltc_btc":array(
        //                 "decimal_places":8,
        //                 "min_price":0.00000001,
        //                 "max_price":10000,
        //                 "min_amount":0.0001,
        //                 "min_total":0.0001,
        //                 "hidden":0,
        //                 "fee":0.2,
        //                 "fee_buyer":0.2,
        //                 "fee_seller":0.2
        //             ),
        //         ),
        //     }
        //
        $markets = $this->safe_dict($response, 'pairs', array());
        $keys = is_array($markets) ? array_keys($markets) : array();
        $result = array();
        for ($i = 0; $i < count($keys); $i++) {
            $id = $keys[$i];
            $market = $markets[$id];
            list($baseId, $quoteId) = explode('_', $id);
            $base = strtoupper($baseId);
            $quote = strtoupper($quoteId);
            $base = $this->safe_currency_code($base);
            $quote = $this->safe_currency_code($quote);
            $hidden = $this->safe_integer($market, 'hidden');
            $feeString = $this->safe_string($market, 'fee');
            $feeString = Precise::string_div($feeString, '100');
            // yobit maker = taker
            $result[] = 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' => ($hidden === 0),
                'contract' => false,
                'linear' => null,
                'inverse' => null,
                'taker' => $this->parse_number($feeString),
                'maker' => $this->parse_number($feeString),
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->parse_number($this->parse_precision($this->safe_string($market, 'decimal_places'))),
                    'price' => $this->parse_number($this->parse_precision($this->safe_string($market, 'decimal_places'))),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => $this->safe_number($market, 'min_amount'),
                        'max' => $this->safe_number($market, 'max_amount'),
                    ),
                    'price' => array(
                        'min' => $this->safe_number($market, 'min_price'),
                        'max' => $this->safe_number($market, 'max_price'),
                    ),
                    'cost' => array(
                        'min' => $this->safe_number($market, 'min_total'),
                        'max' => null,
                    ),
                ),
                'created' => null,
                'info' => $market,
            );
        }
        return $result;
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         * @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(
            'pair' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit; // default = 150, max = 2000
        }
        $response = $this->publicGetDepthPair ($this->extend($request, $params));
        $market_id_in_reponse = (is_array($response) && array_key_exists($market['id'], $response));
        if (!$market_id_in_reponse) {
            throw new ExchangeError($this->id . ' ' . $market['symbol'] . ' order book is empty or not available');
        }
        $orderbook = $response[$market['id']];
        return $this->parse_order_book($orderbook, $symbol);
    }

    public function fetch_order_books(?array $symbols = null, ?int $limit = null, $params = array ()): OrderBooks {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data for multiple markets
         * @param {string[]|null} $symbols list of unified market $symbols, all $symbols fetched if null, default is null
         * @param {int} [$limit] max number of entries per orderbook to return, default is null
         * @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 $symbol
         */
        $this->load_markets();
        $ids = null;
        if ($symbols === null) {
            $ids = implode('-', $this->ids);
            // max URL length is 2083 $symbols, including http schema, hostname, tld, etc...
            if (strlen($ids) > 2048) {
                $numIds = count($this->ids);
                throw new ExchangeError($this->id . ' fetchOrderBooks() has ' . (string) $numIds . ' $symbols exceeding max URL length, you are required to specify a list of $symbols in the first argument to fetchOrderBooks');
            }
        } else {
            $ids = $this->market_ids($symbols);
            $ids = implode('-', $ids);
        }
        $request = array(
            'pair' => $ids,
            // 'ignore_invalid' => true,
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetDepthPair ($this->extend($request, $params));
        $result = array();
        $ids = is_array($response) ? array_keys($response) : array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $ids[$i];
            $symbol = $this->safe_symbol($id);
            $result[$symbol] = $this->parse_order_book($response[$id], $symbol);
        }
        return $result;
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "high" => 0.03497582,
        //         "low" => 0.03248474,
        //         "avg" => 0.03373028,
        //         "vol" => 120.11485715062999,
        //         "vol_cur" => 3572.24914074,
        //         "last" => 0.0337611,
        //         "buy" => 0.0337442,
        //         "sell" => 0.03377798,
        //         "updated" => 1537522009
        //     }
        //
        $timestamp = $this->safe_timestamp($ticker, 'updated');
        $last = $this->safe_string($ticker, 'last');
        return $this->safe_ticker(array(
            'symbol' => $this->safe_symbol(null, $market),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => $this->safe_string($ticker, 'buy'),
            'bidVolume' => null,
            'ask' => $this->safe_string($ticker, 'sell'),
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => $this->safe_string($ticker, 'avg'),
            'baseVolume' => $this->safe_string($ticker, 'vol_cur'),
            'quoteVolume' => $this->safe_string($ticker, 'vol'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_tickers_helper(string $idsString, $params = array ()): array {
        $request = array(
            'pair' => $idsString,
        );
        $tickers = $this->publicGetTickerPair ($this->extend($request, $params));
        $result = array();
        $keys = is_array($tickers) ? array_keys($tickers) : array();
        for ($k = 0; $k < count($keys); $k++) {
            $id = $keys[$k];
            $ticker = $tickers[$id];
            $market = $this->safe_market($id);
            $symbol = $market['symbol'];
            $result[$symbol] = $this->parse_ticker($ticker, $market);
        }
        return $result;
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
         * @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
         * @param {array} [$params->all] you can set to `true` for convenience to fetch all tickers from this exchange by sending multiple requests
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?$id=ticker-structure ticker structures~
         */
        $allSymbols = null;
        list($allSymbols, $params) = $this->handle_param_bool($params, 'all', false);
        if ($symbols === null && !$allSymbols) {
            throw new ArgumentsRequired($this->id . ' fetchTickers() requires "symbols" argument or use `$params["all"] = true` to send multiple requests for all markets');
        }
        $this->load_markets();
        $promises = array();
        $maxLength = $this->safe_integer($this->options, 'maxUrlLength', 2048);
        // max URL length is 2048 $symbols, including http schema, hostname, tld, etc...
        $lenghtOfBaseUrl = 40; // safe space for the url including api-base and endpoint dir is 30 chars
        if ($allSymbols) {
            $symbols = $this->symbols;
            $ids = '';
            for ($i = 0; $i < count($this->ids); $i++) {
                $id = $this->ids[$i];
                $prefix = ($ids === '') ? '' : '-';
                $ids .= $prefix . $id;
                if (strlen($ids) > $maxLength) {
                    $promises[] = $this->fetch_tickers_helper($ids, $params);
                    $ids = '';
                }
            }
            if ($ids !== '') {
                $promises[] = $this->fetch_tickers_helper($ids, $params);
            }
        } else {
            $symbols = $this->market_symbols($symbols);
            $ids = $this->market_ids($symbols);
            $idsLength = count($ids);
            $idsString = implode('-', $ids);
            $actualLength = strlen($idsString) . $lenghtOfBaseUrl;
            if ($actualLength > $maxLength) {
                throw new ArgumentsRequired($this->id . ' fetchTickers() is being requested for ' . (string) $idsLength . ' markets (which has an URL length of ' . (string) $actualLength . ' characters), but it exceedes max URL length (' . (string) $maxLength . '), please pass limisted $symbols array to fetchTickers to fit in one request');
            }
            $promises[] = $this->fetch_tickers_helper($idsString, $params);
        }
        $resultAll = $promises;
        $finalResult = array();
        for ($i = 0; $i < count($resultAll); $i++) {
            $result = $this->filter_by_array_tickers($resultAll[$i], 'symbol', $symbols);
            $finalResult = $this->extend($finalResult, $result);
        }
        return $finalResult;
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
         * @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~
         */
        $tickers = $this->fetch_tickers(array( $symbol ), $params);
        return $tickers[$symbol];
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //      {
        //          "type":"bid",
        //          "price":0.14046179,
        //          "amount":0.001,
        //          "tid":200256901,
        //          "timestamp":1649861004
        //      }
        //
        // fetchMyTrades (private)
        //
        //      {
        //          "pair":"doge_usdt",
        //          "type":"sell",
        //          "amount":139,
        //          "rate":0.139,
        //          "order_id":"2101103631773172",
        //          "is_your_order":1,
        //          "timestamp":"1649861561"
        //      }
        //
        $timestamp = $this->safe_timestamp($trade, 'timestamp');
        $side = $this->safe_string($trade, 'type');
        if ($side === 'ask') {
            $side = 'sell';
        } elseif ($side === 'bid') {
            $side = 'buy';
        }
        $priceString = $this->safe_string_2($trade, 'rate', 'price');
        $id = $this->safe_string_2($trade, 'trade_id', 'tid');
        $order = $this->safe_string($trade, 'order_id');
        $marketId = $this->safe_string($trade, 'pair');
        $symbol = $this->safe_symbol($marketId, $market);
        $amountString = $this->safe_string($trade, 'amount');
        // arguments for calculateFee (need to be numbers)
        $price = $this->parse_number($priceString);
        $amount = $this->parse_number($amountString);
        $type = 'limit'; // all trades are still limit trades
        $fee = null;
        $feeCostString = $this->safe_number($trade, 'commission');
        if ($feeCostString !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'commissionCurrency');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCostString,
                'currency' => $feeCurrencyCode,
            );
        }
        $isYourOrder = $this->safe_string($trade, 'is_your_order');
        if ($isYourOrder !== null) {
            if ($fee === null) {
                $feeInNumbers = $this->calculate_fee($symbol, $type, $side, $amount, $price, 'taker');
                $fee = array(
                    'currency' => $this->safe_string($feeInNumbers, 'currency'),
                    'cost' => $this->safe_string($feeInNumbers, 'cost'),
                    'rate' => $this->safe_string($feeInNumbers, 'rate'),
                );
            }
        }
        return $this->safe_trade(array(
            'id' => $id,
            'order' => $order,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => $fee,
            'info' => $trade,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * get the list of most recent trades for a particular $symbol
         * @param {string} $symbol unified $symbol of the $market to fetch trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetTradesPair ($this->extend($request, $params));
        //
        //      {
        //          "doge_usdt" => array(
        //              array(
        //                  "type":"ask",
        //                  "price":0.13956743,
        //                  "amount":0.0008,
        //                  "tid":200256900,
        //                  "timestamp":1649860521
        //              ),
        //          )
        //      }
        //
        if (gettype($response) === 'array' && array_keys($response) === array_keys(array_keys($response))) {
            $numElements = count($response);
            if ($numElements === 0) {
                return array();
            }
        }
        $result = $this->safe_list($response, $market['id'], array());
        return $this->parse_trades($result, $market, $since, $limit);
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetch the trading fees for multiple markets
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by market symbols
         */
        $this->load_markets();
        $response = $this->publicGetInfo ($params);
        //
        //     {
        //         "server_time":1615856752,
        //         "pairs":array(
        //             "ltc_btc":array(
        //                 "decimal_places":8,
        //                 "min_price":0.00000001,
        //                 "max_price":10000,
        //                 "min_amount":0.0001,
        //                 "min_total":0.0001,
        //                 "hidden":0,
        //                 "fee":0.2,
        //                 "fee_buyer":0.2,
        //                 "fee_seller":0.2
        //             ),
        //             ...
        //         ),
        //     }
        //
        $pairs = $this->safe_dict($response, 'pairs', array());
        $marketIds = is_array($pairs) ? array_keys($pairs) : array();
        $result = array();
        for ($i = 0; $i < count($marketIds); $i++) {
            $marketId = $marketIds[$i];
            $pair = $this->safe_dict($pairs, $marketId, array());
            $symbol = $this->safe_symbol($marketId, null, '_');
            $takerString = $this->safe_string($pair, 'fee_buyer');
            $makerString = $this->safe_string($pair, 'fee_seller');
            $taker = $this->parse_number(Precise::string_div($takerString, '100'));
            $maker = $this->parse_number(Precise::string_div($makerString, '100'));
            $result[$symbol] = array(
                'info' => $pair,
                'symbol' => $symbol,
                'taker' => $taker,
                'maker' => $maker,
                'percentage' => true,
                'tierBased' => false,
            );
        }
        return $result;
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * create a trade order
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type must be '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~
         */
        if ($type === 'market') {
            throw new ExchangeError($this->id . ' createOrder() allows limit orders only');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
            'type' => $side,
            'amount' => $this->amount_to_precision($symbol, $amount),
            'rate' => $this->price_to_precision($symbol, $price),
        );
        $response = $this->privatePostTrade ($this->extend($request, $params));
        //
        //      {
        //          "success":1,
        //          "return" => {
        //              "received":0,
        //              "remains":10,
        //              "order_id":1101103635125179,
        //              "funds" => array(
        //                  "usdt":27.84756553,
        //                  "usdttrc20":0,
        //                  "doge":19.98327206
        //              ),
        //              "funds_incl_orders" => array(
        //                  "usdt":30.35256553,
        //                  "usdttrc20":0,
        //                  "doge":19.98327206
        //               ),
        //               "server_time":1650114256
        //           }
        //       }
        //
        $result = $this->safe_dict($response, 'return');
        return $this->parse_order($result, $market);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * cancels an open order
         * @param {string} $id order $id
         * @param {string} $symbol not used by yobit 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();
        $request = array(
            'order_id' => intval($id),
        );
        $response = $this->privatePostCancelOrder ($this->extend($request, $params));
        //
        //      {
        //          "success":1,
        //          "return" => {
        //              "order_id":1101103632552304,
        //              "funds" => array(
        //                  "usdt":30.71055443,
        //                  "usdttrc20":0,
        //                  "doge":9.98327206
        //              ),
        //              "funds_incl_orders" => array(
        //                  "usdt":31.81275443,
        //                  "usdttrc20":0,
        //                  "doge":9.98327206
        //              ),
        //              "server_time":1649918298
        //          }
        //      }
        //
        $result = $this->safe_dict($response, 'return', array());
        return $this->parse_order($result);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            '0' => 'open',
            '1' => 'closed',
            '2' => 'canceled',
            '3' => 'open', // or partially-filled and canceled? https://github.com/ccxt/ccxt/issues/1594
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder (private)
        //
        //      {
        //          "received":0,
        //          "remains":10,
        //          "order_id":1101103635125179,
        //          "funds" => array(
        //              "usdt":27.84756553,
        //              "usdttrc20":0,
        //              "doge":19.98327206
        //          ),
        //          "funds_incl_orders" => array(
        //              "usdt":30.35256553,
        //              "usdttrc20":0,
        //              "doge":19.98327206
        //          ),
        //          "server_time":1650114256
        //      }
        //
        // fetchOrder (private)
        //
        //      {
        //          "id => "1101103635103335",  // $id-field is manually added in fetchOrder () from exchange response $id-$order dictionary structure
        //          "pair":"doge_usdt",
        //          "type":"buy",
        //          "start_amount":10,
        //          "amount":10,
        //          "rate":0.05,
        //          "timestamp_created":"1650112553",
        //          "status":0
        //      }
        //
        // fetchOpenOrders (private)
        //
        //      {
        //          "id":"1101103635103335", // $id-field is manually added in fetchOpenOrders () from exchange response $id-$order dictionary structure
        //          "pair":"doge_usdt",
        //          "type":"buy",
        //          "amount":10,
        //          "rate":0.05,
        //          "timestamp_created":"1650112553",
        //          "status":0
        //      }
        //
        // cancelOrder (private)
        //
        //      {
        //          "order_id":1101103634000197,
        //          "funds" => array(
        //              "usdt":31.81275443,
        //              "usdttrc20":0,
        //              "doge":9.98327206
        //          ),
        //          "funds_incl_orders" => {
        //              "usdt":31.81275443,
        //              "usdttrc20":0,
        //              "doge":9.98327206
        //          }
        //      }
        //
        $id = $this->safe_string_2($order, 'id', 'order_id');
        $status = $this->parse_order_status($this->safe_string($order, 'status', 'open'));
        if ($id === '0') {
            $id = $this->safe_string($order, 'init_order_id');
            $status = 'closed';
        }
        $timestamp = $this->safe_timestamp_2($order, 'timestamp_created', 'server_time');
        $marketId = $this->safe_string($order, 'pair');
        $symbol = $this->safe_symbol($marketId, $market);
        $amount = $this->safe_string($order, 'start_amount');
        $remaining = $this->safe_string_2($order, 'amount', 'remains');
        $filled = $this->safe_string($order, 'received', '0.0');
        $price = $this->safe_string($order, 'rate');
        $fee = null;
        $type = 'limit';
        $side = $this->safe_string($order, 'type');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => null,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => null,
            'cost' => null,
            'amount' => $amount,
            'remaining' => $remaining,
            'filled' => $filled,
            'status' => $status,
            'fee' => $fee,
            'average' => null,
            'trades' => null,
        ), $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetches information on an order made by the user
         * @param {string} $id order $id
         * @param {string} $symbol not used by yobit 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(
            'order_id' => intval($id),
        );
        $response = $this->privatePostOrderInfo ($this->extend($request, $params));
        $id = (string) $id;
        $orders = $this->safe_dict($response, 'return', array());
        //
        //      {
        //          "success":1,
        //          "return" => {
        //              "1101103635103335" => {
        //                  "pair":"doge_usdt",
        //                  "type":"buy",
        //                  "start_amount":10,
        //                  "amount":10,
        //                  "rate":0.05,
        //                  "timestamp_created":"1650112553",
        //                  "status":0
        //              }
        //          }
        //      }
        //
        return $this->parse_order($this->extend(array( 'id' => $id ), $orders[$id]));
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetch all unfilled currently open orders
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch open orders for
         * @param {int} [$limit] the maximum number of open order structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOpenOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $marketInner = $this->market($symbol);
            $request['pair'] = $marketInner['id'];
        }
        $response = $this->privatePostActiveOrders ($this->extend($request, $params));
        //
        //      {
        //          "success":1,
        //          "return" => {
        //              "1101103634006799" => array(
        //                  "pair":"doge_usdt",
        //                  "type":"buy",
        //                  "amount":10,
        //                  "rate":0.1,
        //                  "timestamp_created":"1650034937",
        //                  "status":0
        //              ),
        //              "1101103634006738" => {
        //                  "pair":"doge_usdt",
        //                  "type":"buy",
        //                  "amount":10,
        //                  "rate":0.1,
        //                  "timestamp_created":"1650034932",
        //                  "status":0
        //              }
        //          }
        //      }
        //
        $result = $this->safe_dict($response, 'return', array());
        return $this->parse_orders($result, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * fetch all $trades made by the user
         * @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~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        // some derived classes use camelcase notation for $request fields
        $request = array(
            // 'from' => 123456789, // $trade ID, from which the display starts numerical 0 (test $result => liqui ignores this field)
            // 'count' => 1000, // the number of $trades for display numerical, default = 1000
            // 'from_id' => $trade ID, from which the display starts numerical 0
            // 'end_id' => $trade ID on which the display ends numerical ∞
            // 'order' => 'ASC', // sorting, default = DESC (test $result => liqui ignores this field, most recent $trade always goes last)
            // 'since' => 1234567890, // UTC start time, default = 0 (test $result => liqui ignores this field)
            // 'end' => 1234567890, // UTC end time, default = ∞ (test $result => liqui ignores this field)
            'pair' => $market['id'],
        );
        if ($limit !== null) {
            $request['count'] = $limit;
        }
        if ($since !== null) {
            $request['since'] = $this->parse_to_int($since / 1000);
        }
        $response = $this->privatePostTradeHistory ($this->extend($request, $params));
        //
        //      {
        //          "success":1,
        //          "return" => {
        //              "200257004" => {
        //                  "pair":"doge_usdt",
        //                  "type":"sell",
        //                  "amount":139,
        //                  "rate":0.139,
        //                  "order_id":"2101103631773172",
        //                  "is_your_order":1,
        //                  "timestamp":"1649861561"
        //              }
        //          }
        //      }
        //
        $trades = $this->safe_dict($response, 'return', array());
        $ids = is_array($trades) ? array_keys($trades) : array();
        $result = array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $this->safe_string($ids, $i);
            $trade = $this->parse_trade($this->extend($trades[$id], array(
                'trade_id' => $id,
            )), $market);
            $result[] = $trade;
        }
        return $this->filter_by_symbol_since_limit($result, $market['symbol'], $since, $limit);
    }

    public function create_deposit_address(string $code, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * create a currency deposit $address
         * @param {string} $code unified currency $code of the currency for the deposit $address
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
         */
        $request = array(
            'need_new' => 1,
        );
        $response = $this->fetch_deposit_address($code, $this->extend($request, $params));
        $address = $this->safe_string($response, 'address');
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => null,
            'network' => null,
            'info' => $response['info'],
        );
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit $address for a $currency associated with this account
         *
         * @see https://yobit.net/en/api
         *
         * @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);
        $currencyId = $currency['id'];
        $networks = $this->safe_dict($this->options, 'networks', array());
        $network = $this->safe_string_upper($params, 'network'); // this line allows the user to specify either ERC20 or ETH
        $network = $this->safe_string($networks, $network, $network); // handle ERC20>ETH alias
        if ($network !== null) {
            if ($network !== 'ERC20') {
                $currencyId = $currencyId . strtolower($network);
            }
            $params = $this->omit($params, 'network');
        }
        $request = array(
            'coinName' => $currencyId,
            'need_new' => 0,
        );
        $response = $this->privatePostGetDepositAddress ($this->extend($request, $params));
        $address = $this->safe_string($response['return'], 'address');
        $this->check_address($address);
        return array(
            'info' => $response,
            'currency' => $code,
            'network' => null,
            'address' => $address,
            'tag' => null,
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         *
         * @see https://yobit.net/en/api
         *
         * make a withdrawal
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
        $this->check_address($address);
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coinName' => $currency['id'],
            'amount' => $amount,
            'address' => $address,
        );
        // no docs on the $tag, yet...
        if ($tag !== null) {
            throw new ExchangeError($this->id . ' withdraw() does not support the $tag argument yet due to a lack of docs on withdrawing with tag/memo on behalf of the exchange.');
        }
        $response = $this->privatePostWithdrawCoinsToAddress ($this->extend($request, $params));
        return array(
            'info' => $response,
            'id' => null,
            'txid' => null,
            'type' => null,
            'currency' => null,
            'network' => null,
            'amount' => null,
            'status' => null,
            'timestamp' => null,
            'datetime' => null,
            'address' => null,
            'addressFrom' => null,
            'addressTo' => null,
            'tag' => null,
            'tagFrom' => null,
            'tagTo' => null,
            'updated' => null,
            'comment' => null,
            'fee' => array(
                'currency' => null,
                'cost' => null,
                'rate' => null,
            ),
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api'][$api];
        $query = $this->omit($params, $this->extract_params($path));
        if ($api === 'private') {
            $this->check_required_credentials();
            $nonce = $this->nonce();
            $body = $this->urlencode($this->extend(array(
                'nonce' => $nonce,
                'method' => $path,
            ), $query));
            $signature = $this->hmac($this->encode($body), $this->encode($this->secret), 'sha512');
            $headers = array(
                'Content-Type' => 'application/x-www-form-urlencoded',
                'Key' => $this->apiKey,
                'Sign' => $signature,
            );
        } elseif ($api === 'public') {
            $url .= '/' . $this->version . '/' . $this->implode_params($path, $params);
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } else {
            $url .= '/' . $this->implode_params($path, $params);
            if ($method === 'GET') {
                if ($query) {
                    $url .= '?' . $this->urlencode($query);
                }
            } else {
                if ($query) {
                    $body = $this->json($query);
                    $headers = array(
                        'Content-Type' => 'application/json',
                    );
                }
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null; // fallback to default error handler
        }
        if (is_array($response) && array_key_exists('success', $response)) {
            //
            // 1 - Liqui only returns the integer 'success' key from their private API
            //
            //     array( "success" => 1, ... ) $httpCode === 200
            //     array( "success" => 0, ... ) $httpCode === 200
            //
            // 2 - However, exchanges derived from Liqui, can return non-integers
            //
            //     It can be a numeric string
            //     array( "sucesss" => "1", ... )
            //     array( "sucesss" => "0", ... ), $httpCode >= 200 (can be 403, 502, etc)
            //
            //     Or just a string
            //     array( "success" => "true", ... )
            //     array( "success" => "false", ... ), $httpCode >= 200
            //
            //     Or a boolean
            //     array( "success" => true, ... )
            //     array( "success" => false, ... ), $httpCode >= 200
            //
            // 3 - Oversimplified, Python PEP8 forbids comparison operator (===) of different types
            //
            // 4 - We do not want to copy-paste and duplicate the $code of this handler to other exchanges derived from Liqui
            //
            // To cover points 1, 2, 3 and 4 combined this handler should work like this:
            //
            $success = $this->safe_value($response, 'success'); // don't replace with safeBool here
            if (gettype($success) === 'string') {
                if (($success === 'true') || ($success === '1')) {
                    $success = true;
                } else {
                    $success = false;
                }
            }
            if (!$success) {
                $code = $this->safe_string($response, 'code');
                $message = $this->safe_string($response, 'error');
                $feedback = $this->id . ' ' . $body;
                $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $feedback);
                $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
                $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
                throw new ExchangeError($feedback); // unknown $message
            }
        }
        return null;
    }
}
