<?php

namespace ccxt;

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

use Exception; // a common import
use ccxt\abstract\bitbank as Exchange;

class bitbank extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'bitbank',
            'name' => 'bitbank',
            'countries' => array( 'JP' ),
            'version' => 'v1',
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => false,
                'borrowMargin' => false,
                'cancelAllOrders' => false,
                'cancelOrder' => true,
                'closeAllPositions' => false,
                'closePosition' => false,
                'createOrder' => true,
                'createOrderWithTakeProfitAndStopLoss' => false,
                'createOrderWithTakeProfitAndStopLossWs' => false,
                'createReduceOnlyOrder' => false,
                'fetchBalance' => true,
                'fetchBorrowInterest' => false,
                'fetchBorrowRate' => false,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchBorrowRates' => false,
                'fetchBorrowRatesPerSymbol' => false,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => false,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                '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,
                'fetchMarkOHLCV' => 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,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => true,
                'fetchTransfer' => false,
                'fetchTransfers' => false,
                'fetchVolatilityHistory' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1min',
                '5m' => '5min',
                '15m' => '15min',
                '30m' => '30min',
                '1h' => '1hour',
                '4h' => '4hour',
                '8h' => '8hour',
                '12h' => '12hour',
                '1d' => '1day',
                '1w' => '1week',
            ),
            'hostname' => 'bitbank.cc',
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/9d616de0-8a88-4468-8e38-d269acab0348',
                'api' => array(
                    'public' => 'https://public.{hostname}',
                    'private' => 'https://api.{hostname}',
                    'markets' => 'https://api.{hostname}',
                ),
                'www' => 'https://bitbank.cc/',
                'doc' => 'https://docs.bitbank.cc/',
                'fees' => 'https://bitbank.cc/docs/fees/',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        '{pair}/ticker',
                        'tickers',
                        'tickers_jpy',
                        '{pair}/depth',
                        '{pair}/transactions',
                        '{pair}/transactions/{yyyymmdd}',
                        '{pair}/candlestick/{candletype}/{yyyymmdd}',
                        '{pair}/circuit_break_info',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'user/assets',
                        'user/spot/order',
                        'user/spot/active_orders',
                        'user/margin/positions',
                        'user/spot/trade_history',
                        'user/deposit_history',
                        'user/unconfirmed_deposits',
                        'user/deposit_originators',
                        'user/withdrawal_account',
                        'user/withdrawal_history',
                        'spot/status',
                        'spot/pairs',
                    ),
                    'post' => array(
                        'user/spot/order',
                        'user/spot/cancel_order',
                        'user/spot/cancel_orders',
                        'user/spot/orders_info',
                        'user/confirm_deposits',
                        'user/confirm_deposits_all',
                        'user/request_withdrawal',
                    ),
                ),
                'markets' => array(
                    'get' => array(
                        'spot/pairs',
                    ),
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true, // todo implement
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => false,
                            'FOK' => false,
                            'PO' => true, // todo => implement
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyRequiresPrice' => false,
                        'marketBuyByCost' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => null,
                        'untilDays' => null,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => null,
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    '20001' => '\\ccxt\\AuthenticationError',
                    '20002' => '\\ccxt\\AuthenticationError',
                    '20003' => '\\ccxt\\AuthenticationError',
                    '20005' => '\\ccxt\\AuthenticationError',
                    '20004' => '\\ccxt\\InvalidNonce',
                    '40020' => '\\ccxt\\InvalidOrder',
                    '40021' => '\\ccxt\\InvalidOrder',
                    '40025' => '\\ccxt\\ExchangeError',
                    '40013' => '\\ccxt\\OrderNotFound',
                    '40014' => '\\ccxt\\OrderNotFound',
                    '50008' => '\\ccxt\\PermissionDenied',
                    '50009' => '\\ccxt\\OrderNotFound',
                    '50010' => '\\ccxt\\OrderNotFound',
                    '60001' => '\\ccxt\\InsufficientFunds',
                    '60005' => '\\ccxt\\InvalidOrder',
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves $data on all markets for bitbank
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#get-all-$pairs-info
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing market $data
         */
        $response = $this->marketsGetSpotPairs ($params);
        //
        //     {
        //       "success" => 1,
        //       "data" => {
        //         "pairs" => array(
        //           {
        //             "name" => "btc_jpy",
        //             "base_asset" => "btc",
        //             "quote_asset" => "jpy",
        //             "maker_fee_rate_base" => "0",
        //             "taker_fee_rate_base" => "0",
        //             "maker_fee_rate_quote" => "-0.0002",
        //             "taker_fee_rate_quote" => "0.0012",
        //             "unit_amount" => "0.0001",
        //             "limit_max_amount" => "1000",
        //             "market_max_amount" => "10",
        //             "market_allowance_rate" => "0.2",
        //             "price_digits" => 0,
        //             "amount_digits" => 4,
        //             "is_enabled" => true,
        //             "stop_order" => false,
        //             "stop_order_and_cancel" => false
        //           }
        //         )
        //       }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $pairs = $this->safe_value($data, 'pairs', array());
        return $this->parse_markets($pairs);
    }

    public function parse_market($entry): array {
        $id = $this->safe_string($entry, 'name');
        $baseId = $this->safe_string($entry, 'base_asset');
        $quoteId = $this->safe_string($entry, 'quote_asset');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        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' => $this->safe_value($entry, 'is_enabled'),
            'contract' => false,
            'linear' => null,
            'inverse' => null,
            'taker' => $this->safe_number($entry, 'taker_fee_rate_quote'),
            'maker' => $this->safe_number($entry, 'maker_fee_rate_quote'),
            'contractSize' => null,
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->parse_number($this->parse_precision($this->safe_string($entry, 'amount_digits'))),
                'price' => $this->parse_number($this->parse_precision($this->safe_string($entry, 'price_digits'))),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($entry, 'unit_amount'),
                    'max' => $this->safe_number($entry, 'limit_max_amount'),
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $entry,
        );
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        $symbol = $this->safe_symbol(null, $market);
        $timestamp = $this->safe_integer($ticker, 'timestamp');
        $last = $this->safe_string($ticker, 'last');
        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' => $this->safe_string($ticker, 'buy'),
            'bidVolume' => null,
            'ask' => $this->safe_string($ticker, 'sell'),
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'vol'),
            'quoteVolume' => null,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/public-api.md#ticker
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
        );
        $response = $this->publicGetPairTicker ($this->extend($request, $params));
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_ticker($data, $market);
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/public-api.md#depth
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the order book for
         * @param {int} [$limit] the maximum amount of order book entries to return
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
        );
        $response = $this->publicGetPairDepth ($this->extend($request, $params));
        $orderbook = $this->safe_value($response, 'data', array());
        $timestamp = $this->safe_integer($orderbook, 'timestamp');
        return $this->parse_order_book($orderbook, $market['symbol'], $timestamp);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades
        //
        //    {
        //        "transaction_id" => "1143247037",
        //        "side" => "buy",
        //        "price" => "3836025",
        //        "amount" => "0.0005",
        //        "executed_at" => "1694249441593"
        //    }
        //
        $timestamp = $this->safe_integer($trade, 'executed_at');
        $market = $this->safe_market(null, $market);
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'amount');
        $id = $this->safe_string_2($trade, 'transaction_id', 'trade_id');
        $takerOrMaker = $this->safe_string($trade, 'maker_taker');
        $fee = null;
        $feeCostString = $this->safe_string($trade, 'fee_amount_quote');
        if ($feeCostString !== null) {
            $fee = array(
                'currency' => $market['quote'],
                'cost' => $feeCostString,
            );
        }
        $orderId = $this->safe_string($trade, 'order_id');
        $type = $this->safe_string($trade, 'type');
        $side = $this->safe_string($trade, 'side');
        return $this->safe_trade(array(
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'id' => $id,
            'order' => $orderId,
            'type' => $type,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => $fee,
            'info' => $trade,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get the list of most recent $trades for a particular $symbol
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/public-api.md#transactions
         *
         * @param {string} $symbol unified $symbol of the $market to fetch $trades for
         * @param {int} [$since] timestamp in ms of the earliest trade to fetch
         * @param {int} [$limit] the maximum amount of $trades to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
        );
        $response = $this->publicGetPairTransactions ($this->extend($request, $params));
        $data = $this->safe_value($response, 'data', array());
        $trades = $this->safe_list($data, 'transactions', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         * fetch the trading fees for multiple markets
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#get-all-$pairs-info
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=fee-structure fee structures~ indexed by $market symbols
         */
        $this->load_markets();
        $response = $this->marketsGetSpotPairs ($params);
        //
        //     {
        //         "success" => "1",
        //         "data" => {
        //           "pairs" => array(
        //             array(
        //               "name" => "btc_jpy",
        //               "base_asset" => "btc",
        //               "quote_asset" => "jpy",
        //               "maker_fee_rate_base" => "0",
        //               "taker_fee_rate_base" => "0",
        //               "maker_fee_rate_quote" => "-0.0002",
        //               "taker_fee_rate_quote" => "0.0012",
        //               "unit_amount" => "0.0001",
        //               "limit_max_amount" => "1000",
        //               "market_max_amount" => "10",
        //               "market_allowance_rate" => "0.2",
        //               "price_digits" => "0",
        //               "amount_digits" => "4",
        //               "is_enabled" => true,
        //               "stop_order" => false,
        //               "stop_order_and_cancel" => false
        //             ),
        //             ...
        //           )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $pairs = $this->safe_value($data, 'pairs', array());
        $result = array();
        for ($i = 0; $i < count($pairs); $i++) {
            $pair = $pairs[$i];
            $marketId = $this->safe_string($pair, 'name');
            $market = $this->safe_market($marketId);
            $symbol = $market['symbol'];
            $result[$symbol] = array(
                'info' => $pair,
                'symbol' => $symbol,
                'maker' => $this->safe_number($pair, 'maker_fee_rate_quote'),
                'taker' => $this->safe_number($pair, 'taker_fee_rate_quote'),
                'percentage' => true,
                'tierBased' => false,
            );
        }
        return $result;
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        //     array(
        //         "0.02501786",
        //         "0.02501786",
        //         "0.02501786",
        //         "0.02501786",
        //         "0.0000",
        //         1591488000000
        //     )
        //
        return array(
            $this->safe_integer($ohlcv, 5),
            $this->safe_number($ohlcv, 0),
            $this->safe_number($ohlcv, 1),
            $this->safe_number($ohlcv, 2),
            $this->safe_number($ohlcv, 3),
            $this->safe_number($ohlcv, 4),
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical $candlestick $data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/public-api.md#$candlestick
         *
         * @param {string} $symbol unified $symbol of the $market to fetch OHLCV $data for
         * @param {string} $timeframe the length of time each candle represents
         * @param {int} [$since] timestamp in ms of the earliest candle to fetch
         * @param {int} [$limit] the maximum amount of candles to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        if ($since === null) {
            if ($limit === null) {
                $limit = 1000; // it doesn't have any defaults, might return 200, might 2000 (i.e. https://public.bitbank.cc/btc_jpy/candlestick/4hour/2020)
            }
            $duration = $this->parse_timeframe($timeframe);
            $since = $this->milliseconds() - $duration * 1000 * $limit;
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
            'candletype' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
            'yyyymmdd' => $this->yyyymmdd($since, ''),
        );
        $response = $this->publicGetPairCandlestickCandletypeYyyymmdd ($this->extend($request, $params));
        //
        //     {
        //         "success":1,
        //         "data":{
        //             "candlestick":[
        //                 {
        //                     "type":"5min",
        //                     "ohlcv":[
        //                         ["0.02501786","0.02501786","0.02501786","0.02501786","0.0000",1591488000000],
        //                         ["0.02501747","0.02501953","0.02501747","0.02501953","0.3017",1591488300000],
        //                         ["0.02501762","0.02501762","0.02500392","0.02500392","0.1500",1591488600000],
        //                     ]
        //                 }
        //             ],
        //             "timestamp":1591508668190
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $candlestick = $this->safe_value($data, 'candlestick', array());
        $first = $this->safe_value($candlestick, 0, array());
        $ohlcv = $this->safe_list($first, 'ohlcv', array());
        return $this->parse_ohlcvs($ohlcv, $market, $timeframe, $since, $limit);
    }

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

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#assets
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $response = $this->privateGetUserAssets ($params);
        //
        //     {
        //       "success" => "1",
        //       "data" => {
        //         "assets" => array(
        //           {
        //             "asset" => "jpy",
        //             "amount_precision" => "4",
        //             "onhand_amount" => "0.0000",
        //             "locked_amount" => "0.0000",
        //             "free_amount" => "0.0000",
        //             "stop_deposit" => false,
        //             "stop_withdrawal" => false,
        //             "withdrawal_fee" => array(
        //               "threshold" => "30000.0000",
        //               "under" => "550.0000",
        //               "over" => "770.0000"
        //             }
        //           ),
        //           array(
        //             "asset" => "btc",
        //             "amount_precision" => "8",
        //             "onhand_amount" => "0.00000000",
        //             "locked_amount" => "0.00000000",
        //             "free_amount" => "0.00000000",
        //             "stop_deposit" => false,
        //             "stop_withdrawal" => false,
        //             "withdrawal_fee" => "0.00060000"
        //           ),
        //         )
        //       }
        //     }
        //
        return $this->parse_balance($response);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'UNFILLED' => 'open',
            'PARTIALLY_FILLED' => 'open',
            'FULLY_FILLED' => 'closed',
            'CANCELED_UNFILLED' => 'canceled',
            'CANCELED_PARTIALLY_FILLED' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        $id = $this->safe_string($order, 'order_id');
        $marketId = $this->safe_string($order, 'pair');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer($order, 'ordered_at');
        $price = $this->safe_string($order, 'price');
        $amount = $this->safe_string($order, 'start_amount');
        $filled = $this->safe_string($order, 'executed_amount');
        $remaining = $this->safe_string($order, 'remaining_amount');
        $average = $this->safe_string($order, 'average_price');
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $type = $this->safe_string_lower($order, 'type');
        $side = $this->safe_string_lower($order, 'side');
        return $this->safe_order(array(
            'id' => $id,
            'clientOrderId' => null,
            'datetime' => $this->iso8601($timestamp),
            'timestamp' => $timestamp,
            'lastTradeTimestamp' => null,
            'status' => $status,
            'symbol' => $market['symbol'],
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'triggerPrice' => null,
            'cost' => null,
            'average' => $average,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'trades' => null,
            'fee' => null,
            'info' => $order,
        ), $market);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#create-new-order
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
            'amount' => $this->amount_to_precision($symbol, $amount),
            'side' => $side,
            'type' => $type,
        );
        if ($type === 'limit') {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $response = $this->privatePostUserSpotOrder ($this->extend($request, $params));
        $data = $this->safe_dict($response, 'data');
        return $this->parse_order($data, $market);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#cancel-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~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'order_id' => $id,
            'pair' => $market['id'],
        );
        $response = $this->privatePostUserSpotCancelOrder ($this->extend($request, $params));
        //
        //    {
        //        "success" => 1,
        //        "data" => {
        //            "order_id" => 0,
        //            "pair" => "string",
        //            "side" => "string",
        //            "type" => "string",
        //            "start_amount" => "string",
        //            "remaining_amount" => "string",
        //            "executed_amount" => "string",
        //            "price" => "string",
        //            "post_only" => false,
        //            "average_price" => "string",
        //            "ordered_at" => 0,
        //            "expire_at" => 0,
        //            "canceled_at" => 0,
        //            "triggered_at" => 0,
        //            "trigger_price" => "string",
        //            "status" => "string"
        //        }
        //    }
        //
        $data = $this->safe_value($response, 'data');
        return $this->parse_order($data);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#fetch-order-information
         *
         * @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~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'order_id' => $id,
            'pair' => $market['id'],
        );
        $response = $this->privateGetUserSpotOrder ($this->extend($request, $params));
        //
        //    {
        //        "success" => 1,
        //        "data" => {
        //          "order_id" => 0,
        //          "pair" => "string",
        //          "side" => "string",
        //          "type" => "string",
        //          "start_amount" => "string",
        //          "remaining_amount" => "string",
        //          "executed_amount" => "string",
        //          "price" => "string",
        //          "post_only" => false,
        //          "average_price" => "string",
        //          "ordered_at" => 0,
        //          "expire_at" => 0,
        //          "triggered_at" => 0,
        //          "triger_price" => "string",
        //          "status" => "string"
        //        }
        //    }
        //
        $data = $this->safe_dict($response, 'data');
        return $this->parse_order($data, $market);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all unfilled currently open $orders
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#fetch-active-$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~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'pair' => $market['id'],
        );
        if ($limit !== null) {
            $request['count'] = $limit;
        }
        if ($since !== null) {
            $request['since'] = $this->parse_to_int($since / 1000);
        }
        $response = $this->privateGetUserSpotActiveOrders ($this->extend($request, $params));
        $data = $this->safe_value($response, 'data', array());
        $orders = $this->safe_list($data, 'orders', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all $trades made by the user
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#fetch-trade-history
         *
         * @param {string} $symbol unified $market $symbol
         * @param {int} [$since] the earliest time in ms to fetch $trades for
         * @param {int} [$limit] the maximum number of $trades structures to retrieve
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['pair'] = $market['id'];
        }
        if ($limit !== null) {
            $request['count'] = $limit;
        }
        if ($since !== null) {
            $request['since'] = $this->parse_to_int($since / 1000);
        }
        $response = $this->privateGetUserSpotTradeHistory ($this->extend($request, $params));
        $data = $this->safe_value($response, 'data', array());
        $trades = $this->safe_list($data, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit $address for a $currency associated with this account
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#get-withdrawal-$accounts
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'asset' => $currency['id'],
        );
        $response = $this->privateGetUserWithdrawalAccount ($this->extend($request, $params));
        $data = $this->safe_value($response, 'data', array());
        // Not sure about this if there could be more than one account...
        $accounts = $this->safe_value($data, 'accounts', array());
        $firstAccount = $this->safe_value($accounts, 0, array());
        $address = $this->safe_string($firstAccount, 'address');
        return array(
            'info' => $response,
            'currency' => $currency['code'],
            'network' => null,
            'address' => $address,
            'tag' => null,
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://github.com/bitbankinc/bitbank-api-docs/blob/38d6d7c6f486c793872fd4b4087a0d090a04cd0a/rest-api.md#new-withdrawal-$request
         *
         * @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);
        if (!(is_array($params) && array_key_exists('uuid', $params))) {
            throw new ExchangeError($this->id . ' uuid is required for withdrawal');
        }
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'asset' => $currency['id'],
            'amount' => $amount,
        );
        $response = $this->privatePostUserRequestWithdrawal ($this->extend($request, $params));
        //
        //     {
        //         "success" => 1,
        //         "data" => {
        //             "uuid" => "string",
        //             "asset" => "btc",
        //             "amount" => 0,
        //             "account_uuid" => "string",
        //             "fee" => 0,
        //             "status" => "DONE",
        //             "label" => "string",
        //             "txid" => "string",
        //             "address" => "string",
        //             "requested_at" => 0
        //         }
        //     }
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_transaction($data, $currency);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // withdraw
        //
        //     {
        //         "uuid" => "string",
        //         "asset" => "btc",
        //         "amount" => 0,
        //         "account_uuid" => "string",
        //         "fee" => 0,
        //         "status" => "DONE",
        //         "label" => "string",
        //         "txid" => "string",
        //         "address" => "string",
        //         "requested_at" => 0
        //     }
        //
        $txid = $this->safe_string($transaction, 'txid');
        $currency = $this->safe_currency(null, $currency);
        return array(
            'id' => $txid,
            'txid' => $txid,
            'timestamp' => null,
            'datetime' => null,
            'network' => null,
            'addressFrom' => null,
            'address' => null,
            'addressTo' => null,
            'amount' => null,
            'type' => null,
            'currency' => $currency['code'],
            'status' => null,
            'updated' => null,
            'tagFrom' => null,
            'tag' => null,
            'tagTo' => null,
            'comment' => null,
            'internal' => null,
            'fee' => null,
            'info' => $transaction,
        );
    }

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

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $query = $this->omit($params, $this->extract_params($path));
        $url = $this->implode_hostname($this->urls['api'][$api]) . '/';
        if (($api === 'public') || ($api === 'markets')) {
            $url .= $this->implode_params($path, $params);
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } else {
            $this->check_required_credentials();
            $nonce = (string) $this->nonce();
            $auth = $nonce;
            $url .= $this->version . '/' . $this->implode_params($path, $params);
            if ($method === 'POST') {
                $body = $this->json($query);
                $auth .= $body;
            } else {
                $auth .= '/' . $this->version . '/' . $path;
                if ($query) {
                    $query = $this->urlencode($query);
                    $url .= '?' . $query;
                    $auth .= '?' . $query;
                }
            }
            $headers = array(
                'Content-Type' => 'application/json',
                'ACCESS-KEY' => $this->apiKey,
                'ACCESS-NONCE' => $nonce,
                'ACCESS-SIGNATURE' => $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256'),
            );
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $httpCode, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return null;
        }
        $success = $this->safe_integer($response, 'success');
        $data = $this->safe_value($response, 'data');
        if (!$success || !$data) {
            $errorMessages = array(
                '10000' => 'URL does not exist',
                '10001' => 'A system error occurred. Please contact support',
                '10002' => 'Invalid JSON format. Please check the contents of transmission',
                '10003' => 'A system error occurred. Please contact support',
                '10005' => 'A timeout error occurred. Please wait for a while and try again',
                '20001' => 'API authentication failed',
                '20002' => 'Illegal API key',
                '20003' => 'API key does not exist',
                '20004' => 'API Nonce does not exist',
                '20005' => 'API signature does not exist',
                '20011' => 'Two-step verification failed',
                '20014' => 'SMS authentication failed',
                '30001' => 'Please specify the order quantity',
                '30006' => 'Please specify the order ID',
                '30007' => 'Please specify the order ID array',
                '30009' => 'Please specify the stock',
                '30012' => 'Please specify the order price',
                '30013' => 'Trade Please specify either',
                '30015' => 'Please specify the order type',
                '30016' => 'Please specify asset name',
                '30019' => 'Please specify uuid',
                '30039' => 'Please specify the amount to be withdrawn',
                '40001' => 'The order quantity is invalid',
                '40006' => 'Count value is invalid',
                '40007' => 'End time is invalid',
                '40008' => 'end_id Value is invalid',
                '40009' => 'The from_id value is invalid',
                '40013' => 'The order ID is invalid',
                '40014' => 'The order ID array is invalid',
                '40015' => 'Too many specified orders',
                '40017' => 'Incorrect issue name',
                '40020' => 'The order price is invalid',
                '40021' => 'The trading classification is invalid',
                '40022' => 'Start date is invalid',
                '40024' => 'The order type is invalid',
                '40025' => 'Incorrect asset name',
                '40028' => 'uuid is invalid',
                '40048' => 'The amount of withdrawal is illegal',
                '50003' => 'Currently, this account is in a state where you can not perform the operation you specified. Please contact support',
                '50004' => 'Currently, this account is temporarily registered. Please try again after registering your account',
                '50005' => 'Currently, this account is locked. Please contact support',
                '50006' => 'Currently, this account is locked. Please contact support',
                '50008' => 'User identification has not been completed',
                '50009' => 'Your order does not exist',
                '50010' => 'Can not cancel specified order',
                '50011' => 'API not found',
                '60001' => 'The number of possessions is insufficient',
                '60002' => 'It exceeds the quantity upper limit of the tender buying order',
                '60003' => 'The specified quantity exceeds the limit',
                '60004' => 'The specified quantity is below the threshold',
                '60005' => 'The specified price is above the limit',
                '60006' => 'The specified price is below the lower limit',
                '70001' => 'A system error occurred. Please contact support',
                '70002' => 'A system error occurred. Please contact support',
                '70003' => 'A system error occurred. Please contact support',
                '70004' => 'We are unable to accept orders transaction is currently suspended',
                '70005' => 'Order can not be accepted because purchase order is currently suspended',
                '70006' => 'We can not accept orders because we are currently unsubscribed ',
                '70009' => 'We are currently temporarily restricting orders to be carried out. Please use the limit order.',
                '70010' => 'We are temporarily raising the minimum order quantity system load is now rising.',
            );
            $code = $this->safe_string($data, 'code');
            $message = $this->safe_string($errorMessages, $code, 'Error');
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $code, $message);
            throw new ExchangeError($this->id . ' ' . $this->json($response));
        }
        return null;
    }
}
