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

class eterbase extends Exchange {

    public function describe() {
        return $this->deep_extend(parent::describe (), array(
            'id' => 'eterbase',
            'name' => 'Eterbase',
            'countries' => array( 'SK' ), // Slovakia
            'rateLimit' => 500,
            'version' => 'v1',
            'certified' => true,
            'has' => array(
                'CORS' => false,
                'publicAPI' => true,
                'privateAPI' => true,
                'cancelOrder' => true,
                'createOrder' => true,
                'deposit' => false,
                'fetchBalance' => true,
                'fetchClosedOrders' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => false,
                'fetchMarkets' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchOrderTrades' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '1m' => '1',
                '5m' => '5',
                '15m' => '15',
                '1h' => '60',
                '4h' => '240',
                '1d' => '1440',
                '1w' => '10080',
            ),
            'urls' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/82067900-faeb0f80-96d9-11ea-9f22-0071cfcb9871.jpg',
                'api' => 'https://api.eterbase.exchange',
                'www' => 'https://www.eterbase.com',
                'doc' => 'https://developers.eterbase.exchange',
                'fees' => 'https://www.eterbase.com/exchange/fees',
                'referral' => 'https://eterbase.exchange/invite/1wjjh4Pe',
            ),
            'api' => array(
                'markets' => array(
                    'get' => array(
                        '{id}/order-book',
                    ),
                ),
                'public' => array(
                    'get' => array(
                        'ping',
                        'assets',
                        'markets',
                        'tickers',
                        'tickers/{id}/ticker',
                        'markets/{id}/trades',
                        'markets/{id}/ohlcv',
                        'wstoken',
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'accounts/{id}/balances',
                        'accounts/{id}/orders',
                        'accounts/{id}/fills',
                        'orders/{id}/fills',
                        'orders/{id}',
                    ),
                    'post' => array(
                        'orders',
                        'accounts/{id}/withdrawals',
                    ),
                    'delete' => array(
                        'orders/{id}',
                    ),
                ),
                'feed' => array(
                    'get' => array(
                        'feed',
                    ),
                ),
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => 0.35 / 100,
                    'maker' => 0.35 / 100,
                ),
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'uid' => true,
            ),
            'precisionMode' => SIGNIFICANT_DIGITS,
            'options' => array(
                'createMarketBuyOrderRequiresPrice' => true,
            ),
            'exceptions' => array(
                'exact' => array(
                    'Invalid cost' => '\\ccxt\\InvalidOrder', // array("message":"Invalid cost","_links":array("self":array("href":"/orders","templated":false)))
                    'Invalid order ID' => '\\ccxt\\InvalidOrder', // array("message":"Invalid order ID","_links":array("self":array("href":"/orders/4a151805-d594-4a96-9d64-e3984f2441f7","templated":false)))
                    'Invalid market !' => '\\ccxt\\BadSymbol', // array("message":"Invalid market !","_links":array("self":array("href":"/markets/300/order-book","templated":false)))
                ),
                'broad' => array(
                    'Failed to convert argument' => '\\ccxt\\BadRequest',
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()) {
        $response = $this->publicGetPing ($params);
        //
        //     array( "pong" => 1556354416582 )
        //
        return $this->safe_integer($response, 'pong');
    }

    public function fetch_markets($params = array ()) {
        $response = $this->publicGetMarkets ($params);
        //
        //     array(
        //         {
        //             "id":33,
        //             "symbol":"ETHUSDT",
        //             "base":"ETH",
        //             "quote":"USDT",
        //             "priceSigDigs":5,
        //             "qtySigDigs":8,
        //             "costSigDigs":8,
        //             "verificationLevelUser":1,
        //             "verificationLevelCorporate":11,
        //             "group":"USD",
        //             "tradingRules":[
        //                 array("attribute":"Qty","condition":"Min","value":0.006),
        //                 array("attribute":"Qty","condition":"Max","value":1000),
        //                 array("attribute":"Cost","condition":"Min","value":1),
        //                 array("attribute":"Cost","condition":"Max","value":210000)
        //             ),
        //             "allowedOrderTypes":[1,2,3,4],
        //             "state":"Trading"
        //         }
        //     ]
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $market = $this->parse_market($response[$i]);
            $result[] = $market;
        }
        return $result;
    }

    public function parse_market($market) {
        //
        //     {
        //         "$id":33,
        //         "$symbol":"ETHUSDT",
        //         "$base":"ETH",
        //         "$quote":"USDT",
        //         "priceSigDigs":5,
        //         "qtySigDigs":8,
        //         "costSigDigs":8,
        //         "verificationLevelUser":1,
        //         "verificationLevelCorporate":11,
        //         "group":"USD",
        //         "tradingRules":array(
        //             array("$attribute":"Qty","$condition":"Min","$value":0.006),
        //             array("$attribute":"Qty","$condition":"Max","$value":1000),
        //             array("$attribute":"Cost","$condition":"Min","$value":1),
        //             array("$attribute":"Cost","$condition":"Max","$value":210000)
        //         ),
        //         "allowedOrderTypes":[1,2,3,4],
        //         "$state":"Trading"
        //     }
        //
        $id = $this->safe_string($market, 'id');
        // $numericId = $this->safe_string($market, 'id');
        $baseId = $this->safe_string($market, 'base');
        $quoteId = $this->safe_string($market, 'quote');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $state = $this->safe_string($market, 'state');
        $active = ($state === 'Trading');
        $precision = array(
            'price' => $this->safe_integer($market, 'priceSigDigs'),
            'amount' => $this->safe_integer($market, 'qtySigDigs'),
            'cost' => $this->safe_integer($market, 'costSigDigs'),
        );
        $rules = $this->safe_value($market, 'tradingRules', array());
        $minAmount = null;
        $maxAmount = null;
        $minCost = null;
        $maxCost = null;
        for ($i = 0; $i < count($rules); $i++) {
            $rule = $rules[$i];
            $attribute = $this->safe_string($rule, 'attribute');
            $condition = $this->safe_string($rule, 'condition');
            $value = $this->safe_float($rule, 'value');
            if (($attribute === 'Qty') && ($condition === 'Min')) {
                $minAmount = $value;
            } else if (($attribute === 'Qty') && ($condition === 'Max')) {
                $maxAmount = $value;
            } else if (($attribute === 'Cost') && ($condition === 'Min')) {
                $minCost = $value;
            } else if (($attribute === 'Cost') && ($condition === 'Max')) {
                $maxCost = $value;
            }
        }
        return array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'info' => $market,
            'active' => $active,
            'precision' => $precision,
            'limits' => array(
                'amount' => array(
                    'min' => $minAmount,
                    'max' => $maxAmount,
                ),
                'price' => array(
                    'min' => null,
                    'max' => null,
                ),
                'cost' => array(
                    'min' => $minCost,
                    'max' => $maxCost,
                ),
            ),
        );
    }

