# -*- 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 math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadResponse
from ccxt.base.errors import DDoSProtection
from ccxt.base.decimal_to_precision import TRUNCATE
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
from ccxt.base.decimal_to_precision import NO_PADDING


class bytetrade(Exchange):

    def describe(self):
        return self.deep_extend(super(bytetrade, self).describe(), {
            'id': 'bytetrade',
            'name': 'ByteTrade',
            'countries': ['HK'],
            'rateLimit': 500,
            'requiresWeb3': True,
            'certified': True,
            # new metainfo interface
            'has': {
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '1d': '1d',
                '5d': '5d',
                '1w': '1w',
                '1M': '1M',
            },
            'urls': {
                'test': {
                    'market': 'https://api-v2-test.byte-trade.com',
                    'public': 'https://api-v2-test.byte-trade.com',
                },
                'logo': 'https://user-images.githubusercontent.com/1294454/67288762-2f04a600-f4e6-11e9-9fd6-c60641919491.jpg',
                'api': {
                    'market': 'https://api-v2.byte-trade.com',
                    'public': 'https://api-v2.byte-trade.com',
                },
                'www': 'https://www.byte-trade.com',
                'doc': 'https://docs.byte-trade.com/#description',
            },
            'api': {
                'market': {
                    'get': [
                        'klines',        # Kline of a symbol
                        'depth',         # Market Depth of a symbol
                        'trades',        # Trade records of a symbol
                        'tickers',
                    ],
                },
                'public': {
                    'get': [
                        'symbols',        # Reference information of trading instrument, including base currency, quote precision, etc.
                        'currencies',     # The list of currencies available
                        'balance',        # Get the balance of an account
                        'orders/open',    # Get the open orders of an account
                        'orders/closed',  # Get the closed orders of an account
                        'orders/all',     # Get the open and closed orders of an account
                        'orders',         # Get the details of an order of an account
                        'orders/trades',  # Get detail match results
                        'depositaddress',  # Get deposit address
                        'withdrawals',    # Get withdrawals info
                        'deposits',       # Get deposit info
                        'transfers',      # Get transfer info
                    ],
                    'post': [
                        'transaction/createorder',    # Post create order transaction to blockchain
                        'transaction/cancelorder',    # Post cancel order transaction to blockchain
                        'transaction/withdraw',       # Post withdraw transaction to blockchain
                        'transaction/transfer',       # Post transfer transaction to blockchain
                    ],
                },
            },
            'fees': {
                'trading': {
                    'taker': 0.0008,
                    'maker': 0.0008,
                },
            },
            'commonCurrencies': {
                '1': 'ByteTrade',
                '44': 'ByteHub',
                '48': 'Blocktonic',
                '133': 'TerraCredit',
            },
            'exceptions': {
                'vertify error': AuthenticationError,  # typo on the exchange side, 'vertify'
                'verify error': AuthenticationError,  # private key signature is incorrect
                'transaction already in network': BadRequest,  # same transaction submited
                'invalid argument': BadRequest,
            },
        })

    async def fetch_currencies(self, params={}):
        currencies = await self.publicGetCurrencies(params)
        result = {}
        for i in range(0, len(currencies)):
            currency = currencies[i]
            id = self.safe_string(currency, 'code')
            code = None
            if id in self.commonCurrencies:
                code = self.commonCurrencies[id]
            else:
                code = self.safe_string(currency, 'name')
            name = self.safe_string(currency, 'fullname')
            # in byte-trade.com DEX, request https://api-v2.byte-trade.com/currencies will return currencies,
            # the api doc is https://github.com/Bytetrade/bytetrade-official-api-docs/wiki/rest-api#get-currencies-get-currencys-supported-in-bytetradecom
            # we can see the coin name is none-unique in the result, the coin which code is 18 is the CyberMiles ERC20, and the coin which code is 35 is the CyberMiles main chain, but their name is same.
            # that is because bytetrade is a DEX, supports people create coin with the same name, but the id(code) of coin is unique, so we should use the id or name and id as the identity of coin.
            # For coin name and symbol is same with CCXT, I use name@id as the key of commonCurrencies dict.
            # [{
            #     "name": "CMT",      # currency name, non-unique
            #     "code": "18",       # currency id, unique
            #     "type": "crypto",
            #     "fullname": "CyberMiles",
            #     "active": True,
            #     "chainType": "ethereum",
            #     "basePrecision": 18,
            #     "transferPrecision": 10,
            #     "externalPrecision": 18,
            #     "chainContractAddress": "0xf85feea2fdd81d51177f6b8f35f0e6734ce45f5f",
            #     "limits": {
            #       "deposit": {
            #         "min": "0",
            #         "max": "-1"
            #       },
            #       "withdraw": {
            #         "min": "0",
            #         "max": "-1"
            #       }
            #     }
            #   },
            #   {
            #     "name": "CMT",
            #     "code": "35",
            #     "type": "crypto",
            #     "fullname": "CyberMiles",
            #     "active": True,
            #     "chainType": "cmt",
            #     "basePrecision": 18,
            #     "transferPrecision": 10,
            #     "externalPrecision": 18,
            #     "chainContractAddress": "0x0000000000000000000000000000000000000000",
            #     "limits": {
            #       "deposit": {
            #         "min": "1",
            #         "max": "-1"
            #       },
            #       "withdraw": {
            #         "min": "10",
            #         "max": "-1"
            #       }
            #     }
            #   }
            #   ]
            active = self.safe_value(currency, 'active')
            limits = self.safe_value(currency, 'limits')
            deposit = self.safe_value(limits, 'deposit')
            amountPrecision = self.safe_integer(currency, 'basePrecision')
            maxDeposit = self.safe_float(deposit, 'max')
            if maxDeposit == -1.0:
                maxDeposit = None
            withdraw = self.safe_value(limits, 'withdraw')
            maxWithdraw = self.safe_float(withdraw, 'max')
            if maxWithdraw == -1.0:
                maxWithdraw = None
            result[code] = {
                'id': id,
                'code': code,
                'name': name,
                'active': active,
                'precision': amountPrecision,
                'fee': None,
                'limits': {
                    'amount': {'min': None, 'max': None},
                    'price': {'min': None, 'max': None},
                    'cost': {'min': None, 'max': None},
                    'deposit': {
                        'min': self.safe_float(deposit, 'min'),
                        'max': maxDeposit,
                    },
                    'withdraw': {
                        'min': self.safe_float(withdraw, 'min'),
                        'max': maxWithdraw,
                    },
                },
                'info': currency,
            }
        return result

    async def fetch_markets(self, params={}):
        markets = await self.publicGetSymbols(params)
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'symbol')
            base = self.safe_string(market, 'baseName')
            quote = self.safe_string(market, 'quoteName')
            baseId = self.safe_string(market, 'base')
            quoteId = self.safe_string(market, 'quote')
            normalBase = base.split('@' + baseId)[0]
            normalQuote = quote.split('@' + quoteId)[0]
            if quoteId == '126':
                normalQuote = 'ZAR'  # The id 126 coin is a special coin whose name on the chain is actually ZAR, but it is changed to ZCN after creation, so it must be changed to ZAR when placing the transaction in the chain
            normalSymbol = normalBase + '/' + normalQuote
            if baseId in self.commonCurrencies:
                base = self.commonCurrencies[baseId]
            if quoteId in self.commonCurrencies:
                quote = self.commonCurrencies[quoteId]
            symbol = base + '/' + quote
            limits = self.safe_value(market, 'limits', {})
            amount = self.safe_value(limits, 'amount', {})
            price = self.safe_value(limits, 'price', {})
            precision = self.safe_value(market, 'precision', {})
            active = self.safe_string(market, 'active')
            entry = {
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'info': market,
                'active': active,
                'precision': {
                    'amount': self.safe_integer(precision, 'amount'),
                    'price': self.safe_integer(precision, 'price'),
                },
                'normalSymbol': normalSymbol,
                'limits': {
                    'amount': {
                        'min': self.safe_float(amount, 'min'),
                        'max': self.safe_float(amount, 'max'),
                    },
                    'price': {
                        'min': self.safe_float(price, 'min'),
                        'max': self.safe_float(price, 'max'),
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
            }
            result.append(entry)
        return result

    async def fetch_balance(self, params={}):
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired(self.id + ' fetchDeposits() requires self.apiKey or userid argument')
        await self.load_markets()
        request = {
            'userid': self.apiKey,
        }
        balances = await self.publicGetBalance(self.extend(request, params))
        result = {'info': balances}
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'code')
            code = self.safe_currency_code(currencyId, None)
            account = self.account()
            account['free'] = self.safe_float(balance, 'free')
            account['used'] = self.safe_float(balance, 'used')
            result[code] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default = maximum = 100
        response = await self.marketGetDepth(self.extend(request, params))
        timestamp = self.safe_value(response, 'timestamp')
        orderbook = self.parse_order_book(response, timestamp)
        return orderbook

    def parse_ticker(self, ticker, market=None):
        timestamp = self.safe_integer(ticker, 'timestamp')
        #
        #     [
        #         {
        #             "symbol":"68719476706",
        #             "name":"ETH/BTC",
        #             "base":"2",
        #             "quote":"32",
        #             "timestamp":1575905991933,
        #             "datetime":"2019-12-09T15:39:51.933Z",
        #             "high":"0",
        #             "low":"0",
        #             "open":"0",
        #             "close":"0",
        #             "last":"0",
        #             "change":"0",
        #             "percentage":"0",
        #             "baseVolume":"0",
        #             "quoteVolume":"0"
        #         }
        #     ]
        #
        symbol = None
        marketId = self.safe_string(ticker, 'symbol')
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
        else:
            baseId = self.safe_string(ticker, 'base')
            quoteId = self.safe_string(ticker, 'quote')
            if (baseId is not None) and (quoteId is not None):
                base = self.safe_currency_code(baseId)
                quote = self.safe_currency_code(quoteId)
                symbol = base + '/' + quote
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': self.safe_float(ticker, 'weightedAvgPrice'),
            'open': self.safe_float(ticker, 'open'),
            'close': self.safe_float(ticker, 'close'),
            'last': self.safe_float(ticker, 'last'),
            'previousClose': None,  # previous day close
            'change': self.safe_float(ticker, 'change'),
            'percentage': self.safe_float(ticker, 'percentage'),
            'average': None,
            'baseVolume': self.safe_float(ticker, 'baseVolume'),
            'quoteVolume': self.safe_float(ticker, '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.marketGetTickers(self.extend(request, params))
        #
        #     [
        #         {
        #             "symbol":"68719476706",
        #             "name":"ETH/BTC",
        #             "base":"2",
        #             "quote":"32",
        #             "timestamp":1575905991933,
        #             "datetime":"2019-12-09T15:39:51.933Z",
        #             "high":"0",
        #             "low":"0",
        #             "open":"0",
        #             "close":"0",
        #             "last":"0",
        #             "change":"0",
        #             "percentage":"0",
        #             "baseVolume":"0",
        #             "quoteVolume":"0"
        #         }
        #     ]
        #
        if isinstance(response, list):
            ticker = self.safe_value(response, 0)
            if ticker is None:
                raise BadResponse(self.id + ' fetchTicker() returned an empty response')
            return self.parse_ticker(ticker, market)
        return self.parse_ticker(response, market)

    def parse_tickers(self, rawTickers, symbols=None):
        tickers = []
        for i in range(0, len(rawTickers)):
            tickers.append(self.parse_ticker(rawTickers[i]))
        return self.filter_by_array(tickers, 'symbol', symbols)

    async def fetch_bids_asks(self, symbols=None, params={}):
        await self.load_markets()
        rawTickers = await self.marketGetDepth(params)
        return self.parse_tickers(rawTickers, symbols)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        rawTickers = await self.marketGetTickers(params)
        return self.parse_tickers(rawTickers, symbols)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     [
        #         1591505760000,
        #         "242.7",
        #         "242.76",
        #         "242.69",
        #         "242.76",
        #         "0.1892"
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_float(ohlcv, 1),
            self.safe_float(ohlcv, 2),
            self.safe_float(ohlcv, 3),
            self.safe_float(ohlcv, 4),
            self.safe_float(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
            'timeframe': self.timeframes[timeframe],
        }
        if since is not None:
            request['since'] = since
        if limit is not None:
            request['limit'] = limit
        response = await self.marketGetKlines(self.extend(request, params))
        #
        #     [
        #         [1591505760000,"242.7","242.76","242.69","242.76","0.1892"],
        #         [1591505820000,"242.77","242.83","242.7","242.72","0.6378"],
        #         [1591505880000,"242.72","242.73","242.61","242.72","0.4141"],
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_trade(self, trade, market=None):
        timestamp = self.safe_integer(trade, 'timestamp')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = self.safe_float(trade, 'cost')
        id = self.safe_string(trade, 'id')
        type = self.safe_string(trade, 'type')
        takerOrMaker = self.safe_string(trade, 'takerOrMaker')
        side = self.safe_string(trade, 'side')
        datetime = self.iso8601(timestamp)  # self.safe_string(trade, 'datetime')
        order = self.safe_string(trade, 'order')
        fee = self.safe_value(trade, 'fee')
        symbol = None
        if market is None:
            marketId = self.safe_string(trade, 'symbol')
            market = self.safe_value(self.markets_by_id, marketId)
        if market is not None:
            symbol = market['symbol']
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': datetime,
            'symbol': symbol,
            'id': id,
            'order': order,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    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['since'] = since
        if limit is not None:
            request['limit'] = limit  # default = 100, maximum = 500
        response = await self.marketGetTrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def parse_order(self, order, market=None):
        status = self.safe_string(order, 'status')
        symbol = None
        marketId = self.safe_string(order, 'symbol')
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
        else:
            baseId = self.safe_string(order, 'base')
            quoteId = self.safe_string(order, 'quote')
            if (baseId is not None) and (quoteId is not None):
                base = self.safe_currency_code(baseId)
                quote = self.safe_currency_code(quoteId)
                symbol = base + '/' + quote
        if (symbol is None) and (market is not None):
            symbol = market['symbol']
        timestamp = self.safe_integer(order, 'timestamp')
        datetime = self.safe_string(order, 'datetime')
        lastTradeTimestamp = self.safe_integer(order, 'lastTradeTimestamp')
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'amount')
        filled = self.safe_float(order, 'filled')
        remaining = self.safe_float(order, 'remaining')
        cost = self.safe_float(order, 'cost')
        average = self.safe_float(order, 'average')
        id = self.safe_string(order, 'id')
        type = self.safe_string(order, 'type')
        side = self.safe_string(order, 'side')
        fee = self.safe_value(order, 'fee')
        return {
            'info': order,
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': datetime,
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': type,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': None,
        }

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.check_required_dependencies()
        if self.apiKey is None:
            raise ArgumentsRequired('createOrder() requires self.apiKey or userid in params')
        await self.load_markets()
        market = self.market(symbol)
        sideNum = None
        typeNum = None
        if side == 'sell':
            sideNum = 1
        else:
            sideNum = 2
        if type == 'limit':
            typeNum = 1
        else:
            typeNum = 2
            price = 0
        normalSymbol = market['normalSymbol']
        baseId = market['baseId']
        baseCurrency = self.currency(market['base'])
        amountTruncated = self.amount_to_precision(symbol, amount)
        amountChain = self.to_wei(amountTruncated, baseCurrency['precision'])
        quoteId = market['quoteId']
        quoteCurrency = self.currency(market['quote'])
        priceRounded = self.price_to_precision(symbol, price)
        priceChain = self.to_wei(priceRounded, quoteCurrency['precision'])
        now = self.milliseconds()
        expiration = self.milliseconds()
        datetime = self.iso8601(now)
        datetime = datetime.split('.')[0]
        expirationDatetime = self.iso8601(expiration)
        expirationDatetime = expirationDatetime.split('.')[0]
        defaultDappId = 'Sagittarius'
        dappId = self.safe_string(params, 'dappId', defaultDappId)
        defaultFee = self.safe_string(self.options, 'fee', '300000000000000')
        totalFeeRate = self.safe_string(params, 'totalFeeRate', 8)
        chainFeeRate = self.safe_string(params, 'chainFeeRate', 1)
        fee = self.safe_string(params, 'fee', defaultFee)
        eightBytes = self.integer_pow('2', '64')
        allByteStringArray = [
            self.number_to_be(1, 32),
            self.number_to_le(int(math.floor(now / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(int(math.floor(expiration / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(32, 1),
            self.number_to_le(0, 8),
            self.number_to_le(fee, 8),  # string for 32 bit php
            self.number_to_le(len(self.apiKey), 1),
            self.encode(self.apiKey),
            self.number_to_le(sideNum, 1),
            self.number_to_le(typeNum, 1),
            self.number_to_le(len(normalSymbol), 1),
            self.encode(normalSymbol),
            self.number_to_le(self.integer_divide(amountChain, eightBytes), 8),
            self.number_to_le(self.integer_modulo(amountChain, eightBytes), 8),
            self.number_to_le(self.integer_divide(priceChain, eightBytes), 8),
            self.number_to_le(self.integer_modulo(priceChain, eightBytes), 8),
            self.number_to_le(0, 2),
            self.number_to_le(int(math.floor(now / 1000)), 4),
            self.number_to_le(int(math.floor(expiration / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(int(chainFeeRate), 2),
            self.number_to_le(1, 1),
            self.number_to_le(int(totalFeeRate), 2),
            self.number_to_le(int(quoteId), 4),
            self.number_to_le(int(baseId), 4),
            self.number_to_le(0, 1),
            self.number_to_le(1, 1),
            self.number_to_le(len(dappId), 1),
            self.encode(dappId),
            self.number_to_le(0, 1),
        ]
        txByteStringArray = [
            self.number_to_le(int(math.floor(now / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(int(math.floor(expiration / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(32, 1),
            self.number_to_le(0, 8),
            self.number_to_le(fee, 8),  # string for 32 bit php
            self.number_to_le(len(self.apiKey), 1),
            self.encode(self.apiKey),
            self.number_to_le(sideNum, 1),
            self.number_to_le(typeNum, 1),
            self.number_to_le(len(normalSymbol), 1),
            self.encode(normalSymbol),
            self.number_to_le(self.integer_divide(amountChain, eightBytes), 8),
            self.number_to_le(self.integer_modulo(amountChain, eightBytes), 8),
            self.number_to_le(self.integer_divide(priceChain, eightBytes), 8),
            self.number_to_le(self.integer_modulo(priceChain, eightBytes), 8),
            self.number_to_le(0, 2),
            self.number_to_le(int(math.floor(now / 1000)), 4),
            self.number_to_le(int(math.floor(expiration / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(int(chainFeeRate), 2),
            self.number_to_le(1, 1),
            self.number_to_le(int(totalFeeRate), 2),
            self.number_to_le(int(quoteId), 4),
            self.number_to_le(int(baseId), 4),
            self.number_to_le(0, 1),
            self.number_to_le(1, 1),
            self.number_to_le(len(dappId), 1),
            self.encode(dappId),
            self.number_to_le(0, 1),
        ]
        txbytestring = self.binary_concat_array(txByteStringArray)
        txidhash = self.hash(txbytestring, 'sha256', 'hex')
        txid = txidhash[0:40]
        orderidByteStringArray = [
            self.number_to_le(len(txid), 1),
            self.encode(txid),
            self.number_to_be(0, 4),
        ]
        orderidbytestring = self.binary_concat_array(orderidByteStringArray)
        orderidhash = self.hash(orderidbytestring, 'sha256', 'hex')
        orderid = orderidhash[0:40]
        bytestring = self.binary_concat_array(allByteStringArray)
        hash = self.hash(bytestring, 'sha256', 'hex')
        signature = self.ecdsa(hash, self.secret, 'secp256k1', None, True)
        recoveryParam = self.binary_to_base16(self.number_to_le(self.sum(signature['v'], 31), 1))
        mySignature = recoveryParam + signature['r'] + signature['s']
        operation = {
            'now': datetime,
            'expiration': expirationDatetime,
            'fee': fee,
            'creator': self.apiKey,
            'side': sideNum,
            'order_type': typeNum,
            'market_name': normalSymbol,
            'amount': amountChain,
            'price': priceChain,
            'use_btt_as_fee': False,
            'money_id': int(quoteId),
            'stock_id': int(baseId),
            'custom_no_btt_fee_rate': int(totalFeeRate),
            'custom_btt_fee_rate': int(chainFeeRate),
        }
        fatty = {
            'timestamp': datetime,
            'expiration': expirationDatetime,
            'operations': [
                [
                    32,
                    operation,
                ],
            ],
            'validate_type': 0,
            'dapp': dappId,
            'signatures': [
                mySignature,
            ],
        }
        request = {
            'trObj': self.json(fatty),
        }
        response = await self.publicPostTransactionCreateorder(request)
        timestamp = self.milliseconds()
        statusCode = self.safe_string(response, 'code')
        status = 'open' if (statusCode == '0') else 'failed'
        return {
            'info': response,
            'id': orderid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': None,
            'type': None,
            'side': None,
            'price': None,
            'amount': None,
            'filled': None,
            'remaining': None,
            'cost': None,
            'trades': None,
            'fee': None,
            'clientOrderId': None,
            'average': None,
        }

    async def fetch_order(self, id, symbol=None, params={}):
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchOrder() requires self.apiKey or userid argument')
        await self.load_markets()
        request = {
            'userid': self.apiKey,
        }
        market = None
        if symbol is not None:
            market = self.markets[symbol]
            request['symbol'] = market['id']
        request['id'] = id
        response = await self.publicGetOrders(self.extend(request, params))
        return self.parse_order(response, market)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchOpenOrders() requires self.apiKey or userid argument')
        await self.load_markets()
        request = {
            'userid': self.apiKey,
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrdersOpen(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchClosedOrders() requires self.apiKey or userid argument')
        await self.load_markets()
        market = None
        request = {
            'userid': self.apiKey,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrdersClosed(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchOrders() requires self.apiKey or userid argument')
        await self.load_markets()
        market = None
        request = {
            'userid': self.apiKey,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrdersAll(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def cancel_order(self, id, symbol=None, params={}):
        if self.apiKey is None:
            raise ArgumentsRequired('cancelOrder() requires hasAlreadyAuthenticatedSuccessfully')
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        baseId = market['baseId']
        quoteId = market['quoteId']
        normalSymbol = market['normalSymbol']
        feeAmount = '300000000000000'
        now = self.milliseconds()
        expiration = 0
        datetime = self.iso8601(now)
        datetime = datetime.split('.')[0]
        expirationDatetime = self.iso8601(expiration)
        expirationDatetime = expirationDatetime.split('.')[0]
        defaultDappId = 'Sagittarius'
        dappId = self.safe_string(params, 'dappId', defaultDappId)
        byteStringArray = [
            self.number_to_be(1, 32),
            self.number_to_le(int(math.floor(now / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(expiration, 4),
            self.number_to_le(1, 1),
            self.number_to_le(33, 1),
            self.number_to_le(0, 8),
            self.number_to_le(feeAmount, 8),  # string for 32 bit php
            self.number_to_le(len(self.apiKey), 1),
            self.encode(self.apiKey),
            self.number_to_le(len(normalSymbol), 1),
            self.encode(normalSymbol),
            self.base16_to_binary(id),
            self.number_to_le(int(quoteId), 4),
            self.number_to_le(int(baseId), 4),
            self.number_to_le(0, 1),
            self.number_to_le(1, 1),
            self.number_to_le(len(dappId), 1),
            self.encode(dappId),
            self.number_to_le(0, 1),
        ]
        bytestring = self.binary_concat_array(byteStringArray)
        hash = self.hash(bytestring, 'sha256', 'hex')
        signature = self.ecdsa(hash, self.secret, 'secp256k1', None, True)
        recoveryParam = self.binary_to_base16(self.number_to_le(self.sum(signature['v'], 31), 1))
        mySignature = recoveryParam + signature['r'] + signature['s']
        operation = {
            'fee': feeAmount,
            'creator': self.apiKey,
            'order_id': id,
            'market_name': normalSymbol,
            'money_id': int(quoteId),
            'stock_id': int(baseId),
        }
        fatty = {
            'timestamp': datetime,
            'expiration': expirationDatetime,
            'operations': [
                [
                    33,
                    operation,
                ],
            ],
            'validate_type': 0,
            'dapp': dappId,
            'signatures': [
                mySignature,
            ],
        }
        request = {
            'trObj': self.json(fatty),
        }
        response = await self.publicPostTransactionCancelorder(request)
        timestamp = self.milliseconds()
        statusCode = self.safe_string(response, 'code')
        status = 'canceled' if (statusCode == '0') else 'failed'
        return {
            'info': response,
            'id': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': None,
            'type': None,
            'side': None,
            'price': None,
            'amount': None,
            'filled': None,
            'remaining': None,
            'cost': None,
            'trades': None,
            'fee': None,
            'clientOrderId': None,
            'average': None,
        }

    async def transfer(self, code, amount, address, message='', params={}):
        self.check_required_dependencies()
        if self.apiKey is None:
            raise ArgumentsRequired('transfer() requires self.apiKey')
        await self.load_markets()
        currency = self.currency(code)
        amountTruncate = self.decimal_to_precision(amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING)
        amountChain = self.to_wei(amountTruncate, currency['precision'])
        assetType = int(currency['id'])
        now = self.milliseconds()
        expiration = now
        datetime = self.iso8601(now)
        datetime = datetime.split('.')[0]
        expirationDatetime = self.iso8601(expiration)
        expirationDatetime = expirationDatetime.split('.')[0]
        feeAmount = '300000000000000'
        defaultDappId = 'Sagittarius'
        dappId = self.safe_string(params, 'dappId', defaultDappId)
        eightBytes = self.integer_pow('2', '64')
        byteStringArray = [
            self.number_to_be(1, 32),
            self.number_to_le(int(math.floor(now / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(int(math.floor(expiration / 1000)), 4),
            self.number_to_le(1, 1),
            self.number_to_le(28, 1),
            self.number_to_le(0, 8),
            self.number_to_le(feeAmount, 8),  # string for 32 bit php
            self.number_to_le(len(self.apiKey), 1),
            self.encode(self.apiKey),
            self.number_to_le(len(address), 1),
            self.encode(address),
            self.number_to_le(assetType, 4),
            self.number_to_le(self.integer_divide(amountChain, eightBytes), 8),
            self.number_to_le(self.integer_modulo(amountChain, eightBytes), 8),
            self.number_to_le(1, 1),
            self.number_to_le(len(message), 1),
            self.encode(message),
            self.number_to_le(0, 1),
            self.number_to_le(1, 1),
            self.number_to_le(len(dappId), 1),
            self.encode(dappId),
            self.number_to_le(0, 1),
        ]
        bytestring = self.binary_concat_array(byteStringArray)
        hash = self.hash(bytestring, 'sha256', 'hex')
        signature = self.ecdsa(hash, self.secret, 'secp256k1', None, True)
        recoveryParam = self.binary_to_base16(self.number_to_le(self.sum(signature['v'], 31), 1))
        mySignature = recoveryParam + signature['r'] + signature['s']
        operation = {
            'fee': '300000000000000',
            'from': self.apiKey,
            'to': address,
            'asset_type': int(currency['id']),
            'amount': str(amountChain),
            'message': message,
        }
        fatty = {
            'timestamp': datetime,
            'expiration': expirationDatetime,
            'operations': [
                [
                    28,
                    operation,
                ],
            ],
            'validate_type': 0,
            'dapp': dappId,
            'signatures': [
                mySignature,
            ],
        }
        request = {
            'trObj': self.json(fatty),
        }
        response = await self.publicPostTransactionTransfer(request)
        timestamp = self.milliseconds()
        statusCode = self.safe_string(response, 'code')
        status = ''
        if statusCode == '0':
            status = 'submit success'
        else:
            status = 'submit fail'
        return {
            'info': response,
            'id': '',
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': None,
            'type': None,
            'side': None,
            'price': None,
            'amount': None,
            'filled': None,
            'remaining': None,
            'cost': None,
            'fee': None,
            'clientOrderId': None,
            'average': None,
            'trades': None,
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchMyTrades() requires self.apiKey or userid argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'userid': self.apiKey,
        }
        if symbol is not None:
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetOrdersTrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchDeposits() requires self.apiKey or userid argument')
        currency = None
        request = {
            'userid': self.apiKey,
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['since'] = since
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetDeposits(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()
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchWithdrawals() requires self.apiKey or userid argument')
        currency = None
        request = {
            'userid': self.apiKey,
        }
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if since is not None:
            request['since'] = since
        if limit is not None:
            request['limit'] = limit
        response = await self.publicGetWithdrawals(self.extend(request, params))
        return self.parse_transactions(response, currency, since, limit)

    def parse_transaction_status(self, status):
        statuses = {
            'DEPOSIT_FAILED': 'failed',
            'FEE_SEND_FAILED': 'failed',
            'FEE_FAILED': 'failed',
            'PAY_SEND_FAILED': 'failed',
            'PAY_FAILED': 'failed',
            'BTT_FAILED': 'failed',
            'WITHDDRAW_FAILED': 'failed',
            'USER_FAILED': 'failed',
            'FEE_EXECUED': 'pending',
            'PAY_EXECUED': 'pending',
            'WITHDDRAW_EXECUTED': 'pending',
            'USER_EXECUED': 'pending',
            'BTT_SUCCED': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        id = self.safe_string(transaction, 'id')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'tag')
        if tag is not None:
            if len(tag) < 1:
                tag = None
        txid = self.safe_value(transaction, 'txid')
        currencyId = self.safe_string(transaction, 'code')
        code = self.safe_currency_code(currencyId, currency)
        timestamp = self.safe_integer(transaction, 'timestamp')
        datetime = self.safe_string(transaction, 'datetime')
        type = self.safe_string(transaction, 'type')
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        amount = self.safe_float(transaction, 'amount')
        feeInfo = self.safe_value(transaction, 'fee')
        feeCost = self.safe_float(feeInfo, 'cost')
        feeCurrencyId = self.safe_string(feeInfo, 'code')
        feeCode = self.safe_currency_code(feeCurrencyId, currency)
        fee = {
            'cost': feeCost,
            'currency': feeCode,
        }
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': datetime,
            'address': address,
            'tag': tag,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'fee': fee,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        if not ('userid' in params) and (self.apiKey is None):
            raise ArgumentsRequired('fetchDepositAddress() requires self.apiKey or userid argument')
        currency = self.currency(code)
        request = {
            'userid': self.apiKey,
            'code': currency['id'],
        }
        response = await self.publicGetDepositaddress(request)
        address = self.safe_string(response[0], 'address')
        tag = self.safe_string(response[0], 'tag')
        chainType = self.safe_string(response[0], 'chainType')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'chainType': chainType,
            'info': response,
        }

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_required_dependencies()
        self.check_address(address)
        await self.load_markets()
        if self.apiKey is None:
            raise ArgumentsRequired(self.id + ' withdraw() requires self.apiKey')
        addressResponse = await self.fetch_deposit_address(code)
        chainTypeString = self.safe_string(addressResponse, 'chainType')
        chainId = self.safe_string(addressResponse['info'][0], 'chainId')
        middleAddress = ''
        if chainTypeString == 'eos':
            middleAddress = address
        else:
            middleAddress = self.safe_string(addressResponse, 'address')
        operationId = 18
        if chainTypeString != 'ethereum' and chainTypeString != 'etc' and chainTypeString != 'eos' and chainTypeString != 'cmt' and chainTypeString != 'naka':
            operationId = 26
        now = self.milliseconds()
        expiration = 0
        datetime = self.iso8601(now)
        datetime = datetime.split('.')[0]
        expirationDatetime = self.iso8601(expiration)
        expirationDatetime = expirationDatetime.split('.')[0]
        defaultDappId = 'Sagittarius'
        dappId = self.safe_string(params, 'dappId', defaultDappId)
        feeAmount = '300000000000000'
        currency = self.currency(code)
        coinId = currency['id']
        amountTruncate = self.decimal_to_precision(amount, TRUNCATE, currency['info']['basePrecision'] - currency['info']['transferPrecision'], DECIMAL_PLACES, NO_PADDING)
        amountChain = self.to_wei(amountTruncate, currency['info']['externalPrecision'])
        eightBytes = self.integer_pow('2', '64')
        assetFee = 0
        byteStringArray = []
        if operationId == 26:
            assetFee = currency['info']['fee']
            byteStringArray = [
                self.number_to_be(1, 32),
                self.number_to_le(int(math.floor(now / 1000)), 4),
                self.number_to_le(1, 1),
                self.number_to_le(int(math.floor(expiration / 1000)), 4),
                self.number_to_le(1, 1),
                self.number_to_le(operationId, 1),
                self.number_to_le(0, 8),
                self.number_to_le(feeAmount, 8),  # string for 32 bit php
                self.number_to_le(len(self.apiKey), 1),
                self.encode(self.apiKey),
                self.number_to_le(len(address), 1),
                self.encode(address),
                self.number_to_le(int(coinId), 4),
                self.number_to_le(self.integer_divide(amountChain, eightBytes), 8),
                self.number_to_le(self.integer_modulo(amountChain, eightBytes), 8),
                self.number_to_le(1, 1),
                self.number_to_le(self.integer_divide(assetFee, eightBytes), 8),
                self.number_to_le(self.integer_modulo(assetFee, eightBytes), 8),
                self.number_to_le(0, 1),
                self.number_to_le(1, 1),
                self.number_to_le(len(dappId), 1),
                self.encode(dappId),
                self.number_to_le(0, 1),
            ]
        else:
            byteStringArray = [
                self.number_to_be(1, 32),
                self.number_to_le(int(math.floor(now / 1000)), 4),
                self.number_to_le(1, 1),
                self.number_to_le(int(math.floor(expiration / 1000)), 4),
                self.number_to_le(1, 1),
                self.number_to_le(operationId, 1),
                self.number_to_le(0, 8),
                self.number_to_le(feeAmount, 8),  # string for 32 bit php
                self.number_to_le(len(self.apiKey), 1),
                self.encode(self.apiKey),
                self.number_to_le(int(math.floor(now / 1000)), 4),
                self.number_to_le(1, 1),
                self.number_to_le(4, 1),
                self.number_to_le(0, 8),
                self.number_to_le(feeAmount, 8),
                self.number_to_le(len(self.apiKey), 1),
                self.encode(self.apiKey),
                self.number_to_le(len(middleAddress), 1),
                self.encode(middleAddress),
                self.number_to_le(int(coinId), 4),
                self.number_to_le(self.integer_divide(amountChain, eightBytes), 8),
                self.number_to_le(self.integer_modulo(amountChain, eightBytes), 8),
                self.number_to_le(0, 1),
                self.number_to_le(1, 1),
                self.number_to_le(len(dappId), 1),
                self.encode(dappId),
                self.number_to_le(0, 1),
            ]
        bytestring = self.binary_concat_array(byteStringArray)
        hash = self.hash(bytestring, 'sha256', 'hex')
        signature = self.ecdsa(hash, self.secret, 'secp256k1', None, True)
        recoveryParam = self.binary_to_base16(self.number_to_le(self.sum(signature['v'], 31), 1))
        mySignature = recoveryParam + signature['r'] + signature['s']
        fatty = None
        request = None
        operation = None
        chainContractAddress = self.safe_string(currency['info'], 'chainContractAddress')
        if operationId == 26:
            operation = {
                'fee': feeAmount,
                'from': self.apiKey,
                'to_external_address': address,
                'asset_type': int(coinId),
                'amount': amountChain,
                'asset_fee': assetFee,
            }
            fatty = {
                'timestamp': datetime,
                'expiration': expirationDatetime,
                'operations': [
                    [
                        operationId,
                        operation,
                    ],
                ],
                'validate_type': 0,
                'dapp': dappId,
                'signatures': [
                    mySignature,
                ],
            }
            request = {
                'chainType': chainId,
                'trObj': self.json(fatty),
                'chainContractAddress': chainContractAddress,
            }
        else:
            operation = {
                'fee': feeAmount,
                'from': self.apiKey,
                'to_external_address': middleAddress,
                'asset_type': int(coinId),
                'amount': amountChain,
                'asset_fee': assetFee,
            }
            middle = {
                'fee': feeAmount,
                'proposaler': self.apiKey,
                'expiration_time': datetime,
                'proposed_ops': [{
                    'op': [4, operation],
                }],
            }
            fatty = {
                'timestamp': datetime,
                'expiration': expirationDatetime,
                'operations': [
                    [
                        operationId,
                        middle,
                    ],
                ],
                'validate_type': 0,
                'dapp': dappId,
                'signatures': [
                    mySignature,
                ],
            }
            if chainTypeString == 'eos':
                request = {
                    'chainType': chainId,
                    'toExternalAddress': 'noneed',
                    'trObj': self.json(fatty),
                    'chainContractAddress': chainContractAddress,
                }
            else:
                request = {
                    'chainType': chainId,
                    'toExternalAddress': address,
                    'trObj': self.json(fatty),
                    'chainContractAddress': chainContractAddress,
                }
        response = await self.publicPostTransactionWithdraw(request)
        return {
            'info': response,
            'id': self.safe_string(response, 'id'),
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api]
        url += '/' + path
        if params:
            url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if code == 503:
            raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
        if response is None:
            return  # fallback to default error handler
        if 'code' in response:
            status = self.safe_string(response, 'code')
            if status == '1':
                message = self.safe_string(response, 'msg')
                feedback = self.id + ' ' + body
                self.throw_exactly_matched_exception(self.exceptions, message, feedback)
                raise ExchangeError(feedback)
