# -*- 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
import hashlib
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
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 DDoSProtection
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce


class kucoin2 (Exchange):

    def describe(self):
        return self.deep_extend(super(kucoin2, self).describe(), {
            'id': 'kucoin2',
            'name': 'KuCoin',
            'countries': ['SC'],
            'rateLimit': 334,
            'version': 'v2',
            'certified': True,
            'comment': 'Platform 2.0',
            'has': {
                'fetchMarkets': True,
                'fetchCurrencies': True,
                'fetchTicker': True,
                'fetchOrderBook': True,
                'fetchOrder': True,
                'fetchClosedOrders': True,
                'fetchOpenOrders': True,
                'fetchDepositAddress': True,
                'withdraw': True,
                'fetchDeposits': True,
                'fetchWithdrawals': True,
                'fetchBalance': True,
                'fetchTrades': True,
                'fetchMyTrades': True,
                'createOrder': True,
                'cancelOrder': True,
                'fetchAccounts': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/51909432-b0a72780-23dd-11e9-99ba-73d23c8d4eed.jpg',
                'referral': 'https://www.kucoin.com/?r=E5wkqe',
                'api': {
                    'public': 'https://openapi-v2.kucoin.com',
                    'private': 'https://openapi-v2.kucoin.com',
                },
                'test': {
                    'public': 'https://openapi-sandbox.kucoin.com',
                    'private': 'https://openapi-sandbox.kucoin.com',
                },
                'www': 'https://www.kucoin.com',
                'doc': [
                    'https://docs.kucoin.com',
                ],
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
                'password': True,
            },
            'api': {
                'public': {
                    'get': [
                        'timestamp',
                        'symbols',
                        'market/orderbook/level{level}',
                        'market/histories',
                        'market/candles',
                        'market/stats',
                        'currencies',
                        'currencies/{currency}',
                    ],
                    'post': [
                        'bullet-public',
                    ],
                },
                'private': {
                    'get': [
                        'accounts',
                        'accounts/{accountId}',
                        'accounts/{accountId}/ledgers',
                        'accounts/{accountId}/holds',
                        'deposit-addresses',
                        'deposits',
                        'withdrawals',
                        'withdrawals/quotas',
                        'orders',
                        'orders/{orderId}',
                        'fills',
                    ],
                    'post': [
                        'accounts',
                        'accounts/inner-transfer',
                        'deposit-addresses',
                        'withdrawals',
                        'orders',
                        'bullet-private',
                    ],
                    'delete': [
                        'withdrawals/{withdrawalId}',
                        'orders/{orderId}',
                    ],
                },
            },
            'timeframes': {
                '1m': '1min',
                '3m': '3min',
                '5m': '5min',
                '15m': '15min',
                '30m': '30min',
                '1h': '1hour',
                '2h': '2hour',
                '4h': '4hour',
                '6h': '6hour',
                '8h': '8hour',
                '12h': '12hour',
                '1d': '1day',
                '1w': '1week',
            },
            'exceptions': {
                '400': BadRequest,
                '401': AuthenticationError,
                '403': NotSupported,
                '404': NotSupported,
                '405': NotSupported,
                '429': DDoSProtection,
                '500': ExchangeError,
                '503': ExchangeNotAvailable,
                '200004': InsufficientFunds,
                '300000': InvalidOrder,
                '400001': AuthenticationError,
                '400002': InvalidNonce,
                '400003': AuthenticationError,
                '400004': AuthenticationError,
                '400005': AuthenticationError,
                '400006': AuthenticationError,
                '400007': AuthenticationError,
                '400008': NotSupported,
                '400100': ArgumentsRequired,
                '411100': AccountSuspended,
                '500000': ExchangeError,
                'order_not_exist': OrderNotFound,  # {"code":"order_not_exist","msg":"order_not_exist"} ¯\_(ツ)_/¯
            },
            'options': {
                'version': 'v1',
                'symbolSeparator': '-',
            },
        })

    def nonce(self):
        return self.milliseconds()

    async def load_time_difference(self):
        response = await self.publicGetTimestamp()
        after = self.milliseconds()
        kucoinTime = self.safe_integer(response, 'data')
        self.options['timeDifference'] = int(after - kucoinTime)
        return self.options['timeDifference']

    async def fetch_markets(self, params={}):
        response = await self.publicGetSymbols(params)
        #
        # {quoteCurrency: 'BTC',
        #   symbol: 'KCS-BTC',
        #   quoteMaxSize: '9999999',
        #   quoteIncrement: '0.000001',
        #   baseMinSize: '0.01',
        #   quoteMinSize: '0.00001',
        #   enableTrading: True,
        #   priceIncrement: '0.00000001',
        #   name: 'KCS-BTC',
        #   baseIncrement: '0.01',
        #   baseMaxSize: '9999999',
        #   baseCurrency: 'KCS'}
        #
        responseData = response['data']
        result = {}
        for i in range(0, len(responseData)):
            entry = responseData[i]
            id = entry['name']
            baseId = entry['baseCurrency']
            quoteId = entry['quoteCurrency']
            base = self.common_currency_code(baseId)
            quote = self.common_currency_code(quoteId)
            symbol = base + '/' + quote
            active = entry['enableTrading']
            baseMax = self.safe_float(entry, 'baseMaxSize')
            baseMin = self.safe_float(entry, 'baseMinSize')
            quoteMax = self.safe_float(entry, 'quoteMaxSize')
            quoteMin = self.safe_float(entry, 'quoteMinSize')
            priceIncrement = self.safe_float(entry, 'priceIncrement')
            precision = {
                'amount': -math.log10(self.safe_float(entry, 'quoteIncrement')),
                'price': -math.log10(priceIncrement),
            }
            limits = {
                'amount': {
                    'min': quoteMin,
                    'max': quoteMax,
                },
                'price': {
                    'min': max(baseMin / quoteMax, priceIncrement),
                    'max': baseMax / quoteMin,
                },
            }
            result[symbol] = {
                'id': id,
                'symbol': symbol,
                'baseId': baseId,
                'quoteId': quoteId,
                'base': base,
                'quote': quote,
                'active': active,
                'precision': precision,
                'limits': limits,
                'info': entry,
            }
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicGetCurrencies(params)
        #
        # {precision: 10,
        #   name: 'KCS',
        #   fullName: 'KCS shares',
        #   currency: 'KCS'}
        #
        responseData = response['data']
        result = {}
        for i in range(0, len(responseData)):
            entry = responseData[i]
            id = self.safe_string(entry, 'name')
            name = entry['fullName']
            code = self.common_currency_code(id)
            precision = self.safe_integer(entry, 'precision')
            result[code] = {
                'id': id,
                'name': name,
                'code': code,
                'precision': precision,
                'info': entry,
            }
        return result

    async def fetch_accounts(self, params={}):
        response = await self.privateGetAccounts(params)
        responseData = response['data']
        result = {}
        for i in range(0, len(responseData)):
            entry = responseData[i]
            accountId = entry['type']
            result[accountId] = {
                'accountId': accountId,
                'type': accountId,  # main or trade
                'currency': None,
                'info': entry,
            }
        return result

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "symbol": "ETH-BTC",
        #         "high": "0.1",
        #         "vol": "3891.5909166",
        #         "low": "0.024",
        #         "changePrice": "0.031809",
        #         "changeRate": "31809",
        #         "close": "0.03181",
        #         "volValue": "119.5545894397034",
        #         "open": "0.000001",
        #     }
        #
        change = self.safe_float(ticker, 'changePrice')
        percentage = self.safe_float(ticker, 'changeRate')
        open = self.safe_float(ticker, 'open')
        last = self.safe_float(ticker, 'close')
        high = self.safe_float(ticker, 'high')
        low = self.safe_float(ticker, 'low')
        baseVolume = self.safe_float(ticker, 'vol')
        quoteVolume = self.safe_float(ticker, 'volValue')
        symbol = None
        if market:
            symbol = market['symbol']
        return {
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': high,
            'low': low,
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = await self.publicGetMarketStats(self.extend(request, params))
        #
        #     {
        #         "code": "200000",
        #         "data": {
        #             "symbol": "ETH-BTC",
        #             "high": "0.1",
        #             "vol": "3891.5909166",
        #             "low": "0.024",
        #             "changePrice": "0.031809",
        #             "changeRate": "31809",
        #             "close": "0.03181",
        #             "volValue": "119.5545894397034",
        #             "open": "0.000001",
        #         },
        #     }
        #
        return self.parse_ticker(response['data'], market)

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        #
        #   [["1545904980",             #Start time of the candle cycle
        #       "0.058",                  #opening price
        #       "0.049",                  #closing price
        #       "0.058",                  #highest price
        #       "0.049",                  #lowest price
        #       "0.018",                  #Transaction amount
        #       "0.000945"], ...]       #Transaction volume
        #
        timestamp = self.safe_integer(ohlcv, 0)
        open = self.safe_float(ohlcv, 1)
        close = self.safe_float(ohlcv, 2)
        high = self.safe_float(ohlcv, 3)
        low = self.safe_float(ohlcv, 4)
        volume = self.safe_float(ohlcv, 6)
        return [timestamp, open, high, low, close, volume]

    async def fetch_ohlcv(self, symbol, timeframe='15m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        marketId = market['id']
        request = {
            'symbol': marketId,
            'endAt': self.seconds(),  # required param
            'type': self.timeframes[timeframe],
        }
        if since is not None:
            request['startAt'] = int(math.floor(since / 1000))
        response = await self.publicGetMarketCandles(self.extend(request, params))
        responseData = response['data']
        return self.parse_ohlcvs(responseData, market, timeframe, since, limit)

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currencyId = self.currencyId(code)
        request = {'currency': currencyId}
        response = await self.privateGetDepositAddresses(self.extend(request, params))
        address = self.safe_string(response, 'address')
        memo = self.safe_string(response, 'memo')
        return {
            'address': address,
            'tag': memo,
        }

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        marketId = self.market_id(symbol)
        request = {'symbol': marketId, 'level': 3}
        response = await self.publicGetMarketOrderbookLevelLevel(self.extend(request, params))
        #
        # {sequence: '1547731421688',
        #   asks: [['5c419328ef83c75456bd615c', '0.9', '0.09'], ...],
        #   bids: [['5c419328ef83c75456bd615c', '0.9', '0.09'], ...],}
        #
        responseData = response['data']
        timestamp = self.safe_integer(responseData, 'sequence')
        return self.parse_order_book(responseData, timestamp, 'bids', 'asks', 1, 2)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        marketId = self.market_id(symbol)
        # required param, cannot be used twice
        clientOid = self.uuid()
        request = {
            'clientOid': clientOid,
            'price': price,
            'side': side,
            'size': amount,
            'symbol': marketId,
            'type': type,
        }
        response = await self.privatePostOrders(self.extend(request, params))
        responseData = response['data']
        return {
            'id': responseData['orderId'],
            'symbol': symbol,
            'type': type,
            'side': side,
            'status': 'open',
            'clientOid': clientOid,
            'info': responseData,
        }

    async def cancel_order(self, id, symbol=None, params={}):
        request = {'orderId': id}
        response = self.privateDeleteOrdersOrderId(self.extend(request, params))
        return response

    async def fetch_orders_by_status(self, status, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'status': status,
        }
        if since is not None:
            request['startAt'] = since
        if limit is not None:
            request['pageSize'] = limit
        response = await self.privateGetOrders(self.extend(request, params))
        responseData = response['data']
        orders = responseData['items']
        return self.parse_orders(orders, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_status('done', symbol, since, limit, params)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        return self.fetch_orders_by_status('active', symbol, since, limit, params)

    async def fetch_order(self, id, symbol=None, params={}):
        request = {
            'orderId': id,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
        response = await self.privateGetOrdersOrderId(self.extend(request, params))
        responseData = response['data']
        return self.parse_order(responseData, market)

    def parse_symbol(self, id):
        quote, base = id.split(self.options['symbolSeparator'])
        base = self.common_currency_code(base)
        quote = self.common_currency_code(quote)
        return base + '/' + quote

    def parse_order(self, order, market=None):
        #
        #   {"id": "5c35c02703aa673ceec2a168",
        #     "symbol": "BTC-USDT",
        #     "opType": "DEAL",
        #     "type": "limit",
        #     "side": "buy",
        #     "price": "10",
        #     "size": "2",
        #     "funds": "0",
        #     "dealFunds": "0.166",
        #     "dealSize": "2",
        #     "fee": "0",
        #     "feeCurrency": "USDT",
        #     "stp": "",
        #     "stop": "",
        #     "stopTriggered": False,
        #     "stopPrice": "0",
        #     "timeInForce": "GTC",
        #     "postOnly": False,
        #     "hidden": False,
        #     "iceberge": False,
        #     "visibleSize": "0",
        #     "cancelAfter": 0,
        #     "channel": "IOS",
        #     "clientOid": "",
        #     "remark": "",
        #     "tags": "",
        #     "isActive": False,
        #     "cancelExist": False,
        #     "createdAt": 1547026471000}
        #
        symbol = None
        if market is not None:
            symbol = market['symbol']
        else:
            marketId = self.safe_string(order, 'symbol')
            if marketId is not None:
                symbol = self.parse_symbol(marketId)
        orderId = self.safe_string(order, 'id')
        type = self.safe_string(order, 'type')
        timestamp = self.safe_string(order, 'createdAt')
        datetime = self.iso8601(timestamp)
        price = self.safe_float(order, 'price')
        side = self.safe_string(order, 'side')
        feeCurrency = self.safe_string(order, 'feeCurrency')
        fee = self.safe_float(order, 'fee')
        amount = self.safe_float(order, 'size')
        filled = self.safe_float(order, 'dealSize')
        remaining = amount - filled
        # bool
        status = 'open' if order['isActive'] else 'closed'
        fees = {
            'currency': feeCurrency,
            'cost': fee,
        }
        return {
            'id': orderId,
            'symbol': symbol,
            'type': type,
            'side': side,
            'amount': amount,
            'price': price,
            'filled': filled,
            'remaining': remaining,
            'timestamp': timestamp,
            'datetime': datetime,
            'fee': fees,
            'status': status,
            'info': order,
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        marketId = market['id']
        request = {
            'symbol': marketId,
        }
        if since is not None:
            request['startAt'] = since
        if limit is not None:
            request['pageSize'] = limit
        response = await self.privateGetFills(self.extend(request, params))
        data = self.safe_value(response, 'data', {})
        trades = self.safe_value(data, 'items', [])
        return self.parse_trades(trades, market, since, limit)

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if since is not None:
            request['startAt'] = int(math.floor(since / 1000))
        if limit is not None:
            request['pageSize'] = limit
        response = await self.publicGetMarketHistories(self.extend(request, params))
        #     {
        #         "code": "200000",
        #         "data": [
        #             {
        #                 "sequence": "1548764654235",
        #                 "side": "sell",
        #                 "size":"0.6841354",
        #                 "price":"0.03202",
        #                 "time":1548848575203567174
        #             }
        #         ]
        #     }
        #
        trades = self.safe_value(response, 'data', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "sequence": "1548764654235",
        #         "side": "sell",
        #         "size":"0.6841354",
        #         "price":"0.03202",
        #         "time":1548848575203567174
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "symbol":"BTC-USDT",
        #         "tradeId":"5c35c02709e4f67d5266954e",
        #         "orderId":"5c35c02703aa673ceec2a168",
        #         "counterOrderId":"5c1ab46003aa676e487fa8e3",
        #         "side":"buy",
        #         "liquidity":"taker",
        #         "forceTaker":true,
        #         "price":"0.083",
        #         "size":"0.8424304",
        #         "funds":"0.0699217232",
        #         "fee":"0",
        #         "feeRate":"0",
        #         "feeCurrency":"USDT",
        #         "stop":"",
        #         "type":"limit",
        #         "createdAt":1547026472000
        #     }
        #
        symbol = None
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_value(self.markets_by_id, marketId, market)
        if market is not None:
            symbol = market['symbol']
        id = self.safe_string(trade, 'tradeId')
        if id is not None:
            id = str(id)
        orderId = self.safe_string(trade, 'orderId')
        amount = self.safe_float(trade, 'size')
        timestamp = self.safe_integer(trade, 'time')
        if timestamp is not None:
            timestamp = int(timestamp / 1000000)
        else:
            timestamp = self.safe_integer(trade, 'createdAt')
        price = self.safe_float(trade, 'price')
        side = self.safe_string(trade, 'side')
        fee = {
            'cost': self.safe_float(trade, 'fee'),
            'rate': self.safe_float(trade, 'feeRate'),
            'feeCurrency': self.safe_string(trade, 'feeCurrency'),
        }
        type = self.safe_string(trade, 'type')
        cost = self.safe_float(trade, 'funds')
        if amount is not None:
            if price is not None:
                cost = amount * price
        return {
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        await self.load_markets()
        self.check_address(address)
        currency = self.currencyId(code)
        request = {
            'currency': currency,
            'address': address,
        }
        if tag is not None:
            request['memo'] = tag
        response = await self.privatePostWithdrawal(self.extend(request, params))
        #
        # {"withdrawalId": "5bffb63303aa675e8bbe18f9"}
        #
        responseData = response['data']
        return {
            'id': self.safe_string(responseData, 'withdrawalId'),
            'info': responseData,
        }

    def parse_transaction_status(self, status):
        statuses = {
            'SUCCESS': 'ok',
            'PROCESSING': 'ok',
            'FAILURE': 'failed',
        }
        return self.safe_string(statuses, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # Deposits
        #   {"address": "0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
        #     "memo": "5c247c8a03aa677cea2a251d",
        #     "amount": 1,
        #     "fee": 0.0001,
        #     "currency": "KCS",
        #     "isInner": False,
        #     "walletTxId": "5bbb57386d99522d9f954c5a@test004",
        #     "status": "SUCCESS",
        #     "createdAt": 1544178843000,
        #     "updatedAt": 1544178891000}
        # Withdrawals
        #   {"id": "5c2dc64e03aa675aa263f1ac",
        #     "address": "0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
        #     "memo": "",
        #     "currency": "ETH",
        #     "amount": 1.0000000,
        #     "fee": 0.0100000,
        #     "walletTxId": "3e2414d82acce78d38be7fe9",
        #     "isInner": False,
        #     "status": "FAILURE",
        #     "createdAt": 1546503758000,
        #     "updatedAt": 1546504603000}
        #
        code = None
        currencyId = self.safe_string(transaction, 'currency')
        currency = self.safe_value(self.currencies_by_id, currencyId)
        if currency is not None:
            code = currency['code']
        else:
            code = self.common_currency_code(currencyId)
        address = self.safe_string(transaction, 'address')
        amount = self.safe_float(transaction, 'amount')
        txid = self.safe_string(transaction, 'walletTxId')
        type = txid is 'withdrawal' if None else 'deposit'
        rawStatus = self.safe_string(transaction, 'status')
        status = self.parse_transaction_status(rawStatus)
        fees = {
            'cost': self.safe_float(transaction, 'fee'),
        }
        if fees['cost'] is not None and amount is not None:
            fees['rate'] = fees['cost'] / amount
        tag = self.safe_string(transaction, 'memo')
        timestamp = self.safe_integer_2(transaction, 'updatedAt', 'createdAt')
        datetime = self.iso8601(timestamp)
        return {
            'address': address,
            'tag': tag,
            'currency': code,
            'amount': amount,
            'txid': txid,
            'type': type,
            'status': status,
            'fee': fees,
            'timestamp': timestamp,
            'datetime': datetime,
            'info': transaction,
        }

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['startAt'] = since
        if limit is not None:
            request['pageSize'] = limit
        response = await self.privateGetDeposits(self.extend(request, params))
        #
        # paginated
        # {code: '200000',
        #   data:
        #    {totalNum: 0,
        #      totalPage: 0,
        #      pageSize: 10,
        #      currentPage: 1,
        #      items: [...]
        #     }}
        #
        responseData = response['data']['items']
        return self.parseTransactions(responseData, currency, since, limit)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['startAt'] = since
        if limit is not None:
            request['pageSize'] = limit
        response = await self.privateGetWithdrawals(self.extend(request, params))
        #
        # paginated
        # {code: '200000',
        #   data:
        #    {totalNum: 0,
        #      totalPage: 0,
        #      pageSize: 10,
        #      currentPage: 1,
        #      items: [...]} }
        #
        responseData = response['data']['items']
        return self.parseTransactions(responseData, currency, since, limit)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        request = {
            'type': 'main',
        }
        response = await self.privateGetAccounts(self.extend(request, params))
        responseData = response['data']
        result = {'info': responseData}
        for i in range(0, len(responseData)):
            entry = responseData[i]
            currencyId = entry['currency']
            code = self.common_currency_code(currencyId)
            account = {}
            account['total'] = self.safe_float(entry, 'balance', 0)
            account['free'] = self.safe_float(entry, 'available', 0)
            account['used'] = self.safe_float(entry, 'holds', 0)
            result[code] = account
        return self.parse_balance(result)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        if self.urls['api'][api] != self.urls['test'][api]:
            raise ExchangeError("KuCoin Platform 2.0 API currently works in preflight sandbox test mode. To proceed with KuCoin Platform 2.0 API in sandbox test mode and acknowledge self warning, please, set .urls['api'] = .urls['test'] after instantiating the exchange class. To trade with the exchange in live mode, please, use " + self.name + " API v1 as you would normally do. The official launch of KuCoin Platform 2.0 will be announced soon.")  # eslint-disable-line quotes
        #
        # the v2 URL is https://openapi-v2.kucoin.com/api/v1/endpoint
        #                                †                 ↑
        #
        endpoint = '/api/' + self.options['version'] + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        endpart = ''
        headers = headers is not headers if None else {}
        if query:
            if method != 'GET':
                body = self.json(query)
                endpart = body
                headers['Content-Type'] = 'application/json'
            else:
                endpoint += '?' + self.urlencode(query)
        url = self.urls['api'][api] + endpoint
        if api == 'private':
            self.check_required_credentials()
            timestamp = str(self.nonce())
            headers = self.extend({
                'KC-API-KEY': self.apiKey,
                'KC-API-TIMESTAMP': timestamp,
                'KC-API-PASSPHRASE': self.password,
            }, headers)
            payload = timestamp + method + endpoint + endpart
            headers['KC-API-SIGN'] = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'base64')
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response):
        if not response:
            return
        #
        # bad
        # {"code":"400100","msg":"validation.createOrder.clientOidIsRequired"}
        # good
        # {code: '200000',
        #   data: {...},}
        #
        errorCode = self.safe_string(response, 'code')
        if errorCode in self.exceptions:
            Exception = self.exceptions[errorCode]
            message = self.safe_string(response, 'msg', '')
            raise Exception(self.id + ' ' + message)
