<?php

namespace ccxt;

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

use Exception; // a common import
use \ccxt\ExchangeError;
use \ccxt\BadRequest;
use \ccxt\AddressPending;
use \ccxt\InvalidOrder;

class vcc extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'vcc',
            'name' => 'VCC Exchange',
            'countries' => array( 'VN' ), // Vietnam
            'rateLimit' => 1000,
            'version' => 'v3',
            'has' => array(
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'createOrder' => true,
                'editOrder' => false,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchTicker' => 'emulated',
                'fetchTickers' => true,
                'fetchTrades' => true,
                'fetchTradingFees' => false,
                'fetchTransactions' => true,
                'fetchWithdrawals' => true,
            ),
            'timeframes' => array(
                '1m' => '60000',
                '5m' => '300000',
                '15m' => '900000',
                '30m' => '1800000',
                '1h' => '3600000',
                '2h' => '7200000',
                '4h' => '14400000',
                '6h' => '21600000',
                '12h' => '43200000',
                '1d' => '86400000',
                '1w' => '604800000',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/100545356-8427f500-326c-11eb-9539-7d338242d61b.jpg',
                'api' => array(
                    'public' => 'https://api.vcc.exchange',
                    'private' => 'https://api.vcc.exchange',
                ),
                'www' => 'https://vcc.exchange',
                'doc' => array(
                    'https://vcc.exchange/api',
                ),
                'fees' => 'https://support.vcc.exchange/hc/en-us/articles/360016401754',
                'referral' => 'https://vcc.exchange?ref=l4xhrH',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'summary',
                        'exchange_info',
                        'assets', // Available Currencies
                        'ticker', // Ticker list for all symbols
                        'trades/{market_pair}', // Recent trades
                        'orderbook/{market_pair}', // Orderbook
                        'chart/bars', // Candles
                        'tick_sizes',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'user',
                        'balance', // Get trading balance
                        'orders/{order_id}', // Get a single order by order_id
                        'orders/open', // Get open orders
                        'orders', // Get closed orders
                        'orders/trades', // Get trades history
                        'deposit-address', // Generate or get deposit address
                        'transactions', // Get deposit/withdrawal history
                    ),
                    'post' => array(
                        'orders', // Create new order
                    ),
                    'put' => array(
                        'orders/{order_id}/cancel', // Cancel order
                        'orders/cancel-by-type',
                        'orders/cancel-all',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => false,
                    'percentage' => true,
                    'maker' => 0.2 / 100,
                    'taker' => 0.2 / 100,
                ),
            ),
            'exceptions' => array(
                'exact' => array(),
                'broad' => array(
                    'limit may not be greater than' => '\\ccxt\\BadRequest', // array("message":"The given data was invalid.","errors":array("limit":["The limit may not be greater than 1000."]))
                    'Insufficient balance' => '\\ccxt\\InsufficientFunds', // array("message":"Insufficient balance.")
                    'Unauthenticated' => '\\ccxt\\AuthenticationError', // array("message":"Unauthenticated.") // wrong api key
                    'signature is invalid' => '\\ccxt\\AuthenticationError', // array("message":"The given data was invalid.","errors":array("signature":["HMAC signature is invalid"]))
                    'Timeout' => '\\ccxt\\RequestTimeout', // array("code":504,"message":"Gateway Timeout","description":"")
                    'Too many requests' => '\\ccxt\\RateLimitExceeded', // array("code":429,"message":"Too many requests","description":"Too many requests")
                    'quantity field is required' => '\\ccxt\\InvalidOrder', // array("message":"The given data was invalid.","errors":array("quantity":["The quantity field is required when type is market."]))
                    'price field is required' => '\\ccxt\\InvalidOrder',  // array("message":"The given data was invalid.","errors":array("price":["The price field is required when type is limit."]))
                    'error_security_level' => '\\ccxt\\PermissionDenied', // array("message":"error_security_level")
                    'pair is invalid' => '\\ccxt\\BadSymbol', // array("message":"The given data was invalid.","errors":array("coin":["Trading pair is invalid","Trading pair is offline"]))
                    // array("message":"The given data was invalid.","errors":array("type":["The selected type is invalid."]))
                    // array("message":"The given data was invalid.","errors":array("trade_type":["The selected trade type is invalid."]))
                    'type is invalid' => '\\ccxt\\InvalidOrder',
                    'Data not found' => '\\ccxt\\OrderNotFound', // array("message":"Data not found")
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()) {
        $response = $this->publicGetExchangeInfo ($params);
        //
        //     {
        //         "message":null,
        //         "dataVersion":"4677e56a42f0c29872f3a6e75f5d39d2f07c748c",
        //         "$data":array(
        //             "timezone":"UTC",
        //             "serverTime":1605821914333,
        //             "symbols":array(
        //                 array(
        //                     "$id":"btcvnd",
        //                     "$symbol":"BTC\/VND",
        //                     "coin":"btc",
        //                     "currency":"vnd",
        //                     "$baseId":1,
        //                     "$quoteId":0,
        //                     "$active":true,
        //                     "base_precision":"0.0000010000",
        //                     "quote_precision":"1.0000000000",
        //                     "minimum_quantity":"0.0000010000",
        //                     "minimum_amount":"250000.0000000000",
        //                     "$precision":array("price":0,"amount":6,"cost":6),
        //                     "$limits":array(
        //                         "amount":array("min":"0.0000010000"),
        //                         "price":array("min":"1.0000000000"),
        //                         "cost":array("min":"250000.0000000000"),
        //                     ),
        //                 ),
        //             ),
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $markets = $this->safe_value($data, 'symbols');
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $this->safe_value($markets, $i);
            $symbol = $this->safe_string($market, 'symbol');
            $id = str_replace('/', '_', $symbol);
            $baseId = $this->safe_string($market, 'coin');
            $quoteId = $this->safe_string($market, 'currency');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $active = $this->safe_value($market, 'active');
            $precision = $this->safe_value($market, 'precision', array());
            $limits = $this->safe_value($market, 'limits', array());
            $amountLimits = $this->safe_value($limits, 'amount', array());
            $priceLimits = $this->safe_value($limits, 'price', array());
            $costLimits = $this->safe_value($limits, 'cost', array());
            $entry = array(
                'info' => $market,
                'id' => $id,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'active' => $active,
                'precision' => array(
                    'price' => $this->safe_integer($precision, 'price'),
                    'amount' => $this->safe_integer($precision, 'amount'),
                    'cost' => $this->safe_integer($precision, 'cost'),
                ),
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($amountLimits, 'min'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => $this->safe_float($priceLimits, 'min'),
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => $this->safe_float($costLimits, 'min'),
                        'max' => null,
                    ),
                ),
            );
            $result[] = $entry;
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $response = $this->publicGetAssets ($params);
        //
        //     {
        //         "message":null,
        //         "dataVersion":"2514c8012d94ea375018fc13e0b5d4d896e435df",
        //         "$data":array(
        //             "BTC":array(
        //                 "name":"Bitcoin",
        //                 "unified_cryptoasset_id":1,
        //                 "can_withdraw":1,
        //                 "can_deposit":1,
        //                 "min_withdraw":"0.0011250000",
        //                 "max_withdraw":"100.0000000000",
        //                 "maker_fee":"0.002",
        //                 "taker_fee":"0.002",
        //                 "decimal":8,
        //                 "withdrawal_fee":"0.0006250000",
        //             ),
        //         ),
        //     }
        //
        $result = array();
        $data = $this->safe_value($response, 'data');
        $ids = is_array($data) ? array_keys($data) : array();
        for ($i = 0; $i < count($ids); $i++) {
            $id = $this->safe_string_lower($ids, $i);
            $currency = $this->safe_value($data, $ids[$i]);
            $code = $this->safe_currency_code($id);
            $canDeposit = $this->safe_value($currency, 'can_deposit');
            $canWithdraw = $this->safe_value($currency, 'can_withdraw');
            $active = ($canDeposit && $canWithdraw);
            $result[$code] = array(
                'id' => $id,
                'code' => $code,
                'name' => $this->safe_string($currency, 'name'),
                'active' => $active,
                'fee' => $this->safe_float($currency, 'withdrawal_fee'),
                'precision' => $this->safe_integer($currency, 'decimal'),
                'limits' => array(
                    'withdraw' => array(
                        'min' => $this->safe_float($currency, 'min_withdraw'),
                        'max' => $this->safe_float($currency, 'max_withdraw'),
                    ),
                ),
            );
        }
        return $result;
    }

    public function fetch_trading_fee($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array_merge(array(
            'symbol' => $market['id'],
        ), $this->omit($params, 'symbol'));
        $response = $this->privateGetTradingFeeSymbol ($request);
        //
        //     {
        //         takeLiquidityRate => '0.001',
        //         provideLiquidityRate => '-0.0001'
        //     }
        //
        return array(
            'info' => $response,
            'maker' => $this->safe_float($response, 'provideLiquidityRate'),
            'taker' => $this->safe_float($response, 'takeLiquidityRate'),
        );
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $response = $this->privateGetBalance ($params);
        //
        //     {
        //         "message":null,
        //         "dataVersion":"7168e6c99e90f60673070944d987988eef7d91fa",
        //         "$data":array(
        //             "vnd":array("$balance":0,"available_balance":0),
        //             "btc":array("$balance":0,"available_balance":0),
        //             "eth":array("$balance":0,"available_balance":0),
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $result = array( 'info' => $response );
        $currencyIds = is_array($data) ? array_keys($data) : array();
        for ($i = 0; $i < count($currencyIds); $i++) {
            $currencyId = $currencyIds[$i];
            $code = $this->safe_currency_code($currencyId);
            $balance = $this->safe_value($data, $currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_float($balance, 'available_balance');
            $account['total'] = $this->safe_float($balance, 'balance');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         "low":"415805323.0000000000",
        //         "high":"415805323.0000000000",
        //         "open":"415805323.0000000000",
        //         "close":"415805323.0000000000",
        //         "time":"1605845940000",
        //         "volume":"0.0065930000",
        //         "opening_time":1605845963263,
        //         "closing_time":1605845963263
        //     }
        //
        return array(
            $this->safe_integer($ohlcv, 'time'),
            $this->safe_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float($ohlcv, 'volume'),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'coin' => $market['baseId'],
            'currency' => $market['quoteId'],
            'resolution' => $this->timeframes[$timeframe],
        );
        $limit = ($limit === null) ? 100 : $limit;
        $limit = min (100, $limit);
        $duration = $this->parse_timeframe($timeframe);
        if ($since === null) {
            $end = $this->seconds();
            $request['to'] = $end;
            $request['from'] = $end - $limit * $duration;
        } else {
            $start = intval($since / 1000);
            $request['from'] = $start;
            $request['to'] = $this->sum($start, $limit * $duration);
        }
        $response = $this->publicGetChartBars (array_merge($request, $params));
        //
        //     array(
        //         array("low":"415805323.0000000000","high":"415805323.0000000000","open":"415805323.0000000000","close":"415805323.0000000000","time":"1605845940000","volume":"0.0065930000","opening_time":1605845963263,"closing_time":1605845963263),
        //         array("low":"416344148.0000000000","high":"416344148.0000000000","open":"415805323.0000000000","close":"416344148.0000000000","time":"1605846000000","volume":"0.0052810000","opening_time":1605846011490,"closing_time":1605846011490),
        //         array("low":"416299269.0000000000","high":"417278376.0000000000","open":"416344148.0000000000","close":"417278376.0000000000","time":"1605846060000","volume":"0.0136750000","opening_time":1605846070727,"closing_time":1605846102282),
        //     )
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_pair' => $market['id'],
            // 'depth' => 0, // 0 = full orderbook, 5, 10, 20, 50, 100, 500
            'level' => 2, // 1 = best bidask, 2 = aggregated by price, 3 = no aggregation
        );
        if ($limit !== null) {
            if (($limit !== 0) && ($limit !== 5) && ($limit !== 10) && ($limit !== 20) && ($limit !== 50) && ($limit !== 100) && ($limit !== 500)) {
                throw new BadRequest($this->id . ' fetchOrderBook $limit must be 0, 5, 10, 20, 50, 100, 500 if specified');
            }
            $request['depth'] = $limit;
        }
        $response = $this->publicGetOrderbookMarketPair (array_merge($request, $params));
        //
        //     {
        //         "message":null,
        //         "dataVersion":"376cee43af26deabcd3762ab11a876b6e7a71e82",
        //         "$data":{
        //             "bids":[
        //                 ["413342637.0000000000","0.165089"],
        //                 ["413274576.0000000000","0.03"],
        //                 ["413274574.0000000000","0.03"],
        //             ],
        //             "asks":[
        //                 ["416979125.0000000000","0.122835"],
        //                 ["417248934.0000000000","0.030006"],
        //                 ["417458879.0000000000","0.1517"],
        //             ],
        //             "$timestamp":"1605841619147"
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $timestamp = $this->safe_value($data, 'timestamp');
        return $this->parse_order_book($data, $timestamp, 'bids', 'asks', 0, 1);
    }

    public function parse_ticker($ticker, $market = null) {
        //
        //     {
        //         "base_id":1,
        //         "quote_id":0,
        //         "last_price":"411119457",
        //         "max_price":"419893173.0000000000",
        //         "min_price":"401292577.0000000000",
        //         "open_price":null,
        //         "base_volume":"10.5915050000",
        //         "quote_volume":"4367495977.4484430060",
        //         "isFrozen":0
        //     }
        //
        $timestamp = $this->milliseconds();
        $baseVolume = $this->safe_float($ticker, 'base_volume');
        $quoteVolume = $this->safe_float($ticker, 'quote_volume');
        $open = $this->safe_float($ticker, 'open_price');
        $last = $this->safe_float($ticker, 'last_price');
        $change = null;
        $percentage = null;
        $average = null;
        if ($last !== null && $open !== null) {
            $change = $last - $open;
            $average = $this->sum($last, $open) / 2;
            if ($open > 0) {
                $percentage = $change / $open * 100;
            }
        }
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        $symbol = ($market === null) ? null : $market['symbol'];
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'max_price'),
            'low' => $this->safe_float($ticker, 'min_price'),
            'bid' => $this->safe_float($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_float($ticker, 'ask'),
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $response = $this->publicGetTicker ($params);
        //
        //     {
        //         "message":null,
        //         "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e",
        //         "$data":{
        //             "BTC_VND":array(
        //                 "base_id":1,
        //                 "quote_id":0,
        //                 "last_price":"411119457",
        //                 "max_price":"419893173.0000000000",
        //                 "min_price":"401292577.0000000000",
        //                 "open_price":null,
        //                 "base_volume":"10.5915050000",
        //                 "quote_volume":"4367495977.4484430060",
        //                 "isFrozen":0
        //             ),
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $ticker = $this->safe_value($data, $market['id']);
        return $this->parse_ticker($ticker, $market);
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $response = $this->publicGetTicker ($params);
        //
        //     {
        //         "message":null,
        //         "dataVersion":"fc521161aebe506178b8588cd2adb598eaf1018e",
        //         "$data":{
        //             "BTC_VND":array(
        //                 "base_id":1,
        //                 "quote_id":0,
        //                 "last_price":"411119457",
        //                 "max_price":"419893173.0000000000",
        //                 "min_price":"401292577.0000000000",
        //                 "open_price":null,
        //                 "base_volume":"10.5915050000",
        //                 "quote_volume":"4367495977.4484430060",
        //                 "isFrozen":0
        //             ),
        //         }
        //     }
        //
        $result = array();
        $data = $this->safe_value($response, 'data');
        $marketIds = is_array($data) ? array_keys($data) : array();
        for ($i = 0; $i < count($marketIds); $i++) {
            $marketId = $marketIds[$i];
            $market = $this->safe_market($marketId, null, '_');
            $symbol = $market['symbol'];
            $result[$symbol] = $this->parse_ticker($data[$marketId], $market);
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function parse_trade($trade, $market = null) {
        //
        // public fetchTrades
        //
        //     {
        //         "trade_id":181509285,
        //         "$price":"415933022.0000000000",
        //         "base_volume":"0.0022080000",
        //         "quote_volume":"918380.1125760000",
        //         "trade_timestamp":1605842150357,
        //         "type":"buy",
        //     }
        //
        // private fetchMyTrades
        //
        //     {
        //         "trade_type":"sell",
        //         "$fee":"0.0610578086",
        //         "$id":1483372,
        //         "created_at":1606581578368,
        //         "currency":"usdt",
        //         "coin":"btc",
        //         "$price":"17667.1900000000",
        //         "quantity":"0.0017280000",
        //         "$amount":"30.5289043200",
        //     }
        //
        $timestamp = $this->safe_integer_2($trade, 'trade_timestamp', 'created_at');
        $baseId = $this->safe_string_upper($trade, 'coin');
        $quoteId = $this->safe_string_upper($trade, 'currency');
        $marketId = null;
        if (($baseId !== null) && ($quoteId !== null)) {
            $marketId = $baseId . '_' . $quoteId;
        }
        $market = $this->safe_market($marketId, $market, '_');
        $symbol = $market['symbol'];
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float_2($trade, 'base_volume', 'quantity');
        $cost = $this->safe_float_2($trade, 'quote_volume', 'amount');
        if ($cost === null) {
            if (($price !== null) && ($amount !== null)) {
                $cost = $price * $amount;
            }
        }
        $side = $this->safe_string_2($trade, 'type', 'trade_type');
        $id = $this->safe_string_2($trade, 'trade_id', 'id');
        $feeCost = $this->safe_float($trade, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $market['quote'],
            );
        }
        return array(
            'info' => $trade,
            'id' => $id,
            'order' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_pair' => $market['id'],
            // 'type' => 'buy', // 'sell'
            // 'count' => $limit, // default 500, max 1000
        );
        if ($limit !== null) {
            $request['count'] = min (1000, $limit);
        }
        $response = $this->publicGetTradesMarketPair (array_merge($request, $params));
        //
        //     {
        //         "message":null,
        //         "dataVersion":"1f811b533143f739008a3e4ecaaab2ec82ea50d4",
        //         "$data":array(
        //             array(
        //                 "trade_id":181509285,
        //                 "price":"415933022.0000000000",
        //                 "base_volume":"0.0022080000",
        //                 "quote_volume":"918380.1125760000",
        //                 "trade_timestamp":1605842150357,
        //                 "type":"buy",
        //             ),
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data');
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_transactions($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'type' => type, // 'deposit', 'withdraw'
            // 'start' => intval($since / 1000),
            // 'end' => $this->seconds(),
        );
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['currency'] = $currency['id'];
        }
        if ($limit !== null) {
            $request['limit'] = min (1000, $limit);
        }
        if ($since !== null) {
            $request['start'] = $since;
        }
        $response = $this->privateGetTransactions (array_merge($request, $params));
        //
        //     {
        //         "message":null,
        //         "dataVersion":"1fdfb0ec85b666871d62fe59d098d01839b05e97",
        //         "$data":{
        //             "current_page":1,
        //             "$data":array(
        //                 {
        //                     "id":85391,
        //                     "user_id":253063,
        //                     "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131",
        //                     "internal_transaction_id":null,
        //                     "temp_transaction_id":"2367",
        //                     "$currency":"usdt",
        //                     "amount":"30.0000000000",
        //                     "btc_amount":"0.0000000000",
        //                     "usdt_amount":"0.0000000000",
        //                     "fee":"0.0000000000",
        //                     "tx_cost":"0.0000000000",
        //                     "confirmation":0,
        //                     "deposit_code":null,
        //                     "status":"success",
        //                     "bank_name":null,
        //                     "foreign_bank_account":null,
        //                     "foreign_bank_account_holder":null,
        //                     "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44",
        //                     "destination_tag":null,
        //                     "error_detail":null,
        //                     "refunded":"0.0000000000",
        //                     "transaction_date":"2020-11-28",
        //                     "transaction_timestamp":"1606563143.959",
        //                     "created_at":1606563143959,
        //                     "updated_at":1606563143959,
        //                     "transaction_email_timestamp":0,
        //                     "network":null,
        //                     "collect_tx_id":null,
        //                     "collect_id":null
        //                 }
        //             ),
        //             "first_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1",
        //             "from":1,
        //             "last_page":1,
        //             "last_page_url":"http:\/\/api.vcc.exchange\/v3\/transactions?page=1",
        //             "next_page_url":null,
        //             "path":"http:\/\/api.vcc.exchange\/v3\/transactions",
        //             "per_page":10,
        //             "prev_page_url":null,
        //             "to":1,
        //             "total":1
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $data = $this->safe_value($data, 'data', array());
        return $this->parse_transactions($data, $currency, $since, $limit);
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        $request = array( 'type' => 'deposit' );
        return $this->fetch_transactions($code, $since, $limit, array_merge($request, $params));
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        $request = array( 'type' => 'withdraw' );
        return $this->fetch_transactions($code, $since, $limit, array_merge($request, $params));
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // fetchTransactions, fetchDeposits, fetchWithdrawals
        //
        //     {
        //         "$id":85391,
        //         "user_id":253063,
        //         "transaction_id":"0x885719cee5910ca509a223d208797510e80eb27a2f1d51a71bb4ccb82d538131",
        //         "internal_transaction_id":null,
        //         "temp_transaction_id":"2367",
        //         "$currency":"usdt",
        //         "$amount":"30.0000000000",
        //         "btc_amount":"0.0000000000",
        //         "usdt_amount":"0.0000000000",
        //         "$fee":"0.0000000000",
        //         "tx_cost":"0.0000000000",
        //         "confirmation":0,
        //         "deposit_code":null,
        //         "$status":"success",
        //         "bank_name":null,
        //         "foreign_bank_account":null,
        //         "foreign_bank_account_holder":null,
        //         "blockchain_address":"0xd54b84AD27E4c4a8C9E0b2b53701DeFc728f6E44",
        //         "destination_tag":null,
        //         "error_detail":null,
        //         "refunded":"0.0000000000",
        //         "transaction_date":"2020-11-28",
        //         "transaction_timestamp":"1606563143.959",
        //         "created_at":1606563143959,
        //         "updated_at":1606563143959,
        //         "transaction_email_timestamp":0,
        //         "network":null,
        //         "collect_tx_id":null,
        //         "collect_id":null
        //     }
        //
        $id = $this->safe_string($transaction, 'id');
        $timestamp = $this->safe_integer($transaction, 'created_at');
        $updated = $this->safe_integer($transaction, 'updated_at');
        $currencyId = $this->safe_string($transaction, 'currency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $amount = $this->safe_float($transaction, 'amount');
        if ($amount !== null) {
            $amount = abs($amount);
        }
        $address = $this->safe_string($transaction, 'blockchain_address');
        $txid = $this->safe_string($transaction, 'transaction_id');
        $tag = $this->safe_string($transaction, 'destination_tag');
        $fee = null;
        $feeCost = $this->safe_float($transaction, 'fee');
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        $type = $amount > 0 ? 'deposit' : 'withdrawal';
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => $address,
            'addressTo' => $address,
            'addressFrom' => null,
            'tag' => $tag,
            'tagTo' => $tag,
            'tagFrom' => null,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => $updated,
            'fee' => $fee,
        );
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            'pending' => 'pending',
            'error' => 'failed',
            'success' => 'ok',
            'cancel' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction_type($type) {
        $types = array(
            'deposit' => 'deposit',
            'withdraw' => 'withdrawal',
        );
        return $this->safe_string($types, $type, $type);
    }

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

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'coin' => $market['baseId'],
            'currency' => $market['quoteId'],
            'trade_type' => $side,
            'type' => $type,
        );
        if ($type === 'ceiling_market') {
            $ceiling = $this->safe_value($params, 'ceiling');
            if ($ceiling !== null) {
                $request['ceiling'] = $this->cost_to_precision($symbol, $ceiling);
            } else if ($price !== null) {
                $request['ceiling'] = $this->cost_to_precision($symbol, $amount * $price);
            } else {
                throw new InvalidOrder($this->id . ' createOrder() requires a $price argument or a $ceiling parameter for ' . $type . ' orders');
            }
        } else {
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($type === 'limit') {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $stopPrice = $this->safe_value_2($params, 'stop_price', 'stopPrice');
        if ($stopPrice !== null) {
            $request['is_stop'] = 1;
            $request['stop_condition'] = ($side === 'buy') ? 'le' : 'ge'; // ge = greater than or equal, le = less than or equal
            $request['stop_price'] = $this->price_to_precision($symbol, $price);
        }
        $params = $this->omit($params, array( 'stop_price', 'stopPrice' ));
        $response = $this->privatePostOrders (array_merge($request, $params));
        //
        // ceiling_market order
        //
        //     {
        //         "message":null,
        //         "dataVersion":"213fc0d433f38307f736cae1cbda4cc310469b7a",
        //         "$data":{
        //             "coin":"btc",
        //             "currency":"usdt",
        //             "trade_type":"buy",
        //             "$type":"ceiling_market",
        //             "$ceiling":"30",
        //             "user_id":253063,
        //             "email":"igor.kroitor@gmail.com",
        //             "$side":"buy",
        //             "quantity":"0.00172800",
        //             "status":"pending",
        //             "fee":0,
        //             "created_at":1606571333035,
        //             "updated_at":1606571333035,
        //             "instrument_symbol":"BTCUSDT",
        //             "remaining":"0.00172800",
        //             "fee_rate":"0.002",
        //             "id":88214435
        //         }
        //     }
        //
        // limit order
        //
        //     {
        //         "message":null,
        //         "dataVersion":"d9b1159d2bcefa2388be156e32ddc7cc324400ee",
        //         "$data":{
        //             "id":41230,
        //             "trade_type":"sell",
        //             "$type":"limit",
        //             "quantity":"1",
        //             "$price":"14.99",
        //             "currency":"usdt",
        //             "coin":"neo",
        //             "status":"pending",
        //             "is_stop" => "1",
        //             "stop_price" => "13",
        //             "stop_condition" => "ge",
        //             "fee":0,
        //             "created_at":1560244052168,
        //             "updated_at":1560244052168
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        return $this->parse_order($data, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'order_id' => $id,
        );
        $response = $this->privatePutOrdersOrderIdCancel (array_merge($request, $params));
        return $this->parse_order($response);
    }

    public function cancel_all_orders($symbol = null, $params = array ()) {
        $type = $this->safe_string($params, 'type');
        $method = ($type === null) ? 'privatePutOrdersCancelAll' : 'privatePutOrdersCancelByType';
        $request = array();
        if ($type !== null) {
            $request['type'] = $type;
        }
        $this->load_markets();
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff",
        //         "$data":array(
        //             "$data":array(
        //                 array(
        //                     "id":410,
        //                     "trade_type":"sell",
        //                     "currency":"usdt",
        //                     "coin":"neo",
        //                     "$type":"limit",
        //                     "quantity":"1.0000000000",
        //                     "price":"14.9900000000",
        //                     "executed_quantity":"0.0000000000",
        //                     "executed_price":"0.0000000000",
        //                     "fee":"0.0000000000",
        //                     "status":"canceled",
        //                     "created_at":1560244052168,
        //                     "updated_at":1560244052168,
        //                 ),
        //             ),
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_orders($data);
    }

    public function parse_order_status($status) {
        $statuses = array(
            'pending' => 'open',
            'stopping' => 'open',
            'executing' => 'open',
            'executed' => 'closed',
            'canceled' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // ceiling_market
        //
        //     {
        //         "coin":"btc",
        //         "currency":"usdt",
        //         "trade_type":"buy",
        //         "$type":"ceiling_market",
        //         "ceiling":"30",
        //         "user_id":253063,
        //         "email":"igor.kroitor@gmail.com",
        //         "$side":"buy",
        //         "quantity":"0.00172800",
        //         "$status":"pending",
        //         "$fee":0,
        //         "created_at":1606571333035,
        //         "updated_at":1606571333035,
        //         "instrument_symbol":"BTCUSDT",
        //         "$remaining":"0.00172800",
        //         "fee_rate":"0.002",
        //         "$id":88214435
        //     }
        //
        // limit $order
        //
        //     {
        //         "$id":41230,
        //         "trade_type":"sell",
        //         "$type":"limit",
        //         "quantity":"1",
        //         "$price":"14.99",
        //         "currency":"usdt",
        //         "coin":"neo",
        //         "$status":"pending",
        //         "is_stop" => "1",
        //         "stop_price" => "13",
        //         "stop_condition" => "ge",
        //         "$fee":0,
        //         "created_at":1560244052168,
        //         "updated_at":1560244052168
        //     }
        //
        $created = $this->safe_value($order, 'created_at');
        $updated = $this->safe_value($order, 'updated_at');
        $baseId = $this->safe_string_upper($order, 'coin');
        $quoteId = $this->safe_string_upper($order, 'currency');
        $marketId = $baseId . '_' . $quoteId;
        $market = $this->safe_market($marketId, $market, '_');
        $symbol = $market['symbol'];
        $amount = $this->safe_float($order, 'quantity');
        $filled = $this->safe_float($order, 'executed_quantity');
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $cost = $this->safe_float($order, 'ceiling');
        $id = $this->safe_string($order, 'id');
        $average = null;
        $price = $this->safe_float($order, 'price');
        // in case of $market $order
        if (!$price) {
            $price = $this->safe_float($order, 'executed_price');
            $average = $price;
        }
        $remaining = $this->safe_float($order, 'remaining');
        if (($filled === null) && ($amount !== null) && ($remaining !== null)) {
            $filled = max (0, $amount - $remaining);
        }
        if ($filled !== null) {
            if (($amount !== null) && ($remaining === null)) {
                $remaining = max (0, $amount - $filled);
            }
            if (($price !== null) && ($cost === null)) {
                $cost = $filled * $price;
            }
            if (($average === null) && ($cost !== null) && ($filled > 0)) {
                $average = $cost / $filled;
            }
        }
        $type = $this->safe_string($order, 'type');
        $side = $this->safe_string($order, 'trade_type');
        $fee = array(
            'currency' => $market['quote'],
            'cost' => $this->safe_float($order, 'fee'),
            'rate' => $this->safe_float($order, 'fee_rate'),
        );
        $lastTradeTimestamp = null;
        if ($updated !== $created) {
            $lastTradeTimestamp = $updated;
        }
        $stopPrice = $this->safe_float($order, 'stopPrice');
        return array(
            'id' => $id,
            'clientOrderId' => $id,
            'timestamp' => $created,
            'datetime' => $this->iso8601($created),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'status' => $status,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'average' => $average,
            'amount' => $amount,
            'cost' => $cost,
            'filled' => $filled,
            'remaining' => $remaining,
            'fee' => $fee,
            'trades' => null,
            'info' => $order,
        );
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'order_id' => $id,
        );
        $response = $this->privateGetOrdersOrderId (array_merge($request, $params));
        //
        //     {
        //         "message":null,
        //         "dataVersion":"57448aa1fb8f227254e8e2e925b3ade8e1e5bbef",
        //         "$data":{
        //             "$id":88265741,
        //             "user_id":253063,
        //             "email":"igor.kroitor@gmail.com",
        //             "updated_at":1606581578141,
        //             "created_at":1606581578141,
        //             "coin":"btc",
        //             "currency":"usdt",
        //             "type":"market",
        //             "trade_type":"sell",
        //             "executed_price":"17667.1900000000",
        //             "price":null,
        //             "executed_quantity":"0.0017280000",
        //             "quantity":"0.0017280000",
        //             "fee":"0.0610578086",
        //             "status":"executed",
        //             "is_stop":0,
        //             "stop_condition":null,
        //             "stop_price":null,
        //             "ceiling":null
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        return $this->parse_order($data);
    }

    public function fetch_orders_with_method($method, $symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'page' => 1,
            // 'limit' => $limit, // max 1000
            // 'start_date' => $since,
            // 'end_date' => $this->milliseconds(),
            // 'currency' => $market['quoteId'],
            // 'coin' => $market['baseId'],
            // 'trade_type' => 'buy', // or 'sell'
            // 'hide_canceled' => 0, // 1 to exclude canceled orders
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['coin'] = $market['baseId'];
            $request['currency'] = $market['quoteId'];
        }
        if ($since !== null) {
            $request['start_date'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = min (1000, $limit); // max 1000
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "message":null,
        //         "dataVersion":"89aa11497f23fdd34cf9de9c55acfad863c78780",
        //         "$data":array(
        //             "current_page":1,
        //             "$data":array(
        //                 array(
        //                     "id":88489678,
        //                     "email":"igor.kroitor@gmail.com",
        //                     "updated_at":1606628593567,
        //                     "created_at":1606628593567,
        //                     "coin":"btc",
        //                     "currency":"usdt",
        //                     "type":"$limit",
        //                     "trade_type":"buy",
        //                     "executed_price":"0.0000000000",
        //                     "price":"10000.0000000000",
        //                     "executed_quantity":"0.0000000000",
        //                     "quantity":"0.0010000000",
        //                     "fee":"0.0000000000",
        //                     "status":"pending",
        //                     "is_stop":0,
        //                     "stop_condition":null,
        //                     "stop_price":null,
        //                     "ceiling":null,
        //                 ),
        //             ),
        //             "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1",
        //             "from":1,
        //             "last_page":1,
        //             "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/open?page=1",
        //             "next_page_url":null,
        //             "path":"http:\/\/api.vcc.exchange\/v3\/orders\/open",
        //             "per_page":10,
        //             "prev_page_url":null,
        //             "to":1,
        //             "total":1,
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $data = $this->safe_value($data, 'data', array());
        return $this->parse_orders($data, $market, $since, $limit);
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        return $this->fetch_orders_with_method('privateGetOrdersOpen', $symbol, $since, $limit, $params);
    }

    public function fetch_closed_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        return $this->fetch_orders_with_method('privateGetOrders', $symbol, $since, $limit, $params);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'page' => 1,
            // 'limit' => $limit, // max 1000
            // 'start_date' => $since,
            // 'end_date' => $this->milliseconds(),
            // 'currency' => $market['quoteId'],
            // 'coin' => $market['baseId'],
            // 'trade_type' => 'buy', // or 'sell'
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['coin'] = $market['baseId'];
            $request['currency'] = $market['quoteId'];
        }
        if ($since !== null) {
            $request['start_date'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = min (1000, $limit); // max 1000
        }
        $response = $this->privateGetOrdersTrades (array_merge($request, $params));
        //
        //     {
        //         "message":null,
        //         "dataVersion":"eb890af684cf84e20044e9a9771b96302e7b8dec",
        //         "$data":array(
        //             "current_page":1,
        //             "$data":array(
        //                 array(
        //                     "trade_type":"sell",
        //                     "fee":"0.0610578086",
        //                     "id":1483372,
        //                     "created_at":1606581578368,
        //                     "currency":"usdt",
        //                     "coin":"btc",
        //                     "price":"17667.1900000000",
        //                     "quantity":"0.0017280000",
        //                     "amount":"30.5289043200",
        //                 ),
        //             ),
        //             "first_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1",
        //             "from":1,
        //             "last_page":1,
        //             "last_page_url":"http:\/\/api.vcc.exchange\/v3\/orders\/trades?page=1",
        //             "next_page_url":null,
        //             "path":"http:\/\/api.vcc.exchange\/v3\/orders\/trades",
        //             "per_page":10,
        //             "prev_page_url":null,
        //             "to":2,
        //             "total":2,
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $data = $this->safe_value($data, 'data', array());
        return $this->parse_trades($data, $market, $since, $limit);
    }

    public function fetch_deposit_address($code, $params = array ()) {
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        $response = $this->privateGetDepositAddress (array_merge($request, $params));
        //
        //     {
        //         "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff",
        //         "$data":{
        //             "$status" => "REQUESTED",
        //             "blockchain_address" => "",
        //             "$currency" => "btc"
        //         }
        //     }
        //
        //     {
        //         "dataVersion":"6d72fb82a9c613c8166581a887e1723ce5a937ff",
        //         "$data":{
        //             "$status" => "PROVISIONED",
        //             "blockchain_address" => "rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy",
        //             "blockchain_tag" => "920396135",
        //             "$currency" => "xrp"
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data');
        $status = $this->safe_string($data, 'status');
        if ($status === 'REQUESTED') {
            throw new AddressPending($this->id . ' is generating ' . $code . ' deposit $address, call fetchDepositAddress one more time later to retrieve the generated address');
        }
        $address = $this->safe_string($data, 'blockchain_address');
        $this->check_address($address);
        $tag = $this->safe_string($data, 'blockchain_tag');
        $currencyId = $this->safe_string($data, 'currency');
        return array(
            'currency' => $this->safe_currency_code($currencyId),
            'address' => $address,
            'tag' => $tag,
            'info' => $data,
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->version . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        if ($query) {
            $url .= '?' . $this->urlencode($query);
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = (string) $this->milliseconds();
            if ($method !== 'GET') {
                $body = $this->json($query);
            }
            $auth = $method . ' ' . $url;
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
            $headers = array(
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
                'timestamp' => $timestamp,
                'signature' => $signature,
            );
        }
        $url = $this->urls['api'][$api] . '/' . $url;
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors($code, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return;
        }
        //
        //     array("$message":"Insufficient balance.")
        //     array("$message":"Unauthenticated.") // wrong api key
        //     array("$message":"The given data was invalid.","errors":array("signature":["HMAC signature is invalid"]))
        //     array("$code":504,"$message":"Gateway Timeout","description":"")
        //     array("$code":429,"$message":"Too many requests","description":"Too many requests")
        //
        $message = $this->safe_string($response, 'message');
        if ($message !== null) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            throw new ExchangeError($feedback);
        }
    }
}
