# -*- coding: utf-8 -*-

# 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

from ccxt.async_support.base.exchange import Exchange

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
import hashlib
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadResponse
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import DECIMAL_PLACES


class livecoin(Exchange):

    def describe(self):
        return self.deep_extend(super(livecoin, self).describe(), {
            'id': 'livecoin',
            'name': 'LiveCoin',
            'countries': ['US', 'UK', 'RU'],
            'rateLimit': 1000,
            'userAgent': self.userAgents['chrome'],
            'has': {
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27980768-f22fc424-638a-11e7-89c9-6010a54ff9be.jpg',
                'api': 'https://api.livecoin.net',
                'www': 'https://www.livecoin.net',
                'doc': 'https://www.livecoin.net/api?lang=en',
                'referral': 'https://livecoin.net/?from=Livecoin-CQ1hfx44',
            },
            'api': {
                'public': {
                    'get': [
                        'exchange/all/order_book',
                        'exchange/last_trades',
                        'exchange/maxbid_minask',
                        'exchange/order_book',
                        'exchange/restrictions',
                        'exchange/ticker',  # omit params to get all tickers at once
                        'info/coinInfo',
                    ],
                },
                'private': {
                    'get': [
                        'exchange/client_orders',
                        'exchange/order',
                        'exchange/trades',
                        'exchange/commission',
                        'exchange/commissionCommonInfo',
                        'payment/balances',
                        'payment/balance',
                        'payment/get/address',
                        'payment/history/size',
                        'payment/history/transactions',
                    ],
                    'post': [
                        'exchange/buylimit',
                        'exchange/buymarket',
                        'exchange/cancellimit',
                        'exchange/selllimit',
                        'exchange/sellmarket',
                        'payment/out/capitalist',
                        'payment/out/card',
                        'payment/out/coin',
                        'payment/out/okpay',
                        'payment/out/payeer',
                        'payment/out/perfectmoney',
                        'payment/voucher/amount',
                        'payment/voucher/make',
                        'payment/voucher/redeem',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.18 / 100,
                    'taker': 0.18 / 100,
                },
            },
            'commonCurrencies': {
                'BIT': 'First Bitcoin',
                'BTCH': 'Bithash',
                'CPC': 'Capricoin',
                'CBC': 'CryptoBossCoin',  # conflict with CBC(CashBet Coin)
                'CPT': 'Cryptos',  # conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081
                'EDR': 'E-Dinar Coin',  # conflicts with EDR for Endor Protocol and EDRCoin
                'eETT': 'EETT',
                'FirstBlood': '1ST',
                'FORTYTWO': '42',
                'LEO': 'LeoCoin',
                'MIOTA': 'IOTA',  # https://github.com/ccxt/ccxt/issues/7487
                'ORE': 'Orectic',
                'PLN': 'Plutaneum',  # conflict with Polish Zloty
                'RUR': 'RUB',
                'SCT': 'SpaceCoin',
                'TCT': 'Twecrypto',
                'TPI': 'ThaneCoin',
                'UNUS': 'LEO',  # https://github.com/ccxt/ccxt/issues/7496
                'WAX': 'WAXP',
                'wETT': 'WETT',
                'XBT': 'Bricktox',
            },
            'exceptions': {
                'exact': {
                    '1': ExchangeError,
                    '10': AuthenticationError,
                    '100': ExchangeError,  # invalid parameters
                    '101': AuthenticationError,
                    '102': AuthenticationError,
                    '103': InvalidOrder,  # invalid currency
                    '104': InvalidOrder,  # invalid amount
                    '105': InvalidOrder,  # unable to block funds
                    '11': AuthenticationError,
                    '12': AuthenticationError,
                    '2': AuthenticationError,  # "User not found"
                    '20': AuthenticationError,
                    '30': AuthenticationError,
                    '31': NotSupported,
                    '32': ExchangeError,
                    '429': RateLimitExceeded,
                    '503': ExchangeNotAvailable,
                },
                'broad': {
                    'insufficient funds': InsufficientFunds,  # https://github.com/ccxt/ccxt/issues/5749
                    'NOT FOUND': OrderNotFound,
                    'Cannot find order': OrderNotFound,
                    'Minimal amount is': InvalidOrder,
                },
            },
        })

    async def fetch_markets(self, params={}):
        response = await self.publicGetExchangeTicker(params)
        restrictions = await self.publicGetExchangeRestrictions()
        restrictionsById = self.index_by(restrictions['restrictions'], 'currencyPair')
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'symbol')
            baseId, quoteId = id.split('/')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            coinRestrictions = self.safe_value(restrictionsById, symbol)
            precision = {
                'price': 5,
                'amount': 8,
                'cost': 8,
            }
            limits = {
                'amount': {
                    'min': math.pow(10, -precision['amount']),
                    'max': math.pow(10, precision['amount']),
                },
            }
            if coinRestrictions:
                precision['price'] = self.safe_integer(coinRestrictions, 'priceScale', 5)
                limits['amount']['min'] = self.safe_float(coinRestrictions, 'minLimitQuantity', limits['amount']['min'])
            limits['price'] = {
                'min': math.pow(10, -precision['price']),
                'max': math.pow(10, precision['price']),
            }
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': True,
                'precision': precision,
                'limits': limits,
                'info': market,
            })
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicGetInfoCoinInfo(params)
        if isinstance(response, basestring):
            if response.find('site is under maintenance') >= 0:
                raise OnMaintenance(self.id + ' fetchCurrencies() failed to fetch the currencies, the exchange is on maintenance')
            else:
                raise BadResponse(self.id + ' fetchCurrencies() failed to fetch the currencies')
        currencies = self.safe_value(response, 'info')
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'symbol')
            # todo: will need to rethink the fees
            # to add support for multiple withdrawal/deposit methods and
            # differentiated fees for each particular method
            code = self.safe_currency_code(id)
            precision = 8  # default precision, todo: fix "magic constants"
            walletStatus = self.safe_string(currency, 'walletStatus')
            active = (walletStatus == 'normal')
            name = self.safe_string(currency, 'name')
            fee = self.safe_float(currency, 'withdrawFee')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'active': active,
                'fee': fee,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_float(currency, 'minOrderAmount'),
                        'max': math.pow(10, precision),
                    },
                    'price': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'cost': {
                        'min': self.safe_float(currency, 'minOrderAmount'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'minWithdrawAmount'),
                        'max': math.pow(10, precision),
                    },
                    'deposit': {
                        'min': self.safe_float(currency, 'minDepositAmount'),
                        'max': None,
                    },
                },
            }
        result = self.append_fiat_currencies(result)
        return result

    def append_fiat_currencies(self, result):
        precision = 8
        defaults = {
            'info': None,
            'active': True,
            'fee': None,
            'precision': precision,
            'limits': {
                'withdraw': {'min': None, 'max': None},
                'deposit': {'min': None, 'max': None},
                'amount': {'min': None, 'max': None},
                'cost': {'min': None, 'max': None},
                'price': {
                    'min': math.pow(10, -precision),
                    'max': math.pow(10, precision),
                },
            },
            'id': None,
            'code': None,
            'name': None,
        }
        currencies = [
            {'id': 'USD', 'code': 'USD', 'name': 'US Dollar'},
            {'id': 'EUR', 'code': 'EUR', 'name': 'Euro'},
            # {'id': 'RUR', 'code': 'RUB', 'name': 'Russian ruble'},
        ]
        currencies.append({
            'id': 'RUR',
            'code': self.safe_currency_code('RUR'),
            'name': 'Russian ruble',
        })
        for i in range(0, len(currencies)):
            currency = currencies[i]
            code = currency['code']
            result[code] = self.extend(defaults, currency)
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetPaymentBalances(params)
        result = {'info': response}
        for i in range(0, len(response)):
            balance = response[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = None
            if code in result:
                account = result[code]
            else:
                account = self.account()
            if balance['type'] == 'total':
                account['total'] = self.safe_float(balance, 'value')
            if balance['type'] == 'available':
                account['free'] = self.safe_float(balance, 'value')
            if balance['type'] == 'trade':
                account['used'] = self.safe_float(balance, 'value')
            result[code] = account
        return self.parse_balance(result)

    async def fetch_trading_fees(self, params={}):
        await self.load_markets()
        response = await self.privateGetExchangeCommissionCommonInfo(params)
        commission = self.safe_float(response, 'commission')
        return {
            'info': response,
            'maker': commission,
            'taker': commission,
        }

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        request = {
            'currencyPair': self.market_id(symbol),
            'groupByPrice': 'false',
        }
        if limit is not None:
            request['depth'] = limit  # 100
        response = await self.publicGetExchangeOrderBook(self.extend(request, params))
        timestamp = self.safe_integer(response, 'timestamp')
        return self.parse_order_book(response, timestamp)

    def parse_ticker(self, ticker, market=None):
        timestamp = self.milliseconds()
        symbol = None
        if market:
            symbol = market['symbol']
        vwap = self.safe_float(ticker, 'vwap')
        baseVolume = self.safe_float(ticker, 'volume')
        quoteVolume = None
        if baseVolume is not None and vwap is not None:
            quoteVolume = baseVolume * vwap
        last = self.safe_float(ticker, 'last')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'best_bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'best_ask'),
            'askVolume': None,
            'vwap': self.safe_float(ticker, 'vwap'),
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.publicGetExchangeTicker(params)
        tickers = self.index_by(response, 'symbol')
        ids = list(tickers.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            market = self.safe_market(id)
            symbol = market['symbol']
            ticker = tickers[id]
            result[symbol] = self.parse_ticker(ticker, market)
        return self.filter_by_array(result, 'symbol', symbols)

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currencyPair': market['id'],
        }
        ticker = await self.publicGetExchangeTicker(self.extend(request, params))
        return self.parse_ticker(ticker, market)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "time": 1409935047,
        #         "id": 99451,
        #         "price": 350,
        #         "quantity": 2.85714285,
        #         "type": "BUY"
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "datetime": 1435844369,
        #         "id": 30651619,
        #         "type": "sell",
        #         "symbol": "BTC/EUR",
        #         "price": 230,
        #         "quantity": 0.1,
        #         "commission": 0,
        #         "clientorderid": 1472837650
        #     }
        timestamp = self.safe_timestamp_2(trade, 'time', 'datetime')
        fee = None
        feeCost = self.safe_float(trade, 'commission')
        if feeCost is not None:
            feeCurrency = market['quote'] if market else None
            fee = {
                'cost': feeCost,
                'currency': feeCurrency,
            }
        orderId = self.safe_string(trade, 'clientorderid')
        id = self.safe_string(trade, 'id')
        side = self.safe_string_lower(trade, 'type')
        amount = self.safe_float(trade, 'quantity')
        price = self.safe_float(trade, 'price')
        cost = None
        if amount is not None:
            if price is not None:
                cost = amount * price
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market, '/')
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'currencyPair': market['id'],
            # 'orderDesc': 'true',  # or 'false', if True then new orders will be first, otherwise old orders will be first.
            # 'offset': 0,  # page offset, position of the first item on the page
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['currencyPair'] = market['id']
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetExchangeTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "datetime": 1435844369,
        #             "id": 30651619,
        #             "type": "sell",
        #             "symbol": "BTC/EUR",
        #             "price": 230,
        #             "quantity": 0.1,
        #             "commission": 0,
        #             "clientorderid": 1472837650
        #         },
        #         {
        #             "datetime": 1435844356,
        #             "id": 30651618,
        #             "type": "sell",
        #             "symbol": "BTC/EUR",
        #             "price": 230,
        #             "quantity": 0.2,
        #             "commission": 0.092,
        #             "clientorderid": 1472837651
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currencyPair': market['id'],
        }
        response = await self.publicGetExchangeLastTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "time": 1409935047,
        #             "id": 99451,
        #             "price": 350,
        #             "quantity": 2.85714285,
        #             "type": "BUY"
        #         },
        #         {
        #             "time": 1409934792,
        #             "id": 99450,
        #             "price": 350,
        #             "quantity": 0.57142857,
        #             "type": "SELL"
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'orderId': id,
        }
        response = await self.privateGetExchangeOrder(self.extend(request, params))
        return self.parse_order(response)

    def parse_order_status(self, status):
        statuses = {
            'OPEN': 'open',
            'PARTIALLY_FILLED': 'open',
            'EXECUTED': 'closed',
            'CANCELLED': 'canceled',
            'PARTIALLY_FILLED_AND_CANCELLED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        timestamp = None
        if 'lastModificationTime' in order:
            timestamp = self.safe_string(order, 'lastModificationTime')
            if timestamp is not None:
                if timestamp.find('T') >= 0:
                    timestamp = self.parse8601(timestamp)
                else:
                    timestamp = self.safe_integer(order, 'lastModificationTime')
        # TODO currently not supported by livecoin
        # trades = self.parse_trades(order['trades'], market, since, limit)
        trades = None
        status = self.parse_order_status(self.safe_string_2(order, 'status', 'orderStatus'))
        marketId = self.safe_string_2(order, 'symbol', 'currencyPair')
        symbol = self.safe_symbol(marketId, market, '/')
        type = self.safe_string_lower(order, 'type')
        side = None
        if type is not None:
            orderType = type.split('_')
            type = orderType[0]
            side = orderType[1]
        price = self.safe_float(order, 'price')
        # of the next two lines the latter overrides the former, if present in the order structure
        remaining = self.safe_float(order, 'remainingQuantity')
        remaining = self.safe_float(order, 'remaining_quantity', remaining)
        amount = self.safe_float(order, 'quantity', remaining)
        filled = None
        if remaining is not None:
            filled = amount - remaining
        cost = None
        if filled is not None and price is not None:
            cost = filled * price
        feeRate = self.safe_float(order, 'commission_rate')
        feeCost = None
        if cost is not None and feeRate is not None:
            feeCost = cost * feeRate
        if (market is None) and (symbol in self.markets):
            market = self.markets[symbol]
        feeCurrency = None
        if market is not None:
            feeCurrency = market['quote']
        return {
            'info': order,
            'id': order['id'],
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': cost,
            'filled': filled,
            'remaining': remaining,
            'trades': trades,
            'fee': {
                'cost': feeCost,
                'currency': feeCurrency,
                'rate': feeRate,
            },
            'average': None,
        }

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['currencyPair'] = market['id']
        if since is not None:
            request['issuedFrom'] = int(since)
        if limit is not None:
            request['endRow'] = limit - 1
        response = await self.privateGetExchangeClientOrders(self.extend(request, params))
        result = []
        rawOrders = []
        if response['data']:
            rawOrders = response['data']
        for i in range(0, len(rawOrders)):
            order = rawOrders[i]
            result.append(self.parse_order(order, market))
        return self.sort_by(result, 'timestamp')

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'openClosed': 'OPEN',
        }
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'openClosed': 'CLOSED',
        }
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        method = 'privatePostExchange' + self.capitalize(side) + type
        market = self.market(symbol)
        request = {
            'quantity': self.amount_to_precision(symbol, amount),
            'currencyPair': market['id'],
        }
        if type == 'limit':
            request['price'] = self.price_to_precision(symbol, price)
        response = await getattr(self, method)(self.extend(request, params))
        result = {
            'info': response,
            'id': str(response['orderId']),
        }
        success = self.safe_value(response, 'success')
        if success:
            result['status'] = 'open'
        return result

    async def cancel_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'orderId': id,
            'currencyPair': market['id'],
        }
        response = await self.privatePostExchangeCancellimit(self.extend(request, params))
        message = self.safe_string(response, 'message', self.json(response))
        if 'success' in response:
            if not response['success']:
                raise InvalidOrder(message)
            elif 'cancelled' in response:
                if response['cancelled']:
                    return {
                        'status': 'canceled',
                        'info': response,
                    }
                else:
                    raise OrderNotFound(message)
        raise ExchangeError(self.id + ' cancelOrder() failed: ' + self.json(response))

    async def withdraw(self, code, amount, address, tag=None, params={}):
        # Sometimes the response with be {key: null} for all keys.
        # An example is if you attempt to withdraw more than is allowed when withdrawal fees are considered.
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        wallet = address
        if tag is not None:
            wallet += '::' + tag
        request = {
            'amount': self.decimal_to_precision(amount, TRUNCATE, currency['precision'], DECIMAL_PLACES),
            'currency': currency['id'],
            'wallet': wallet,
        }
        response = await self.privatePostPaymentOutCoin(self.extend(request, params))
        id = self.safe_integer(response, 'id')
        if id is None:
            raise InsufficientFunds(self.id + ' insufficient funds to cover requested withdrawal amount post fees ' + self.json(response))
        return {
            'info': response,
            'id': id,
        }

    def parse_transaction(self, transaction, currency=None):
        #    {
        #        "id": "c853093d5aa06df1c92d79c2...",(tx on deposits, address on withdrawals)
        #        "type": "DEPOSIT",
        #        "date": 1553186482676,
        #        "amount": 712.61266,
        #        "fee": 0,
        #        "fixedCurrency": "XVG",
        #        "taxCurrency": "XVG",
        #        "variableAmount": null,
        #        "variableCurrency": null,
        #        "external": "Coin",
        #        "login": "USERNAME",
        #        "externalKey": "....87diPBy......3hTtuwUT78Yi",(address on deposits, tx on withdrawals)
        #        "documentId": 1110662453
        #    },
        txid = None
        address = None
        id = self.safe_string(transaction, 'documentId')
        amount = self.safe_float(transaction, 'amount')
        timestamp = self.safe_integer(transaction, 'date')
        type = self.safe_string_lower(transaction, 'type')
        currencyId = self.safe_string(transaction, 'fixedCurrency')
        feeCost = self.safe_float(transaction, 'fee')
        code = self.safe_currency_code(currencyId, currency)
        if type == 'withdrawal':
            txid = self.safe_string(transaction, 'externalKey')
            address = self.safe_string(transaction, 'id')
        elif type == 'deposit':
            address = self.safe_string(transaction, 'externalKey')
            txid = self.safe_string(transaction, 'id')
        status = None
        if type == 'deposit':
            status = 'ok'  # Deposits is not registered until they are in account. Withdrawals are left as None, not entirely sure about theyre status.
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'address': address,
            'tag': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': {
                'currency': code,
                'cost': feeCost,
            },
        }

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        endtime = 2505600000  # 29 days - exchange has maximum 30 days.
        now = self.milliseconds()
        request = {
            'types': 'DEPOSIT',
            'end': now,
            'start': int(since) if (since is not None) else now - endtime,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
        if limit is not None:
            request['limit'] = limit  # default is 100
        response = await self.privateGetPaymentHistoryTransactions(self.extend(request, params))
        return self.parse_transactions(response, currency, since, limit)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        endtime = 2505600000  # 29 days - exchange has maximum 30 days.
        now = self.milliseconds()
        request = {
            'types': 'WITHDRAWAL',
            'end': now,
            'start': int(since) if (since is not None) else now - endtime,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
        if limit is not None:
            request['limit'] = limit  # default is 100
        if since is not None:
            request['start'] = since
        response = await self.privateGetPaymentHistoryTransactions(self.extend(request, params))
        return self.parse_transactions(response, currency, since, limit)

    async def fetch_deposit_address(self, currency, params={}):
        request = {
            'currency': currency,
        }
        response = await self.privateGetPaymentGetAddress(self.extend(request, params))
        address = self.safe_string(response, 'wallet')
        tag = None
        if address.find(':') >= 0:
            parts = address.split(':')
            address = parts[0]
            tag = parts[2]
        self.check_address(address)
        return {
            'currency': currency,
            'address': address,
            'tag': tag,
            'info': response,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'] + '/' + path
        query = self.urlencode(self.keysort(params))
        if method == 'GET':
            if params:
                url += '?' + query
        if api == 'private':
            self.check_required_credentials()
            if method == 'POST':
                body = query
            signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
            headers = {
                'Api-Key': self.apiKey,
                'Sign': signature.upper(),
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if response is None:
            return  # fallback to default error handler
        if code >= 300:
            feedback = self.id + ' ' + body
            errorCode = self.safe_string(response, 'errorCode')
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            raise ExchangeError(feedback)
        # returns status code 200 even if success == False
        success = self.safe_value(response, 'success', True)
        if not success:
            feedback = self.id + ' ' + body
            message = self.safe_string_2(response, 'message', 'exception')
            if message is not None:
                self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)