    public function fetch_currencies($params = array ()) {
        $response = $this->publicGetAssets ($params);
        //
        //     array(
        //         {
        //             "$id":"LINK",
        //             "$name":"ChainLink Token",
        //             "precisionDisplay":8,
        //             "precisionMax":18,
        //             "precisionBasis":1000000000000000000,
        //             "precisionStep":1,
        //             "verificationLevelMin":"null",
        //             "cmcId":"LINK",
        //             "txnUrl":"https://etherscan.io/tx/{txnId}",
        //             "$state":"Active",
        //             "$type":"Crypto",
        //             "isReference":false,
        //             "withdrawalMin":"0",
        //             "withdrawalMax":"50587",
        //             "withdrawalFee":"0.55",
        //             "$depositEnabled":true,
        //             "$withdrawalEnabled":true,
        //             "description":"",
        //             "coingeckoUrl":"https://www.coingecko.com/en/coins/chainlink",
        //             "coinmarketcapUrl":"https://coinmarketcap.com/currencies/chainlink",
        //             "eterbaseUrl":"https://www.eterbase.com/system-status/LINK",
        //             "explorerUrl":"https://etherscan.io/token/0x514910771af9ca656af840dff83e8264ecf986ca",
        //             "withdrawalMemoAllowed":false,
        //             "countries":array(),
        //             "networks":array()
        //         }
        //     )
        //
        $result = array();
        for ($i = 0; $i < count($response); $i++) {
            $currency = $response[$i];
            $id = $this->safe_string($currency, 'id');
            $precision = $this->safe_integer($currency, 'precisionDisplay');
            $code = $this->safe_currency_code($id);
            $depositEnabled = $this->safe_value($currency, 'depositEnabled');
            $withdrawalEnabled = $this->safe_value($currency, 'withdrawalEnabled');
            $state = $this->safe_string($currency, 'state');
            $active = $depositEnabled && $withdrawalEnabled && ($state === 'Active');
            $type = $this->safe_string_lower($currency, 'type');
            $name = $this->safe_string($currency, 'name');
            $result[$code] = array(
                'id' => $id,
                'info' => $currency,
                'code' => $code,
                'type' => $type,
                'name' => $name,
                'active' => $active,
                'fee' => $this->safe_float($currency, 'withdrawalFee'),
                'precision' => $precision,
                'limits' => array(
                    'amount' => array(
                        'min' => pow(10, -$precision),
                        'max' => pow(10, $precision),
                    ),
                    'price' => array(
                        'min' => pow(10, -$precision),
                        'max' => pow(10, $precision),
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => $this->safe_float($currency, 'withdrawalMin'),
                        'max' => $this->safe_float($currency, 'withdrawalMax'),
                    ),
                ),
            );
        }
        return $result;
    }

