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

class gopax extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'gopax',
            'name' => 'GOPAX',
            'countries' => array( 'KR' ), // South Korea
            'version' => 'v1',
            'rateLimit' => 50,
            'hostname' => 'gopax.co.kr', // or 'gopax.com'
            'certified' => true,
            'pro' => true,
            'has' => array(
                'cancelOrder' => true,
                'createMarketOrder' => true,
                'createOrder' => true,
                'fetchBalance' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => 'emulated',
                'fetchDepositAddresses' => true,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTransactions' => true,
            ),
            'timeframes' => array(
                '1m' => '1',
                '5m' => '5',
                '30m' => '30',
                '1d' => '1440',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/102897212-ae8a5e00-4478-11eb-9bab-91507c643900.jpg',
                'api' => array(
                    'public' => 'https://api.{hostname}', // or 'https://api.gopax.co.kr'
                    'private' => 'https://api.{hostname}',
                ),
                'www' => 'https://www.gopax.co.kr',
                'doc' => 'https://gopax.github.io/API/index.en.html',
                'fees' => 'https://www.gopax.com/feeinfo',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'notices',
                        'assets',
                        'price-tick-size',
                        'trading-pairs',
                        'trading-pairs/{tradingPair}/ticker',
                        'trading-pairs/{tradingPair}/book',
                        'trading-pairs/{tradingPair}/trades',
                        'trading-pairs/{tradingPair}/stats',
                        'trading-pairs/{tradingPair}/price-tick-size',
                        'trading-pairs/stats',
                        'trading-pairs/{tradingPair}/candles',
                        'time',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'balances',
                        'balances/{assetName}',
                        'orders',
                        'orders/{orderId}',
                        'orders/clientOrderId/{clientOrderId}',
                        'trades',
                        'deposit-withdrawal-status',
                        'crypto-deposit-addresses',
                        'crypto-withdrawal-addresses',
                    ),
                    'post' => array(
                        'orders',
                    ),
                    'delete' => array(
                        'orders/{orderId}',
                        'orders/clientOrderId/{clientOrderId}',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'percentage' => true,
                    'tierBased' => false,
                    'maker' => 0.04 / 100,
                    'taker' => 0.04 / 100,
                ),
            ),
            'exceptions' => array(
                'broad' => array(
                    'ERROR_INVALID_ORDER_TYPE' => '\\ccxt\\InvalidOrder',
                    'ERROR_INVALID_AMOUNT' => '\\ccxt\\InvalidOrder',
                    'ERROR_INVALID_TRADING_PAIR' => '\\ccxt\\BadSymbol', // Unlikely to be triggered, due to ccxt.gopax.js implementation
                    'No such order ID' => '\\ccxt\\OrderNotFound', // array("errorMessage":"No such order ID","errorCode":202,"errorData":"Order server error => 202")
                    // 'Not enough amount' => '\\ccxt\\InsufficientFunds', // array("errorMessage":"Not enough amount, try increasing your order amount","errorCode":10212,"errorData":array())
                    'Forbidden order type' => '\\ccxt\\InvalidOrder',
                    'the client order ID will be reusable which order has already been completed or canceled' => '\\ccxt\\InvalidOrder',
                    'ERROR_NO_SUCH_TRADING_PAIR' => '\\ccxt\\BadSymbol', // Unlikely to be triggered, due to ccxt.gopax.js implementation
                    'ERROR_INVALID_ORDER_SIDE' => '\\ccxt\\InvalidOrder',
                    'ERROR_NOT_HEDGE_TOKEN_USER' => '\\ccxt\\InvalidOrder',
                    'ORDER_EVENT_ERROR_NOT_ALLOWED_BID_ORDER' => '\\ccxt\\InvalidOrder', // Triggered only when the exchange is locked
                    'ORDER_EVENT_ERROR_INSUFFICIENT_BALANCE' => '\\ccxt\\InsufficientFunds',
                    'Invalid option combination' => '\\ccxt\\InvalidOrder',
                    'No such client order ID' => '\\ccxt\\OrderNotFound',
                ),
                'exact' => array(
                    '100' => '\\ccxt\\BadSymbol', // Invalid asset name
                    '101' => '\\ccxt\\BadSymbol', // Invalid trading pair
                    '103' => '\\ccxt\\InvalidOrder', // Invalid order type
                    '104' => '\\ccxt\\BadSymbol', // Invalid trading pair
                    '105' => '\\ccxt\\BadSymbol', // Trading pair temporarily disabled
                    '106' => '\\ccxt\\BadSymbol', // Invalid asset name
                    '107' => '\\ccxt\\InvalidOrder', // Invalid order amount
                    '108' => '\\ccxt\\InvalidOrder', // Invalid order price
                    '111' => '\\ccxt\\InvalidOrder', // Invalid event type
                    '201' => '\\ccxt\\InsufficientFunds', // Not enough balance
                    '202' => '\\ccxt\\InvalidOrder', // Invalid order ID
                    '203' => '\\ccxt\\InvalidOrder', // Order amount X order price too large
                    '204' => '\\ccxt\\InvalidOrder', // Bid order temporarily unavailable
                    '205' => '\\ccxt\\InvalidOrder', // Invalid side
                    '206' => '\\ccxt\\InvalidOrder', // Invalid order option combination
                    '10004' => '\\ccxt\\AuthenticationError', // Not authorized
                    // '10004' => '\\ccxt\\ExchangeError', // API key not exist
                    // '10004' => '\\ccxt\\ExchangeError', // User KYC not approved
                    // '10004' => '\\ccxt\\ExchangeError', // User account is frozen
                    // '10004' => '\\ccxt\\ExchangeError', // User is under deactivation process
                    // '10004' => '\\ccxt\\ExchangeError', // 2FA is not enabled
                    // '10004' => '\\ccxt\\ExchangeError', // Invalid signature
                    '10041' => '\\ccxt\\BadRequest', // Invalid exchange
                    '10056' => '\\ccxt\\BadRequest', // No registered asset
                    '10057' => '\\ccxt\\BadSymbol', // No registered trading pair
                    '10059' => '\\ccxt\\BadSymbol', // Invalid trading pair
                    '10062' => '\\ccxt\\BadRequest', // Invalid chart interval
                    '10069' => '\\ccxt\\OrderNotFound', // array("errorMessage":"No such order ID => 73152094","errorCode":10069,"errorData":"73152094")
                    '10155' => '\\ccxt\\AuthenticationError', // array("errorMessage":"Invalid API key","errorCode":10155)
                    '10166' => '\\ccxt\\BadRequest', // Invalid chart range
                    '10212' => '\\ccxt\\InvalidOrder', // array("errorMessage":"Not enough amount, try increasing your order amount","errorCode":10212,"errorData":array())
                    '10221' => '\\ccxt\\OrderNotFound', // No such client order ID
                    '10222' => '\\ccxt\\InvalidOrder', // Client order ID being used
                    '10223' => '\\ccxt\\InvalidOrder', // Soon the client order ID will be reusable which order has already been completed or canceled
                    '10227' => '\\ccxt\\InvalidOrder', // Invalid client order ID format
                    '10319' => '\\ccxt\\BadRequest', // Pagination is required as you have too many orders
                    '10358' => '\\ccxt\\InvalidOrder', // Invalid order type
                    '10359' => '\\ccxt\\InvalidOrder', // Invalid order side
                    '10360' => '\\ccxt\\InvalidOrder', // Invalid order status
                    '10361' => '\\ccxt\\InvalidOrder', // Invalid order time in force
                    '10362' => '\\ccxt\\InvalidOrder', // Invalid order protection
                    '10363' => '\\ccxt\\InvalidOrder', // Invalid forced completion reason
                ),
            ),
            'options' => array(
                'createMarketBuyOrderRequiresPrice' => true,
            ),
        ));
    }

    public function fetch_time($params = array ()) {
        $response = $this->publicGetTime ($params);
        //
        //     array("serverTime":1608327726656)
        //
        return $this->safe_integer($response, 'serverTime');
    }

    public function fetch_markets($params = array ()) {
        $response = $this->publicGetTradingPairs ($params);
        //
        //     array(
        //         array(
        //             "$id":1,
        //             "name":"ETH-KRW",
        //             "baseAsset":"ETH",
        //             "quoteAsset":"KRW",
        //             "baseAssetScale":8,
        //             "quoteAssetScale":0,
        //             "priceMin":1,
        //             "restApiOrderAmountMin":array(
        //                 "limitAsk":array("amount":10000,"unit":"KRW"),
        //                 "limitBid":array("amount":10000,"unit":"KRW"),
        //                 "$marketAsk":array("amount":0.001,"unit":"ETH"),
        //                 "$marketBid":array("amount":10000,"unit":"KRW"),
        //             ),
        //             "$makerFeePercent":0.2,
        //             "$takerFeePercent":0.2,
        //         ),
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $market = $response[$i];
            $id = $this->safe_string($market, 'name');
            $numericId = $this->safe_integer($market, 'id');
            $baseId = $this->safe_string($market, 'baseAsset');
            $quoteId = $this->safe_string($market, 'quoteAsset');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $base . '/' . $quote;
            $precision = array(
                'price' => $this->safe_integer($market, 'quoteAssetScale'),
                'amount' => $this->safe_integer($market, 'baseAssetScale'),
            );
            $minimums = $this->safe_value($market, 'restApiOrderAmountMin', array());
            $marketAsk = $this->safe_value($minimums, 'marketAsk', array());
            $marketBid = $this->safe_value($minimums, 'marketBid', array());
            $takerFeePercent = $this->safe_float($market, 'takerFeePercent');
            $makerFeePercent = $this->safe_float($market, 'makerFeePercent');
            $taker = floatval($this->decimal_to_precision($takerFeePercent / 100, ROUND, 0.00000001, TICK_SIZE));
            $maker = floatval($this->decimal_to_precision($makerFeePercent / 100, ROUND, 0.00000001, TICK_SIZE));
            $result[] = array(
                'id' => $id,
                'info' => $market,
                'numericId' => $numericId,
                'symbol' => $symbol,
                'base' => $base,
                'quote' => $quote,
                'baseId' => $this->safe_string($market, 'baseAsset'),
                'quoteId' => $this->safe_string($market, 'quoteAsset'),
                'active' => true,
                'taker' => $taker,
                'maker' => $maker,
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($marketAsk, 'amount'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => $this->safe_float($market, 'priceMin'),
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => $this->safe_float($marketBid, 'amount'),
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $response = $this->publicGetAssets ($params);
        //
        //     array(
        //         array(
        //             "$id":"KRW",
        //             "$name":"대한민국 원",
        //             "scale":0,
        //             "withdrawalFee":1000,
        //             "withdrawalAmountMin":5000
        //         ),
        //         array(
        //             "$id":"ETH",
        //             "$name":"이더리움",
        //             "scale":8,
        //             "withdrawalFee":0.03,
        //             "withdrawalAmountMin":0.015
        //         ),
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $currency = $response[$i];
            $id = $this->safe_string($currency, 'id');
            $code = $this->safe_currency_code($id);
            $name = $this->safe_string($currency, 'name');
            $fee = $this->safe_float($currency, 'withdrawalFee');
            $precision = $this->safe_float($currency, 'scale');
            $result[$code] = array(
                'id' => $id,
                'info' => $currency,
                'code' => $code,
                'name' => $name,
                'active' => true,
                'fee' => $fee,
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => $this->safe_float($currency, 'withdrawalAmountMin'),
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'tradingPair' => $market['id'],
            // 'level' => 3, // 1 best bidask, 2 top 50 bidasks, 3 all bidasks
        );
        $response = $this->publicGetTradingPairsTradingPairBook (array_merge($request, $params));
        //
        //     {
        //         "sequence":17691957,
        //         "bid":[
        //             ["17690499",25019000,0.00008904,"1608326468921"],
        //             ["17691894",25010000,0.4295,"1608326499940"],
        //             ["17691895",25009000,0.2359,"1608326499953"],
        //         ],
        //         "ask":[
        //             ["17689176",25024000,0.000098,"1608326442006"],
        //             ["17691351",25031000,0.206,"1608326490418"],
        //             ["17691571",25035000,0.3996,"1608326493742"],
        //         ]
        //     }
        //
        $nonce = $this->safe_integer($response, 'sequence');
        $result = $this->parse_order_book($response, null, 'bid', 'ask', 1, 2);
        $result['nonce'] = $nonce;
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker
        //
        //     {
        //         "price":25087000,
        //         "ask":25107000,
        //         "askVolume":0.05837704,
        //         "bid":25087000,
        //         "bidVolume":0.00398628,
        //         "volume":350.09171591,
        //         "$quoteVolume":8721016926.06529,
        //         "time":"2020-12-18T21:42:13.774Z",
        //     }
        //
        // fetchTickers
        //
        //     {
        //         "name":"ETH-KRW",
        //         "$open":690500,
        //         "high":719500,
        //         "low":681500,
        //         "close":709500,
        //         "volume":2784.6081544,
        //         "time":"2020-12-18T21:54:50.795Z"
        //     }
        //
        $marketId = $this->safe_string($ticker, 'name');
        $symbol = $this->safe_symbol($marketId, $market, '-');
        $timestamp = $this->parse8601($this->safe_string($ticker, 'time'));
        $open = $this->safe_float($ticker, 'open');
        $last = $this->safe_float_2($ticker, 'price', 'close');
        $change = null;
        $percentage = null;
        $average = null;
        if (($last !== null) && ($open !== null)) {
            $average = $this->sum($last, $open) / 2;
            $change = $last - $open;
            if ($open > 0) {
                $percentage = $change / $open * 100;
            }
        }
        $baseVolume = $this->safe_float($ticker, 'volume');
        $quoteVolume = $this->safe_float($ticker, 'quoteVolume');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        return array(
            'symbol' => $symbol,
            'info' => $ticker,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'high'),
            'low' => $this->safe_float($ticker, 'low'),
            'bid' => $this->safe_float($ticker, 'bid'),
            'bidVolume' => $this->safe_float($ticker, 'bidVolume'),
            'ask' => $this->safe_float($ticker, 'ask'),
            'askVolume' => $this->safe_float($ticker, 'askVolume'),
            'vwap' => $vwap,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
        );
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'tradingPair' => $market['id'],
        );
        $response = $this->publicGetTradingPairsTradingPairTicker (array_merge($request, $params));
        //
        //     {
        //         "price":25087000,
        //         "ask":25107000,
        //         "askVolume":0.05837704,
        //         "bid":25087000,
        //         "bidVolume":0.00398628,
        //         "volume":350.09171591,
        //         "quoteVolume":8721016926.06529,
        //         "time":"2020-12-18T21:42:13.774Z",
        //     }
        //
        return $this->parse_ticker($response, $market);
    }

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

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $response = $this->publicGetTradingPairsStats ($params);
        //
        //     array(
        //         {
        //             "name":"ETH-KRW",
        //             "open":690500,
        //             "high":719500,
        //             "low":681500,
        //             "close":709500,
        //             "volume":2784.6081544,
        //             "time":"2020-12-18T21:54:50.795Z"
        //         }
        //     )
        //
        return $this->parse_tickers($response, $symbols);
    }

    public function parse_public_trade($trade, $market = null) {
        $timestamp = $this->parse8601($this->safe_string($trade, 'time'));
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float($trade, 'amount');
        $symbol = null;
        if (is_array($market) && array_key_exists('symbol', $market)) {
            $symbol = $this->safe_string($market, 'symbol');
        }
        return array(
            'info' => $trade,
            'id' => $this->safe_string($trade, 'id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => null, // Not mandatory to specify
            'type' => null, // Not mandatory to specify
            'side' => $this->safe_string($trade, 'side'),
            'takerOrMaker' => null,
            'price' => $price,
            'amount' => $amount,
            'cost' => $price * $amount,
            'fee' => null,
        );
    }

    public function parse_private_trade($trade, $market = null) {
        $timestamp = $this->parse8601($this->safe_string($trade, 'timestamp'));
        $symbol = str_replace('-', '/', $this->safe_string($trade, 'tradingPairName'));
        $side = $this->safe_string($trade, 'side');
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float($trade, 'baseAmount');
        $feeCurrency = mb_substr($symbol, 0, 3 - 0);
        if ($side === 'sell') {
            $feeCurrency = mb_substr($symbol, 4);
        }
        $fee = array(
            'cost' => $this->safe_float($trade, 'fee'),
            'currency' => $feeCurrency,
            'rate' => null,
        );
        return array(
            'info' => $trade,
            'id' => $this->safe_string($trade, 'id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => $this->safe_integer($trade, 'orderId'),
            'type' => null,
            'side' => $side,
            'takerOrMaker' => $this->safe_string($trade, 'position'),
            'price' => $price,
            'amount' => $amount,
            'cost' => $price * $amount,
            'fee' => $fee,
        );
    }

    public function parse_trade($trade, $market = null) {
        //
        // public fetchTrades
        //
        //     {
        //         "time":"2020-12-19T12:17:43.000Z",
        //         "date":1608380263,
        //         "$id":23903608,
        //         "$price":25155000,
        //         "$amount":0.0505,
        //         "$side":"sell",
        //     }
        //
        // private fetchMyTrades
        //
        //     {
        //         "$id" => 73953,                             // trading event ID
        //         "$orderId" => 453324,                       // order ID
        //         "baseAmount" => 3,                         // traded base asset $amount
        //         "quoteAmount" => 3000000,                  // traded quote asset $amount
        //         "$fee" => 0.0012,                           // $fee
        //         "$price" => 1000000,                        // $price
        //         "$timestamp" => "2020-09-25T04:06:30.000Z", // trading time
        //         "$side" => "buy",                           // buy, sell
        //         "tradingPairName" => "ZEC-KRW",            // order book
        //         "position" => "maker"                      // maker, taker
        //     }
        //
        //     {
        //         "tradeId" => 74072,            // $trade ID
        //         "$orderId" => 453529,           // order ID
        //         "$side" => 2,                   // 1(bid), 2(ask)
        //         "$type" => 1,                   // 1(limit), 2($market)
        //         "baseAmount" => 0.01,          // filled base asset $amount (in ZEC for this case)
        //         "quoteAmount" => 1,            // filled quote asset $amount (in KRW for this case)
        //         "$fee" => 0.0004,               // $fee
        //         "$price" => 100,                // $price
        //         "isSelfTrade" => false,        // whether both of matching orders are yours
        //         "occurredAt" => 1603932107,    // $trade occurrence time
        //         "tradingPairName" => "ZEC-KRW" // order book
        //     }
        //
        $id = $this->safe_string_2($trade, 'id', 'tradeId');
        $orderId = $this->safe_integer($trade, 'orderId');
        $timestamp = $this->parse8601($this->safe_string_2($trade, 'time', 'timestamp'));
        $timestamp = $this->safe_timestamp($trade, 'occuredAt', $timestamp);
        $marketId = $this->safe_string($trade, 'tradingPairName');
        $market = $this->safe_market($marketId, $market, '-');
        $symbol = $market['symbol'];
        $side = $this->safe_string($trade, 'side');
        if ($side === '1') {
            $side = 'buy';
        } else if ($side === '2') {
            $side = 'sell';
        }
        $type = $this->safe_string($trade, 'type');
        if ($type === '1') {
            $type = 'limit';
        } else if ($type === '2') {
            $type = 'market';
        }
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float_2($trade, 'amount', 'baseAmount');
        $cost = $this->safe_float($trade, 'quoteAmount');
        if ($cost === null) {
            if (($price !== null) && ($amount !== null)) {
                $cost = $price * $amount;
            }
        }
        $feeCost = $this->safe_float($trade, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $market['base'],
            );
        }
        $takerOrMaker = $this->safe_string($trade, 'position');
        return array(
            'info' => $trade,
            'id' => $id,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            '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(
            'tradingPair' => $market['id'],
            // 'limit' => $limit,
            // 'pastmax' => id, // read data older than this ID
            // 'latestmin' => id, // read data newer than this ID
            // 'after' => intval($since / 1000),
            // 'before' => $this->seconds(),
        );
        if ($since !== null) {
            $request['after'] = intval($since / 1000);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetTradingPairsTradingPairTrades (array_merge($request, $params));
        //
        //     array(
        //         array("time":"2020-12-19T12:17:43.000Z","date":1608380263,"id":23903608,"price":25155000,"amount":0.0505,"side":"sell"),
        //         array("time":"2020-12-19T12:17:13.000Z","date":1608380233,"id":23903604,"price":25140000,"amount":0.019,"side":"sell"),
        //         array("time":"2020-12-19T12:16:49.000Z","date":1608380209,"id":23903599,"price":25140000,"amount":0.0072,"side":"sell"),
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     array(
        //         1606780800000, // timestamp
        //         21293000,      // low
        //         21300000,      // high
        //         21294000,      // open
        //         21300000,      // close
        //         1.019126,      // volume
        //     )
        //
        return array(
            $this->safe_integer($ohlcv, 0),
            $this->safe_float($ohlcv, 3),
            $this->safe_float($ohlcv, 2),
            $this->safe_float($ohlcv, 1),
            $this->safe_float($ohlcv, 4),
            $this->safe_float($ohlcv, 5),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $limit = ($limit === null) ? 1024 : $limit; // default 1024
        $request = array(
            'tradingPair' => $market['id'],
            // 'start' => $since,
            // 'end' => $this->milliseconds(),
            'interval' => $this->timeframes[$timeframe],
        );
        $duration = $this->parse_timeframe($timeframe);
        if ($since === null) {
            $end = $this->milliseconds();
            $request['end'] = $end;
            $request['start'] = $end - $limit * $duration * 1000;
        } else {
            $request['start'] = $since;
            $request['end'] = $this->sum($since, $limit * $duration * 1000);
        }
        $response = $this->publicGetTradingPairsTradingPairCandles (array_merge($request, $params));
        //
        //     [
        //         [1606780800000,21293000,21300000,21294000,21300000,1.019126],
        //         [1606780860000,21237000,21293000,21293000,21263000,0.96800057],
        //         [1606780920000,21240000,21240000,21240000,21240000,0.11068715],
        //     ]
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function parse_balance_response($response) {
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($response); $i++) {
            $balance = $response[$i];
            $currencyId = $this->safe_string_2($balance, 'asset', 'isoAlpha3');
            $code = $this->safe_currency_code($currencyId);
            $hold = $this->safe_float($balance, 'hold');
            $pendingWithdrawal = $this->safe_float($balance, 'pendingWithdrawal');
            $account = $this->account();
            $account['free'] = $this->safe_float($balance, 'avail');
            $account['used'] = $this->sum($hold, $pendingWithdrawal);
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $response = $this->privateGetBalances ($params);
        //
        //     array(
        //         array(
        //             "asset" => "KRW",                   // asset name
        //             "avail" => 1759466.76,              // available amount to place order
        //             "hold" => 16500,                    // outstanding amount on order books
        //             "pendingWithdrawal" => 0,           // amount being withdrawn
        //             "lastUpdatedAt" => "1600684352032", // balance last update time
        //         ),
        //     )
        //
        return $this->parse_balance_response($response);
    }

    public function parse_order_status($status) {
        $statuses = array(
            'placed' => 'open',
            'cancelled' => 'canceled',
            'completed' => 'closed',
            'updated' => 'open',
            'reserved' => 'open',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // cancelOrder
        //
        //     array() // empty object
        //
        // fetchOrder, fetchOrders, fetchOpenOrders, createOrder
        //
        //     {
        //         "$id" => "453324",                          // $order ID
        //         "$clientOrderId" => "zeckrw23456",          // client $order ID (showed only when it exists)
        //         "$status" => "$updated",                     // placed, cancelled, completed, $updated, reserved
        //         "forcedCompletionReason" => null,     // the reason in case it was canceled in the middle (protection or $timeInForce)
        //         "tradingPairName" => "ZEC-KRW",            // $order book
        //         "$side" => "buy",                           // buy, sell
        //         "$type" => "limit",                         // limit, $market
        //         "$price" => 1000000,                        // $price
        //         "$stopPrice" => null,                  // stop $price (showed only for stop orders)
        //         "$amount" => 4,                             // initial $amount
        //         "$remaining" => 1,                          // outstanding $amount
        //         "protection" => "yes",                     // whether protection is activated (yes or no)
        //         "$timeInForce" => "gtc",                    // limit order's time in force (gtc/po/ioc/fok)
        //         "createdAt" => "2020-09-25T04:06:20.000Z", // $order placement time
        //         "updatedAt" => "2020-09-25T04:06:29.000Z", // $order last update time
        //         "$balanceChange" => {
        //             "baseGross" => 3,                      // base asset balance's gross change (in ZEC for this case)
        //             "$baseFee" => array(
        //                 "$taking" => 0,                     // base asset $fee imposed as taker
        //                 "$making" => -0.0012                // base asset $fee imposed as maker
        //             ),
        //             "baseNet" => 2.9988,                   // base asset balance's net change (in ZEC for this case)
        //             "quoteGross" => -3000000,              // quote asset balance's gross change (in KRW for
        //             "$quoteFee" => array(
        //                 "$taking" => 0,                     // quote asset $fee imposed as taker
        //                 "$making" => 0                      // quote asset $fee imposed as maker
        //             ),
        //             "quoteNet" => -3000000                 // quote asset balance's net change (in KRW for this case)
        //         }
        //     }
        //
        $id = $this->safe_string($order, 'id');
        $clientOrderId = $this->safe_string($order, 'clientOrderId');
        $timestamp = $this->parse8601($this->safe_string($order, 'createdAt'));
        $type = $this->safe_string($order, 'type');
        $side = $this->safe_string($order, 'side');
        $timeInForce = $this->safe_string_upper($order, 'timeInForce');
        $price = $this->safe_float($order, 'price');
        $amount = $this->safe_float($order, 'amount');
        $stopPrice = $this->safe_float($order, 'stopPrice');
        $remaining = $this->safe_float($order, 'remaining');
        $marketId = $this->safe_string($order, 'tradingPairName');
        $market = $this->safe_market($marketId, $market, '-');
        $status = $this->parse_order_status($this->safe_string($order, 'status'));
        $balanceChange = $this->safe_value($order, 'balanceChange', array());
        $filled = $this->safe_float($balanceChange, 'baseNet');
        $cost = $this->safe_float($balanceChange, 'quoteNet');
        if ($cost !== null) {
            $cost = abs($cost);
        }
        $updated = null;
        if (($filled === null) && ($amount !== null) && ($remaining !== null)) {
            $filled = max (0, $amount - $remaining);
        }
        if (($filled !== null) && ($filled > 0)) {
            $updated = $this->parse8601($this->safe_string($order, 'updatedAt'));
        }
        if (($cost === null) && ($price !== null) && ($filled !== null)) {
            $cost = $filled * $price;
        }
        $fee = null;
        if ($side === 'buy') {
            $baseFee = $this->safe_value($balanceChange, 'baseFee', array());
            $taking = $this->safe_float($baseFee, 'taking');
            $making = $this->safe_float($baseFee, 'making');
            $fee = array(
                'currency' => $market['base'],
                'cost' => $this->sum($taking, $making),
            );
        } else {
            $quoteFee = $this->safe_value($balanceChange, 'quoteFee', array());
            $taking = $this->safe_float($quoteFee, 'taking');
            $making = $this->safe_float($quoteFee, 'making');
            $fee = array(
                'currency' => $market['quote'],
                'cost' => $this->sum($taking, $making),
            );
        }
        $postOnly = null;
        if ($timeInForce !== null) {
            $postOnly = ($timeInForce === 'PO');
        }
        return array(
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'datetime' => $this->iso8601($timestamp),
            'timestamp' => $timestamp,
            'lastTradeTimestamp' => $updated,
            'status' => $status,
            'symbol' => $market['symbol'],
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'average' => null,
            'amount' => $amount,
            'filled' => $filled,
            'remaining' => $remaining,
            'cost' => $cost,
            'trades' => null,
            'fee' => $fee,
            'info' => $order,
        );
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $method = null;
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        $params = $this->omit($params, 'clientOrderId');
        $request = array();
        if ($clientOrderId === null) {
            $method = 'privateGetOrdersOrderId';
            $request['orderId'] = $id;
        } else {
            $method = 'privateGetOrdersClientOrderIdClientOrderId';
            $request['clientOrderId'] = $clientOrderId;
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     {
        //         "$id" => "453324",                          // order ID
        //         "$clientOrderId" => "zeckrw23456",          // client order ID (showed only when it exists)
        //         "status" => "updated",                     // placed, cancelled, completed, updated, reserved
        //         "forcedCompletionReason" => null,     // the reason in case it was canceled in the middle (protection or timeInForce)
        //         "tradingPairName" => "ZEC-KRW",            // order book
        //         "side" => "buy",                           // buy, sell
        //         "type" => "limit",                         // limit, market
        //         "price" => 1000000,                        // price
        //         "stopPrice" => null,                  // stop price (showed only for stop orders)
        //         "amount" => 4,                             // initial amount
        //         "remaining" => 1,                          // outstanding amount
        //         "protection" => "yes",                     // whether protection is activated (yes or no)
        //         "timeInForce" => "gtc",                    // limit order's time in force (gtc/po/ioc/fok)
        //         "createdAt" => "2020-09-25T04:06:20.000Z", // order placement time
        //         "updatedAt" => "2020-09-25T04:06:29.000Z", // order last update time
        //         "balanceChange" => {
        //             "baseGross" => 3,                      // base asset balance's gross change (in ZEC for this case)
        //             "baseFee" => array(
        //                 "taking" => 0,                     // base asset fee imposed as taker
        //                 "making" => -0.0012                // base asset fee imposed as maker
        //             ),
        //             "baseNet" => 2.9988,                   // base asset balance's net change (in ZEC for this case)
        //             "quoteGross" => -3000000,              // quote asset balance's gross change (in KRW for
        //             "quoteFee" => array(
        //                 "taking" => 0,                     // quote asset fee imposed as taker
        //                 "making" => 0                      // quote asset fee imposed as maker
        //             ),
        //             "quoteNet" => -3000000                 // quote asset balance's net change (in KRW for this case)
        //         }
        //     }
        //
        return $this->parse_order($response);
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'includePast' => 'true', // if true, completed and canceled orders are included as the result, they are accessible for one hour only from its completion or cancellation time
            // 'pagination' => 'false', // if the result is more than 3,000 orders, set this value as true to access 1000 orders at max per each page
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $response = $this->privateGetOrders (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "id" => "453324",                          // order ID
        //             "clientOrderId" => "zeckrw23456",          // client order ID (showed only when it exists)
        //             "status" => "updated",                     // placed, cancelled, completed, updated, reserved
        //             "forcedCompletionReason" => null,     // the reason in case it was canceled in the middle (protection or timeInForce)
        //             "tradingPairName" => "ZEC-KRW",            // order book
        //             "side" => "buy",                           // buy, sell
        //             "type" => "$limit",                         // $limit, $market
        //             "price" => 1000000,                        // price
        //             "stopPrice" => null,                  // stop price (showed only for stop orders)
        //             "amount" => 4,                             // initial amount
        //             "remaining" => 1,                          // outstanding amount
        //             "protection" => "yes",                     // whether protection is activated (yes or no)
        //             "timeInForce" => "gtc",                    // $limit order's time in force (gtc/po/ioc/fok)
        //             "createdAt" => "2020-09-25T04:06:20.000Z", // order placement time
        //             "updatedAt" => "2020-09-25T04:06:29.000Z", // order last update time
        //             "balanceChange" => array(
        //                 "baseGross" => 3,                      // base asset balance's gross change (in ZEC for this case)
        //                 "baseFee" => array(
        //                     "taking" => 0,                     // base asset fee imposed as taker
        //                     "making" => -0.0012                // base asset fee imposed as maker
        //                 ),
        //                 "baseNet" => 2.9988,                   // base asset balance's net change (in ZEC for this case)
        //                 "quoteGross" => -3000000,              // quote asset balance's gross change (in KRW for
        //                 "quoteFee" => array(
        //                     "taking" => 0,                     // quote asset fee imposed as taker
        //                     "making" => 0                      // quote asset fee imposed as maker
        //                 ),
        //                 "quoteNet" => -3000000                 // quote asset balance's net change (in KRW for this case)
        //             }
        //         ),
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_open_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        $request = array(
            'includePast' => 'false',
        );
        return $this->fetch_orders($symbol, $since, $limit, array_merge($request, $params));
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            // 'clientOrderId' => 'test4321', // max 20 characters of [a-zA-Z0-9_-]
            'tradingPairName' => $market['id'],
            'side' => $side, // buy, sell
            'type' => $type, // limit, $market
            // 'price' => $this->price_to_precision($symbol, $price),
            // 'stopPrice' => $this->price_to_precision($symbol, $stopPrice), // optional, becomes a stop order if set
            // 'amount' => $this->amount_to_precision($symbol, $amount),
            // 'protection' => 'no', // whether protection is activated
            // 'timeInForce' => 'gtc', // gtc, po, ioc, fok
        );
        if ($type === 'limit') {
            $request['price'] = $this->price_to_precision($symbol, $price);
            $request['amount'] = $this->amount_to_precision($symbol, $amount);
        } else if ($type === 'market') {
            // for $market buy it requires the $amount of quote currency to spend
            if ($side === 'buy') {
                $total = $amount;
                $createMarketBuyOrderRequiresPrice = $this->safe_value($this->options, 'createMarketBuyOrderRequiresPrice', true);
                if ($createMarketBuyOrderRequiresPrice) {
                    if ($price === null) {
                        throw new InvalidOrder($this->id . " createOrder() requires the $price argument with $market buy orders to calculate $total order cost ($amount to spend), where cost = $amount * $price-> Supply a $price argument to createOrder() call if you want the cost to be calculated for you from $price and $amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the $total cost value in the 'amount' argument");
                    }
                    $total = $price * $amount;
                }
                $precision = $market['precision']['price'];
                $request['amount'] = $this->decimal_to_precision($total, TRUNCATE, $precision, $this->precisionMode);
            } else {
                $request['amount'] = $this->amount_to_precision($symbol, $amount);
            }
        }
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $request['clientOrderId'] = $clientOrderId;
            $params = $this->omit($params, 'clientOrderId');
        }
        $stopPrice = $this->safe_float($params, 'stopPrice');
        if ($stopPrice !== null) {
            $request['stopPrice'] = $this->price_to_precision($symbol, $stopPrice);
            $params = $this->omit($params, 'stopPrice');
        }
        $timeInForce = $this->safe_string_lower($params, 'timeInForce');
        if ($timeInForce !== null) {
            $request['timeInForce'] = $timeInForce;
            $params = $this->omit($params, 'timeInForce');
        }
        $response = $this->privatePostOrders (array_merge($request, $params));
        //
        //     {
        //         "id" => "453327",                          // order ID
        //         "$clientOrderId" => "test4321",             // client order ID (showed only when it exists)
        //         "status" => "reserved",                    // placed, cancelled, completed, updated, reserved
        //         "forcedCompletionReason" => null,     // the reason in case it was canceled in the middle (protection or $timeInForce)
        //         "tradingPairName" => "BCH-KRW",            // order book
        //         "$side" => "sell",                          // buy, sell
        //         "$type" => "limit",                         // limit, $market
        //         "$price" => 11000000,                       // $price
        //         "$stopPrice" => 12000000,                   // stop $price (showed only for stop orders)
        //         "$amount" => 0.5,                           // initial $amount
        //         "remaining" => 0.5,                        // outstanding $amount
        //         "protection" => "no",                      // whether protection is activated (yes or no)
        //         "$timeInForce" => "gtc",                    // limit order's time in force (gtc/po/ioc/fok)
        //         "createdAt" => "2020-09-25T04:51:31.000Z", // order placement time
        //         "balanceChange" => {
        //             "baseGross" => 0,                      // base asset balance's gross change (in BCH for this case)
        //             "baseFee" => array(
        //                 "taking" => 0,                     // base asset fee imposed as taker
        //                 "making" => 0                      // base asset fee imposed as maker
        //             ),
        //             "baseNet" => 0,                        // base asset balance's net change (in BCH for this case)
        //             "quoteGross" => 0,                     // quote asset balance's gross change (in KRW for
        //             "quoteFee" => array(
        //                 "taking" => 0,                     // quote asset fee imposed as taker
        //                 "making" => 0                      // quote asset fee imposed as maker
        //             ),
        //             "quoteNet" => 0                        // quote asset balance's net change (in KRW for this case)
        //         }
        //     }
        //
        return $this->parse_order($response, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array();
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        $method = null;
        if ($clientOrderId === null) {
            $method = 'privateDeleteOrdersOrderId';
            $request['orderId'] = $id;
        } else {
            $method = 'privateDeleteOrdersClientOrderIdClientOrderId';
            $request['clientOrderId'] = $clientOrderId;
            $params = $this->omit($params, 'clientOrderId');
        }
        $response = $this->$method (array_merge($request, $params));
        //
        //     array()
        //
        $order = $this->parse_order($response);
        return array_merge($order, array( 'id' => $id ));
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'limit' => $limit, // max 100
            // 'pastmax' => id, // read data older than this id
            // 'latestmin' => id, // read data newer than this id
            // 'after' => intval($since / 1000), // Read data after this timestamp in seconds
            // 'before' => $this->seconds(), // Read data before this timestamp in seconds
            'deepSearch' => 'true', // read data older than one month ago are inclusively looked up only when it is "true"
        );
        if ($since !== null) {
            $request['after'] = intval($since / 1000);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetTrades (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             "id" => 73953,                             // trading event ID
        //             "orderId" => 453324,                       // order ID
        //             "baseAmount" => 3,                         // traded base asset amount
        //             "quoteAmount" => 3000000,                  // traded quote asset amount
        //             "fee" => 0.0012,                           // fee
        //             "price" => 1000000,                        // price
        //             "timestamp" => "2020-09-25T04:06:30.000Z", // trading time
        //             "side" => "buy",                           // buy, sell
        //             "tradingPairName" => "ZEC-KRW",            // order book
        //             "position" => "maker"                      // maker, taker
        //         ),
        //     )
        //
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_deposit_address($depositAddress, $currency = null) {
        //
        //     {
        //         "asset" => "BTC",                                  // asset name
        //         "$address" => "1CwC2cMFu1jRQUBtw925cENbT1kctJBMdm", // deposit $address
        //         "memoId" => null,                                  // memo ID (showed only for assets using memo ID)
        //         "createdAt" => 1594802312                          // deposit $address creation time
        //     }
        //
        $address = $this->safe_string($depositAddress, 'address');
        $tag = $this->safe_string($depositAddress, 'memoId');
        $currencyId = $this->safe_string($depositAddress, 'asset');
        $code = $this->safe_currency_code($currencyId);
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => $tag,
            'info' => $depositAddress,
        );
    }

    public function parse_deposit_addresses($addresses, $codes = null) {
        $result = array();
        for ($i = 0; $i < count($addresses); $i++) {
            $address = $this->parse_deposit_address($addresses[$i]);
            $result[] = $address;
        }
        if ($codes) {
            $result = $this->filter_by_array($result, 'currency', $codes);
        }
        return $this->index_by($result, 'currency');
    }

    public function fetch_deposit_addresses($codes = null, $params = array ()) {
        $this->load_markets();
        $response = $this->privateGetCryptoDepositAddresses ($params);
        //
        //     array(
        //         array(
        //             "asset" => "BTC",                                  // asset name
        //             "address" => "1CwC2cMFu1jRQUBtw925cENbT1kctJBMdm", // deposit address
        //             "memoId" => null,                                  // memo ID (showed only for assets using memo ID)
        //             "createdAt" => 1594802312                          // deposit address creation time
        //         ),
        //     )
        //
        return $this->parse_deposit_addresses($response, $codes);
    }

    public function fetch_deposit_address($code, $params = array ()) {
        $this->load_markets();
        $response = $this->fetch_deposit_addresses(null, $params);
        $address = $this->safe_value($response, $code);
        if ($address === null) {
            throw new InvalidAddress($this->id . ' fetchDepositAddress() ' . $code . ' $address not found');
        }
        return $address;
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            'reviewing' => 'pending',
            'rejected' => 'rejected',
            'processing' => 'pending',
            'failed' => 'failed',
            'completed' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        //     {
        //         "$id" => 640,                     // deposit/withdrawal event ID
        //         "asset" => "BTC",                // asset name
        //         "$type" => "crypto_withdrawal",   // fiat_withdrawal, fiat_deposit, crypto_withdrawal, crypto_deposit
        //         "netAmount" => 0.0001,           // $amount
        //         "feeAmount" => 0.0005,           // $fee (null if there is no imposed $fee)
        //         "$status" => "completed",         // reviewing, rejected, processing, failed, completed
        //         "reviewStartedAt" => 1595556218, // request time
        //         "completedAt" => 1595556902,     // completion time (showed only in case of completed)
        //         "txId" => "eaca5ad3...",         // tx ID
        //         "sourceAddress" => null,         // sender address (showed only in case of crypto_deposit)
        //         "destinationAddress => "3H8...", // recipient address (showed only in case of crypto_withdrawal)
        //         "destinationMemoId" => null      // recipient address's memo ID
        //     }
        //
        $id = $this->safe_string($transaction, 'id');
        $txid = $this->safe_string($transaction, 'txId');
        $currencyId = $this->safe_string($transaction, 'asset');
        $code = $this->safe_currency_code($currencyId, $currency);
        $type = $this->safe_string($transaction, 'type');
        if (($type === 'crypto_withdrawal') || ($type === 'fiat_withdrawal')) {
            $type = 'withdrawal';
        } else if (($type === 'crypto_deposit' || $type === 'fiat_deposit')) {
            $type = 'deposit';
        }
        $amount = $this->safe_float($transaction, 'netAmount');
        $feeCost = $this->safe_float($transaction, 'feeAmount');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        $timestamp = $this->safe_timestamp($transaction, 'reviewStartedAt');
        $updated = $this->safe_timestamp($transaction, 'completedAt');
        $addressFrom = $this->safe_string($transaction, 'sourceAddress');
        $addressTo = $this->safe_string($transaction, 'destinationAddress');
        $tagFrom = $this->safe_string($transaction, 'sourceMemoId');
        $tagTo = $this->safe_string($transaction, 'destinationMemoId');
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'addressFrom' => $addressFrom,
            'address' => $addressTo,
            'addressTo' => $addressTo,
            'tagFrom' => $tagFrom,
            'tag' => $tagTo,
            'tagTo' => $tagTo,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => $updated,
            'comment' => null,
            'fee' => $fee,
        );
    }

    public function fetch_transactions($code = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'limit' => $limit, // max 20
            // 'latestmin' => $limit, // read data older than this id
            // 'after' => $this->milliseconds(),
            // 'before' => $since,
            // 'completedOnly' => 'no',
        );
        if ($since !== null) {
            $request['before'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetDepositWithdrawalStatus (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             "id" => 640,                     // deposit/withdrawal event ID
        //             "asset" => "BTC",                // asset name
        //             "type" => "crypto_withdrawal",   // fiat_withdrawal, fiat_deposit, crypto_withdrawal, crypto_deposit
        //             "netAmount" => 0.0001,           // amount
        //             "feeAmount" => 0.0005,           // fee (null if there is no imposed fee)
        //             "status" => "completed",         // reviewing, rejected, processing, failed, completed
        //             "reviewStartedAt" => 1595556218, // $request time
        //             "completedAt" => 1595556902,     // completion time (showed only in case of completed)
        //             "txId" => "eaca5ad3...",         // tx ID
        //             "sourceAddress" => null,         // sender address (showed only in case of crypto_deposit)
        //             "destinationAddress => "3H8...", // recipient address (showed only in case of crypto_withdrawal)
        //             "destinationMemoId" => null      // recipient address's memo ID
        //         ),
        //     )
        //
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        return $this->parse_transactions($response, $currency, $since, $limit, $params);
    }

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

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $endpoint = '/' . $this->implode_params($path, $params);
        $url = $this->implode_params($this->urls['api'][$api], array( 'hostname' => $this->hostname )) . $endpoint;
        $query = $this->omit($params, $this->extract_params($path));
        if ($api === 'public') {
            if ($query) {
                $url .= '?' . $this->urlencode($query);
            }
        } else if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = (string) $this->nonce();
            $auth = 't' . $timestamp . $method . $endpoint;
            $headers = array(
                'api-key' => $this->apiKey,
                'timestamp' => $timestamp,
            );
            if ($method === 'POST') {
                $headers['Content-Type'] = 'application/json';
                $body = $this->json($params);
                $auth .= $body;
            } else if ($endpoint === '/orders') {
                if ($query) {
                    $urlQuery = '?' . $this->urlencode($query);
                    $auth .= $urlQuery;
                    $url .= $urlQuery;
                }
            } else if ($method === 'GET') {
                if ($query) {
                    $url .= '?' . $this->urlencode($query);
                }
            }
            $rawSecret = base64_decode($this->secret);
            $signature = $this->hmac($this->encode($auth), $rawSecret, 'sha512', 'base64');
            $headers['signature'] = $signature;
        }
        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("$errorMessage":"Invalid API key","$errorCode":10155)
        //
        if (gettype($response) === 'array' && count(array_filter(array_keys($response), 'is_string')) != 0) {
            $errorCode = $this->safe_string($response, 'errorCode');
            $errorMessage = $this->safe_string($response, 'errorMessage');
            $feedback = $this->id . ' ' . $body;
            if ($errorMessage !== null) {
                $this->throw_broadly_matched_exception($this->exceptions['broad'], $body, $feedback);
            }
            if ($errorCode !== null) {
                $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            }
            if (($errorCode !== null) || ($errorMessage !== null)) {
                throw new ExchangeError($feedback);
            }
        }
    }
}
