<?php

namespace ccxt\async;

// 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\async\abstract\probit as Exchange;
use ccxt\ExchangeError;
use ccxt\AuthenticationError;
use ccxt\ArgumentsRequired;
use ccxt\InvalidAddress;
use ccxt\InvalidOrder;
use ccxt\BadResponse;
use ccxt\Precise;
use \React\Async;
use \React\Promise\PromiseInterface;

class probit extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'probit',
            'name' => 'ProBit',
            'countries' => array( 'SC', 'KR' ), // Seychelles, South Korea
            'rateLimit' => 50, // ms
            'pro' => true,
            'has' => array(
                'CORS' => true,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelOrder' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrder' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => 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,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => true,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => true,
                'fetchFundingHistory' => false,
                'fetchFundingInterval' => false,
                'fetchFundingIntervals' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchGreeks' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchIsolatedPositions' => false,
                'fetchLeverage' => false,
                'fetchLeverages' => false,
                'fetchLeverageTiers' => false,
                'fetchLiquidations' => false,
                'fetchLongShortRatio' => false,
                'fetchLongShortRatioHistory' => false,
                'fetchMarginAdjustmentHistory' => false,
                'fetchMarginMode' => false,
                'fetchMarginModes' => false,
                'fetchMarketLeverageTiers' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMarkPrice' => false,
                'fetchMarkPrices' => false,
                'fetchMyLiquidations' => false,
                'fetchMySettlementHistory' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => false,
                'fetchOpenInterestHistory' => false,
                'fetchOpenInterests' => false,
                'fetchOpenOrders' => true,
                'fetchOption' => false,
                'fetchOptionChain' => false,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchPosition' => false,
                'fetchPositionHistory' => false,
                'fetchPositionMode' => false,
                'fetchPositions' => false,
                'fetchPositionsForSymbol' => false,
                'fetchPositionsHistory' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchSettlementHistory' => false,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTransactions' => 'emulated',
                'fetchTransfer' => false,
                'fetchTransfers' => false,
                'fetchUnderlyingAssets' => false,
                'fetchVolatilityHistory' => false,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'sandbox' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'signIn' => true,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1m',
                '3m' => '3m',
                '5m' => '5m',
                '10m' => '10m',
                '15m' => '15m',
                '30m' => '30m',
                '1h' => '1h',
                '4h' => '4h',
                '6h' => '6h',
                '12h' => '12h',
                '1d' => '1D',
                '1w' => '1W',
                '1M' => '1M',
            ),
            'version' => 'v1',
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/79268032-c4379480-7ea2-11ea-80b3-dd96bb29fd0d.jpg',
                'api' => array(
                    'accounts' => 'https://accounts.probit.com',
                    'public' => 'https://api.probit.com/api/exchange',
                    'private' => 'https://api.probit.com/api/exchange',
                ),
                'www' => 'https://www.probit.com',
                'doc' => array(
                    'https://docs-en.probit.com',
                    'https://docs-ko.probit.com',
                ),
                'fees' => 'https://support.probit.com/hc/en-us/articles/360020968611-Trading-Fees',
                'referral' => 'https://www.probit.com/r/34608773',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'market' => 1,
                        'currency' => 1,
                        'currency_with_platform' => 1,
                        'time' => 1,
                        'ticker' => 1,
                        'order_book' => 1,
                        'trade' => 1,
                        'candle' => 1,
                    ),
                ),
                'private' => array(
                    'post' => array(
                        'new_order' => 2,
                        'cancel_order' => 1,
                        'withdrawal' => 2,
                    ),
                    'get' => array(
                        'balance' => 1,
                        'order' => 1,
                        'open_order' => 1,
                        'order_history' => 1,
                        'trade_history' => 1,
                        'deposit_address' => 1,
                        'transfer/payment' => 1,
                    ),
                ),
                'accounts' => array(
                    'post' => array(
                        'token' => 1,
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'maker' => $this->parse_number('0.002'),
                    'taker' => $this->parse_number('0.002'),
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => false,
                        'triggerDirection' => false,
                        'triggerPriceType' => null,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        // todo
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => false,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyByCost' => true,
                        'marketBuyRequiresPrice' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000, // todo
                        'daysBackCanceled' => 1, // todo
                        'untilDays' => 90,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 4000,
                    ),
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'UNAUTHORIZED' => '\\ccxt\\AuthenticationError',
                    'INVALID_ARGUMENT' => '\\ccxt\\BadRequest', // Parameters are not a valid format, parameters are empty, or out of range, or a parameter was sent when not required.
                    'TRADING_UNAVAILABLE' => '\\ccxt\\ExchangeNotAvailable',
                    'NOT_ENOUGH_BALANCE' => '\\ccxt\\InsufficientFunds',
                    'NOT_ALLOWED_COMBINATION' => '\\ccxt\\BadRequest',
                    'INVALID_ORDER' => '\\ccxt\\InvalidOrder', // Requested order does not exist, or it is not your order
                    'RATE_LIMIT_EXCEEDED' => '\\ccxt\\RateLimitExceeded', // You are sending requests too frequently. Please try it later.
                    'MARKET_UNAVAILABLE' => '\\ccxt\\ExchangeNotAvailable', // Market is closed today
                    'INVALID_MARKET' => '\\ccxt\\BadSymbol', // Requested market is not exist
                    'MARKET_CLOSED' => '\\ccxt\\MarketClosed', // array("errorCode":"MARKET_CLOSED")
                    'MARKET_NOT_FOUND' => '\\ccxt\\BadSymbol', // array("errorCode":"MARKET_NOT_FOUND","message":"8e2b8496-0a1e-5beb-b990-a205b902eabe","details":array())
                    'INVALID_CURRENCY' => '\\ccxt\\BadRequest', // Requested currency is not exist on ProBit system
                    'TOO_MANY_OPEN_ORDERS' => '\\ccxt\\DDoSProtection', // Too many open orders
                    'DUPLICATE_ADDRESS' => '\\ccxt\\InvalidAddress', // Address already exists in withdrawal address list
                    'invalid_grant' => '\\ccxt\\AuthenticationError', // array("error":"invalid_grant")
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                'createMarketBuyOrderRequiresPrice' => true,
                'timeInForce' => array(
                    'limit' => 'gtc',
                    'market' => 'ioc',
                ),
                'networks' => array(
                    'BEP20' => 'BSC',
                    'ERC20' => 'ETH',
                    'TRC20' => 'TRON',
                ),
            ),
            'commonCurrencies' => array(
                'BB' => 'Baby Bali',
                'CBC' => 'CryptoBharatCoin',
                'CTK' => 'Cryptyk',
                'CTT' => 'Castweet',
                'DKT' => 'DAKOTA',
                'EGC' => 'EcoG9coin',
                'EPS' => 'Epanus',  // conflict with EPS Ellipsis https://github.com/ccxt/ccxt/issues/8909
                'FX' => 'Fanzy',
                'GM' => 'GM Holding',
                'GOGOL' => 'GOL',
                'GOL' => 'Goldofir',
                'HUSL' => 'The Hustle App',
                'LAND' => 'Landbox',
                'SST' => 'SocialSwap',
                'TCT' => 'Top Coin Token',
                'TOR' => 'Torex',
                'UNI' => 'UNICORN Token',
                'UNISWAP' => 'UNI',
            ),
        ));
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/market
             *
             * retrieves data on all $markets for probit
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market data
             */
            $response = Async\await($this->publicGetMarket ($params));
            //
            //     {
            //         "data":array(
            //             array(
            //                 "id":"MONA-USDT",
            //                 "base_currency_id":"MONA",
            //                 "quote_currency_id":"USDT",
            //                 "min_price":"0.001",
            //                 "max_price":"9999999999999999",
            //                 "price_increment":"0.001",
            //                 "min_quantity":"0.0001",
            //                 "max_quantity":"9999999999999999",
            //                 "quantity_precision":4,
            //                 "min_cost":"1",
            //                 "max_cost":"9999999999999999",
            //                 "cost_precision":8,
            //                 "taker_fee_rate":"0.2",
            //                 "maker_fee_rate":"0.2",
            //                 "show_in_ui":true,
            //                 "closed":false
            //             ),
            //         )
            //     }
            //
            $markets = $this->safe_value($response, 'data', array());
            return $this->parse_markets($markets);
        }) ();
    }

    public function parse_market(array $market): array {
        $id = $this->safe_string($market, 'id');
        $baseId = $this->safe_string($market, 'base_currency_id');
        $quoteId = $this->safe_string($market, 'quote_currency_id');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $closed = $this->safe_bool($market, 'closed', false);
        $showInUI = $this->safe_bool($market, 'show_in_ui', true);
        $active = !$closed && $showInUI;
        $takerFeeRate = $this->safe_string($market, 'taker_fee_rate');
        $taker = Precise::string_div($takerFeeRate, '100');
        $makerFeeRate = $this->safe_string($market, 'maker_fee_rate');
        $maker = Precise::string_div($makerFeeRate, '100');
        return array(
            'id' => $id,
            'symbol' => $base . '/' . $quote,
            'base' => $base,
            'quote' => $quote,
            'settle' => null,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => null,
            'type' => 'spot',
            'spot' => true,
            'margin' => false,
            'swap' => false,
            'future' => false,
            'option' => false,
            'active' => $active,
            'contract' => false,
            'linear' => null,
            'inverse' => null,
            'taker' => $this->parse_number($taker),
            'maker' => $this->parse_number($maker),
            'contractSize' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->parse_number($this->parse_precision($this->safe_string($market, 'quantity_precision'))),
                'price' => $this->safe_number($market, 'price_increment'),
                'cost' => $this->parse_number($this->parse_precision($this->safe_string($market, 'cost_precision'))),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($market, 'min_quantity'),
                    'max' => $this->safe_number($market, 'max_quantity'),
                ),
                'price' => array(
                    'min' => $this->safe_number($market, 'min_price'),
                    'max' => $this->safe_number($market, 'max_price'),
                ),
                'cost' => array(
                    'min' => $this->safe_number($market, 'min_cost'),
                    'max' => $this->safe_number($market, 'max_cost'),
                ),
            ),
            'created' => null,
            'info' => $market,
        );
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/currency
             *
             * fetches all available $currencies on an exchange
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of $currencies
             */
            $response = Async\await($this->publicGetCurrencyWithPlatform ($params));
            //
            //     {
            //         "data":array(
            //             {
            //                 "id":"USDT",
            //                 "display_name":array("ko-kr":"테더","en-us":"Tether"),
            //                 "show_in_ui":true,
            //                 "platform":[
            //                     array(
            //                         "id":"ETH",
            //                         "priority":1,
            //                         "deposit":true,
            //                         "withdrawal":true,
            //                         "currency_id":"USDT",
            //                         "precision":6,
            //                         "min_confirmation_count":15,
            //                         "require_destination_tag":false,
            //                         "display_name":array("name":array("ko-kr":"ERC-20","en-us":"ERC-20")),
            //                         "min_deposit_amount":"0",
            //                         "min_withdrawal_amount":"1",
            //                         "withdrawal_fee":[
            //                             array("amount":"0.01","priority":2,"currency_id":"ETH"),
            //                             array("amount":"1.5","priority":1,"currency_id":"USDT"),
            //                         ),
            //                         "deposit_fee":array(),
            //                         "suspended_reason":"",
            //                         "deposit_suspended":false,
            //                         "withdrawal_suspended":false
            //                     ),
            //                     {
            //                         "id":"OMNI",
            //                         "priority":2,
            //                         "deposit":true,
            //                         "withdrawal":true,
            //                         "currency_id":"USDT",
            //                         "precision":6,
            //                         "min_confirmation_count":3,
            //                         "require_destination_tag":false,
            //                         "display_name":array("name":array("ko-kr":"OMNI","en-us":"OMNI")),
            //                         "min_deposit_amount":"0",
            //                         "min_withdrawal_amount":"5",
            //                         "withdrawal_fee":[array("amount":"5","priority":1,"currency_id":"USDT")],
            //                         "deposit_fee":array(),
            //                         "suspended_reason":"wallet_maintenance",
            //                         "deposit_suspended":false,
            //                         "withdrawal_suspended":false
            //                     }
            //                 ],
            //                 "stakeable":false,
            //                 "unstakeable":false,
            //                 "auto_stake":false,
            //                 "auto_stake_amount":"0"
            //             }
            //         ]
            //     }
            //
            $currencies = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($currencies); $i++) {
                $currency = $currencies[$i];
                $id = $this->safe_string($currency, 'id');
                $code = $this->safe_currency_code($id);
                $displayName = $this->safe_dict($currency, 'display_name');
                $name = $this->safe_string($displayName, 'en-us');
                $platforms = $this->safe_list($currency, 'platform', array());
                $platformsByPriority = $this->sort_by($platforms, 'priority');
                $networkList = array();
                for ($j = 0; $j < count($platformsByPriority); $j++) {
                    $network = $platformsByPriority[$j];
                    $idInner = $this->safe_string($network, 'id');
                    $networkCode = $this->network_id_to_code($idInner);
                    $withdrawFee = $this->safe_list($network, 'withdrawal_fee', array());
                    $networkFee = $this->safe_dict($withdrawFee, 0, array());
                    for ($k = 0; $k < count($withdrawFee); $k++) {
                        $withdrawPlatform = $withdrawFee[$k];
                        $feeCurrencyId = $this->safe_string($withdrawPlatform, 'currency_id');
                        if ($feeCurrencyId === $id) {
                            $networkFee = $withdrawPlatform;
                            break;
                        }
                    }
                    $networkList[$networkCode] = array(
                        'id' => $idInner,
                        'network' => $networkCode,
                        'active' => null,
                        'deposit' => !$this->safe_bool($network, 'deposit_suspended'),
                        'withdraw' => !$this->safe_bool($network, 'withdrawal_suspended'),
                        'fee' => $this->safe_number($networkFee, 'amount'),
                        'precision' => $this->parse_number($this->parse_precision($this->safe_string($network, 'precision'))),
                        'limits' => array(
                            'withdraw' => array(
                                'min' => $this->safe_number($network, 'min_withdrawal_amount'),
                                'max' => null,
                            ),
                            'deposit' => array(
                                'min' => $this->safe_number($network, 'min_deposit_amount'),
                                'max' => null,
                            ),
                        ),
                        'info' => $network,
                    );
                }
                $result[$code] = $this->safe_currency_structure(array(
                    'id' => $id,
                    'code' => $code,
                    'info' => $currency,
                    'name' => $name,
                    'active' => null,
                    'deposit' => null,
                    'withdraw' => null,
                    'type' => 'crypto',
                    'fee' => null,
                    'precision' => null,
                    'limits' => array(
                        'amount' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'withdraw' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'networks' => $networkList,
                ));
            }
            return $result;
        }) ();
    }

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

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/balance
             *
             * 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~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetBalance ($params));
            //
            //     {
            //         "data" => array(
            //             {
            //                 "currency_id":"XRP",
            //                 "total":"100",
            //                 "available":"0",
            //             }
            //         )
            //     }
            //
            return $this->parse_balance($response);
        }) ();
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/order_book
             *
             * 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
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'market_id' => $market['id'],
            );
            $response = Async\await($this->publicGetOrderBook ($this->extend($request, $params)));
            //
            //     {
            //         $data => array(
            //             array( side => 'buy', price => '0.000031', quantity => '10' ),
            //             array( side => 'buy', price => '0.00356007', quantity => '4.92156877' ),
            //             array( side => 'sell', price => '0.1857', quantity => '0.17' ),
            //         )
            //     }
            //
            $data = $this->safe_value($response, 'data', array());
            $dataBySide = $this->group_by($data, 'side');
            return $this->parse_order_book($dataBySide, $market['symbol'], null, 'buy', 'sell', 'price', 'quantity');
        }) ();
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/ticker
             *
             * 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
             * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
             */
            Async\await($this->load_markets());
            $request = array();
            if ($symbols !== null) {
                $marketIds = $this->market_ids($symbols);
                $request['market_ids'] = implode(',', $marketIds);
            }
            $response = Async\await($this->publicGetTicker ($this->extend($request, $params)));
            //
            //     {
            //         "data":array(
            //             {
            //                 "last":"0.022902",
            //                 "low":"0.021693",
            //                 "high":"0.024093",
            //                 "change":"-0.000047",
            //                 "base_volume":"15681.986",
            //                 "quote_volume":"360.514403624",
            //                 "market_id":"ETH-BTC",
            //                 "time":"2020-04-12T18:43:38.000Z"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_tickers($data, $symbols);
        }) ();
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/ticker
             *
             * 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~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'market_ids' => $market['id'],
            );
            $response = Async\await($this->publicGetTicker ($this->extend($request, $params)));
            //
            //     {
            //         "data":array(
            //             {
            //                 "last":"0.022902",
            //                 "low":"0.021693",
            //                 "high":"0.024093",
            //                 "change":"-0.000047",
            //                 "base_volume":"15681.986",
            //                 "quote_volume":"360.514403624",
            //                 "market_id":"ETH-BTC",
            //                 "time":"2020-04-12T18:43:38.000Z"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_value($response, 'data', array());
            $ticker = $this->safe_value($data, 0);
            if ($ticker === null) {
                throw new BadResponse($this->id . ' fetchTicker() returned an empty response');
            }
            return $this->parse_ticker($ticker, $market);
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "last":"0.022902",
        //         "low":"0.021693",
        //         "high":"0.024093",
        //         "change":"-0.000047",
        //         "base_volume":"15681.986",
        //         "quote_volume":"360.514403624",
        //         "market_id":"ETH-BTC",
        //         "time":"2020-04-12T18:43:38.000Z"
        //     }
        //
        $timestamp = $this->parse8601($this->safe_string($ticker, 'time'));
        $marketId = $this->safe_string($ticker, 'market_id');
        $symbol = $this->safe_symbol($marketId, $market, '-');
        $close = $this->safe_string($ticker, 'last');
        $change = $this->safe_string($ticker, 'change');
        $baseVolume = $this->safe_string($ticker, 'base_volume');
        $quoteVolume = $this->safe_string($ticker, 'quote_volume');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $close,
            'last' => $close,
            'previousClose' => null, // previous day $close
            'change' => $change,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/trade
             *
             * 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~
             */
            Async\await($this->load_markets());
            $market = null;
            $now = $this->milliseconds();
            $request = array(
                'limit' => 100,
                'start_time' => $this->iso8601($now - 31536000000), // -365 days
                'end_time' => $this->iso8601($now),
            );
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['market_id'] = $market['id'];
            }
            if ($since !== null) {
                $request['start_time'] = $this->iso8601($since);
                $request['end_time'] = $this->iso8601(min ($now, $since + 31536000000));
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->privateGetTradeHistory ($this->extend($request, $params)));
            //
            //     {
            //         "data" => array(
            //             {
            //                 "id":"BTC-USDT:183566",
            //                 "order_id":"17209376",
            //                 "side":"sell",
            //                 "fee_amount":"0.657396569175",
            //                 "fee_currency_id":"USDT",
            //                 "status":"settled",
            //                 "price":"6573.96569175",
            //                 "quantity":"0.1",
            //                 "cost":"657.396569175",
            //                 "time":"2018-08-10T06:06:46.000Z",
            //                 "market_id":"BTC-USDT"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/trade-1
             *
             * 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~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'market_id' => $market['id'],
                'start_time' => '1970-01-01T00:00:00.000Z',
                'end_time' => $this->iso8601($this->milliseconds()),
            );
            if ($since !== null) {
                $request['start_time'] = $this->iso8601($since);
            }
            if ($limit !== null) {
                $request['limit'] = min ($limit, 1000);
            } else {
                $request['limit'] = 1000; // required to set any value
            }
            $response = Async\await($this->publicGetTrade ($this->extend($request, $params)));
            //
            //     {
            //         "data":array(
            //             array(
            //                 "id":"ETH-BTC:3331886",
            //                 "price":"0.022981",
            //                 "quantity":"12.337",
            //                 "time":"2020-04-12T20:55:42.371Z",
            //                 "side":"sell",
            //                 "tick_direction":"down"
            //             ),
            //             {
            //                 "id":"ETH-BTC:3331885",
            //                 "price":"0.022982",
            //                 "quantity":"6.472",
            //                 "time":"2020-04-12T20:55:39.652Z",
            //                 "side":"sell",
            //                 "tick_direction":"down"
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $market, $since, $limit);
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades (public)
        //
        //     {
        //         "id":"ETH-BTC:3331886",
        //         "price":"0.022981",
        //         "quantity":"12.337",
        //         "time":"2020-04-12T20:55:42.371Z",
        //         "side":"sell",
        //         "tick_direction":"down"
        //     }
        //
        // fetchMyTrades (private)
        //
        //     {
        //         "id":"BTC-USDT:183566",
        //         "order_id":"17209376",
        //         "side":"sell",
        //         "fee_amount":"0.657396569175",
        //         "fee_currency_id":"USDT",
        //         "status":"settled",
        //         "price":"6573.96569175",
        //         "quantity":"0.1",
        //         "cost":"657.396569175",
        //         "time":"2018-08-10T06:06:46.000Z",
        //         "market_id":"BTC-USDT"
        //     }
        //
        $timestamp = $this->parse8601($this->safe_string($trade, 'time'));
        $id = $this->safe_string($trade, 'id');
        $marketId = null;
        if ($id !== null) {
            $parts = explode(':', $id);
            $marketId = $this->safe_string($parts, 0);
        }
        $marketId = $this->safe_string($trade, 'market_id', $marketId);
        $symbol = $this->safe_symbol($marketId, $market, '-');
        $side = $this->safe_string($trade, 'side');
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'quantity');
        $orderId = $this->safe_string($trade, 'order_id');
        $feeCostString = $this->safe_string($trade, 'fee_amount');
        $fee = null;
        if ($feeCostString !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'fee_currency_id');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCostString,
                'currency' => $feeCurrencyCode,
            );
        }
        return $this->safe_trade(array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_time($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/time
             *
             * fetches the current integer $timestamp in milliseconds from the exchange server
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int} the current integer $timestamp in milliseconds from the exchange server
             */
            $response = Async\await($this->publicGetTime ($params));
            //
            //     array( "data":"2020-04-12T18:54:25.390Z" )
            //
            $timestamp = $this->parse8601($this->safe_string($response, 'data'));
            return $timestamp;
        }) ();
    }

    public function normalize_ohlcv_timestamp($timestamp, $timeframe, $after = false) {
        $duration = $this->parse_timeframe($timeframe);
        if ($timeframe === '1M') {
            $iso8601 = $this->iso8601($timestamp);
            $parts = explode('-', $iso8601);
            $year = $this->safe_string($parts, 0);
            $month = $this->safe_integer($parts, 1);
            $monthString = null;
            if ($after) {
                $monthString = $this->sum($month, (string) 1);
            }
            if ($month < 10) {
                $monthString = '0' . (string) $month;
            }
            return $year . '-' . $monthString . '-01T00:00:00.000Z';
        } elseif ($timeframe === '1w') {
            $timestamp = $this->parse_to_int($timestamp / 1000);
            $firstSunday = 259200; // 1970-01-04T00:00:00.000Z
            $difference = $timestamp - $firstSunday;
            $numWeeks = (int) floor($difference / $duration);
            $previousSunday = $this->sum($firstSunday, $numWeeks * $duration);
            if ($after) {
                $previousSunday = $this->sum($previousSunday, $duration);
            }
            return $this->iso8601($previousSunday * 1000);
        } else {
            $timestamp = $this->parse_to_int($timestamp / 1000);
            $timestamp = $duration * $this->parse_to_int($timestamp / $duration);
            if ($after) {
                $timestamp = $this->sum($timestamp, $duration);
            }
            return $this->iso8601($timestamp * 1000);
        }
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/candle
             *
             * fetches historical candlestick $data containing the open, high, low, and close price, and the volume of a $market
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV $data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->until] timestamp in ms of the earliest candle to fetch
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $interval = $this->safe_string($this->timeframes, $timeframe, $timeframe);
            $limit = ($limit === null) ? 100 : $limit;
            $requestLimit = $this->sum($limit, 1);
            $requestLimit = min (1000, $requestLimit); // max 1000
            $request = array(
                'market_ids' => $market['id'],
                'interval' => $interval,
                'sort' => 'asc', // 'asc' will always include the start_time, 'desc' will always include end_time
                'limit' => $requestLimit, // max 1000
            );
            $now = $this->milliseconds();
            $until = $this->safe_integer($params, 'until');
            $durationMilliseconds = $this->parse_timeframe($timeframe) * 1000;
            $startTime = $since;
            $endTime = ($until !== null) ? $until - $durationMilliseconds : $now;
            if ($since === null) {
                if ($limit === null) {
                    $limit = $requestLimit;
                }
                $startLimit = $limit - 1;
                $startTime = $endTime - $startLimit * $durationMilliseconds;
            } else {
                if ($limit !== null) {
                    $endByLimit = $this->sum($since, $limit * $durationMilliseconds);
                    $endTime = min ($endTime, $endByLimit);
                }
            }
            $startTimeNormalized = $this->normalize_ohlcv_timestamp($startTime, $timeframe);
            $endTimeNormalized = $this->normalize_ohlcv_timestamp($endTime, $timeframe, true);
            $request['start_time'] = $startTimeNormalized;
            $request['end_time'] = $endTimeNormalized;
            $response = Async\await($this->publicGetCandle ($this->extend($request, $params)));
            //
            //     {
            //         "data":array(
            //             array(
            //                 "market_id":"ETH-BTC",
            //                 "open":"0.02811",
            //                 "close":"0.02811",
            //                 "low":"0.02811",
            //                 "high":"0.02811",
            //                 "base_volume":"0.0005",
            //                 "quote_volume":"0.000014055",
            //                 "start_time":"2018-11-30T18:19:00.000Z",
            //                 "end_time":"2018-11-30T18:20:00.000Z"
            //             ),
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     {
        //         "market_id":"ETH-BTC",
        //         "open":"0.02811",
        //         "close":"0.02811",
        //         "low":"0.02811",
        //         "high":"0.02811",
        //         "base_volume":"0.0005",
        //         "quote_volume":"0.000014055",
        //         "start_time":"2018-11-30T18:19:00.000Z",
        //         "end_time":"2018-11-30T18:20:00.000Z"
        //     }
        //
        return array(
            $this->parse8601($this->safe_string($ohlcv, 'start_time')),
            $this->safe_number($ohlcv, 'open'),
            $this->safe_number($ohlcv, 'high'),
            $this->safe_number($ohlcv, 'low'),
            $this->safe_number($ohlcv, 'close'),
            $this->safe_number($ohlcv, 'base_volume'),
        );
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/open_order-1
             *
             * 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 orders structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $since = $this->parse8601($since);
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['market_id'] = $market['id'];
            }
            $response = Async\await($this->privateGetOpenOrder ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data');
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/order
             *
             * fetches information on multiple closed orders made by the user
             * @param {string} $symbol unified $market $symbol of the $market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $request = array(
                'start_time' => $this->iso8601(0),
                'end_time' => $this->iso8601($this->milliseconds()),
                'limit' => 100,
            );
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['market_id'] = $market['id'];
            }
            if ($since) {
                $request['start_time'] = $this->iso8601($since);
            }
            if ($limit) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->privateGetOrderHistory ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data');
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/order-3
             *
             * fetches information on an $order made by the user
             * @param {string} $id the $order $id
             * @param {string} $symbol unified $symbol of the $market the $order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'market_id' => $market['id'],
            );
            $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'client_order_id');
            if ($clientOrderId !== null) {
                $request['client_order_id'] = $clientOrderId;
            } else {
                $request['order_id'] = $id;
            }
            $query = $this->omit($params, array( 'clientOrderId', 'client_order_id' ));
            $response = Async\await($this->privateGetOrder ($this->extend($request, $query)));
            $data = $this->safe_value($response, 'data', array());
            $order = $this->safe_dict($data, 0);
            return $this->parse_order($order, $market);
        }) ();
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'open' => 'open',
            'cancelled' => 'canceled',
            'filled' => 'closed',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        //     {
        //         $id,
        //         user_id,
        //         market_id,
        //         "type" => "orderType",
        //         "side" => "side",
        //         quantity,
        //         limit_price,
        //         "time_in_force" => "timeInForce",
        //         filled_cost,
        //         filled_quantity,
        //         open_quantity,
        //         cancelled_quantity,
        //         "status" => "orderStatus",
        //         "time" => "date",
        //         client_order_id,
        //     }
        //
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $id = $this->safe_string($order, 'id');
        $type = $this->safe_string($order, 'type');
        $side = $this->safe_string($order, 'side');
        $marketId = $this->safe_string($order, 'market_id');
        $symbol = $this->safe_symbol($marketId, $market, '-');
        $timestamp = $this->parse8601($this->safe_string($order, 'time'));
        $price = $this->safe_string($order, 'limit_price');
        $filled = $this->safe_string($order, 'filled_quantity');
        $remaining = $this->safe_string($order, 'open_quantity');
        $canceledAmount = $this->safe_string($order, 'cancelled_quantity');
        if ($canceledAmount !== null) {
            $remaining = Precise::string_add($remaining, $canceledAmount);
        }
        $amount = $this->safe_string($order, 'quantity', Precise::string_add($filled, $remaining));
        $cost = $this->safe_string_2($order, 'filled_cost', 'cost');
        if ($type === 'market') {
            $price = null;
        }
        $clientOrderId = $this->safe_string($order, 'client_order_id');
        $timeInForce = $this->safe_string_upper($order, 'time_in_force');
        return $this->safe_order(array(
            'id' => $id,
            'info' => $order,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'side' => $side,
            'status' => $status,
            'price' => $price,
            'triggerPrice' => null,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'average' => null,
            'cost' => $cost,
            'fee' => null,
            'trades' => null,
        ), $market);
    }

    public function cost_to_precision($symbol, $cost) {
        return $this->decimal_to_precision($cost, TRUNCATE, $this->markets[$symbol]['precision']['cost'], $this->precisionMode);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade $order
             *
             * @see https://docs-en.probit.com/reference/order-1
             *
             * @param {string} $symbol unified $symbol of the $market to create an $order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much you want to trade in units of the 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
             * @param {float} [$params->cost] the quote quantity that can be used alternative for the $amount for $market buy orders
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $options = $this->safe_value($this->options, 'timeInForce');
            $defaultTimeInForce = $this->safe_value($options, $type);
            $timeInForce = $this->safe_string_2($params, 'timeInForce', 'time_in_force', $defaultTimeInForce);
            $request = array(
                'market_id' => $market['id'],
                'type' => $type,
                'side' => $side,
                'time_in_force' => $timeInForce,
            );
            $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'client_order_id');
            if ($clientOrderId !== null) {
                $request['client_order_id'] = $clientOrderId;
            }
            $quoteAmount = null;
            if ($type === 'limit') {
                $request['limit_price'] = $this->price_to_precision($symbol, $price);
                $request['quantity'] = $this->amount_to_precision($symbol, $amount);
            } elseif ($type === 'market') {
                // for $market buy it requires the $amount of quote currency to spend
                if ($side === 'buy') {
                    $createMarketBuyOrderRequiresPrice = true;
                    list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
                    $cost = $this->safe_string($params, 'cost');
                    $params = $this->omit($params, 'cost');
                    if ($cost !== null) {
                        $quoteAmount = $this->cost_to_precision($symbol, $cost);
                    } elseif ($createMarketBuyOrderRequiresPrice) {
                        if ($price === null) {
                            throw new InvalidOrder($this->id . ' createOrder() requires the $price argument for $market buy orders to calculate the total $cost to spend ($amount * $price), alternatively set the $createMarketBuyOrderRequiresPrice option or param to false and pass the $cost to spend in the $amount argument');
                        } else {
                            $amountString = $this->number_to_string($amount);
                            $priceString = $this->number_to_string($price);
                            $costRequest = Precise::string_mul($amountString, $priceString);
                            $quoteAmount = $this->cost_to_precision($symbol, $costRequest);
                        }
                    } else {
                        $quoteAmount = $this->cost_to_precision($symbol, $amount);
                    }
                    $request['cost'] = $quoteAmount;
                } else {
                    $request['quantity'] = $this->amount_to_precision($symbol, $amount);
                }
            }
            $query = $this->omit($params, array( 'timeInForce', 'time_in_force', 'clientOrderId', 'client_order_id' ));
            $response = Async\await($this->privatePostNewOrder ($this->extend($request, $query)));
            //
            //     {
            //         "data" => {
            //             id,
            //             user_id,
            //             market_id,
            //             "type" => "orderType",
            //             "side" => "side",
            //             quantity,
            //             limit_price,
            //             "time_in_force" => "timeInForce",
            //             filled_cost,
            //             filled_quantity,
            //             open_quantity,
            //             cancelled_quantity,
            //             "status" => "orderStatus",
            //             "time" => "date",
            //             client_order_id,
            //         }
            //     }
            //
            $data = $this->safe_value($response, 'data');
            $order = $this->parse_order($data, $market);
            // a workaround for incorrect huge amounts
            // returned by the exchange on $market buys
            if (($type === 'market') && ($side === 'buy')) {
                $order['amount'] = null;
                $order['cost'] = $this->parse_number($quoteAmount);
                $order['remaining'] = null;
            }
            return $order;
        }) ();
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/order-2
             *
             * cancels an open order
             * @param {string} $id order $id
             * @param {string} $symbol unified $symbol of the $market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'market_id' => $market['id'],
                'order_id' => $id,
            );
            $response = Async\await($this->privatePostCancelOrder ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data');
            return $this->parse_order($data);
        }) ();
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        $address = $this->safe_string($depositAddress, 'address');
        $tag = $this->safe_string($depositAddress, 'destination_tag');
        $currencyId = $this->safe_string($depositAddress, 'currency_id');
        $currency = $this->safe_currency($currencyId, $currency);
        $code = $currency['code'];
        $network = $this->safe_string($depositAddress, 'platform_id');
        $this->check_address($address);
        return array(
            'info' => $depositAddress,
            'currency' => $code,
            'network' => $network,
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function fetch_deposit_address(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/deposit_address
             *
             * fetch the deposit address for a $currency associated with this account
             * @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~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $request = array(
                'currency_id' => $currency['id'],
                // 'platform_id' => 'TRON', (undocumented)
            );
            $networks = $this->safe_value($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) {
                $request['platform_id'] = $network;
                $params = $this->omit($params, 'platform_id');
            }
            $response = Async\await($this->privateGetDepositAddress ($this->extend($request, $params)));
            //
            // without 'platform_id'
            //     {
            //         "data":array(
            //             {
            //                 "currency_id":"ETH",
            //                 "address":"0x12e2caf3c4051ba1146e612f532901a423a9898a",
            //                 "destination_tag":null
            //             }
            //         )
            //     }
            //
            // with 'platform_id'
            //     {
            //         "data":array(
            //             {
            //                 "platform_id":"TRON",
            //                 "address":"TDQLMxBTa6MzuoZ6deSGZkqET3Ek8v7uC6",
            //                 "destination_tag":null
            //             }
            //         )
            //     }
            //
            $data = $this->safe_value($response, 'data', array());
            $firstAddress = $this->safe_value($data, 0);
            if ($firstAddress === null) {
                throw new InvalidAddress($this->id . ' fetchDepositAddress() returned an empty response');
            }
            return $this->parse_deposit_address($firstAddress, $currency);
        }) ();
    }

    public function fetch_deposit_addresses(?array $codes = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($codes, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/deposit_address
             *
             * fetch deposit addresses for multiple currencies and chain types
             * @param {string[]|null} $codes list of unified $currency $codes, default is null
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=address-structure address structures~
             */
            Async\await($this->load_markets());
            $request = array();
            if ($codes) {
                $currencyIds = array();
                for ($i = 0; $i < count($codes); $i++) {
                    $currency = $this->currency($codes[$i]);
                    $currencyIds[] = $currency['id'];
                }
                $request['currency_id'] = implode(',', $codes);
            }
            $response = Async\await($this->privateGetDepositAddress ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_deposit_addresses($data, $codes);
        }) ();
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $address, $tag, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/withdrawal
             *
             * 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);
            // In order to use this method
            // you need to allow API withdrawal from the API Settings Page, and
            // and register the list of withdrawal addresses and destination tags on the API Settings page
            // you can only withdraw to the registered addresses using the API
            $this->check_address($address);
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            if ($tag === null) {
                $tag = '';
            }
            $request = array(
                'currency_id' => $currency['id'],
                // 'platform_id' => 'ETH', // if omitted it will use the default platform for the $currency
                'address' => $address,
                'destination_tag' => $tag,
                'amount' => $this->number_to_string($amount),
                // which $currency to pay the withdrawal fees
                // only applicable for currencies that accepts multiple withdrawal fee options
                // 'fee_currency_id' => 'ETH', // if omitted it will use the default fee policy for each $currency
                // whether the $amount field includes fees
                // 'include_fee' => false, // makes sense only when fee_currency_id is equal to currency_id
            );
            $networks = $this->safe_value($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) {
                $request['platform_id'] = $network;
                $params = $this->omit($params, 'network');
            }
            $response = Async\await($this->privatePostWithdrawal ($this->extend($request, $params)));
            $data = $this->safe_dict($response, 'data');
            return $this->parse_transaction($data, $currency);
        }) ();
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all deposits made to an account
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch deposits for
             * @param {int} [$limit] the maximum number of transaction structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            $request = array(
                'type' => 'deposit',
            );
            $result = Async\await($this->fetch_transactions($code, $since, $limit, $this->extend($request, $params)));
            return $result;
        }) ();
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all withdrawals made to an account
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch withdrawals for
             * @param {int} [$limit] the maximum number of transaction structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            $request = array(
                'type' => 'withdrawal',
            );
            $result = Async\await($this->fetch_transactions($code, $since, $limit, $this->extend($request, $params)));
            return $result;
        }) ();
    }

    public function fetch_deposits_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch history of deposits and withdrawals
             *
             * @see https://docs-en.probit.com/reference/transferpayment
             *
             * @param {string} $code unified $currency $code
             * @param {int} [$since] the earliest time in ms to fetch transactions for
             * @param {int} [$limit] the maximum number of transaction structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch transactions for
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            Async\await($this->load_markets());
            $currency = null;
            $request = array();
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['currency_id'] = $currency['id'];
            }
            if ($since !== null) {
                $request['start_time'] = $this->iso8601($since);
            } else {
                $request['start_time'] = $this->iso8601(1);
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $request['end_time'] = $this->iso8601($until);
                $params = $this->omit($params, array( 'until' ));
            } else {
                $request['end_time'] = $this->iso8601($this->milliseconds());
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            } else {
                $request['limit'] = 100;
            }
            $response = Async\await($this->privateGetTransferPayment ($this->extend($request, $params)));
            //
            //     {
            //         "data" => array(
            //             {
            //                 "id" => "01211d4b-0e68-41d6-97cb-298bfe2cab67",
            //                 "type" => "deposit",
            //                 "status" => "done",
            //                 "amount" => "0.01",
            //                 "address" => "0x9e7430fc0bdd14745bd00a1b92ed25133a7c765f",
            //                 "time" => "2023-06-14T12:03:11.000Z",
            //                 "hash" => "0x0ff5bedc9e378f9529acc6b9840fa8c2ef00fd0275e0bac7fa0589a9b5d1712e",
            //                 "currency_id" => "ETH",
            //                 "confirmations":0,
            //                 "fee" => "0",
            //                 "destination_tag" => null,
            //                 "platform_id" => "ETH",
            //                 "fee_currency_id" => "ETH",
            //                 "payment_service_name":null,
            //                 "payment_service_display_name":null,
            //                 "crypto":null
            //             }
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_transactions($data, $currency, $since, $limit);
        }) ();
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        //     {
        //         "id" => "01211d4b-0e68-41d6-97cb-298bfe2cab67",
        //         "type" => "deposit",
        //         "status" => "done",
        //         "amount" => "0.01",
        //         "address" => "0x9e7430fc0bdd14745bd00a1b92ed25133a7c765f",
        //         "time" => "2023-06-14T12:03:11.000Z",
        //         "hash" => "0x0ff5bedc9e378f9529acc6b9840fa8c2ef00fd0275e0bac7fa0589a9b5d1712e",
        //         "currency_id" => "ETH",
        //         "confirmations":0,
        //         "fee" => "0",
        //         "destination_tag" => null,
        //         "platform_id" => "ETH",
        //         "fee_currency_id" => "ETH",
        //         "payment_service_name":null,
        //         "payment_service_display_name":null,
        //         "crypto":null
        //     }
        //
        $id = $this->safe_string($transaction, 'id');
        $networkId = $this->safe_string($transaction, 'platform_id');
        $networkCode = $this->network_id_to_code($networkId);
        $amount = $this->safe_number($transaction, 'amount');
        $address = $this->safe_string($transaction, 'address');
        $tag = $this->safe_string($transaction, 'destination_tag');
        $txid = $this->safe_string($transaction, 'hash');
        $timestamp = $this->parse8601($this->safe_string($transaction, 'time'));
        $type = $this->safe_string($transaction, 'type');
        $currencyId = $this->safe_string($transaction, 'currency_id');
        $code = $this->safe_currency_code($currencyId);
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $feeCostString = $this->safe_string($transaction, 'fee');
        $fee = null;
        if ($feeCostString !== null && $feeCostString !== '0') {
            $fee = array(
                'currency' => $code,
                'cost' => $this->parse_number($feeCostString),
            );
        }
        return array(
            'id' => $id,
            'currency' => $code,
            'amount' => $amount,
            'network' => $networkCode,
            'addressFrom' => null,
            'address' => $address,
            'addressTo' => $address,
            'tagFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'status' => $status,
            'type' => $type,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'updated' => null,
            'internal' => null,
            'comment' => null,
            'fee' => $fee,
            'info' => $transaction,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            'requested' => 'pending',
            'pending' => 'pending',
            'confirming' => 'pending',
            'confirmed' => 'pending',
            'applying' => 'pending',
            'done' => 'ok',
            'cancelled' => 'canceled',
            'cancelling' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_deposit_withdraw_fees(?array $codes = null, $params = array ()) {
        return Async\async(function () use ($codes, $params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/currency
             *
             * fetch deposit and withdraw fees
             * @param {string[]|null} $codes list of unified currency $codes
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=fee-structure fees structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->publicGetCurrencyWithPlatform ($params));
            //
            //  {
            //     "data" => array(
            //       {
            //       "id" => "AFX",
            //       "display_name" => array(
            //       "ko-kr" => "아프릭스",
            //       "en-us" => "Afrix"
            //       ),
            //       "show_in_ui" => true,
            //       "platform" => array(
            //       {
            //       "id" => "ZYN",
            //       "priority" => 1,
            //       "deposit" => true,
            //       "withdrawal" => true,
            //       "currency_id" => "AFX",
            //       "precision" => 18,
            //       "min_confirmation_count" => 60,
            //       "require_destination_tag" => false,
            //       "allow_withdrawal_destination_tag" => false,
            //       "display_name" => {
            //       "name" => array(
            //       "ko-kr" => "지네코인",
            //       "en-us" => "Wethio"
            //       }
            //       ),
            //       "min_deposit_amount" => "0",
            //       "min_withdrawal_amount" => "0",
            //       "withdrawal_fee" => array(
            //       array(
            //       "currency_id" => "ZYN",
            //       "amount" => "0.5",
            //       "priority" => 1
            //       }
            //       ),
            //       "deposit_fee" => array(),
            //       "suspended_reason" => "",
            //       "deposit_suspended" => false,
            //       "withdrawal_suspended" => false,
            //       "platform_currency_display_name" => array()
            //       }
            //       ),
            //       "internal_transfer" => array(
            //       "suspended_reason" => null,
            //       "suspended" => false
            //       ),
            //       "stakeable" => false,
            //       "unstakeable" => false,
            //       "auto_stake" => false,
            //       "auto_stake_amount" => "0"
            //       ),
            //     )
            //  }
            //
            $data = $this->safe_list($response, 'data');
            return $this->parse_deposit_withdraw_fees($data, $codes, 'id');
        }) ();
    }

    public function parse_deposit_withdraw_fee($fee, ?array $currency = null) {
        //
        // {
        //     "id" => "USDT",
        //     "display_name" => array( "ko-kr" => '테더', "en-us" => "Tether" ),
        //     "show_in_ui" => true,
        //     "platform" => [
        //       array(
        //         "id" => "ETH",
        //         "priority" => "1",
        //         "deposit" => true,
        //         "withdrawal" => true,
        //         "currency_id" => "USDT",
        //         "precision" => "6",
        //         "min_confirmation_count" => "15",
        //         "require_destination_tag" => false,
        //         "allow_withdrawal_destination_tag" => false,
        //         "display_name" => [Object],
        //         "min_deposit_amount" => "0",
        //         "min_withdrawal_amount" => "1",
        //         "withdrawal_fee" => [Array],
        //         "deposit_fee" => array(),
        //         "suspended_reason" => '',
        //         "deposit_suspended" => false,
        //         "withdrawal_suspended" => false,
        //         "platform_currency_display_name" => [Object]
        //       ),
        //     ],
        //     "internal_transfer" => array( suspended_reason => null, suspended => false ),
        //     "stakeable" => false,
        //     "unstakeable" => false,
        //     "auto_stake" => false,
        //     "auto_stake_amount" => "0"
        //   }
        //
        $depositWithdrawFee = $this->deposit_withdraw_fee(array());
        $platforms = $this->safe_value($fee, 'platform', array());
        $depositResult = array(
            'fee' => null,
            'percentage' => null,
        );
        for ($i = 0; $i < count($platforms); $i++) {
            $network = $platforms[$i];
            $networkId = $this->safe_string($network, 'id');
            $networkCode = $this->network_id_to_code($networkId, $currency['code']);
            $withdrawalFees = $this->safe_value($network, 'withdrawal_fee', array());
            $withdrawFee = $this->safe_number($withdrawalFees[0], 'amount');
            if (strlen($withdrawalFees)) {
                $withdrawResult = array(
                    'fee' => $withdrawFee,
                    'percentage' => ($withdrawFee !== null) ? false : null,
                );
                if ($i === 0) {
                    $depositWithdrawFee['withdraw'] = $withdrawResult;
                }
                $depositWithdrawFee['networks'][$networkCode] = array(
                    'withdraw' => $withdrawResult,
                    'deposit' => $depositResult,
                );
            }
        }
        $depositWithdrawFee['info'] = $fee;
        return $depositWithdrawFee;
    }

    public function nonce() {
        return $this->milliseconds();
    }

    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 === 'accounts') {
            $this->check_required_credentials();
            $url .= $this->implode_params($path, $params);
            $auth = $this->apiKey . ':' . $this->secret;
            $auth64 = base64_encode($auth);
            $headers = array(
                'Authorization' => 'Basic ' . $auth64,
                'Content-Type' => 'application/json',
            );
            if ($query) {
                $body = $this->json($query);
            }
        } else {
            $url .= $this->version . '/';
            if ($api === 'public') {
                $url .= $this->implode_params($path, $params);
                if ($query) {
                    $url .= '?' . $this->urlencode($query);
                }
            } elseif ($api === 'private') {
                $now = $this->milliseconds();
                $this->check_required_credentials();
                $expires = $this->safe_integer($this->options, 'expires');
                if (($expires === null) || ($expires < $now)) {
                    throw new AuthenticationError($this->id . ' access token expired, call signIn() method');
                }
                $accessToken = $this->safe_string($this->options, 'accessToken');
                $headers = array(
                    'Authorization' => 'Bearer ' . $accessToken,
                );
                $url .= $this->implode_params($path, $params);
                if ($method === 'GET') {
                    if ($query) {
                        $url .= '?' . $this->urlencode($query);
                    }
                } elseif ($query) {
                    $body = $this->json($query);
                    $headers['Content-Type'] = 'application/json';
                }
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function sign_in($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://docs-en.probit.com/reference/token
             *
             * sign in, must be called prior to using other authenticated methods
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return $response from exchange
             */
            $this->check_required_credentials();
            $request = array(
                'grant_type' => 'client_credentials', // the only supported value
            );
            $response = Async\await($this->accountsPostToken ($this->extend($request, $params)));
            //
            //     {
            //         "access_token" => "0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=",
            //         "token_type" => "bearer",
            //         "expires_in" => 900
            //     }
            //
            $expiresIn = $this->safe_integer($response, 'expires_in');
            $accessToken = $this->safe_string($response, 'access_token');
            $this->options['accessToken'] = $accessToken;
            $this->options['expires'] = $this->sum($this->milliseconds(), $expiresIn * 1000);
            return $response;
        }) ();
    }

    public function handle_errors(int $code, 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('errorCode', $response)) {
            $errorCode = $this->safe_string($response, 'errorCode');
            if ($errorCode !== null) {
                $errMessage = $this->safe_string($response, 'message', '');
                $details = $this->safe_value($response, 'details');
                $feedback = $this->id . ' ' . $errorCode . ' ' . $errMessage . ' ' . $this->json($details);
                if (is_array($this->exceptions) && array_key_exists('exact', $this->exceptions)) {
                    $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
                }
                if (is_array($this->exceptions) && array_key_exists('broad', $this->exceptions)) {
                    $this->throw_broadly_matched_exception($this->exceptions['broad'], $errMessage, $feedback);
                }
                throw new ExchangeError($feedback);
            }
        }
        return null;
    }
}
