# -*- 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.base.exchange import Exchange
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import InvalidNonce
from ccxt.base.precise import Precise


class coinbase(Exchange):

    def describe(self):
        return self.deep_extend(super(coinbase, self).describe(), {
            'id': 'coinbase',
            'name': 'Coinbase',
            'countries': ['US'],
            'rateLimit': 400,  # 10k calls per hour
            'version': 'v2',
            'userAgent': self.userAgents['chrome'],
            'headers': {
                'CB-VERSION': '2018-05-30',
            },
            'has': {
                'CORS': True,
                'cancelOrder': False,
                'createDepositAddress': True,
                'createOrder': False,
                'deposit': False,
                'fetchBalance': True,
                'fetchClosedOrders': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': False,
                'fetchMarkets': True,
                'fetchMyTrades': False,
                'fetchOHLCV': False,
                'fetchOpenOrders': False,
                'fetchOrder': False,
                'fetchOrderBook': False,
                'fetchL2OrderBook': False,
                'fetchLedger': True,
                'fetchOrders': False,
                'fetchTicker': True,
                'fetchTickers': False,
                'fetchTime': True,
                'fetchBidsAsks': False,
                'fetchTrades': False,
                'withdraw': False,
                'fetchTransactions': False,
                'fetchDeposits': True,
                'fetchWithdrawals': True,
                'fetchMySells': True,
                'fetchMyBuys': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg',
                'api': 'https://api.coinbase.com',
                'www': 'https://www.coinbase.com',
                'doc': 'https://developers.coinbase.com/api/v2',
                'fees': 'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees',
                'referral': 'https://www.coinbase.com/join/58cbe25a355148797479dbd2',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
            },
            'api': {
                'public': {
                    'get': [
                        'currencies',
                        'time',
                        'exchange-rates',
                        'users/{user_id}',
                        'prices/{symbol}/buy',
                        'prices/{symbol}/sell',
                        'prices/{symbol}/spot',
                    ],
                },
                'private': {
                    'get': [
                        'accounts',
                        'accounts/{account_id}',
                        'accounts/{account_id}/addresses',
                        'accounts/{account_id}/addresses/{address_id}',
                        'accounts/{account_id}/addresses/{address_id}/transactions',
                        'accounts/{account_id}/transactions',
                        'accounts/{account_id}/transactions/{transaction_id}',
                        'accounts/{account_id}/buys',
                        'accounts/{account_id}/buys/{buy_id}',
                        'accounts/{account_id}/sells',
                        'accounts/{account_id}/sells/{sell_id}',
                        'accounts/{account_id}/deposits',
                        'accounts/{account_id}/deposits/{deposit_id}',
                        'accounts/{account_id}/withdrawals',
                        'accounts/{account_id}/withdrawals/{withdrawal_id}',
                        'payment-methods',
                        'payment-methods/{payment_method_id}',
                        'user',
                        'user/auth',
                    ],
                    'post': [
                        'accounts',
                        'accounts/{account_id}/primary',
                        'accounts/{account_id}/addresses',
                        'accounts/{account_id}/transactions',
                        'accounts/{account_id}/transactions/{transaction_id}/complete',
                        'accounts/{account_id}/transactions/{transaction_id}/resend',
                        'accounts/{account_id}/buys',
                        'accounts/{account_id}/buys/{buy_id}/commit',
                        'accounts/{account_id}/sells',
                        'accounts/{account_id}/sells/{sell_id}/commit',
                        'accounts/{account_id}/deposists',
                        'accounts/{account_id}/deposists/{deposit_id}/commit',
                        'accounts/{account_id}/withdrawals',
                        'accounts/{account_id}/withdrawals/{withdrawal_id}/commit',
                    ],
                    'put': [
                        'accounts/{account_id}',
                        'user',
                    ],
                    'delete': [
                        'accounts/{id}',
                        'accounts/{account_id}/transactions/{transaction_id}',
                    ],
                },
            },
            'exceptions': {
                'exact': {
                    'two_factor_required': AuthenticationError,  # 402 When sending money over 2fa limit
                    'param_required': ExchangeError,  # 400 Missing parameter
                    'validation_error': ExchangeError,  # 400 Unable to validate POST/PUT
                    'invalid_request': ExchangeError,  # 400 Invalid request
                    'personal_details_required': AuthenticationError,  # 400 User’s personal detail required to complete self request
                    'identity_verification_required': AuthenticationError,  # 400 Identity verification is required to complete self request
                    'jumio_verification_required': AuthenticationError,  # 400 Document verification is required to complete self request
                    'jumio_face_match_verification_required': AuthenticationError,  # 400 Document verification including face match is required to complete self request
                    'unverified_email': AuthenticationError,  # 400 User has not verified their email
                    'authentication_error': AuthenticationError,  # 401 Invalid auth(generic)
                    'invalid_token': AuthenticationError,  # 401 Invalid Oauth token
                    'revoked_token': AuthenticationError,  # 401 Revoked Oauth token
                    'expired_token': AuthenticationError,  # 401 Expired Oauth token
                    'invalid_scope': AuthenticationError,  # 403 User hasn’t authenticated necessary scope
                    'not_found': ExchangeError,  # 404 Resource not found
                    'rate_limit_exceeded': RateLimitExceeded,  # 429 Rate limit exceeded
                    'internal_server_error': ExchangeError,  # 500 Internal server error
                },
                'broad': {
                    'request timestamp expired': InvalidNonce,  # {"errors":[{"id":"authentication_error","message":"request timestamp expired"}]}
                },
            },
            'commonCurrencies': {
                'CGLD': 'CELO',
            },
            'options': {
                'fetchCurrencies': {
                    'expires': 5000,
                },
                'accounts': [
                    'wallet',
                    'fiat',
                    # 'vault',
                ],
            },
        })

    def fetch_time(self, params={}):
        response = self.publicGetTime(params)
        #
        #     {
        #         "data": {
        #             "epoch": 1589295679,
        #             "iso": "2020-05-12T15:01:19Z"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        return self.safe_timestamp(data, 'epoch')

    def fetch_accounts(self, params={}):
        self.load_markets()
        request = {
            'limit': 100,
        }
        response = self.privateGetAccounts(self.extend(request, params))
        #
        #     {
        #         "id": "XLM",
        #         "name": "XLM Wallet",
        #         "primary": False,
        #         "type": "wallet",
        #         "currency": {
        #             "code": "XLM",
        #             "name": "Stellar Lumens",
        #             "color": "#000000",
        #             "sort_index": 127,
        #             "exponent": 7,
        #             "type": "crypto",
        #             "address_regex": "^G[A-Z2-7]{55}$",
        #             "asset_id": "13b83335-5ede-595b-821e-5bcdfa80560f",
        #             "destination_tag_name": "XLM Memo ID",
        #             "destination_tag_regex": "^[-~]{1,28}$"
        #         },
        #         "balance": {
        #             "amount": "0.0000000",
        #             "currency": "XLM"
        #         },
        #         "created_at": null,
        #         "updated_at": null,
        #         "resource": "account",
        #         "resource_path": "/v2/accounts/XLM",
        #         "allow_deposits": True,
        #         "allow_withdrawals": True
        #     }
        #
        data = self.safe_value(response, 'data', [])
        result = []
        for i in range(0, len(data)):
            account = data[i]
            currency = self.safe_value(account, 'currency', {})
            currencyId = self.safe_string(currency, 'code')
            code = self.safe_currency_code(currencyId)
            result.append({
                'id': self.safe_string(account, 'id'),
                'type': self.safe_string(account, 'type'),
                'code': code,
                'info': account,
            })
        return result

    def create_deposit_address(self, code, params={}):
        accountId = self.safe_string(params, 'account_id')
        params = self.omit(params, 'account_id')
        if accountId is None:
            self.load_accounts()
            for i in range(0, len(self.accounts)):
                account = self.accounts[i]
                if account['code'] == code and account['type'] == 'wallet':
                    accountId = account['id']
                    break
        if accountId is None:
            raise ExchangeError(self.id + ' createDepositAddress could not find the account with matching currency code, specify an `account_id` extra param')
        request = {
            'account_id': accountId,
        }
        response = self.privatePostAccountsAccountIdAddresses(self.extend(request, params))
        #
        #     {
        #         "data": {
        #             "id": "05b1ebbf-9438-5dd4-b297-2ddedc98d0e4",
        #             "address": "coinbasebase",
        #             "address_info": {
        #                 "address": "coinbasebase",
        #                 "destination_tag": "287594668"
        #             },
        #             "name": null,
        #             "created_at": "2019-07-01T14:39:29Z",
        #             "updated_at": "2019-07-01T14:39:29Z",
        #             "network": "eosio",
        #             "uri_scheme": "eosio",
        #             "resource": "address",
        #             "resource_path": "/v2/accounts/14cfc769-e852-52f3-b831-711c104d194c/addresses/05b1ebbf-9438-5dd4-b297-2ddedc98d0e4",
        #             "warnings": [
        #                 {
        #                     "title": "Only send EOS(EOS) to self address",
        #                     "details": "Sending any other cryptocurrency will result in permanent loss.",
        #                     "image_url": "https://dynamic-assets.coinbase.com/deaca3d47b10ed4a91a872e9618706eec34081127762d88f2476ac8e99ada4b48525a9565cf2206d18c04053f278f693434af4d4629ca084a9d01b7a286a7e26/asset_icons/1f8489bb280fb0a0fd643c1161312ba49655040e9aaaced5f9ad3eeaf868eadc.png"
        #                 },
        #                 {
        #                     "title": "Both an address and EOS memo are required to receive EOS",
        #                     "details": "If you send funds without an EOS memo or with an incorrect EOS memo, your funds cannot be credited to your account.",
        #                     "image_url": "https://www.coinbase.com/assets/receive-warning-2f3269d83547a7748fb39d6e0c1c393aee26669bfea6b9f12718094a1abff155.png"
        #                 }
        #             ],
        #             "warning_title": "Only send EOS(EOS) to self address",
        #             "warning_details": "Sending any other cryptocurrency will result in permanent loss.",
        #             "destination_tag": "287594668",
        #             "deposit_uri": "eosio:coinbasebase?dt=287594668",
        #             "callback_url": null
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        tag = self.safe_string(data, 'destination_tag')
        address = self.safe_string(data, 'address')
        return {
            'currency': code,
            'tag': tag,
            'address': address,
            'info': response,
        }

    def fetch_my_sells(self, symbol=None, since=None, limit=None, params={}):
        # they don't have an endpoint for all historical trades
        request = self.prepare_account_request(limit, params)
        self.load_markets()
        query = self.omit(params, ['account_id', 'accountId'])
        sells = self.privateGetAccountsAccountIdSells(self.extend(request, query))
        return self.parse_trades(sells['data'], None, since, limit)

    def fetch_my_buys(self, symbol=None, since=None, limit=None, params={}):
        # they don't have an endpoint for all historical trades
        request = self.prepare_account_request(limit, params)
        self.load_markets()
        query = self.omit(params, ['account_id', 'accountId'])
        buys = self.privateGetAccountsAccountIdBuys(self.extend(request, query))
        return self.parse_trades(buys['data'], None, since, limit)

    def fetch_transactions_with_method(self, method, code=None, since=None, limit=None, params={}):
        request = self.prepare_account_request_with_currency_code(code, limit, params)
        self.load_markets()
        query = self.omit(params, ['account_id', 'accountId'])
        response = getattr(self, method)(self.extend(request, query))
        return self.parse_transactions(response['data'], None, since, limit)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        # fiat only, for crypto transactions use fetchLedger
        return self.fetch_transactions_with_method('privateGetAccountsAccountIdWithdrawals', code, since, limit, params)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        # fiat only, for crypto transactions use fetchLedger
        return self.fetch_transactions_with_method('privateGetAccountsAccountIdDeposits', code, since, limit, params)

    def parse_transaction_status(self, status):
        statuses = {
            'created': 'pending',
            'completed': 'ok',
            'canceled': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, market=None):
        #
        # fiat deposit
        #
        #     {
        #         "id": "f34c19f3-b730-5e3d-9f72",
        #         "status": "completed",
        #         "payment_method": {
        #             "id": "a022b31d-f9c7-5043-98f2",
        #             "resource": "payment_method",
        #             "resource_path": "/v2/payment-methods/a022b31d-f9c7-5043-98f2"
        #         },
        #         "transaction": {
        #             "id": "04ed4113-3732-5b0c-af86-b1d2146977d0",
        #             "resource": "transaction",
        #             "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86"
        #         },
        #         "user_reference": "2VTYTH",
        #         "created_at": "2017-02-09T07:01:18Z",
        #         "updated_at": "2017-02-09T07:01:26Z",
        #         "resource": "deposit",
        #         "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72",
        #         "committed": True,
        #         "payout_at": "2017-02-12T07:01:17Z",
        #         "instant": False,
        #         "fee": {"amount": "0.00", "currency": "EUR"},
        #         "amount": {"amount": "114.02", "currency": "EUR"},
        #         "subtotal": {"amount": "114.02", "currency": "EUR"},
        #         "hold_until": null,
        #         "hold_days": 0,
        #         "hold_business_days": 0,
        #         "next_step": null
        #     }
        #
        # fiat_withdrawal
        #
        #     {
        #         "id": "cfcc3b4a-eeb6-5e8c-8058",
        #         "status": "completed",
        #         "payment_method": {
        #             "id": "8b94cfa4-f7fd-5a12-a76a",
        #             "resource": "payment_method",
        #             "resource_path": "/v2/payment-methods/8b94cfa4-f7fd-5a12-a76a"
        #         },
        #         "transaction": {
        #             "id": "fcc2550b-5104-5f83-a444",
        #             "resource": "transaction",
        #             "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/fcc2550b-5104-5f83-a444"
        #         },
        #         "user_reference": "MEUGK",
        #         "created_at": "2018-07-26T08:55:12Z",
        #         "updated_at": "2018-07-26T08:58:18Z",
        #         "resource": "withdrawal",
        #         "resource_path": "/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/cfcc3b4a-eeb6-5e8c-8058",
        #         "committed": True,
        #         "payout_at": "2018-07-31T08:55:12Z",
        #         "instant": False,
        #         "fee": {"amount": "0.15", "currency": "EUR"},
        #         "amount": {"amount": "13130.69", "currency": "EUR"},
        #         "subtotal": {"amount": "13130.84", "currency": "EUR"},
        #         "idem": "e549dee5-63ed-4e79-8a96",
        #         "next_step": null
        #     }
        #
        subtotalObject = self.safe_value(transaction, 'subtotal', {})
        feeObject = self.safe_value(transaction, 'fee', {})
        id = self.safe_string(transaction, 'id')
        timestamp = self.parse8601(self.safe_value(transaction, 'created_at'))
        updated = self.parse8601(self.safe_value(transaction, 'updated_at'))
        type = self.safe_string(transaction, 'resource')
        amount = self.safe_number(subtotalObject, 'amount')
        currencyId = self.safe_string(subtotalObject, 'currency')
        currency = self.safe_currency_code(currencyId)
        feeCost = self.safe_number(feeObject, 'amount')
        feeCurrencyId = self.safe_string(feeObject, 'currency')
        feeCurrency = self.safe_currency_code(feeCurrencyId)
        fee = {
            'cost': feeCost,
            'currency': feeCurrency,
        }
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        if status is None:
            committed = self.safe_value(transaction, 'committed')
            status = 'ok' if committed else 'pending'
        return {
            'info': transaction,
            'id': id,
            'txid': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': None,
            'tag': None,
            'type': type,
            'amount': amount,
            'currency': currency,
            'status': status,
            'updated': updated,
            'fee': fee,
        }

    def parse_trade(self, trade, market=None):
        #
        #     {
        #         "id": "67e0eaec-07d7-54c4-a72c-2e92826897df",
        #         "status": "completed",
        #         "payment_method": {
        #             "id": "83562370-3e5c-51db-87da-752af5ab9559",
        #             "resource": "payment_method",
        #             "resource_path": "/v2/payment-methods/83562370-3e5c-51db-87da-752af5ab9559"
        #         },
        #         "transaction": {
        #             "id": "441b9494-b3f0-5b98-b9b0-4d82c21c252a",
        #             "resource": "transaction",
        #             "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/transactions/441b9494-b3f0-5b98-b9b0-4d82c21c252a"
        #         },
        #         "amount": {"amount": "1.00000000", "currency": "BTC"},
        #         "total": {"amount": "10.25", "currency": "USD"},
        #         "subtotal": {"amount": "10.10", "currency": "USD"},
        #         "created_at": "2015-01-31T20:49:02Z",
        #         "updated_at": "2015-02-11T16:54:02-08:00",
        #         "resource": "buy",
        #         "resource_path": "/v2/accounts/2bbf394c-193b-5b2a-9155-3b4732659ede/buys/67e0eaec-07d7-54c4-a72c-2e92826897df",
        #         "committed": True,
        #         "instant": False,
        #         "fee": {"amount": "0.15", "currency": "USD"},
        #         "payout_at": "2015-02-18T16:54:00-08:00"
        #     }
        #
        symbol = None
        totalObject = self.safe_value(trade, 'total', {})
        amountObject = self.safe_value(trade, 'amount', {})
        subtotalObject = self.safe_value(trade, 'subtotal', {})
        feeObject = self.safe_value(trade, 'fee', {})
        id = self.safe_string(trade, 'id')
        timestamp = self.parse8601(self.safe_value(trade, 'created_at'))
        if market is None:
            baseId = self.safe_string(amountObject, 'currency')
            quoteId = self.safe_string(totalObject, 'currency')
            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
        orderId = None
        side = self.safe_string(trade, 'resource')
        type = None
        costString = self.safe_string(subtotalObject, 'amount')
        amountString = self.safe_string(amountObject, 'amount')
        cost = self.parse_number(costString)
        amount = self.parse_number(amountString)
        price = self.parse_number(Precise.string_div(costString, amountString))
        feeCost = self.safe_number(feeObject, 'amount')
        feeCurrencyId = self.safe_string(feeObject, 'currency')
        feeCurrency = self.safe_currency_code(feeCurrencyId)
        fee = {
            'cost': feeCost,
            'currency': feeCurrency,
        }
        return {
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_markets(self, params={}):
        response = self.fetch_currencies_from_cache(params)
        currencies = self.safe_value(response, 'currencies', {})
        exchangeRates = self.safe_value(response, 'exchangeRates', {})
        data = self.safe_value(currencies, 'data', [])
        dataById = self.index_by(data, 'id')
        rates = self.safe_value(self.safe_value(exchangeRates, 'data', {}), 'rates', {})
        baseIds = list(rates.keys())
        result = []
        for i in range(0, len(baseIds)):
            baseId = baseIds[i]
            base = self.safe_currency_code(baseId)
            type = 'fiat' if (baseId in dataById) else 'crypto'
            # https://github.com/ccxt/ccxt/issues/6066
            if type == 'crypto':
                for j in range(0, len(data)):
                    quoteCurrency = data[j]
                    quoteId = self.safe_string(quoteCurrency, 'id')
                    quote = self.safe_currency_code(quoteId)
                    symbol = base + '/' + quote
                    id = baseId + '-' + quoteId
                    result.append({
                        'id': id,
                        'symbol': symbol,
                        'base': base,
                        'quote': quote,
                        'baseId': baseId,
                        'quoteId': quoteId,
                        'active': None,
                        'info': quoteCurrency,
                        'precision': {
                            'amount': None,
                            'price': None,
                        },
                        'limits': {
                            'amount': {
                                'min': None,
                                'max': None,
                            },
                            'price': {
                                'min': None,
                                'max': None,
                            },
                            'cost': {
                                'min': self.safe_number(quoteCurrency, 'min_size'),
                                'max': None,
                            },
                        },
                    })
        return result

    def fetch_currencies_from_cache(self, params={}):
        options = self.safe_value(self.options, 'fetchCurrencies', {})
        timestamp = self.safe_integer(options, 'timestamp')
        expires = self.safe_integer(options, 'expires', 1000)
        now = self.milliseconds()
        if (timestamp is None) or ((now - timestamp) > expires):
            currencies = self.publicGetCurrencies(params)
            exchangeRates = self.publicGetExchangeRates(params)
            self.options['fetchCurrencies'] = self.extend(options, {
                'currencies': currencies,
                'exchangeRates': exchangeRates,
                'timestamp': now,
            })
        return self.safe_value(self.options, 'fetchCurrencies', {})

    def fetch_currencies(self, params={}):
        response = self.fetch_currencies_from_cache(params)
        currencies = self.safe_value(response, 'currencies', {})
        #
        #     {
        #         "data":[
        #             {"id":"AED","name":"United Arab Emirates Dirham","min_size":"0.01000000"},
        #             {"id":"AFN","name":"Afghan Afghani","min_size":"0.01000000"},
        #             {"id":"ALL","name":"Albanian Lek","min_size":"0.01000000"},
        #             {"id":"AMD","name":"Armenian Dram","min_size":"0.01000000"},
        #             {"id":"ANG","name":"Netherlands Antillean Gulden","min_size":"0.01000000"},
        #             # ...
        #         ],
        #     }
        #
        exchangeRates = self.safe_value(response, 'exchangeRates', {})
        #
        #     {
        #         "data":{
        #             "currency":"USD",
        #             "rates":{
        #                 "AED":"3.67",
        #                 "AFN":"78.21",
        #                 "ALL":"110.42",
        #                 "AMD":"474.18",
        #                 "ANG":"1.75",
        #                 # ...
        #             },
        #         }
        #     }
        #
        data = self.safe_value(currencies, 'data', [])
        dataById = self.index_by(data, 'id')
        rates = self.safe_value(self.safe_value(exchangeRates, 'data', {}), 'rates', {})
        keys = list(rates.keys())
        result = {}
        for i in range(0, len(keys)):
            key = keys[i]
            type = 'fiat' if (key in dataById) else 'crypto'
            currency = self.safe_value(dataById, key, {})
            id = self.safe_string(currency, 'id', key)
            name = self.safe_string(currency, 'name')
            code = self.safe_currency_code(id)
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,  # the original payload
                'type': type,
                'name': name,
                'active': True,
                'fee': None,
                'precision': None,
                'limits': {
                    'amount': {
                        'min': self.safe_number(currency, 'min_size'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
            }
        return result

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        timestamp = self.seconds()
        market = self.market(symbol)
        request = self.extend({
            'symbol': market['id'],
        }, params)
        buy = self.publicGetPricesSymbolBuy(request)
        sell = self.publicGetPricesSymbolSell(request)
        spot = self.publicGetPricesSymbolSpot(request)
        ask = self.safe_number(buy['data'], 'amount')
        bid = self.safe_number(sell['data'], 'amount')
        last = self.safe_number(spot['data'], 'amount')
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'bid': bid,
            'ask': ask,
            'last': last,
            'high': None,
            'low': None,
            'bidVolume': None,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': None,
            'quoteVolume': None,
            'info': {
                'buy': buy,
                'sell': sell,
                'spot': spot,
            },
        }

    def fetch_balance(self, params={}):
        self.load_markets()
        request = {
            'limit': 100,
        }
        response = self.privateGetAccounts(self.extend(request, params))
        balances = self.safe_value(response, 'data')
        accounts = self.safe_value(params, 'type', self.options['accounts'])
        result = {'info': response}
        for b in range(0, len(balances)):
            balance = balances[b]
            if self.in_array(balance['type'], accounts):
                currencyId = self.safe_string(balance['balance'], 'currency')
                code = self.safe_currency_code(currencyId)
                total = self.safe_number(balance['balance'], 'amount')
                free = total
                used = None
                if code in result:
                    result[code]['free'] = self.sum(result[code]['free'], total)
                    result[code]['total'] = self.sum(result[code]['total'], total)
                else:
                    account = {
                        'free': free,
                        'used': used,
                        'total': total,
                    }
                    result[code] = account
        return self.parse_balance(result)

    def fetch_ledger(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        currency = None
        if code is not None:
            currency = self.currency(code)
        request = self.prepare_account_request_with_currency_code(code, limit, params)
        query = self.omit(params, ['account_id', 'accountId'])
        # for pagination use parameter 'starting_after'
        # the value for the next page can be obtained from the result of the previous call in the 'pagination' field
        # eg: instance.last_json_response.pagination.next_starting_after
        response = self.privateGetAccountsAccountIdTransactions(self.extend(request, query))
        return self.parse_ledger(response['data'], currency, since, limit)

    def parse_ledger_entry_status(self, status):
        types = {
            'completed': 'ok',
        }
        return self.safe_string(types, status, status)

    def parse_ledger_entry_type(self, type):
        types = {
            'buy': 'trade',
            'sell': 'trade',
            'fiat_deposit': 'transaction',
            'fiat_withdrawal': 'transaction',
            'exchange_deposit': 'transaction',  # fiat withdrawal(from coinbase to coinbasepro)
            'exchange_withdrawal': 'transaction',  # fiat deposit(to coinbase from coinbasepro)
            'send': 'transaction',  # crypto deposit OR withdrawal
            'pro_deposit': 'transaction',  # crypto withdrawal(from coinbase to coinbasepro)
            'pro_withdrawal': 'transaction',  # crypto deposit(to coinbase from coinbasepro)
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item, currency=None):
        #
        # crypto deposit transaction
        #
        #     {
        #         id: '34e4816b-4c8c-5323-a01c-35a9fa26e490',
        #         type: 'send',
        #         status: 'completed',
        #         amount: {amount: '28.31976528', currency: 'BCH'},
        #         native_amount: {amount: '2799.65', currency: 'GBP'},
        #         description: null,
        #         created_at: '2019-02-28T12:35:20Z',
        #         updated_at: '2019-02-28T12:43:24Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/34e4816b-4c8c-5323-a01c-35a9fa26e490',
        #         instant_exchange: False,
        #         network: {
        #             status: 'confirmed',
        #             hash: '56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701',
        #             transaction_url: 'https://bch.btc.com/56222d865dae83774fccb2efbd9829cf08c75c94ce135bfe4276f3fb46d49701'
        #         },
        #         from: {resource: 'bitcoin_cash_network', currency: 'BCH'},
        #         details: {title: 'Received Bitcoin Cash', subtitle: 'From Bitcoin Cash address'}
        #     }
        #
        # crypto withdrawal transaction
        #
        #     {
        #         id: '459aad99-2c41-5698-ac71-b6b81a05196c',
        #         type: 'send',
        #         status: 'completed',
        #         amount: {amount: '-0.36775642', currency: 'BTC'},
        #         native_amount: {amount: '-1111.65', currency: 'GBP'},
        #         description: null,
        #         created_at: '2019-03-20T08:37:07Z',
        #         updated_at: '2019-03-20T08:49:33Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/459aad99-2c41-5698-ac71-b6b81a05196c',
        #         instant_exchange: False,
        #         network: {
        #             status: 'confirmed',
        #             hash: '2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b',
        #             transaction_url: 'https://blockchain.info/tx/2732bbcf35c69217c47b36dce64933d103895277fe25738ffb9284092701e05b',
        #             transaction_fee: {amount: '0.00000000', currency: 'BTC'},
        #             transaction_amount: {amount: '0.36775642', currency: 'BTC'},
        #             confirmations: 15682
        #         },
        #         to: {
        #             resource: 'bitcoin_address',
        #             address: '1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX',
        #             currency: 'BTC',
        #             address_info: {address: '1AHnhqbvbYx3rnZx8uC7NbFZaTe4tafFHX'}
        #         },
        #         idem: 'da0a2f14-a2af-4c5a-a37e-d4484caf582bsend',
        #         application: {
        #             id: '5756ab6e-836b-553b-8950-5e389451225d',
        #             resource: 'application',
        #             resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d'
        #         },
        #         details: {title: 'Sent Bitcoin', subtitle: 'To Bitcoin address'}
        #     }
        #
        # withdrawal transaction from coinbase to coinbasepro
        #
        #     {
        #         id: '5b1b9fb8-5007-5393-b923-02903b973fdc',
        #         type: 'pro_deposit',
        #         status: 'completed',
        #         amount: {amount: '-0.00001111', currency: 'BCH'},
        #         native_amount: {amount: '0.00', currency: 'GBP'},
        #         description: null,
        #         created_at: '2019-02-28T13:31:58Z',
        #         updated_at: '2019-02-28T13:31:58Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/5b1b9fb8-5007-5393-b923-02903b973fdc',
        #         instant_exchange: False,
        #         application: {
        #             id: '5756ab6e-836b-553b-8950-5e389451225d',
        #             resource: 'application',
        #             resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d'
        #         },
        #         details: {title: 'Transferred Bitcoin Cash', subtitle: 'To Coinbase Pro'}
        #     }
        #
        # withdrawal transaction from coinbase to gdax
        #
        #     {
        #         id: 'badb7313-a9d3-5c07-abd0-00f8b44199b1',
        #         type: 'exchange_deposit',
        #         status: 'completed',
        #         amount: {amount: '-0.43704149', currency: 'BCH'},
        #         native_amount: {amount: '-51.90', currency: 'GBP'},
        #         description: null,
        #         created_at: '2019-03-19T10:30:40Z',
        #         updated_at: '2019-03-19T10:30:40Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c01d7364-edd7-5f3a-bd1d-de53d4cbb25e/transactions/badb7313-a9d3-5c07-abd0-00f8b44199b1',
        #         instant_exchange: False,
        #         details: {title: 'Transferred Bitcoin Cash', subtitle: 'To GDAX'}
        #     }
        #
        # deposit transaction from gdax to coinbase
        #
        #     {
        #         id: '9c4b642c-8688-58bf-8962-13cef64097de',
        #         type: 'exchange_withdrawal',
        #         status: 'completed',
        #         amount: {amount: '0.57729420', currency: 'BTC'},
        #         native_amount: {amount: '4418.72', currency: 'GBP'},
        #         description: null,
        #         created_at: '2018-02-17T11:33:33Z',
        #         updated_at: '2018-02-17T11:33:33Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/9c4b642c-8688-58bf-8962-13cef64097de',
        #         instant_exchange: False,
        #         details: {title: 'Transferred Bitcoin', subtitle: 'From GDAX'}
        #     }
        #
        # deposit transaction from coinbasepro to coinbase
        #
        #     {
        #         id: '8d6dd0b9-3416-568a-889d-8f112fae9e81',
        #         type: 'pro_withdrawal',
        #         status: 'completed',
        #         amount: {amount: '0.40555386', currency: 'BTC'},
        #         native_amount: {amount: '1140.27', currency: 'GBP'},
        #         description: null,
        #         created_at: '2019-03-04T19:41:58Z',
        #         updated_at: '2019-03-04T19:41:58Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/8d6dd0b9-3416-568a-889d-8f112fae9e81',
        #         instant_exchange: False,
        #         application: {
        #             id: '5756ab6e-836b-553b-8950-5e389451225d',
        #             resource: 'application',
        #             resource_path: '/v2/applications/5756ab6e-836b-553b-8950-5e389451225d'
        #         },
        #         details: {title: 'Transferred Bitcoin', subtitle: 'From Coinbase Pro'}
        #     }
        #
        # sell trade
        #
        #     {
        #         id: 'a9409207-df64-585b-97ab-a50780d2149e',
        #         type: 'sell',
        #         status: 'completed',
        #         amount: {amount: '-9.09922880', currency: 'BTC'},
        #         native_amount: {amount: '-7285.73', currency: 'GBP'},
        #         description: null,
        #         created_at: '2017-03-27T15:38:34Z',
        #         updated_at: '2017-03-27T15:38:34Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/transactions/a9409207-df64-585b-97ab-a50780d2149e',
        #         instant_exchange: False,
        #         sell: {
        #             id: 'e3550b4d-8ae6-5de3-95fe-1fb01ba83051',
        #             resource: 'sell',
        #             resource_path: '/v2/accounts/c6afbd34-4bd0-501e-8616-4862c193cd84/sells/e3550b4d-8ae6-5de3-95fe-1fb01ba83051'
        #         },
        #         details: {
        #             title: 'Sold Bitcoin',
        #             subtitle: 'Using EUR Wallet',
        #             payment_method_name: 'EUR Wallet'
        #         }
        #     }
        #
        # buy trade
        #
        #     {
        #         id: '63eeed67-9396-5912-86e9-73c4f10fe147',
        #         type: 'buy',
        #         status: 'completed',
        #         amount: {amount: '2.39605772', currency: 'ETH'},
        #         native_amount: {amount: '98.31', currency: 'GBP'},
        #         description: null,
        #         created_at: '2017-03-27T09:07:56Z',
        #         updated_at: '2017-03-27T09:07:57Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/transactions/63eeed67-9396-5912-86e9-73c4f10fe147',
        #         instant_exchange: False,
        #         buy: {
        #             id: '20b25b36-76c6-5353-aa57-b06a29a39d82',
        #             resource: 'buy',
        #             resource_path: '/v2/accounts/8902f85d-4a69-5d74-82fe-8e390201bda7/buys/20b25b36-76c6-5353-aa57-b06a29a39d82'
        #         },
        #         details: {
        #             title: 'Bought Ethereum',
        #             subtitle: 'Using EUR Wallet',
        #             payment_method_name: 'EUR Wallet'
        #         }
        #     }
        #
        # fiat deposit transaction
        #
        #     {
        #         id: '04ed4113-3732-5b0c-af86-b1d2146977d0',
        #         type: 'fiat_deposit',
        #         status: 'completed',
        #         amount: {amount: '114.02', currency: 'EUR'},
        #         native_amount: {amount: '97.23', currency: 'GBP'},
        #         description: null,
        #         created_at: '2017-02-09T07:01:21Z',
        #         updated_at: '2017-02-09T07:01:22Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/04ed4113-3732-5b0c-af86-b1d2146977d0',
        #         instant_exchange: False,
        #         fiat_deposit: {
        #             id: 'f34c19f3-b730-5e3d-9f72-96520448677a',
        #             resource: 'fiat_deposit',
        #             resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/deposits/f34c19f3-b730-5e3d-9f72-96520448677a'
        #         },
        #         details: {
        #             title: 'Deposited funds',
        #             subtitle: 'From SEPA Transfer(GB47 BARC 20..., reference CBADVI)',
        #             payment_method_name: 'SEPA Transfer(GB47 BARC 20..., reference CBADVI)'
        #         }
        #     }
        #
        # fiat withdrawal transaction
        #
        #     {
        #         id: '957d98e2-f80e-5e2f-a28e-02945aa93079',
        #         type: 'fiat_withdrawal',
        #         status: 'completed',
        #         amount: {amount: '-11000.00', currency: 'EUR'},
        #         native_amount: {amount: '-9698.22', currency: 'GBP'},
        #         description: null,
        #         created_at: '2017-12-06T13:19:19Z',
        #         updated_at: '2017-12-06T13:19:19Z',
        #         resource: 'transaction',
        #         resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/transactions/957d98e2-f80e-5e2f-a28e-02945aa93079',
        #         instant_exchange: False,
        #         fiat_withdrawal: {
        #             id: 'f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7',
        #             resource: 'fiat_withdrawal',
        #             resource_path: '/v2/accounts/91cd2d36-3a91-55b6-a5d4-0124cf105483/withdrawals/f4bf1fd9-ab3b-5de7-906d-ed3e23f7a4e7'
        #         },
        #         details: {
        #             title: 'Withdrew funds',
        #             subtitle: 'To HSBC BANK PLC(GB74 MIDL...)',
        #             payment_method_name: 'HSBC BANK PLC(GB74 MIDL...)'
        #         }
        #     }
        #
        amountInfo = self.safe_value(item, 'amount', {})
        amount = self.safe_number(amountInfo, 'amount')
        direction = None
        if amount < 0:
            direction = 'out'
            amount = -amount
        else:
            direction = 'in'
        currencyId = self.safe_string(amountInfo, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        #
        # the address and txid do not belong to the unified ledger structure
        #
        #     address = None
        #     if item['to']:
        #         address = self.safe_string(item['to'], 'address')
        #     }
        #     txid = None
        #
        fee = None
        networkInfo = self.safe_value(item, 'network', {})
        # txid = network['hash']  # txid does not belong to the unified ledger structure
        feeInfo = self.safe_value(networkInfo, 'transaction_fee')
        if feeInfo is not None:
            feeCurrencyId = self.safe_string(feeInfo, 'currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId, currency)
            feeAmount = self.safe_number(feeInfo, 'amount')
            fee = {
                'cost': feeAmount,
                'currency': feeCurrencyCode,
            }
        timestamp = self.parse8601(self.safe_value(item, 'created_at'))
        id = self.safe_string(item, 'id')
        type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
        status = self.parse_ledger_entry_status(self.safe_string(item, 'status'))
        path = self.safe_string(item, 'resource_path')
        accountId = None
        if path is not None:
            parts = path.split('/')
            numParts = len(parts)
            if numParts > 3:
                accountId = parts[3]
        return {
            'info': item,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'direction': direction,
            'account': accountId,
            'referenceId': None,
            'referenceAccount': None,
            'type': type,
            'currency': code,
            'amount': amount,
            'before': None,
            'after': None,
            'status': status,
            'fee': fee,
        }

    def find_account_id(self, code):
        self.load_markets()
        self.load_accounts()
        for i in range(0, len(self.accounts)):
            account = self.accounts[i]
            if account['code'] == code:
                return account['id']
        return None

    def prepare_account_request(self, limit=None, params={}):
        accountId = self.safe_string_2(params, 'account_id', 'accountId')
        if accountId is None:
            raise ArgumentsRequired(self.id + ' method requires an account_id(or accountId) parameter')
        request = {
            'account_id': accountId,
        }
        if limit is not None:
            request['limit'] = limit
        return request

    def prepare_account_request_with_currency_code(self, code=None, limit=None, params={}):
        accountId = self.safe_string_2(params, 'account_id', 'accountId')
        if accountId is None:
            if code is None:
                raise ArgumentsRequired(self.id + ' method requires an account_id(or accountId) parameter OR a currency code argument')
            accountId = self.find_account_id(code)
            if accountId is None:
                raise ExchangeError(self.id + ' could not find account id for ' + code)
        request = {
            'account_id': accountId,
        }
        if limit is not None:
            request['limit'] = limit
        return request

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        fullPath = '/' + self.version + '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if method == 'GET':
            if query:
                fullPath += '?' + self.urlencode(query)
        url = self.urls['api'] + fullPath
        if api == 'private':
            self.check_required_credentials()
            nonce = str(self.nonce())
            payload = ''
            if method != 'GET':
                if query:
                    body = self.json(query)
                    payload = body
            auth = nonce + method + fullPath + payload
            signature = self.hmac(self.encode(auth), self.encode(self.secret))
            headers = {
                'CB-ACCESS-KEY': self.apiKey,
                'CB-ACCESS-SIGN': signature,
                'CB-ACCESS-TIMESTAMP': nonce,
                'Content-Type': 'application/json',
            }
        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
        feedback = self.id + ' ' + body
        #
        #    {"error": "invalid_request", "error_description": "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
        #
        # or
        #
        #    {
        #      "errors": [
        #        {
        #          "id": "not_found",
        #          "message": "Not found"
        #        }
        #      ]
        #    }
        #
        errorCode = self.safe_string(response, 'error')
        if errorCode is not None:
            errorMessage = self.safe_string(response, 'error_description')
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], errorMessage, feedback)
            raise ExchangeError(feedback)
        errors = self.safe_value(response, 'errors')
        if errors is not None:
            if isinstance(errors, list):
                numErrors = len(errors)
                if numErrors > 0:
                    errorCode = self.safe_string(errors[0], 'id')
                    errorMessage = self.safe_string(errors[0], 'message')
                    if errorCode is not None:
                        self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
                        self.throw_broadly_matched_exception(self.exceptions['broad'], errorMessage, feedback)
                        raise ExchangeError(feedback)
        data = self.safe_value(response, 'data')
        if data is None:
            raise ExchangeError(self.id + ' failed due to a malformed response ' + self.json(response))
