<?php

namespace ccxt\async;

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

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

class qtrade extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'qtrade',
            'name' => 'qTrade',
            'countries' => array( 'US' ),
            'rateLimit' => 1000,
            'version' => 'v1',
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/51840849/80491487-74a99c00-896b-11ea-821e-d307e832f13e.jpg',
                'api' => 'https://api.qtrade.io',
                'www' => 'https://qtrade.io',
                'doc' => 'https://qtrade-exchange.github.io/qtrade-docs',
                'referral' => 'https://qtrade.io/?ref=BKOQWVFGRH2C',
            ),
            'has' => array(
                'CORS' => false,
                'fetchTrades' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchMarkets' => true,
                'fetchCurrencies' => true,
                'fetchBalance' => true,
                'fetchOrderBook' => true,
                'fetchOrder' => true,
                'fetchOrders' => true,
                'fetchMyTrades' => true,
                'fetchClosedOrders' => true,
                'fetchOpenOrders' => true,
                'fetchOHLCV' => true,
                'createOrder' => true,
                'cancelOrder' => true,
                'createMarketOrder' => false,
                'withdraw' => true,
                'fetchDepositAddress' => true,
                'fetchTransactions' => false,
                'fetchDeposits' => true,
                'fetchWithdrawals' => true,
                'fetchDeposit' => true,
                'fetchWithdrawal' => true,
            ),
            'timeframes' => array(
                '5m' => 'fivemin',
                '15m' => 'fifteenmin',
                '30m' => 'thirtymin',
                '1h' => 'onehour',
                '2h' => 'twohour',
                '4h' => 'fourhour',
                '1d' => 'oneday',
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'ticker/{market_string}',
                        'tickers',
                        'currency/{code}',
                        'currencies',
                        'common',
                        'market/{market_string}',
                        'markets',
                        'market/{market_string}/trades',
                        'orderbook/{market_string}',
                        'market/{market_string}/ohlcv/{interval}',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'me',
                        'balances',
                        'balances_all', // undocumented
                        'market/{market_string}',
                        'orders',
                        'order/{order_id}',
                        'trades',
                        'withdraw/{withdraw_id}',
                        'withdraws',
                        'deposit/{deposit_id}',
                        'deposits',
                        'transfers',
                    ),
                    'post' => array(
                        'cancel_order',
                        'withdraw',
                        'deposit_address/{currency}',
                        'sell_limit',
                        'buy_limit',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => 0.0025,
                    'maker' => 0.0,
                ),
                'funding' => array(
                    'withdraw' => array(),
                ),
            ),
            'exceptions' => array(
                'exact' => array(
                    'invalid_auth' => '\\ccxt\\AuthenticationError',
                    'insuff_funds' => '\\ccxt\\InsufficientFunds',
                ),
            ),
        ));
    }

    public function fetch_markets($params = array ()) {
        $response = yield $this->publicGetMarkets ($params);
        //
        //     {
        //         "$data":{
        //             "$markets":array(
        //                 {
        //                     "id":5,
        //                     "market_currency":"BAC",
        //                     "base_currency":"BTC",
        //                     "maker_fee":"0.0025",
        //                     "taker_fee":"0.0025",
        //                     "metadata":array(
        //                         "delisting_date":"7/15/2018",
        //                         "market_notices":array(
        //                             array(
        //                                 "message":"Delisting Notice => This $market has been delisted due to low volume. Please cancel your orders and withdraw your funds by 7/15/2018.",
        //                                 "type":"warning"
        //                             }
        //                         )
        //                     ),
        //                     "can_trade":false,
        //                     "can_cancel":true,
        //                     "can_view":false,
        //                     "market_string":"BAC_BTC",
        //                     "minimum_sell_amount":"0.0001",
        //                     "minimum_buy_value":"0.0001",
        //                     "market_precision":8,
        //                     "base_precision":8
        //                 ),
        //             ),
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $markets = $this->safe_value($data, 'markets', array());
        $result = array();
        for ($i = 0; $i < count($markets); $i++) {
            $market = $markets[$i];
            $marketId = $this->safe_string($market, 'market_string');
            $numericId = $this->safe_integer($market, 'id');
            $baseId = $this->safe_string($market, 'market_currency');
            $quoteId = $this->safe_string($market, 'base_currency');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $symbol = $base . '/' . $quote;
            $precision = array(
                'amount' => $this->safe_integer($market, 'market_precision'),
                'price' => $this->safe_integer($market, 'base_precision'),
            );
            $canView = $this->safe_value($market, 'can_view', false);
            $canTrade = $this->safe_value($market, 'can_trade', false);
            $active = $canTrade && $canView;
            $result[] = array(
                'symbol' => $symbol,
                'id' => $marketId,
                'numericId' => $numericId,
                'baseId' => $baseId,
                'quoteId' => $quoteId,
                'base' => $base,
                'quote' => $quote,
                'active' => $active,
                'precision' => $precision,
                'taker' => $this->safe_float($market, 'taker_fee'),
                'maker' => $this->safe_float($market, 'maker_fee'),
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($market, 'minimum_buy_value'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'info' => $market,
            );
        }
        return $result;
    }

    public function fetch_currencies($params = array ()) {
        $response = yield $this->publicGetCurrencies ($params);
        //
        //     {
        //         "$data":{
        //             "$currencies":array(
        //                 {
        //                     "$code":"DGB",
        //                     "long_name":"Digibyte",
        //                     "$type":"bitcoin_like",
        //                     "precision":8,
        //                     "$config":array(
        //                         "price":0.0035,
        //                         "withdraw_fee":"10",
        //                         "deposit_types":array(
        //                             array(
        //                                 "label":"Address",
        //                                 "lookup_mode":"address",
        //                                 "render_type":"address",
        //                                 "deposit_type":"address",
        //                                 "lookup_config":array()
        //                             }
        //                         ),
        //                         "default_signer":103,
        //                         "address_version":30,
        //                         "satoshi_per_byte":300,
        //                         "required_confirmations":200,
        //                         "required_generate_confirmations":300
        //                     ),
        //                     "metadata":array(),
        //                     "minimum_order":"0.0001",
        //                     "$status":"ok",
        //                     "can_withdraw":true,
        //                     "delisted":false,
        //                     "deposit_disabled":false,
        //                     "withdraw_disabled":false,
        //                     "deposit_warn_codes":array(),
        //                     "withdraw_warn_codes":array()
        //                 ),
        //             ),
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $currencies = $this->safe_value($data, 'currencies', array());
        $result = array();
        for ($i = 0; $i < count($currencies); $i++) {
            $currency = $currencies[$i];
            $id = $this->safe_string($currency, 'code');
            $code = $this->safe_currency_code($id);
            $name = $this->safe_string($currency, 'long_name');
            $type = $this->safe_string($currency, 'type');
            $canWithdraw = $this->safe_value($currency, 'can_withdraw', true);
            $depositDisabled = $this->safe_value($currency, 'deposit_disabled', false);
            $config = $this->safe_value($currency, 'config', array());
            $status = $this->safe_string($currency, 'status');
            $active = $canWithdraw && ($status === 'ok') && !$depositDisabled;
            $result[$code] = array(
                'id' => $id,
                'code' => $code,
                'info' => $currency,
                'type' => $type,
                'name' => $name,
                'fee' => $this->safe_float($config, 'withdraw_fee'),
                'precision' => $this->safe_integer($currency, 'precision'),
                'active' => $active,
                'limits' => array(
                    'amount' => array(
                        'min' => $this->safe_float($currency, 'minimum_order'),
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
            );
        }
        return $result;
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         "time":"2019-12-07T22:55:00Z",
        //         "open":"0.00197",
        //         "high":"0.00197",
        //         "low":"0.00197",
        //         "close":"0.00197",
        //         "volume":"0.00016676",
        //         "market_volume":"0.08465047"
        //     }
        //
        return array(
            $this->parse8601($this->safe_string($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, 'market_volume'),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '5m', $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_string' => $market['id'],
            'interval' => $this->timeframes[$timeframe],
        );
        $response = yield $this->publicGetMarketMarketStringOhlcvInterval (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "slices":array(
        //                 array("time":"2019-12-07T22:55:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0.00016676","market_volume":"0.08465047"),
        //                 array("time":"2019-12-07T23:00:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"),
        //                 array("time":"2019-12-07T23:05:00Z","open":"0.00197","high":"0.00197","low":"0.00197","close":"0.00197","volume":"0","market_volume":"0"),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $ohlcvs = $this->safe_value($data, 'slices', array());
        return $this->parse_ohlcvs($ohlcvs, $market, $timeframe, $since, $limit);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $marketId = $this->market_id($symbol);
        $request = array( 'market_string' => $marketId );
        $response = yield $this->publicGetOrderbookMarketString (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "buy":array(
        //                 "0.00700015":"4.76196367",
        //                 "0.00700017":"1.89755391",
        //                 "0.00700018":"2.13214088",
        //             ),
        //             "last_change":1588539869958811,
        //             "sell":{
        //                 "0.02418662":"0.19513696",
        //                 "0.02465627":"0.2439212",
        //                 "0.02530277":"0.663475931274359255",
        //             }
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $orderbook = array();
        $sides = array( 'buy' => 'bids', 'sell' => 'asks' );
        $keys = is_array($sides) ? array_keys($sides) : array();
        for ($i = 0; $i < count($keys); $i++) {
            $key = $keys[$i];
            $side = $sides[$key];
            $bidasks = $this->safe_value($data, $key, array());
            $prices = is_array($bidasks) ? array_keys($bidasks) : array();
            $result = array();
            for ($j = 0; $j < count($prices); $j++) {
                $priceAsString = $prices[$j];
                $price = $this->safe_float($prices, $j);
                $amount = $this->safe_float($bidasks, $priceAsString);
                $result[] = array( $price, $amount );
            }
            $orderbook[$side] = $result;
        }
        $timestamp = $this->safe_integer_product($data, 'last_change', 0.001);
        return $this->parse_order_book($orderbook, $timestamp);
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker, fetchTickers
        //
        //     {
        //         "ask":"0.02423119",
        //         "bid":"0.0230939",
        //         "day_avg_price":"0.0247031874349301",
        //         "$day_change":"-0.0237543162270376",
        //         "day_high":"0.02470552",
        //         "day_low":"0.02470172",
        //         "day_open":"0.02530277",
        //         "day_volume_base":"0.00268074",
        //         "day_volume_market":"0.10851798",
        //         "id":41,
        //         "id_hr":"ETH_BTC",
        //         "$last":"0.02470172",
        //         "last_change":1588533365354609
        //     }
        //
        $marketId = $this->safe_string($ticker, 'id_hr');
        $symbol = $this->safe_symbol($marketId, $market, '_');
        $timestamp = $this->safe_integer_product($ticker, 'last_change', 0.001);
        $previous = $this->safe_float($ticker, 'day_open');
        $last = $this->safe_float($ticker, 'last');
        $day_change = $this->safe_float($ticker, 'day_change');
        $percentage = null;
        $change = null;
        $average = $this->safe_float($ticker, 'day_avg_price');
        if ($day_change !== null) {
            $percentage = $day_change * 100;
            if ($previous !== null) {
                $change = $day_change * $previous;
            }
        }
        if (($average === null) && ($last !== null) && ($previous !== null)) {
            $average = $this->sum($last, $previous) / 2;
        }
        $baseVolume = $this->safe_float($ticker, 'day_volume_market');
        $quoteVolume = $this->safe_float($ticker, 'day_volume_base');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        return array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'day_high'),
            'low' => $this->safe_float($ticker, 'day_low'),
            'bid' => $this->safe_float($ticker, 'bid'),
            'bidVolume' => null,
            'ask' => $this->safe_float($ticker, 'ask'),
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => $previous,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $change,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
    }

    public function fetch_tickers($symbols = null, $params = array ()) {
        yield $this->load_markets();
        $response = yield $this->publicGetTickers ($params);
        //
        //     {
        //         "$data":{
        //             "markets":array(
        //                 array(
        //                     "ask":"0.0000003",
        //                     "bid":"0.00000029",
        //                     "day_avg_price":"0.0000002999979728",
        //                     "day_change":"0.0344827586206897",
        //                     "day_high":"0.0000003",
        //                     "day_low":"0.0000003",
        //                     "day_open":"0.00000029",
        //                     "day_volume_base":"0.00591958",
        //                     "day_volume_market":"19732.06666665",
        //                     "id":36,
        //                     "id_hr":"DOGE_BTC",
        //                     "last":"0.0000003",
        //                     "last_change":1588534202130778
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $tickers = $this->safe_value($data, 'markets', array());
        $result = array();
        for ($i = 0; $i < count($tickers); $i++) {
            $ticker = $this->parse_ticker($tickers[$i]);
            $symbol = $ticker['symbol'];
            $result[$symbol] = $ticker;
        }
        return $this->filter_by_array($result, 'symbol', $symbols);
    }

    public function fetch_ticker($symbol, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_string' => $market['id'],
        );
        $response = yield $this->publicGetTickerMarketString (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "ask":"0.02423119",
        //             "bid":"0.0230939",
        //             "day_avg_price":"0.0247031874349301",
        //             "day_change":"-0.0237543162270376",
        //             "day_high":"0.02470552",
        //             "day_low":"0.02470172",
        //             "day_open":"0.02530277",
        //             "day_volume_base":"0.00268074",
        //             "day_volume_market":"0.10851798",
        //             "id":41,
        //             "id_hr":"ETH_BTC",
        //             "last":"0.02470172",
        //             "last_change":1588533365354609
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_ticker($data, $market);
    }

    public function fetch_trades($symbol, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'market_string' => $market['id'],
            // 'older_than' => 123, // returns $trades with id < older_than
            // 'newer_than' => 123, // returns $trades with id > newer_than
        );
        $response = yield $this->publicGetMarketMarketStringTrades (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "$trades":array(
        //                 array(
        //                     "id":85507,
        //                     "amount":"0.09390502",
        //                     "price":"0.02556325",
        //                     "base_volume":"0.00240051",
        //                     "seller_taker":true,
        //                     "side":"sell",
        //                     "created_at":"0001-01-01T00:00:00Z",
        //                     "created_at_ts":1581560391338718
        //                 ),
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $trades = $this->safe_value($data, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'desc' => true, // Returns newest $trades first when true
            // 'older_than' => 123, // returns $trades with id < older_than
            // 'newer_than' => 123, // returns $trades with id > newer_than
        );
        $market = null;
        $numericId = $this->safe_value($params, 'market_id');
        if ($numericId !== null) {
            $request['market_id'] = $numericId; // mutually exclusive with market_string
        } else if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market_string'] = $market['id'];
        }
        $response = yield $this->privateGetTrades (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "$trades":array(
        //                 {
        //                     "id":107331,
        //                     "market_amount":"0.1082536946986",
        //                     "price":"0.0230939",
        //                     "base_amount":"0.00249999",
        //                     "order_id":13790596,
        //                     "market_id":41,
        //                     "market_string":"ETH_BTC",
        //                     "taker":true,
        //                     "base_fee":"0.00001249",
        //                     "side":"sell",
        //                     "created_at":"2020-05-04T06:08:18.513413Z"
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $trades = $this->safe_value($data, 'trades', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function parse_trade($trade, $market = null) {
        //
        // fetchTrades (public)
        //
        //     {
        //         "$id":85507,
        //         "$amount":"0.09390502",
        //         "$price":"0.02556325",
        //         "base_volume":"0.00240051",
        //         "seller_taker":true,
        //         "$side":"sell",
        //         "created_at":"0001-01-01T00:00:00Z",
        //         "created_at_ts":1581560391338718
        //     }
        //
        // fetchMyTrades (private)
        //
        //     {
        //         "$id":107331,
        //         "market_amount":"0.1082536946986",
        //         "$price":"0.0230939",
        //         "base_amount":"0.00249999",
        //         "order_id":13790596,
        //         "market_id":41,
        //         "market_string":"ETH_BTC",
        //         "$taker":true,
        //         "base_fee":"0.00001249",
        //         "$side":"sell",
        //         "created_at":"2020-05-04T06:08:18.513413Z"
        //     }
        //
        // createOrder, fetchOrders, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "base_amount" => "9.58970687",
        //         "base_fee" => "0.02397426",
        //         "created_at" => "0001-01-01T00:00:00Z",
        //         "$id" => 0,
        //         "market_amount" => "0.97179355",
        //         "$price" => "9.86804952",
        //         "$taker" => true
        //     }
        //
        $id = $this->safe_string($trade, 'id');
        $timestamp = $this->safe_integer_product($trade, 'created_at_ts', 0.001);
        if ($timestamp === null) {
            $timestamp = $this->parse8601($this->safe_string($trade, 'created_at'));
        }
        $side = $this->safe_string($trade, 'side');
        $marketId = $this->safe_string($trade, 'market_string');
        $symbol = $this->safe_symbol($marketId, $market, '_');
        $cost = $this->safe_float_2($trade, 'base_volume', 'base_amount');
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float_2($trade, 'market_amount', 'amount');
        if (($cost === null) && ($amount !== null) && ($price !== null)) {
            if ($price !== null) {
                $cost = $price * $amount;
            }
        }
        $fee = null;
        $feeCost = $this->safe_float($trade, 'base_fee');
        if ($feeCost !== null) {
            $feeCurrencyCode = ($market === null) ? null : $market['quote'];
            $fee = array(
                'currency' => $feeCurrencyCode,
                'cost' => $feeCost,
            );
        }
        $taker = $this->safe_value($trade, 'taker', true);
        $takerOrMaker = $taker ? 'taker' : 'maker';
        $orderId = $this->safe_string($trade, 'order_id');
        $result = array(
            'id' => $id,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        );
        return $result;
    }

    public function fetch_balance($params = array ()) {
        yield $this->load_markets();
        $response = yield $this->privateGetBalancesAll ($params);
        //
        //     {
        //         "$data":{
        //             "$balances" => array(
        //                 array( "$balance" => "100000000", "currency" => "BCH" ),
        //                 array( "$balance" => "99992435.78253015", "currency" => "LTC" ),
        //                 array( "$balance" => "99927153.76074182", "currency" => "BTC" ),
        //             ),
        //             "order_balances":array(),
        //             "limit_used":0,
        //             "limit_remaining":4000,
        //             "limit":4000
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $balances = $this->safe_value($data, 'balances', array());
        $result = array(
            'info' => $response,
        );
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = (is_array($result) && array_key_exists($code, $result)) ? $result[$code] : $this->account();
            $account['free'] = $this->safe_float($balance, 'balance');
            $account['used'] = 0;
            $result[$code] = $account;
        }
        $balances = $this->safe_value($data, 'order_balances', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $account = (is_array($result) && array_key_exists($code, $result)) ? $result[$code] : $this->account();
            $account['used'] = $this->safe_float($balance, 'balance');
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        if ($type !== 'limit') {
            throw new InvalidOrder($this->id . ' createOrder() allows limit orders only');
        }
        yield $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'amount' => $this->amount_to_precision($symbol, $amount),
            'market_id' => $market['numericId'],
            'price' => $this->price_to_precision($symbol, $price),
        );
        $method = ($side === 'sell') ? 'privatePostSellLimit' : 'privatePostBuyLimit';
        $response = yield $this->$method (array_merge($request, $params));
        //
        //     {
        //         "$data" => {
        //             "$order" => {
        //                 "created_at" => "2018-04-06T20:46:52.899248Z",
        //                 "id" => 13253,
        //                 "market_amount" => "1",
        //                 "market_amount_remaining" => "0",
        //                 "market_id" => 1,
        //                 "open" => false,
        //                 "order_type" => "sell_limit",
        //                 "$price" => "0.01",
        //                 "trades" => array(
        //                     array(
        //                         "base_amount" => "0.27834267",
        //                         "base_fee" => "0.00069585",
        //                         "created_at" => "0001-01-01T00:00:00Z",
        //                         "id" => 0,
        //                         "market_amount" => "0.02820645",
        //                         "$price" => "9.86805058",
        //                         "taker" => true
        //                     ),
        //                     {
        //                         "base_amount" => "9.58970687",
        //                         "base_fee" => "0.02397426",
        //                         "created_at" => "0001-01-01T00:00:00Z",
        //                         "id" => 0,
        //                         "market_amount" => "0.97179355",
        //                         "$price" => "9.86804952",
        //                         "taker" => true
        //                     }
        //                 )
        //             }
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $order = $this->safe_value($data, 'order', array());
        return $this->parse_order($order, $market);
    }

    public function parse_order($order, $market = null) {
        //
        // createOrder
        //
        //     {
        //         "created_at" => "2018-04-06T20:46:52.899248Z",
        //         "$id" => 13253,
        //         "market_amount" => "1",
        //         "market_amount_remaining" => "0",
        //         "market_id" => 1,
        //         "$open" => false,
        //         "order_type" => "sell_limit",
        //         "$price" => "0.01",
        //         "trades" => array(
        //             array(
        //                 "base_amount" => "0.27834267",
        //                 "base_fee" => "0.00069585",
        //                 "created_at" => "0001-01-01T00:00:00Z",
        //                 "$id" => 0,
        //                 "market_amount" => "0.02820645",
        //                 "$price" => "9.86805058",
        //                 "taker" => true
        //             ),
        //             {
        //                 "base_amount" => "9.58970687",
        //                 "base_fee" => "0.02397426",
        //                 "created_at" => "0001-01-01T00:00:00Z",
        //                 "$id" => 0,
        //                 "market_amount" => "0.97179355",
        //                 "$price" => "9.86804952",
        //                 "taker" => true
        //             }
        //         )
        //     }
        //
        // fetchOrder
        //
        //     {
        //         $id => 13790596,
        //         market_amount => "0.15",
        //         market_amount_remaining => "0",
        //         created_at => "2020-05-04T06:08:18.513413Z",
        //         $price => "0.0230939",
        //         base_amount => "0",
        //         order_type => "sell_limit",
        //         market_id => 41,
        //         market_string => "ETH_BTC",
        //         $open => false,
        //         trades => array(
        //             {
        //                 $id => 107331,
        //                 market_amount => "0.1082536946986",
        //                 $price => "0.0230939",
        //                 base_amount => "0.00249999",
        //                 taker => true,
        //                 base_fee => "0.00001249",
        //                 created_at => "2020-05-04T06:08:18.513413Z",
        //             }
        //         ),
        //         close_reason => "canceled"
        //     }
        //
        $id = $this->safe_string($order, 'id');
        $timestamp = $this->parse8601($this->safe_string($order, 'created_at'));
        $sideType = $this->safe_string($order, 'order_type');
        $orderType = null;
        $side = null;
        if ($sideType !== null) {
            $parts = explode('_', $sideType);
            $side = $this->safe_string($parts, 0);
            $orderType = $this->safe_string($parts, 1);
        }
        $price = $this->safe_float($order, 'price');
        $amount = $this->safe_float($order, 'market_amount');
        $remaining = $this->safe_float($order, 'market_amount_remaining');
        $filled = null;
        $open = $this->safe_value($order, 'open', false);
        $closeReason = $this->safe_string($order, 'close_reason');
        $status = null;
        if ($open) {
            $status = 'open';
        } else if ($closeReason === 'canceled') {
            $status = 'canceled';
        } else {
            $status = 'closed';
        }
        $marketId = $this->safe_string($order, 'market_string');
        $symbol = $this->safe_symbol($marketId, $market, '_');
        $rawTrades = $this->safe_value($order, 'trades', array());
        $parsedTrades = $this->parse_trades($rawTrades, $market, null, null, array(
            'order' => $id,
            'side' => $side,
            'type' => $orderType,
        ));
        $numTrades = is_array($parsedTrades) ? count($parsedTrades) : 0;
        $lastTradeTimestamp = null;
        $feeCost = null;
        $cost = null;
        if ($numTrades > 0) {
            $feeCost = 0;
            $cost = 0;
            $filled = 0;
            $remaining = $amount;
            for ($i = 0; $i < count($parsedTrades); $i++) {
                $trade = $parsedTrades[$i];
                $feeCost = $this->sum($trade['fee']['cost'], $feeCost);
                $lastTradeTimestamp = $this->safe_integer($trade, 'timestamp');
                $cost = $this->sum($trade['cost'], $cost);
                $filled = $this->sum($trade['amount'], $filled);
                $remaining = max (0, $remaining - $trade['amount']);
            }
        }
        $fee = null;
        if ($feeCost !== null) {
            $feeCurrencyCode = ($market === null) ? null : $market['quote'];
            $fee = array(
                'currency' => $feeCurrencyCode,
                'cost' => $feeCost,
            );
        }
        if (($amount !== null) && ($remaining !== null)) {
            $filled = max (0, $amount - $remaining);
        }
        $average = null;
        if ($filled !== null) {
            if (($price !== null) && ($cost === null)) {
                $cost = $filled * $price;
            }
            if (($cost !== null) && ($filled > 0)) {
                $average = $cost / $filled;
            }
        }
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $lastTradeTimestamp,
            'symbol' => $symbol,
            'type' => $orderType,
            'timeInForce' => null,
            'postOnly' => null,
            'side' => $side,
            'price' => $price,
            'stopPrice' => null,
            'average' => $average,
            'amount' => $amount,
            'remaining' => $remaining,
            'filled' => $filled,
            'status' => $status,
            'fee' => $fee,
            'cost' => $cost,
            'trades' => $parsedTrades,
        );
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $request = array(
            'id' => intval($id),
        );
        // successful cancellation returns 200 with no payload
        return yield $this->privatePostCancelOrder (array_merge($request, $params));
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        yield $this->load_markets();
        $request = array( 'order_id' => $id );
        $response = yield $this->privateGetOrderOrderId (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "$order":{
        //                 "$id":13790596,
        //                 "market_amount":"0.15",
        //                 "market_amount_remaining":"0.0417463053014",
        //                 "created_at":"2020-05-04T06:08:18.513413Z",
        //                 "price":"0.0230939",
        //                 "order_type":"sell_limit",
        //                 "market_id":41,
        //                 "market_string":"ETH_BTC",
        //                 "open":true,
        //                 "trades":array(
        //                     {
        //                         "$id":107331,
        //                         "market_amount":"0.1082536946986",
        //                         "price":"0.0230939",
        //                         "base_amount":"0.00249999",
        //                         "taker":true,
        //                         "base_fee":"0.00001249",
        //                         "created_at":"2020-05-04T06:08:18.513413Z"
        //                     }
        //                 )
        //             }
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $order = $this->safe_value($data, 'order', array());
        return $this->parse_order($order);
    }

    public function fetch_orders($symbol = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            // 'open' => true,
            // 'older_than' => 123, // returns $orders with id < older_than
            // 'newer_than' => 123, // returns $orders with id > newer_than
        );
        $market = null;
        $numericId = $this->safe_value($params, 'market_id');
        if ($numericId !== null) {
            $request['market_id'] = $numericId; // mutually exclusive with market_string
        } else if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['market_string'] = $market['id'];
        }
        $response = yield $this->privateGetOrders (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "$orders":array(
        //                 {
        //                     "id":13790596,
        //                     "market_amount":"0.15",
        //                     "market_amount_remaining":"0.0417463053014",
        //                     "created_at":"2020-05-04T06:08:18.513413Z",
        //                     "price":"0.0230939",
        //                     "order_type":"sell_limit",
        //                     "market_id":41,
        //                     "market_string":"ETH_BTC",
        //                     "open":true,
        //                     "trades":array(
        //                         {
        //                             "id":107331,
        //                             "market_amount":"0.1082536946986",
        //                             "price":"0.0230939",
        //                             "base_amount":"0.00249999",
        //                             "taker":true,
        //                             "base_fee":"0.00001249",
        //                             "created_at":"2020-05-04T06:08:18.513413Z"
        //                         }
        //                     )
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $orders = $this->safe_value($data, 'orders', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

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

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

    public function parse_deposit_address($depositAddress, $currency = null) {
        //
        //     {
        //         "$address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //         "currency_status":"ok",
        //         "deposit_methods":array(
        //             "$address":array(
        //                 "deposit_type":"$address",
        //                 "render_type":"$address",
        //                 "label":"Address",
        //                 "$address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //             ),
        //         ),
        //     }
        //
        $code = ($currency === null) ? null : $currency['code'];
        $address = $this->safe_string($depositAddress, 'address');
        $tag = null;
        if ($address !== null) {
            $parts = explode(':', $address);
            $address = $this->safe_string($parts, 0);
            $tag = $this->safe_string($parts, 1);
        }
        $this->check_address($address);
        return array(
            'currency' => $code,
            'address' => $address,
            'tag' => $tag,
            'info' => $depositAddress,
        );
    }

    public function fetch_deposit_address($code, $params = array ()) {
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'currency' => $currency['id'],
        );
        $response = yield $this->privatePostDepositAddressCurrency (array_merge($request, $params));
        //
        //     {
        //         "$data":array(
        //             "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //             "currency_status":"ok",
        //             "deposit_methods":array(
        //                 "address":array(
        //                     "deposit_type":"address",
        //                     "render_type":"address",
        //                     "label":"Address",
        //                     "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                 ),
        //             ),
        //         ),
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        return $this->parse_deposit_address($data, $currency);
    }

    public function fetch_deposit($id, $code = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'deposit_id' => $id,
        );
        $response = yield $this->privateGetDepositDepositId (array_merge($request, $params));
        //
        //     {
        //         "$data":{
        //             "$deposit":{
        //                 "$id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        //                 "amount":"0.13",
        //                 "currency":"ETH",
        //                 "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                 "status":"credited",
        //                 "relay_status":"",
        //                 "network_data":array(
        //                     "confirms":87,
        //                     "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        //                     "sweep_balance":"0.150000000000000000",
        //                     "confirms_required":80,
        //                     "unsigned_sweep_tx":array(
        //                         "chainId":1,
        //                         "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                         "gas":"0x5208",
        //                         "gasPrice":"0x19b45a500",
        //                         "nonce":"0x0",
        //                         "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        //                         "value":"0x214646b6347d800"
        //                     ),
        //                     "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        //                     "tx_index":"0x6f",
        //                     "tx_value":"0.130000000000000000",
        //                     "key_index":311,
        //                     "blockheight":9877869,
        //                     "signed_sweep_tx":array(
        //                         "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        //                         "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
        //                     ),
        //                     "estimated_sweep_tx_fee":144900000000000
        //                 ),
        //                 "created_at":"2020-05-04T05:38:42.145162Z"
        //             }
        //         }
        //     }
        $data = $this->safe_value($response, 'data', array());
        $deposit = $this->safe_value($data, 'deposit', array());
        return $this->parse_transaction($deposit);
    }

    public function fetch_deposits($code = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $response = yield $this->privateGetDeposits ($params);
        //
        //     {
        //         "$data":{
        //             "$deposits":array(
        //                 {
        //                     "id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        //                     "amount":"0.13",
        //                     "$currency":"ETH",
        //                     "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                     "status":"credited",
        //                     "relay_status":"",
        //                     "network_data":array(
        //                         "confirms":87,
        //                         "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        //                         "sweep_balance":"0.150000000000000000",
        //                         "confirms_required":80,
        //                         "unsigned_sweep_tx":array(
        //                             "chainId":1,
        //                             "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                             "gas":"0x5208",
        //                             "gasPrice":"0x19b45a500",
        //                             "nonce":"0x0",
        //                             "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        //                             "value":"0x214646b6347d800"
        //                         ),
        //                         "txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        //                         "tx_index":"0x6f",
        //                         "tx_value":"0.130000000000000000",
        //                         "key_index":311,
        //                         "blockheight":9877869,
        //                         "signed_sweep_tx":array(
        //                             "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        //                             "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
        //                         ),
        //                         "estimated_sweep_tx_fee":144900000000000
        //                     ),
        //                     "created_at":"2020-05-04T05:38:42.145162Z"
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $deposits = $this->safe_value($data, 'deposits', array());
        return $this->parse_transactions($deposits, $currency, $since, $limit);
    }

    public function fetch_withdrawal($id, $code = null, $params = array ()) {
        yield $this->load_markets();
        $request = array(
            'withdraw_id' => $id,
        );
        $response = yield $this->privateGetWithdrawWithdrawId (array_merge($request, $params));
        //
        //     {
        //         $data => {
        //             withdraw => {
        //                 "$id":25524,
        //                 "amount":"0.0417463053014",
        //                 "user_id":0,
        //                 "currency":"ETH",
        //                 "network_data":{
        //                     "unsigned_tx":array(
        //                         "chainId":1,
        //                         "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        //                         "gas":"0x5208",
        //                         "gasPrice":"0x20c8558e9",
        //                         "nonce":"0xf3",
        //                         "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                         "value":"0x71712bcd113308"
        //                     ),
        //                     "estimated_tx_fee":184800004893000,
        //                     "confirms_required":80,
        //                     "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        //                     "confirms":83,
        //                     "signed_tx":array(
        //                         "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        //                         "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
        //                     }
        //                 ),
        //                 "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                 "status":"confirmed",
        //                 "relay_status":"",
        //                 "created_at":"2020-05-05T06:32:19.907061Z",
        //                 "cancel_requested":false
        //             }
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $withdrawal = $this->safe_value($data, 'withdraw', array());
        return $this->parse_transaction($withdrawal);
    }

    public function fetch_withdrawals($code = null, $since = null, $limit = null, $params = array ()) {
        yield $this->load_markets();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
        }
        $response = yield $this->privateGetWithdraws ($params);
        //     {
        //         "$data":{
        //             "withdraws":array(
        //                 {
        //                     "id":25524,
        //                     "amount":"0.0417463053014",
        //                     "user_id":0,
        //                     "$currency":"ETH",
        //                     "network_data":{
        //                         "unsigned_tx":array(
        //                             "chainId":1,
        //                             "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        //                             "gas":"0x5208",
        //                             "gasPrice":"0x20c8558e9",
        //                             "nonce":"0xf3",
        //                             "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                             "value":"0x71712bcd113308"
        //                         ),
        //                         "estimated_tx_fee":184800004893000,
        //                         "confirms_required":80,
        //                         "txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        //                         "confirms":83,
        //                         "signed_tx":array(
        //                             "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        //                             "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
        //                         }
        //                     ),
        //                     "address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                     "status":"confirmed",
        //                     "relay_status":"",
        //                     "created_at":"2020-05-05T06:32:19.907061Z",
        //                     "cancel_requested":false
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $withdrawals = $this->safe_value($data, 'withdraws', array());
        return $this->parse_transactions($withdrawals, $currency, $since, $limit);
    }

    public function parse_transaction($transaction, $currency = null) {
        //
        // fetchDeposits, fetchDeposit
        //
        //     {
        //         "$id":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        //         "$amount":"0.13",
        //         "$currency":"ETH",
        //         "$address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //         "$status":"credited",
        //         "relay_status":"",
        //         "network_data":array(
        //             "confirms":87,
        //             "sweep_txid":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        //             "sweep_balance":"0.150000000000000000",
        //             "confirms_required":80,
        //             "unsigned_sweep_tx":array(
        //                 "chainId":1,
        //                 "from":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                 "gas":"0x5208",
        //                 "gasPrice":"0x19b45a500",
        //                 "nonce":"0x0",
        //                 "to":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        //                 "value":"0x214646b6347d800"
        //             ),
        //             "$txid":"0xaa6e65ed274c4786e5dec3671de96f81021cacdbc453b1a133ab84356f3620a0",
        //             "tx_index":"0x6f",
        //             "tx_value":"0.130000000000000000",
        //             "key_index":311,
        //             "blockheight":9877869,
        //             "signed_sweep_tx":array(
        //                 "hash":"0xa16e65ed274d4686e5dec3671de96f81021cacdbc453b1a133ab85356f3630a0",
        //                 "rawTransaction":"0xd86c8085019b45a1008252099476cb80202b2c31e9d7f595a31fd071ce7f75bb93880214646b6347d8008046a08c6e3bfe8b25bff2b6851c87ea17c63d7b23591210ab0779a568eaa43dc40435a030e964bb2b667072ea7cbc8ab554403e3f3ead9b554743f2fdc2b1e06e998df9"
        //             ),
        //             "estimated_sweep_tx_fee":144900000000000
        //         ),
        //         "created_at":"2020-05-04T05:38:42.145162Z"
        //     }
        //
        // fetchWithdrawals, fetchWithdrawal
        //
        //     {
        //         "$id":25524,
        //         "$amount":"0.0417463053014",
        //         "user_id":0,
        //         "$currency":"ETH",
        //         "network_data":{
        //             "unsigned_tx":array(
        //                 "chainId":1,
        //                 "from":"0x76Cd80202a2C31e9D8F595a31ed071CE7F75BB93",
        //                 "gas":"0x5208",
        //                 "gasPrice":"0x20c8558e9",
        //                 "nonce":"0xf3",
        //                 "to":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //                 "value":"0x71712bcd113308"
        //             ),
        //             "estimated_tx_fee":184800004893000,
        //             "confirms_required":80,
        //             "$txid":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        //             "confirms":83,
        //             "signed_tx":array(
        //                 "hash":"0x79439b62473d61d99ce1dc6c3b8a417da36d45323a394bb0d4af870608fef38d",
        //                 "rawTransaction":"0xf86c81f385021c8558e98252089401b0a9b7b4cde774af0f3e87cb4f1c2ccdba08068771712acd1133078025a0088157d119d924d47413c81b91b9f18ff148623a2ef13dab1895ca3ba546b771a046a021b1e1f64d1a60bb66c19231f641b352326188a9ed3b931b698a939f78d0"
        //             }
        //         ),
        //         "$address":"0xe0cd26f9A60118555247aE6769A5d241D91f07f2",
        //         "$status":"confirmed",
        //         "relay_status":"",
        //         "created_at":"2020-05-05T06:32:19.907061Z",
        //         "cancel_requested":false
        //     }
        //
        // withdraw
        //
        //     {
        //         "$code" => "initiated",
        //         "$id" => 3,
        //         "result" => "Withdraw initiated. Please allow 3-5 minutes for our system to process."
        //     }
        //
        $timestamp = $this->parse8601($this->safe_string($transaction, 'created_at'));
        $id = $this->safe_string($transaction, 'id');
        $networkData = $this->safe_value($transaction, 'network_data', array());
        $unsignedTx = $this->safe_value($networkData, 'unsigned_tx', array());
        $addressFrom = $this->safe_string($unsignedTx, 'from');
        $txid = $this->safe_string($networkData, 'txid');
        $address = $this->safe_string($transaction, 'address');
        $tag = null;
        if ($address !== null) {
            $parts = explode(':', $address);
            $numParts = is_array($parts) ? count($parts) : 0;
            if ($numParts > 1) {
                $address = $this->safe_string($parts, 0);
                $tag = $this->safe_string($parts, 1);
            }
        }
        $addressTo = $address;
        $tagFrom = null;
        $tagTo = $tag;
        $cancelRequested = $this->safe_value($transaction, 'cancel_requested');
        $type = ($cancelRequested === null) ? 'deposit' : 'withdrawal';
        $amount = $this->safe_float($transaction, 'amount');
        $currencyId = $this->safe_string($transaction, 'currency');
        $code = $this->safe_currency_code($currencyId);
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $statusCode = $this->safe_string($transaction, 'code');
        if ($cancelRequested) {
            $status = 'canceled';
        } else if ($status === null) {
            $status = $this->parse_transaction_status($statusCode);
        }
        $fee = null;
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'addressFrom' => $addressFrom,
            'addressTo' => $addressTo,
            'address' => $address,
            'tagFrom' => $tagFrom,
            'tagTo' => $tagTo,
            'tag' => $tag,
            'type' => $type,
            'amount' => $amount,
            'currency' => $code,
            'status' => $status,
            'updated' => null,
            'fee' => $fee,
        );
    }

    public function parse_transaction_status($status) {
        $statuses = array(
            'initiated' => 'pending',
            'needs_create' => 'pending',
            'credited' => 'ok',
            'confirmed' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function withdraw($code, $amount, $address, $tag = null, $params = array ()) {
        yield $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'address' => $address,
            'amount' => $amount,
            'currency' => $currency['id'],
        );
        if ($tag !== null) {
            $request['address'] .= ':' . $tag;
        }
        $response = yield $this->privatePostWithdraw (array_merge($request, $params));
        //
        //     {
        //         "$data" => {
        //             "$code" => "initiated",
        //             "id" => 3,
        //             "$result" => "Withdraw initiated. Please allow 3-5 minutes for our system to process."
        //         }
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $result = $this->parse_transaction($data);
        return array_merge($result, array(
            'currency' => $code,
            'address' => $address,
            'addressTo' => $address,
            'tag' => $tag,
            'tagTo' => $tag,
            'amount' => $amount,
        ));
    }

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

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = '/' . $this->version . '/';
        if ($api === 'private') {
            $url .= 'user/';
        }
        $url .= $this->implode_params($path, $params);
        $request = $this->omit($params, $this->extract_params($path));
        if ($method === 'POST') {
            $body = $this->json($request);
        } else {
            if ($request) {
                $url .= '?' . $this->urlencode($request);
            }
        }
        if ($api === 'private') {
            $timestamp = (string) $this->milliseconds();
            $bodyAsString = ($method === 'POST') ? $body : '';
            $auth = implode("\n", array($method,
                $url,
                $timestamp,
                $bodyAsString,
                $this->secret,)); // eslint-disable-line quotes
            $hash = $this->hash($this->encode($auth), 'sha256', 'base64');
            $key = $this->apiKey;
            if (gettype($key) !== 'string') {
                $key = (string) $key;
            }
            $signature = 'HMAC-SHA256 ' . $key . ':' . $hash;
            $headers = array(
                'Authorization' => $signature,
                'HMAC-Timestamp' => $timestamp,
            );
            if ($method === 'POST') {
                $headers['Content-Type'] = 'application/json';
            }
        }
        $url = $this->urls['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) {
        //
        //     array("$errors":[array("$code":"insuff_funds","title":"Your available balance is too low for that action")])
        //     array("$errors":[array("$code" => "invalid_auth","title" => "Invalid HMAC signature")])
        //
        if ($response === null) {
            return;
        }
        $errors = $this->safe_value($response, 'errors', array());
        $numErrors = is_array($errors) ? count($errors) : 0;
        if ($numErrors < 1) {
            return;
        }
        $feedback = $this->id . ' ' . $body;
        for ($i = 0; $i < count($errors); $i++) {
            $error = $errors[$i];
            $errorCode = $this->safe_string($error, 'code');
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
        }
        throw new ExchangeError($feedback); // unknown message
    }
}