    public function parse_ticker($ticker, $market = null) {
        //
        // fetchTicker
        //
        //     {
        //         "time":1588778516608,
        //         "$marketId":250,
        //         "$symbol" => "ETHUSDT",
        //         "price":0.0,
        //         "change":0.0,
        //         "volumeBase":0.0,
        //         "volume":0.0,
        //         "low":0.0,
        //         "high":0.0,
        //     }
        //
        $marketId = $this->safe_string($ticker, 'marketId');
        $symbol = $this->safe_symbol($marketId, $market);
        $timestamp = $this->safe_integer($ticker, 'time');
        $last = $this->safe_float($ticker, 'price');
        $baseVolume = $this->safe_float($ticker, 'volumeBase');
        $quoteVolume = $this->safe_float($ticker, 'volume');
        $vwap = $this->vwap($baseVolume, $quoteVolume);
        $percentage = $this->safe_float($ticker, 'change');
        $result = array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_float($ticker, 'high'),
            'low' => $this->safe_float($ticker, 'low'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => $vwap,
            'open' => null,
            'close' => $last,
            'last' => $last,
            'previousClose' => null, // previous day close
            'change' => null,
            'percentage' => $percentage,
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        );
        return $result;
    }

    public function fetch_ticker($symbol, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'id' => $market['id'],
        );
        $response = $this->publicGetTickersIdTicker (array_merge($request, $params));
        //
        //     {
        //         "time":1588778516608,
        //         "marketId":250,
        //         "price":0.0,
        //         "change":0.0,
        //         "volumeBase":0.0,
        //         "volume":0.0,
        //         "low":0.0,
        //         "high":0.0,
        //     }
        //
        return $this->parse_ticker($response, $market);
    }

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

    public function fetch_tickers($symbols = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            // 'quote' => 'USDT', // identifier of a quote asset to filter the markets
        );
        $response = $this->publicGetTickers (array_merge($request, $params));
        //
        //     array(
        //         array(
        //             "time":1588831771698,
        //             "marketId":33,
        //             "price":204.54,
        //             "change":-1.03,
        //             "volumeBase":544.9801776699998,
        //             "volume":111550.433735,
        //             "low":200.33,
        //             "high":209.51
        //         ),
        //     )
        //
        return $this->parse_tickers($response, $symbols);
    }

    public function parse_trade($trade, $market) {
        //
        // fetchTrades (public)
        //
        //     {
        //         "$id":251199246,
        //         "$side":2,
        //         "$price":0.022044,
        //         "executedAt":1588830682664,
        //         "qty":0.13545846,
        //         "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c",
        //         "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3"
        //     }
        //
        // fetchMyTrades, fetchOrderTrades (private)
        //
        //     {
        //         "$id" => 123,
        //         "$marketId" => 123,
        //         "$side" => 1,
        //         "qty" => "1.23456",
        //         "$price" => "1.23456",
        //         "$cost" => "1.23456",
        //         "$fee" => "1.23456",
        //         "feeAsset" => "XBASE",
        //         "$liquidity" => 1,
        //         "$orderId" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //         "tradeId" => 123,
        //         "filledAt" => 1556355722341
        //     }
        //
        $price = $this->safe_float($trade, 'price');
        $amount = $this->safe_float($trade, 'qty');
        $fee = null;
        $feeCost = $this->safe_float($trade, 'fee');
        if ($feeCost !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'feeAsset');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            $fee = array(
                'cost' => $feeCost,
                'currency' => $feeCurrencyCode,
            );
        }
        $cost = $this->safe_float($trade, 'qty');
        if (($cost === null) && ($price !== null) && ($amount !== null)) {
            $cost = $price * $amount;
        }
        $timestamp = $this->safe_integer_2($trade, 'executedAt', 'filledAt');
        $tradeSide = $this->safe_string($trade, 'side');
        $side = ($tradeSide === '1') ? 'buy' : 'sell';
        $liquidity = $this->safe_string($trade, 'liquidity');
        $takerOrMaker = null;
        if ($liquidity !== null) {
            $takerOrMaker = ($liquidity === '1') ? 'maker' : 'taker';
        }
        $orderId = $this->safe_string($trade, 'orderId');
        $id = $this->safe_string($trade, 'id');
        $marketId = $this->safe_string($trade, 'marketId');
        $symbol = $this->safe_symbol($marketId, $market);
        return array(
            'info' => $trade,
            'id' => $id,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            '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(
            'id' => $market['id'],
            // 'offset' => 0 // the number of records to skip
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->publicGetMarketsIdTrades (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "id":251199246,
        //             "side":2,
        //             "price":0.022044,
        //             "executedAt":1588830682664,
        //             "qty":0.13545846,
        //             "makerId":"67ed6ef3-33d8-4389-ba70-5c68d9db9f6c",
        //             "takerId":"229ef0d6-fe67-4b5d-9733-824142fab8f3"
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function fetch_order_book($symbol, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'id' => $this->market_id($symbol),
        );
        $response = $this->marketsGetIdOrderBook (array_merge($request, $params));
        //
        //     {
        //         "type":"ob_snapshot",
        //         "marketId":3,
        //         "$timestamp":1588836429847,
        //         "bids":[
        //             [0.021694,8.8793688,1], // price, amount, count
        //             [0.01937,7.1340473,1],
        //             [0.020774,3.314881,1],
        //         ],
        //         "asks":[
        //             [0.02305,8.8793688,1],
        //             [0.028022,3.314881,1],
        //             [0.022598,3.314881,1],
        //         ]
        //     }
        //
        $timestamp = $this->safe_integer($response, 'timestamp');
        return $this->parse_order_book($response, $timestamp);
    }

    public function parse_ohlcv($ohlcv, $market = null) {
        //
        //     {
        //         "time":1588807500000,
        //         "open":0.022077,
        //         "high":0.022077,
        //         "low":0.022051,
        //         "close":0.022051,
        //         "volume":10.532025119999997
        //     }
        //
        return array(
            $this->safe_integer($ohlcv, 'time'),
            $this->safe_float($ohlcv, 'open'),
            $this->safe_float($ohlcv, 'high'),
            $this->safe_float($ohlcv, 'low'),
            $this->safe_float($ohlcv, 'close'),
            $this->safe_float($ohlcv, 'volume'),
        );
    }

    public function fetch_ohlcv($symbol, $timeframe = '1m', $since = null, $limit = null, $params = array ()) {
        $request = array(
            // 'id' => $market['id'],
            'interval' => $this->timeframes[$timeframe],
            // 'start' => 1588830682664, // milliseconds
            // 'end' => 1588830682664, // milliseconds
        );
        $duration = $this->parse_timeframe($timeframe);
        $now = $this->milliseconds();
        if ($since !== null) {
            $request['start'] = $since;
            if ($limit === null) {
                $request['end'] = $now;
            } else {
                $request['end'] = $this->sum($since, $duration * $limit * 1000);
            }
        } else if ($limit !== null) {
            $request['start'] = $now - $duration * $limit * 1000;
            $request['end'] = $now;
        } else {
            throw new ArgumentsRequired($this->id . ' fetchOHLCV() requires a $since argument, or a $limit argument, or both');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request['id'] = $market['id'];
        $response = $this->publicGetMarketsIdOhlcv (array_merge($request, $params));
        //
        //     array(
        //         array("time":1588807500000,"open":0.022077,"high":0.022077,"low":0.022051,"close":0.022051,"volume":10.532025119999997),
        //         array("time":1588807800000,"open":0.022051,"high":0.022051,"low":0.022044,"close":0.022044,"volume":0.655987),
        //         array("time":1588808400000,"open":0.022044,"high":0.022044,"low":0.022044,"close":0.022044,"volume":3.9615545499999993),
        //     )
        //
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function fetch_balance($params = array ()) {
        $this->load_markets();
        $request = array(
            'id' => $this->uid,
        );
        $response = $this->privateGetAccountsIdBalances (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "assetId":"USDT",
        //             "available":"25",
        //             "$balance":"25",
        //             "reserved":"0",
        //             "balanceBtc":"0.0",
        //             "balanceRef":"0.0",
        //         }
        //     )
        //
        $result = array( 'info' => $response );
        for ($i = 0; $i < count($response); $i++) {
            $balance = $response[$i];
            $currencyId = $this->safe_string($balance, 'assetId');
            $code = $this->safe_currency_code($currencyId);
            $account = array(
                'free' => $this->safe_float($balance, 'available'),
                'used' => $this->safe_float($balance, 'reserved'),
                'total' => $this->safe_float($balance, 'balance'),
            );
            $result[$code] = $account;
        }
        return $this->parse_balance($result);
    }

    public function fetch_order($id, $symbol = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'id' => $id,
        );
        $response = $this->privateGetOrdersId (array_merge($request, $params));
        //
        //     {
        //         "$id" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //         "accountId" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //         "marketId" => 123,
        //         "type" => 1,
        //         "side" => 1,
        //         "qty" => "1.23456",
        //         "cost" => "1.23456",
        //         "remainingQty" => "1.23456",
        //         "remainingCost" => "1.23456",
        //         "limitPrice" => "1.23456",
        //         "stopPrice" => "1.23456",
        //         "postOnly" => false,
        //         "timeInForce" => "GTC",
        //         "state" => 1,
        //         "closeReason" => "FILLED",
        //         "placedAt" => 1556355722341,
        //         "closedAt" => 1556355722341
        //     }
        //
        return $this->parse_order($response);
    }

    public function parse_order_status($status) {
        $statuses = array(
            '1' => null, // pending
            '2' => 'open', // open
            '3' => 'open', // partially filled
            '4' => 'closed', // closed
            'FILLED' => 'closed',
            'USER_REQUESTED_CANCEL' => 'canceled',
            'ADMINISTRATIVE_CANCEL' => 'canceled',
            'NOT_ENOUGH_LIQUIDITY' => 'canceled',
            'EXPIRED' => 'expired',
            'ONE_CANCELS_OTHER' => 'canceled',
        );
        return $this->safe_string($statuses, $status);
    }

    public function parse_order($order, $market = null) {
        //
        // fetchOrder, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "$id" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //         "accountId" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //         "$marketId" => 123,
        //         "$type" => 1,
        //         "$side" => 1,
        //         "qty" => "1.23456",
        //         "$cost" => "1.23456",
        //         "remainingQty" => "1.23456",
        //         "$remainingCost" => "1.23456",
        //         "limitPrice" => "1.23456",
        //         "$stopPrice" => "1.23456",
        //         "$postOnly" => false,
        //         "$timeInForce" => "GTC",
        //         "state" => 1,
        //         "closeReason" => "FILLED",
        //         "placedAt" => 1556355722341,
        //         "closedAt" => 1556355722341
        //     }
        //
        // createOrder
        //
        //     $market buy
        //
        //     {
        //         "$id":"ff81127c-8fd5-4846-b683-110639dcd322",
        //         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        //         "$marketId":33,
        //         "$type":1,
        //         "$side":1,
        //         "$cost":"25",
        //         "$postOnly":false,
        //         "$timeInForce":"GTC",
        //         "state":1,
        //         "placedAt":1589510846735
        //     }
        //
        //     $market sell, limit buy, limit sell
        //
        //     {
        //         "$id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1",
        //         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        //         "$marketId":33,
        //         "$type":2,
        //         "$side":1,
        //         "qty":"1000",
        //         "limitPrice":"100",
        //         "$postOnly":false,
        //         "$timeInForce":"GTC",
        //         "state":1,
        //         "placedAt":1589403938682,
        //     }
        //
        $id = $this->safe_string($order, 'id');
        $timestamp = $this->safe_integer($order, 'placedAt');
        $marketId = $this->safe_integer($order, 'marketId');
        $symbol = $this->safe_symbol($marketId, $market);
        $status = $this->parse_order_status($this->safe_string($order, 'state'));
        if ($status === 'closed') {
            $status = $this->parse_order_status($this->safe_string($order, 'closeReason'));
        }
        $orderSide = $this->safe_string($order, 'side');
        $side = ($orderSide === '1') ? 'buy' : 'sell';
        $orderType = $this->safe_string($order, 'type');
        $type = null;
        if ($orderType === '1') {
            $type = 'market';
        } else if ($orderType === '2') {
            $type = 'limit';
        } else if ($orderType === '3') {
            $type = 'stopmarket';
        } else {
            $type = 'stoplimit';
        }
        $price = $this->safe_float($order, 'limitPrice');
        $amount = $this->safe_float($order, 'qty');
        $remaining = $this->safe_float($order, 'remainingQty');
        $filled = null;
        $remainingCost = $this->safe_float($order, 'remainingCost');
        if (($remainingCost !== null) && ($remainingCost === 0.0)) {
            $remaining = 0;
        }
        if (($amount !== null) && ($remaining !== null)) {
            $filled = max (0, $amount - $remaining);
        }
        $cost = $this->safe_float($order, 'cost');
        if ($type === 'market') {
            if ($price === 0.0) {
                if (($cost !== null) && ($filled !== null)) {
                    if (($cost > 0) && ($filled > 0)) {
                        $price = $cost / $filled;
                    }
                }
            }
        }
        $average = null;
        if ($cost !== null) {
            if ($filled) {
                $average = $cost / $filled;
            }
        }
        $timeInForce = $this->safe_string($order, 'timeInForce');
        $stopPrice = $this->safe_float($order, 'stopPrice');
        $postOnly = $this->safe_value($order, 'postOnly');
        return array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $side,
            'price' => $price,
            'stopPrice' => $stopPrice,
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => $remaining,
            'status' => $status,
            'fee' => null,
            'trades' => null,
        );
    }

    public function fetch_orders_by_state($state, $symbol = null, $since = null, $limit = null, $params = array ()) {
        $now = $this->milliseconds();
        $ninetyDays = 90 * 24 * 60 * 60 * 1000; // 90 days timerange max
        $request = array(
            'id' => $this->uid,
            'state' => $state,
            // 'side' => Integer, // 1 = buy, 2 = sell
            // 'offset' => 0, // the number of records to skip
        );
        if ($since === null) {
            $request['from'] = $now - $ninetyDays;
            $request['to'] = $now;
        } else {
            $request['from'] = $since;
            $request['to'] = $this->sum($since, $ninetyDays);
        }
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['marketId'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 50
        }
        $response = $this->privateGetAccountsIdOrders (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "id" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //             "accountId" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //             "marketId" => 123,
        //             "type" => 1,
        //             "side" => 1,
        //             "qty" => "1.23456",
        //             "cost" => "1.23456",
        //             "remainingQty" => "1.23456",
        //             "remainingCost" => "1.23456",
        //             "limitPrice" => "1.23456",
        //             "stopPrice" => "1.23456",
        //             "postOnly" => false,
        //             "timeInForce" => "GTC",
        //             "$state" => 1,
        //             "closeReason" => "FILLED",
        //             "placedAt" => 1556355722341,
        //             "closedAt" => 1556355722341
        //         }
        //     )
        //
        return $this->parse_orders($response, $market, $since, $limit);
    }

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

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

    public function fetch_my_trades($symbol = null, $since = null, $limit = null, $params = array ()) {
        $now = $this->milliseconds();
        $ninetyDays = 90 * 24 * 60 * 60 * 1000; // 90 days timerange max
        $request = array(
            'id' => $this->uid,
            // 'side' => Integer, // 1 = buy, 2 = sell
            // 'offset' => 0, // the number of records to skip
        );
        if ($since === null) {
            $request['from'] = $now - $ninetyDays;
            $request['to'] = $now;
        } else {
            $request['from'] = $since;
            $request['to'] = $this->sum($since, $ninetyDays);
        }
        $this->load_markets();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['marketId'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default 50, max 200
        }
        $response = $this->privateGetAccountsIdFills (array_merge($request, $params));
        //
        //     array(
        //         {
        //             "id" => 123,
        //             "marketId" => 123,
        //             "side" => 1,
        //             "qty" => "1.23456",
        //             "price" => "1.23456",
        //             "cost" => "1.23456",
        //             "fee" => "1.23456",
        //             "feeAsset" => "XBASE",
        //             "liquidity" => 1,
        //             "orderId" => "30a2b5d0-be2e-4d0a-93ed-a7c45fed1792",
        //             "tradeId" => 123,
        //             "filledAt" => 1556355722341
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function fetch_order_trades($id, $symbol = null, $since = null, $limit = null, $params = array ()) {
        $this->load_markets();
        $request = array(
            'id' => $id,
        );
        $trades = $this->privateGetOrdersIdFills (array_merge($request, $params));
        return $this->parse_trades($trades);
    }

    public function create_order($symbol, $type, $side, $amount, $price = null, $params = array ()) {
        $this->load_markets();
        $market = $this->market($symbol);
        $uppercaseType = strtoupper($type);
        if ($uppercaseType === 'MARKET') {
            $type = 1;
        } else if ($uppercaseType === 'LIMIT') {
            $type = 2;
        } else if ($uppercaseType === 'STOPMARKET') {
            $type = 3;
        } else if ($uppercaseType === 'STOPLIMIT') {
            $type = 4;
        }
        $uppercaseSide = strtoupper($side);
        $side = $uppercaseSide === 'BUY' ? 1 : 2;
        $request = array(
            'accountId' => $this->uid,
            'marketId' => $market['id'],
            'type' => $type,
            'side' => $side,
            // 'postOnly' => false,
            // 'timeInForce' => 'GTC',
        );
        $clientOrderId = $this->safe_value_2($params, 'refId', 'clientOrderId');
        $query = $params;
        if ($clientOrderId !== null) {
            $request['refId'] = $clientOrderId;
            $query = $this->omit($params, array( 'refId', 'clientOrderId' ));
        }
        if (($uppercaseType === 'MARKET') && ($uppercaseSide === 'BUY')) {
            // for $market buy it requires the $amount of quote currency to spend
            $cost = $this->safe_float($params, 'cost');
            if ($this->options['createMarketBuyOrderRequiresPrice']) {
                if ($cost === null) {
                    if ($price !== null) {
                        $cost = $amount * $price;
                    } else {
                        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 to supply the $cost in the $amount argument (the exchange-specific behaviour)");
                    }
                }
            } else {
                $cost = ($cost === null) ? $amount : $cost;
            }
            $precision = $market['precision']['price'];
            $request['cost'] = $this->decimal_to_precision($cost, TRUNCATE, $precision, $this->precisionMode);
        } else {
            $request['qty'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($uppercaseType === 'LIMIT') {
            $request['limitPrice'] = $this->price_to_precision($symbol, $price);
        }
        $response = $this->privatePostOrders (array_merge($request, $query));
        //
        // $market buy
        //
        //     {
        //         "id":"ff81127c-8fd5-4846-b683-110639dcd322",
        //         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        //         "marketId":33,
        //         "$type":1,
        //         "$side":1,
        //         "$cost":"25",
        //         "postOnly":false,
        //         "timeInForce":"GTC",
        //         "state":1,
        //         "placedAt":1589510846735
        //     }
        //
        // $market sell, limit buy, limit sell
        //
        //     {
        //         "id":"042a38b0-e369-4ad2-ae73-a18ff6b1dcf1",
        //         "accountId":"6d445378-d8a3-4932-91cd-545d0a4ad2a2",
        //         "marketId":33,
        //         "$type":2,
        //         "$side":1,
        //         "qty":"1000",
        //         "limitPrice":"100",
        //         "postOnly":false,
        //         "timeInForce":"GTC",
        //         "state":1,
        //         "placedAt":1589403938682,
        //     }
        //
        return $this->parse_order($response, $market);
    }

    public function cancel_order($id, $symbol = null, $params = array ()) {
        $request = array(
            'id' => $id,
        );
        return $this->privateDeleteOrdersId (array_merge($request, $params));
    }

    public function withdraw($code, $amount, $address, $tag = null, $params = array ()) {
        $this->check_address($address);
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'id' => $this->uid,
            'accountId' => $this->uid,
            'assetId' => $currency['id'],
            'amount' => $amount,
            // 'cryptoAddress' => $address,
            // 'accountNumber' => 'IBAN', // IBAN account number
            // 'networkId' => 'XBASE', // underlying network
        );
        if ($address !== null) {
            $request['cryptoAddress'] = $address;
            if ($tag !== null) {
                $request['memo'] = $tag;
            }
        }
        $response = $this->privatePostAccountsIdWithdrawals (array_merge($request, $params));
        //
        //     {
        //         "id" => "98b62dde-a87f-45f0-8db8-80ae2d312fa6"
        //     }
        //
        return array(
            'info' => $response,
            'id' => $this->safe_string($response, 'id'),
        );
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $httpHeaders = null, $body = null) {
        $query = $this->omit($params, $this->extract_params($path));
        $request = '/';
        if ($api === 'public') {
            $request .= 'api/' . $this->version;
        } else if ($api === 'private') {
            $request .= 'api/' . $this->version;
        } else if ($api === 'markets') {
            $request .= 'api/' . $api;
        }
        $request .= '/' . $this->implode_params($path, $params);
        if ($method === 'GET') {
            if ($query) {
                $request .= '?' . $this->urlencode($query);
            }
        }
        $url = $this->urls['api'] . $request;
        if ($api === 'private') {
            $this->check_required_credentials();
            $payload = '';
            if ($method !== 'GET') {
                if ($query) {
                    $body = $this->json($query);
                    $payload = $body;
                }
            }
            // construct $signature
            $hasBody = ($method === 'POST') || ($method === 'PUT') || ($method === 'PATCH');
            // $date = 'Mon, 30 Sep 2019 13:57:23 GMT';
            $date = $this->rfc2616($this->milliseconds());
            $headersCSV = 'date' . ' ' . 'request-line';
            $message = 'date' . ':' . ' ' . $date . "\n" . $method . ' ' . $request . ' HTTP/1.1'; // eslint-disable-line quotes
            $digest = '';
            if ($hasBody) {
                $digest = 'SHA-256=' . $this->hash($payload, 'sha256', 'base64');
                $message .= "\ndigest" . ':' . ' ' . $digest;  // eslint-disable-line quotes
                $headersCSV .= ' ' . 'digest';
            }
            $signature = $this->hmac($this->encode($message), $this->encode($this->secret), 'sha256', 'base64');
            $authorizationHeader = 'hmac username="' . $this->apiKey . '",algorithm="hmac-sha256",headers="' . $headersCSV . '",' . 'signature="' . $signature . '"';
            $httpHeaders = array(
                'Date' => $date,
                'Authorization' => $authorizationHeader,
                'Content-Type' => 'application/json',
            );
            if ($hasBody) {
                $httpHeaders['Digest'] = $digest;
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $httpHeaders );
    }

    public function handle_errors($httpCode, $reason, $url, $method, $headers, $body, $response, $requestHeaders, $requestBody) {
        if ($response === null) {
            return; // fallback to default error handler
        }
        //
        //     array("$message":"Invalid cost","_links":array("self":array("href":"/orders","templated":false)))
        //
        $message = $this->safe_string($response, 'message');
        if ($message !== null) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
    }
}
