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

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt_zagrava/ccxt_zagrava/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt_zagrava.async_support.base.exchange import Exchange
from ccxt_zagrava.abstract.bitstamp import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, TradingFees, Transaction, TransferEntry
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidAddress
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import NotSupported
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.errors import OnMaintenance
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class bitstamp(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(bitstamp, self).describe(), {
            'id': 'bitstamp',
            'name': 'Bitstamp',
            'countries': ['GB'],
            # 8000 requests per 10 minutes = 8000 / 600 = 13.33333333 requests per second => 1000ms / 13.33333333 = 75ms between requests on average
            'rateLimit': 75,
            'version': 'v2',
            'userAgent': self.userAgents['chrome'],
            'pro': True,
            'has': {
                'CORS': True,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'closeAllPositions': False,
                'closePosition': False,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': False,
                'createStopMarketOrder': False,
                'createStopOrder': False,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLedger': True,
                'fetchLeverage': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingFees': True,
                'fetchTransactionFees': True,
                'fetchTransactions': 'emulated',
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': True,
                'withdraw': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27786377-8c8ab57e-5fe9-11e7-8ea4-2b05b6bcceec.jpg',
                'api': {
                    'public': 'https://www.bitstamp.net/api',
                    'private': 'https://www.bitstamp.net/api',
                },
                'www': 'https://www.bitstamp.net',
                'doc': 'https://www.bitstamp.net/api',
            },
            'timeframes': {
                '1m': '60',
                '3m': '180',
                '5m': '300',
                '15m': '900',
                '30m': '1800',
                '1h': '3600',
                '2h': '7200',
                '4h': '14400',
                '6h': '21600',
                '12h': '43200',
                '1d': '86400',
                '1w': '259200',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
            },
            'api': {
                'public': {
                    'get': {
                        'ohlc/{pair}/': 1,
                        'order_book/{pair}/': 1,
                        'ticker/': 1,
                        'ticker_hour/{pair}/': 1,
                        'ticker/{pair}/': 1,
                        'transactions/{pair}/': 1,
                        'trading-pairs-info/': 1,
                        'currencies/': 1,
                        'eur_usd/': 1,
                        'travel_rule/vasps/': 1,
                    },
                },
                'private': {
                    'get': {
                        'travel_rule/contacts/': 1,
                        'contacts/{contact_uuid}/': 1,
                        'earn/subscriptions/': 1,
                        'earn/transactions/': 1,
                    },
                    'post': {
                        'account_balances/': 1,
                        'account_balances/{currency}/': 1,
                        'balance/': 1,
                        'balance/{pair}/': 1,
                        'bch_withdrawal/': 1,
                        'bch_address/': 1,
                        'user_transactions/': 1,
                        'user_transactions/{pair}/': 1,
                        'crypto-transactions/': 1,
                        'open_orders/all/': 1,
                        'open_orders/{pair}/': 1,
                        'order_status/': 1,
                        'cancel_order/': 1,
                        'cancel_all_orders/': 1,
                        'cancel_all_orders/{pair}/': 1,
                        'buy/{pair}/': 1,
                        'buy/market/{pair}/': 1,
                        'buy/instant/{pair}/': 1,
                        'sell/{pair}/': 1,
                        'sell/market/{pair}/': 1,
                        'sell/instant/{pair}/': 1,
                        'transfer-to-main/': 1,
                        'transfer-from-main/': 1,
                        'my_trading_pairs/': 1,
                        'fees/trading/': 1,
                        'fees/trading/{market_symbol}': 1,
                        'fees/withdrawal/': 1,
                        'fees/withdrawal/{currency}/': 1,
                        'withdrawal-requests/': 1,
                        'withdrawal/open/': 1,
                        'withdrawal/status/': 1,
                        'withdrawal/cancel/': 1,
                        'liquidation_address/new/': 1,
                        'liquidation_address/info/': 1,
                        'btc_unconfirmed/': 1,
                        'websockets_token/': 1,
                        # individual coins
                        'btc_withdrawal/': 1,
                        'btc_address/': 1,
                        'ripple_withdrawal/': 1,
                        'ripple_address/': 1,
                        'ltc_withdrawal/': 1,
                        'ltc_address/': 1,
                        'eth_withdrawal/': 1,
                        'eth_address/': 1,
                        'xrp_withdrawal/': 1,
                        'xrp_address/': 1,
                        'xlm_withdrawal/': 1,
                        'xlm_address/': 1,
                        'pax_withdrawal/': 1,
                        'pax_address/': 1,
                        'link_withdrawal/': 1,
                        'link_address/': 1,
                        'usdc_withdrawal/': 1,
                        'usdc_address/': 1,
                        'omg_withdrawal/': 1,
                        'omg_address/': 1,
                        'dai_withdrawal/': 1,
                        'dai_address/': 1,
                        'knc_withdrawal/': 1,
                        'knc_address/': 1,
                        'mkr_withdrawal/': 1,
                        'mkr_address/': 1,
                        'zrx_withdrawal/': 1,
                        'zrx_address/': 1,
                        'gusd_withdrawal/': 1,
                        'gusd_address/': 1,
                        'aave_withdrawal/': 1,
                        'aave_address/': 1,
                        'bat_withdrawal/': 1,
                        'bat_address/': 1,
                        'uma_withdrawal/': 1,
                        'uma_address/': 1,
                        'snx_withdrawal/': 1,
                        'snx_address/': 1,
                        'uni_withdrawal/': 1,
                        'uni_address/': 1,
                        'yfi_withdrawal/': 1,
                        'yfi_address/': 1,
                        'audio_withdrawal/': 1,
                        'audio_address/': 1,
                        'crv_withdrawal/': 1,
                        'crv_address/': 1,
                        'algo_withdrawal/': 1,
                        'algo_address/': 1,
                        'comp_withdrawal/': 1,
                        'comp_address/': 1,
                        'grt_withdrawal/': 1,
                        'grt_address/': 1,
                        'usdt_withdrawal/': 1,
                        'usdt_address/': 1,
                        'eurt_withdrawal/': 1,
                        'eurt_address/': 1,
                        'matic_withdrawal/': 1,
                        'matic_address/': 1,
                        'sushi_withdrawal/': 1,
                        'sushi_address/': 1,
                        'chz_withdrawal/': 1,
                        'chz_address/': 1,
                        'enj_withdrawal/': 1,
                        'enj_address/': 1,
                        'alpha_withdrawal/': 1,
                        'alpha_address/': 1,
                        'ftt_withdrawal/': 1,
                        'ftt_address/': 1,
                        'storj_withdrawal/': 1,
                        'storj_address/': 1,
                        'axs_withdrawal/': 1,
                        'axs_address/': 1,
                        'sand_withdrawal/': 1,
                        'sand_address/': 1,
                        'hbar_withdrawal/': 1,
                        'hbar_address/': 1,
                        'rgt_withdrawal/': 1,
                        'rgt_address/': 1,
                        'fet_withdrawal/': 1,
                        'fet_address/': 1,
                        'skl_withdrawal/': 1,
                        'skl_address/': 1,
                        'cel_withdrawal/': 1,
                        'cel_address/': 1,
                        'sxp_withdrawal/': 1,
                        'sxp_address/': 1,
                        'ada_withdrawal/': 1,
                        'ada_address/': 1,
                        'slp_withdrawal/': 1,
                        'slp_address/': 1,
                        'ftm_withdrawal/': 1,
                        'ftm_address/': 1,
                        'perp_withdrawal/': 1,
                        'perp_address/': 1,
                        'dydx_withdrawal/': 1,
                        'dydx_address/': 1,
                        'gala_withdrawal/': 1,
                        'gala_address/': 1,
                        'shib_withdrawal/': 1,
                        'shib_address/': 1,
                        'amp_withdrawal/': 1,
                        'amp_address/': 1,
                        'sgb_withdrawal/': 1,
                        'sgb_address/': 1,
                        'avax_withdrawal/': 1,
                        'avax_address/': 1,
                        'wbtc_withdrawal/': 1,
                        'wbtc_address/': 1,
                        'ctsi_withdrawal/': 1,
                        'ctsi_address/': 1,
                        'cvx_withdrawal/': 1,
                        'cvx_address/': 1,
                        'imx_withdrawal/': 1,
                        'imx_address/': 1,
                        'nexo_withdrawal/': 1,
                        'nexo_address/': 1,
                        'ust_withdrawal/': 1,
                        'ust_address/': 1,
                        'ant_withdrawal/': 1,
                        'ant_address/': 1,
                        'gods_withdrawal/': 1,
                        'gods_address/': 1,
                        'rad_withdrawal/': 1,
                        'rad_address/': 1,
                        'band_withdrawal/': 1,
                        'band_address/': 1,
                        'inj_withdrawal/': 1,
                        'inj_address/': 1,
                        'rly_withdrawal/': 1,
                        'rly_address/': 1,
                        'rndr_withdrawal/': 1,
                        'rndr_address/': 1,
                        'vega_withdrawal/': 1,
                        'vega_address/': 1,
                        '1inch_withdrawal/': 1,
                        '1inch_address/': 1,
                        'ens_withdrawal/': 1,
                        'ens_address/': 1,
                        'mana_withdrawal/': 1,
                        'mana_address/': 1,
                        'lrc_withdrawal/': 1,
                        'lrc_address/': 1,
                        'ape_withdrawal/': 1,
                        'ape_address/': 1,
                        'mpl_withdrawal/': 1,
                        'mpl_address/': 1,
                        'euroc_withdrawal/': 1,
                        'euroc_address/': 1,
                        'sol_withdrawal/': 1,
                        'sol_address/': 1,
                        'dot_withdrawal/': 1,
                        'dot_address/': 1,
                        'near_withdrawal/': 1,
                        'near_address/': 1,
                        'doge_withdrawal/': 1,
                        'doge_address/': 1,
                        'flr_withdrawal/': 1,
                        'flr_address/': 1,
                        'dgld_withdrawal/': 1,
                        'dgld_address/': 1,
                        'ldo_withdrawal/': 1,
                        'ldo_address/': 1,
                        'travel_rule/contacts/': 1,
                        'earn/subscribe/': 1,
                        'earn/subscriptions/setting/': 1,
                        'earn/unsubscribe': 1,
                        'wecan_withdrawal/': 1,
                        'wecan_address/': 1,
                        'trac_withdrawal/': 1,
                        'trac_address/': 1,
                        'eurcv_withdrawal/': 1,
                        'eurcv_address/': 1,
                        'pyusd_withdrawal/': 1,
                        'pyusd_address/': 1,
                        'lmwr_withdrawal/': 1,
                        'lmwr_address/': 1,
                        'pepe_withdrawal/': 1,
                        'pepe_address/': 1,
                        'blur_withdrawal/': 1,
                        'blur_address/': 1,
                        'vext_withdrawal/': 1,
                        'vext_address/': 1,
                        'cspr_withdrawal/': 1,
                        'cspr_address/': 1,
                        'vchf_withdrawal/': 1,
                        'vchf_address/': 1,
                        'veur_withdrawal/': 1,
                        'veur_address/': 1,
                        'truf_withdrawal/': 1,
                        'truf_address/': 1,
                        'wif_withdrawal/': 1,
                        'wif_address/': 1,
                        'smt_withdrawal/': 1,
                        'smt_address/': 1,
                        'sui_withdrawal/': 1,
                        'sui_address/': 1,
                        'jup_withdrawal/': 1,
                        'jup_address/': 1,
                        'ondo_withdrawal/': 1,
                        'ondo_address/': 1,
                        'boba_withdrawal/': 1,
                        'boba_address/': 1,
                        'pyth_withdrawal/': 1,
                        'pyth_address/': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.005'),
                    'maker': self.parse_number('0.005'),
                    'tiers': {
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.005')],
                            [self.parse_number('20000'), self.parse_number('0.0025')],
                            [self.parse_number('100000'), self.parse_number('0.0024')],
                            [self.parse_number('200000'), self.parse_number('0.0022')],
                            [self.parse_number('400000'), self.parse_number('0.0020')],
                            [self.parse_number('600000'), self.parse_number('0.0015')],
                            [self.parse_number('1000000'), self.parse_number('0.0014')],
                            [self.parse_number('2000000'), self.parse_number('0.0013')],
                            [self.parse_number('4000000'), self.parse_number('0.0012')],
                            [self.parse_number('20000000'), self.parse_number('0.0011')],
                            [self.parse_number('50000000'), self.parse_number('0.0010')],
                            [self.parse_number('100000000'), self.parse_number('0.0007')],
                            [self.parse_number('500000000'), self.parse_number('0.0005')],
                            [self.parse_number('2000000000'), self.parse_number('0.0003')],
                            [self.parse_number('6000000000'), self.parse_number('0.0001')],
                            [self.parse_number('20000000000'), self.parse_number('0.00005')],
                            [self.parse_number('20000000001'), self.parse_number('0')],
                        ],
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.005')],
                            [self.parse_number('20000'), self.parse_number('0.0025')],
                            [self.parse_number('100000'), self.parse_number('0.0024')],
                            [self.parse_number('200000'), self.parse_number('0.0022')],
                            [self.parse_number('400000'), self.parse_number('0.0020')],
                            [self.parse_number('600000'), self.parse_number('0.0015')],
                            [self.parse_number('1000000'), self.parse_number('0.0014')],
                            [self.parse_number('2000000'), self.parse_number('0.0013')],
                            [self.parse_number('4000000'), self.parse_number('0.0012')],
                            [self.parse_number('20000000'), self.parse_number('0.0011')],
                            [self.parse_number('50000000'), self.parse_number('0.0010')],
                            [self.parse_number('100000000'), self.parse_number('0.0007')],
                            [self.parse_number('500000000'), self.parse_number('0.0005')],
                            [self.parse_number('2000000000'), self.parse_number('0.0003')],
                            [self.parse_number('6000000000'), self.parse_number('0.0001')],
                            [self.parse_number('20000000000'), self.parse_number('0.00005')],
                            [self.parse_number('20000000001'), self.parse_number('0')],
                        ],
                    },
                },
                'funding': {
                    'tierBased': False,
                    'percentage': False,
                    'withdraw': {},
                    'deposit': {
                        'BTC': 0,
                        'BCH': 0,
                        'LTC': 0,
                        'ETH': 0,
                        'XRP': 0,
                        'XLM': 0,
                        'PAX': 0,
                        'USD': 7.5,
                        'EUR': 0,
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'commonCurrencies': {
                'UST': 'USTC',
            },
            # exchange-specific options
            'options': {
                'networksById': {
                    'bitcoin-cash': 'BCH',
                    'bitcoin': 'BTC',
                    'ethereum': 'ERC20',
                    'litecoin': 'LTC',
                    'stellar': 'XLM',
                    'xrpl': 'XRP',
                    'tron': 'TRC20',
                    'algorand': 'ALGO',
                    'flare': 'FLR',
                    'hedera': 'HBAR',
                    'cardana': 'ADA',
                    'songbird': 'FLR',
                    'avalanche-c-chain': 'AVAX',
                    'solana': 'SOL',
                    'polkadot': 'DOT',
                    'near': 'NEAR',
                    'doge': 'DOGE',
                    'sui': 'SUI',
                    'casper': 'CSRP',
                },
            },
            'exceptions': {
                'exact': {
                    'No permission found': PermissionDenied,
                    'API key not found': AuthenticationError,
                    'IP address not allowed': PermissionDenied,
                    'Invalid nonce': InvalidNonce,
                    'Invalid signature': AuthenticationError,
                    'Authentication failed': AuthenticationError,
                    'Missing key, signature and nonce parameters': AuthenticationError,
                    'Wrong API key format': AuthenticationError,
                    'Your account is frozen': PermissionDenied,
                    'Please update your profile with your FATCA information, before using API.': PermissionDenied,
                    'Order not found.': OrderNotFound,
                    'Price is more than 20% below market price.': InvalidOrder,
                    "Bitstamp.net is under scheduled maintenance. We'll be back soon.": OnMaintenance,  # {"error": "Bitstamp.net is under scheduled maintenance. We'll be back soon."}
                    'Order could not be placed.': ExchangeNotAvailable,  # Order could not be placed(perhaps due to internal error or trade halt). Please retry placing order.
                    'Invalid offset.': BadRequest,
                },
                'broad': {
                    'Minimum order size is': InvalidOrder,  # Minimum order size is 5.0 EUR.
                    'Check your account balance for details.': InsufficientFunds,  # You have only 0.00100000 BTC available. Check your account balance for details.
                    'Ensure self value has at least': InvalidAddress,  # Ensure self value has at least 25 characters(it has 4).
                    'Ensure that there are no more than': InvalidOrder,  # {"status": "error", "reason": {"amount": ["Ensure that there are no more than 0 decimal places."], "__all__": [""]}}
                },
            },
        })

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for bitstamp
        :see: https://www.bitstamp.net/api/#tag/Market-info/operation/GetTradingPairsInfo
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = await self.fetch_markets_from_cache(params)
        #
        #     [
        #         {
        #             "trading": "Enabled",
        #             "base_decimals": 8,
        #             "url_symbol": "btcusd",
        #             "name": "BTC/USD",
        #             "instant_and_market_orders": "Enabled",
        #             "minimum_order": "20.0 USD",
        #             "counter_decimals": 2,
        #             "description": "Bitcoin / U.S. dollar"
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            name = self.safe_string(market, 'name')
            base, quote = name.split('/')
            baseId = base.lower()
            quoteId = quote.lower()
            base = self.safe_currency_code(base)
            quote = self.safe_currency_code(quote)
            minimumOrder = self.safe_string(market, 'minimum_order')
            parts = minimumOrder.split(' ')
            status = self.safe_string(market, 'trading')
            result.append({
                'id': self.safe_string(market, 'url_symbol'),
                'marketId': baseId + '_' + quoteId,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'future': False,
                'swap': False,
                'option': False,
                'active': (status == 'Enabled'),
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'base_decimals'))),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'counter_decimals'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(parts, 0),
                        'max': None,
                    },
                },
                'created': None,
                'info': market,
            })
        return result

    def construct_currency_object(self, id, code, name, precision, minCost, originalPayload):
        currencyType = 'crypto'
        description = self.describe()
        if self.is_fiat(code):
            currencyType = 'fiat'
        tickSize = self.parse_number(self.parse_precision(self.number_to_string(precision)))
        return {
            'id': id,
            'code': code,
            'info': originalPayload,  # the original payload
            'type': currencyType,
            'name': name,
            'active': True,
            'deposit': None,
            'withdraw': None,
            'fee': self.safe_number(description['fees']['funding']['withdraw'], code),
            'precision': tickSize,
            'limits': {
                'amount': {
                    'min': tickSize,
                    'max': None,
                },
                'price': {
                    'min': tickSize,
                    'max': None,
                },
                'cost': {
                    'min': minCost,
                    'max': None,
                },
                'withdraw': {
                    'min': None,
                    'max': None,
                },
            },
            'networks': {},
        }

    async def fetch_markets_from_cache(self, params={}):
        # self method is now redundant
        # currencies are now fetched before markets
        options = self.safe_value(self.options, 'fetchMarkets', {})
        timestamp = self.safe_integer(options, 'timestamp')
        expires = self.safe_integer(options, 'expires', 1000)
        now = self.milliseconds()
        if (timestamp is None) or ((now - timestamp) > expires):
            response = await self.publicGetTradingPairsInfo(params)
            self.options['fetchMarkets'] = self.extend(options, {
                'response': response,
                'timestamp': now,
            })
        return self.safe_value(self.options['fetchMarkets'], 'response')

    async def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://www.bitstamp.net/api/#tag/Market-info/operation/GetTradingPairsInfo
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = await self.fetch_markets_from_cache(params)
        #
        #     [
        #         {
        #             "trading": "Enabled",
        #             "base_decimals": 8,
        #             "url_symbol": "btcusd",
        #             "name": "BTC/USD",
        #             "instant_and_market_orders": "Enabled",
        #             "minimum_order": "20.0 USD",
        #             "counter_decimals": 2,
        #             "description": "Bitcoin / U.S. dollar"
        #         },
        #     ]
        #
        result: dict = {}
        for i in range(0, len(response)):
            market = response[i]
            name = self.safe_string(market, 'name')
            base, quote = name.split('/')
            baseId = base.lower()
            quoteId = quote.lower()
            base = self.safe_currency_code(base)
            quote = self.safe_currency_code(quote)
            description = self.safe_string(market, 'description')
            baseDescription, quoteDescription = description.split(' / ')
            minimumOrder = self.safe_string(market, 'minimum_order')
            parts = minimumOrder.split(' ')
            cost = parts[0]
            if not (base in result):
                baseDecimals = self.safe_integer(market, 'base_decimals')
                result[base] = self.construct_currency_object(baseId, base, baseDescription, baseDecimals, None, market)
            if not (quote in result):
                counterDecimals = self.safe_integer(market, 'counter_decimals')
                result[quote] = self.construct_currency_object(quoteId, quote, quoteDescription, counterDecimals, self.parse_number(cost), market)
        return result

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://www.bitstamp.net/api/#tag/Order-book/operation/GetOrderBook
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
        }
        response = await self.publicGetOrderBookPair(self.extend(request, params))
        #
        #     {
        #         "timestamp": "1583652948",
        #         "microtimestamp": "1583652948955826",
        #         "bids": [
        #             ["8750.00", "1.33685271"],
        #             ["8749.39", "0.07700000"],
        #             ["8746.98", "0.07400000"],
        #         ]
        #         "asks": [
        #             ["8754.10", "1.51995636"],
        #             ["8754.71", "1.40000000"],
        #             ["8754.72", "2.50000000"],
        #         ]
        #     }
        #
        microtimestamp = self.safe_integer(response, 'microtimestamp')
        timestamp = self.parse_to_int(microtimestamp / 1000)
        orderbook = self.parse_order_book(response, market['symbol'], timestamp)
        orderbook['nonce'] = microtimestamp
        return orderbook

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        # {
        #     "timestamp": "1686068944",
        #     "high": "26252",
        #     "last": "26216",
        #     "bid": "26208",
        #     "vwap": "25681",
        #     "volume": "3563.13819902",
        #     "low": "25350",
        #     "ask": "26211",
        #     "open": "25730",
        #     "open_24": "25895",
        #     "percent_change_24": "1.24",
        #     "pair": "BTC/USD"
        # }
        #
        marketId = self.safe_string(ticker, 'pair')
        symbol = self.safe_symbol(marketId, market, None)
        timestamp = self.safe_timestamp(ticker, 'timestamp')
        vwap = self.safe_string(ticker, 'vwap')
        baseVolume = self.safe_string(ticker, 'volume')
        quoteVolume = Precise.string_mul(baseVolume, vwap)
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': None,
            'vwap': vwap,
            'open': self.safe_string(ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://www.bitstamp.net/api/#tag/Tickers/operation/GetMarketTicker
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
        }
        ticker = await self.publicGetTickerPair(self.extend(request, params))
        #
        # {
        #     "timestamp": "1686068944",
        #     "high": "26252",
        #     "last": "26216",
        #     "bid": "26208",
        #     "vwap": "25681",
        #     "volume": "3563.13819902",
        #     "low": "25350",
        #     "ask": "26211",
        #     "open": "25730",
        #     "open_24": "25895",
        #     "percent_change_24": "1.24"
        # }
        #
        return self.parse_ticker(ticker, market)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://www.bitstamp.net/api/#tag/Tickers/operation/GetCurrencyPairTickers
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        response = await self.publicGetTicker(params)
        #
        # {
        #     "timestamp": "1686068944",
        #     "high": "26252",
        #     "last": "26216",
        #     "bid": "26208",
        #     "vwap": "25681",
        #     "volume": "3563.13819902",
        #     "low": "25350",
        #     "ask": "26211",
        #     "open": "25730",
        #     "open_24": "25895",
        #     "percent_change_24": "1.24",
        #     "pair": "BTC/USD"
        # }
        #
        return self.parse_tickers(response, symbols)

    def get_currency_id_from_transaction(self, transaction):
        #
        #     {
        #         "fee": "0.00000000",
        #         "btc_usd": "0.00",
        #         "datetime": XXX,
        #         "usd": 0.0,
        #         "btc": 0.0,
        #         "eth": "0.05000000",
        #         "type": "0",
        #         "id": XXX,
        #         "eur": 0.0
        #     }
        #
        currencyId = self.safe_string_lower(transaction, 'currency')
        if currencyId is not None:
            return currencyId
        transaction = self.omit(transaction, [
            'fee',
            'price',
            'datetime',
            'type',
            'status',
            'id',
        ])
        ids = list(transaction.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            if id.find('_') < 0:
                value = self.safe_integer(transaction, id)
                if (value is not None) and (value != 0):
                    return id
        return None

    def get_market_from_trade(self, trade):
        trade = self.omit(trade, [
            'fee',
            'price',
            'datetime',
            'tid',
            'type',
            'order_id',
            'side',
        ])
        currencyIds = list(trade.keys())
        numCurrencyIds = len(currencyIds)
        if numCurrencyIds > 2:
            raise ExchangeError(self.id + ' getMarketFromTrade() too many keys: ' + self.json(currencyIds) + ' in the trade: ' + self.json(trade))
        if numCurrencyIds == 2:
            marketId = currencyIds[0] + currencyIds[1]
            if marketId in self.markets_by_id:
                return self.safe_market(marketId)
            marketId = currencyIds[1] + currencyIds[0]
            if marketId in self.markets_by_id:
                return self.safe_market(marketId)
        return None

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades(public)
        #
        #      {
        #          "date": "1637845199",
        #          "tid": "209895701",
        #          "amount": "0.00500000",
        #          "type": "0",             # Transaction type: 0 - buy; 1 - sell
        #          "price": "4451.25"
        #      }
        #
        # fetchMyTrades, trades returned within fetchOrder(private)
        #
        #      {
        #          "fee": "0.11128",
        #          "eth_usdt":  4451.25,
        #          "datetime": "2021-11-25 12:59:59.322000",
        #          "usdt": "-22.26",
        #          "order_id":  1429545880227846,
        #          "usd":  0,
        #          "btc":  0,
        #          "eth": "0.00500000",
        #          "type": "2",                    # Transaction type: 0 - deposit; 1 - withdrawal; 2 - market trade; 14 - sub account transfer; 25 - credited with staked assets; 26 - sent assets to staking; 27 - staking reward; 32 - referral reward; 35 - inter account transfer.
        #          "id":  209895701,
        #          "eur":  0
        #      }
        #
        # from fetchOrder(private)
        #
        #      {
        #          "fee": "0.11128",
        #          "price": "4451.25000000",
        #          "datetime": "2021-11-25 12:59:59.322000",
        #          "usdt": "22.25625000",
        #          "tid": 209895701,
        #          "eth": "0.00500000",
        #          "type": 2                       # Transaction type: 0 - deposit; 1 - withdrawal; 2 - market trade
        #      }
        #
        id = self.safe_string_2(trade, 'id', 'tid')
        symbol = None
        side = None
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'amount')
        orderId = self.safe_string(trade, 'order_id')
        type = None
        costString = self.safe_string(trade, 'cost')
        rawMarketId = None
        if market is None:
            keys = list(trade.keys())
            for i in range(0, len(keys)):
                currentKey = keys[i]
                if currentKey != 'order_id' and currentKey.find('_') >= 0:
                    rawMarketId = currentKey
                    market = self.safe_market(rawMarketId, market, '_')
        # if the market is still not defined
        # try to deduce it from used keys
        if market is None:
            market = self.get_market_from_trade(trade)
        feeCostString = self.safe_string(trade, 'fee')
        feeCurrency = market['quote']
        priceId = rawMarketId if (rawMarketId is not None) else market['marketId']
        priceString = self.safe_string(trade, priceId, priceString)
        amountString = self.safe_string(trade, market['baseId'], amountString)
        costString = self.safe_string(trade, market['quoteId'], costString)
        symbol = market['symbol']
        datetimeString = self.safe_string_2(trade, 'date', 'datetime')
        timestamp = None
        if datetimeString is not None:
            if datetimeString.find(' ') >= 0:
                # iso8601
                timestamp = self.parse8601(datetimeString)
            else:
                # string unix epoch in seconds
                timestamp = int(datetimeString)
                timestamp = timestamp * 1000
        # if it is a private trade
        if 'id' in trade:
            if amountString is not None:
                isAmountNeg = Precise.string_lt(amountString, '0')
                if isAmountNeg:
                    side = 'sell'
                    amountString = Precise.string_neg(amountString)
                else:
                    side = 'buy'
        else:
            side = self.safe_string(trade, 'type')
            if side == '1':
                side = 'sell'
            elif side == '0':
                side = 'buy'
            else:
                side = None
        if costString is not None:
            costString = Precise.string_abs(costString)
        fee = None
        if feeCostString is not None:
            fee = {
                'cost': feeCostString,
                'currency': feeCurrency,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://www.bitstamp.net/api/#tag/Transactions-public/operation/GetTransactions
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'time': 'hour',
        }
        response = await self.publicGetTransactionsPair(self.extend(request, params))
        #
        #     [
        #         {
        #             "date": "1551814435",
        #             "tid": "83581898",
        #             "price": "0.03532850",
        #             "type": "1",
        #             "amount": "0.85945907"
        #         },
        #         {
        #             "date": "1551814434",
        #             "tid": "83581896",
        #             "price": "0.03532851",
        #             "type": "1",
        #             "amount": "11.34130961"
        #         },
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     {
        #         "high": "9064.77",
        #         "timestamp": "1593961440",
        #         "volume": "18.49436608",
        #         "low": "9040.87",
        #         "close": "9064.77",
        #         "open": "9040.87"
        #     }
        #
        return [
            self.safe_timestamp(ohlcv, 'timestamp'),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'high'),
            self.safe_number(ohlcv, 'low'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://www.bitstamp.net/api/#tag/Market-info/operation/GetOHLCData
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'step': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        duration = self.parse_timeframe(timeframe)
        if limit is None:
            if since is None:
                request['limit'] = 1000  # we need to specify an allowed amount of `limit` if no `since` is set and there is no default limit by exchange
            else:
                limit = 1000
                start = self.parse_to_int(since / 1000)
                request['start'] = start
                request['end'] = self.sum(start, duration * (limit - 1))
                request['limit'] = limit
        else:
            if since is not None:
                start = self.parse_to_int(since / 1000)
                request['start'] = start
                request['end'] = self.sum(start, duration * (limit - 1))
            request['limit'] = min(limit, 1000)  # min 1, max 1000
        response = await self.publicGetOhlcPair(self.extend(request, params))
        #
        #     {
        #         "data": {
        #             "pair": "BTC/USD",
        #             "ohlc": [
        #                 {"high": "9064.77", "timestamp": "1593961440", "volume": "18.49436608", "low": "9040.87", "close": "9064.77", "open": "9040.87"},
        #                 {"high": "9071.59", "timestamp": "1593961500", "volume": "3.48631711", "low": "9058.76", "close": "9061.07", "open": "9064.66"},
        #                 {"high": "9067.33", "timestamp": "1593961560", "volume": "0.04142833", "low": "9061.94", "close": "9061.94", "open": "9067.33"},
        #             ],
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        ohlc = self.safe_list(data, 'ohlc', [])
        return self.parse_ohlcvs(ohlc, market, timeframe, since, limit)

    def parse_balance(self, response) -> Balances:
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        if response is None:
            response = []
        for i in range(0, len(response)):
            currencyBalance = response[i]
            currencyId = self.safe_string(currencyBalance, 'currency')
            currencyCode = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(currencyBalance, 'available')
            account['used'] = self.safe_string(currencyBalance, 'reserved')
            account['total'] = self.safe_string(currencyBalance, 'total')
            result[currencyCode] = account
        return self.safe_balance(result)

    async def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://www.bitstamp.net/api/#tag/Account-balances/operation/GetAccountBalances
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        response = await self.privatePostAccountBalances(params)
        #
        #     [
        #         {
        #             "currency": "usdt",
        #             "total": "7.00000",
        #             "available": "7.00000",
        #             "reserved": "0.00000"
        #         },
        #         ...
        #     ]
        #
        return self.parse_balance(response)

    async def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        fetch the trading fees for a market
        :see: https://www.bitstamp.net/api/#tag/Fees/operation/GetTradingFeesForCurrency
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `fee structure <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'market_symbol': market['id'],
        }
        response = await self.privatePostFeesTrading(self.extend(request, params))
        #
        #     [
        #         {
        #             "currency_pair": "btcusd",
        #             "fees":
        #                 {
        #                     "maker": "0.15000",
        #                     "taker": "0.16000"
        #                 },
        #             "market": "btcusd"
        #         }
        #         ...
        #     ]
        #
        tradingFeesByMarketId = self.index_by(response, 'currency_pair')
        tradingFee = self.safe_dict(tradingFeesByMarketId, market['id'])
        return self.parse_trading_fee(tradingFee, market)

    def parse_trading_fee(self, fee: dict, market: Market = None) -> TradingFeeInterface:
        marketId = self.safe_string(fee, 'market')
        fees = self.safe_dict(fee, 'fees', {})
        return {
            'info': fee,
            'symbol': self.safe_symbol(marketId, market),
            'maker': self.safe_number(fees, 'maker'),
            'taker': self.safe_number(fees, 'taker'),
            'percentage': None,
            'tierBased': None,
        }

    def parse_trading_fees(self, fees):
        result: dict = {'info': fees}
        symbols = self.symbols
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            fee = self.parse_trading_fee(fees[i])
            result[symbol] = fee
        return result

    async def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets
        :see: https://www.bitstamp.net/api/#tag/Fees/operation/GetAllTradingFees
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt_zagrava.com/#/?id=fee-structure>` indexed by market symbols
        """
        await self.load_markets()
        response = await self.privatePostFeesTrading(params)
        #
        #     [
        #         {
        #             "currency_pair": "btcusd",
        #             "fees":
        #                 {
        #                     "maker": "0.15000",
        #                     "taker": "0.16000"
        #                 },
        #             "market": "btcusd"
        #         }
        #         ...
        #     ]
        #
        return self.parse_trading_fees(response)

    async def fetch_transaction_fees(self, codes: Strings = None, params={}):
        """
         * @deprecated
        please use fetchDepositWithdrawFees instead
        :see: https://www.bitstamp.net/api/#tag/Fees
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `fee structures <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        await self.load_markets()
        response = await self.privatePostFeesWithdrawal(params)
        #
        #     [
        #         {
        #             "currency": "btc",
        #             "fee": "0.00015000",
        #             "network": "bitcoin"
        #         }
        #         ...
        #     ]
        #
        return self.parse_transaction_fees(response)

    def parse_transaction_fees(self, response, codes=None):
        result: dict = {}
        currencies = self.index_by(response, 'currency')
        ids = list(currencies.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            fees = self.safe_value(response, i, {})
            code = self.safe_currency_code(id)
            if (codes is not None) and not self.in_array(code, codes):
                continue
            result[code] = {
                'withdraw_fee': self.safe_number(fees, 'fee'),
                'deposit': {},
                'info': self.safe_dict(currencies, id),
            }
        return result

    async def fetch_deposit_withdraw_fees(self, codes=None, params={}):
        """
        fetch deposit and withdraw fees
        :see: https://www.bitstamp.net/api/#tag/Fees/operation/GetAllWithdrawalFees
        :param str[]|None codes: list of unified currency codes
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `fee structures <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        await self.load_markets()
        response = await self.privatePostFeesWithdrawal(params)
        #
        #     [
        #         {
        #             "currency": "btc",
        #             "fee": "0.00015000",
        #             "network": "bitcoin"
        #         }
        #         ...
        #     ]
        #
        responseByCurrencyId = self.group_by(response, 'currency')
        return self.parse_deposit_withdraw_fees(responseByCurrencyId, codes)

    def parse_deposit_withdraw_fee(self, fee, currency=None):
        result = self.deposit_withdraw_fee(fee)
        for j in range(0, len(fee)):
            networkEntry = fee[j]
            networkId = self.safe_string(networkEntry, 'network')
            networkCode = self.network_id_to_code(networkId)
            withdrawFee = self.safe_number(networkEntry, 'fee')
            result['withdraw'] = {
                'fee': withdrawFee,
                'percentage': None,
            }
            result['networks'][networkCode] = {
                'withdraw': {
                    'fee': withdrawFee,
                    'percentage': None,
                },
                'deposit': {
                    'fee': None,
                    'percentage': None,
                },
            }
        return result

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/OpenInstantBuyOrder
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/OpenMarketBuyOrder
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/OpenLimitBuyOrder
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/OpenInstantSellOrder
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/OpenMarketSellOrder
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/OpenLimitSellOrder
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'amount': self.amount_to_precision(symbol, amount),
        }
        clientOrderId = self.safe_string_2(params, 'client_order_id', 'clientOrderId')
        if clientOrderId is not None:
            request['client_order_id'] = clientOrderId
            params = self.omit(params, ['clientOrderId'])
        response = None
        capitalizedSide = self.capitalize(side)
        if type == 'market':
            if capitalizedSide == 'Buy':
                response = await self.privatePostBuyMarketPair(self.extend(request, params))
            else:
                response = await self.privatePostSellMarketPair(self.extend(request, params))
        elif type == 'instant':
            if capitalizedSide == 'Buy':
                response = await self.privatePostBuyInstantPair(self.extend(request, params))
            else:
                response = await self.privatePostSellInstantPair(self.extend(request, params))
        else:
            request['price'] = self.price_to_precision(symbol, price)
            if capitalizedSide == 'Buy':
                response = await self.privatePostBuyPair(self.extend(request, params))
            else:
                response = await self.privatePostSellPair(self.extend(request, params))
        order = self.parse_order(response, market)
        order['type'] = type
        return order

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/CancelOrder
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request: dict = {
            'id': id,
        }
        response = await self.privatePostCancelOrder(self.extend(request, params))
        #
        #    {
        #        "id": 1453282316578816,
        #        "amount": "0.02035278",
        #        "price": "2100.45",
        #        "type": 0,
        #        "market": "BTC/USD"
        #    }
        #
        return self.parse_order(response)

    async def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/CancelAllOrders
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/CancelOrdersForMarket
        :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = None
        request: dict = {}
        response = None
        if symbol is not None:
            market = self.market(symbol)
            request['pair'] = market['id']
            response = await self.privatePostCancelAllOrdersPair(self.extend(request, params))
        else:
            response = await self.privatePostCancelAllOrders(self.extend(request, params))
        #
        #    {
        #        "canceled": [
        #            {
        #                "id": 1453282316578816,
        #                "amount": "0.02035278",
        #                "price": "2100.45",
        #                "type": 0,
        #                "currency_pair": "BTC/USD",
        #                "market": "BTC/USD"
        #            }
        #        ],
        #        "success": True
        #    }
        #
        canceled = self.safe_list(response, 'canceled')
        return self.parse_orders(canceled)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'In Queue': 'open',
            'Open': 'open',
            'Finished': 'closed',
            'Canceled': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    async def fetch_order_status(self, id: str, symbol: Str = None, params={}):
        await self.load_markets()
        clientOrderId = self.safe_value_2(params, 'client_order_id', 'clientOrderId')
        request: dict = {}
        if clientOrderId is not None:
            request['client_order_id'] = clientOrderId
            params = self.omit(params, ['client_order_id', 'clientOrderId'])
        else:
            request['id'] = id
        response = await self.privatePostOrderStatus(self.extend(request, params))
        return self.parse_order_status(self.safe_string(response, 'status'))

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/GetOrderStatus
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        clientOrderId = self.safe_value_2(params, 'client_order_id', 'clientOrderId')
        request: dict = {}
        if clientOrderId is not None:
            request['client_order_id'] = clientOrderId
            params = self.omit(params, ['client_order_id', 'clientOrderId'])
        else:
            request['id'] = id
        response = await self.privatePostOrderStatus(self.extend(request, params))
        #
        #      {
        #          "status": "Finished",
        #          "id": 1429545880227846,
        #          "amount_remaining": "0.00000000",
        #          "transactions": [
        #              {
        #                  "fee": "0.11128",
        #                  "price": "4451.25000000",
        #                  "datetime": "2021-11-25 12:59:59.322000",
        #                  "usdt": "22.25625000",
        #                  "tid": 209895701,
        #                  "eth": "0.00500000",
        #                  "type": 2
        #              }
        #         ]
        #     }
        #
        return self.parse_order(response, market)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://www.bitstamp.net/api/#tag/Transactions-private/operation/GetUserTransactions
        :see: https://www.bitstamp.net/api/#tag/Transactions-private/operation/GetUserTransactionsForMarket
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        request: dict = {}
        method = 'privatePostUserTransactions'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['pair'] = market['id']
            method += 'Pair'
        if limit is not None:
            request['limit'] = limit
        response = await getattr(self, method)(self.extend(request, params))
        result = self.filter_by(response, 'type', '2')
        return self.parse_trades(result, market, since, limit)

    async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch history of deposits and withdrawals
        :see: https://www.bitstamp.net/api/#tag/Transactions-private/operation/GetUserTransactions
        :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
        :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
        :param int [limit]: max number of deposit/withdrawals to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        request: dict = {}
        if limit is not None:
            request['limit'] = limit
        response = await self.privatePostUserTransactions(self.extend(request, params))
        #
        #     [
        #         {
        #             "fee": "0.00000000",
        #             "btc_usd": "0.00",
        #             "id": 1234567894,
        #             "usd": 0,
        #             "btc": 0,
        #             "datetime": "2018-09-08 09:00:31",
        #             "type": "1",
        #             "xrp": "-20.00000000",
        #             "eur": 0,
        #         },
        #         {
        #             "fee": "0.00000000",
        #             "btc_usd": "0.00",
        #             "id": 1134567891,
        #             "usd": 0,
        #             "btc": 0,
        #             "datetime": "2018-09-07 18:47:52",
        #             "type": "0",
        #             "xrp": "20.00000000",
        #             "eur": 0,
        #         },
        #     ]
        #
        currency = None
        if code is not None:
            currency = self.currency(code)
        transactions = self.filter_by_array(response, 'type', ['0', '1'], False)
        return self.parse_transactions(transactions, currency, since, limit)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :see: https://www.bitstamp.net/api/#tag/Withdrawals/operation/GetWithdrawalRequests
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        request: dict = {}
        if since is not None:
            request['timedelta'] = self.milliseconds() - since
        else:
            request['timedelta'] = 50000000  # use max bitstamp approved value
        response = await self.privatePostWithdrawalRequests(self.extend(request, params))
        #
        #     [
        #         {
        #             "status": 2,
        #             "datetime": "2018-10-17 10:58:13",
        #             "currency": "BTC",
        #             "amount": "0.29669259",
        #             "address": "aaaaa",
        #             "type": 1,
        #             "id": 111111,
        #             "transaction_id": "xxxx",
        #         },
        #         {
        #             "status": 2,
        #             "datetime": "2018-10-17 10:55:17",
        #             "currency": "ETH",
        #             "amount": "1.11010664",
        #             "address": "aaaa",
        #             "type": 16,
        #             "id": 222222,
        #             "transaction_id": "xxxxx",
        #         },
        #     ]
        #
        return self.parse_transactions(response, None, since, limit)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # fetchDepositsWithdrawals
        #
        #     {
        #         "fee": "0.00000000",
        #         "btc_usd": "0.00",
        #         "id": 1234567894,
        #         "usd": 0,
        #         "btc": 0,
        #         "datetime": "2018-09-08 09:00:31",
        #         "type": "1",
        #         "xrp": "-20.00000000",
        #         "eur": 0,
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "status": 2,
        #         "datetime": "2018-10-17 10:58:13",
        #         "currency": "BTC",
        #         "amount": "0.29669259",
        #         "address": "aaaaa",
        #         "type": 1,
        #         "id": 111111,
        #         "transaction_id": "xxxx",
        #     }
        #
        #     {
        #         "id": 3386432,
        #         "type": 14,
        #         "amount": "863.21332500",
        #         "status": 2,
        #         "address": "rE1sdh25BJQ3qFwngiTBwaq3zPGGYcrjp1?dt=1455",
        #         "currency": "XRP",
        #         "datetime": "2018-01-05 15:27:55",
        #         "transaction_id": "001743B03B0C79BA166A064AC0142917B050347B4CB23BA2AB4B91B3C5608F4C"
        #     }
        #
        timestamp = self.parse8601(self.safe_string(transaction, 'datetime'))
        currencyId = self.get_currency_id_from_transaction(transaction)
        code = self.safe_currency_code(currencyId, currency)
        feeCost = self.safe_string(transaction, 'fee')
        feeCurrency = None
        amount = None
        if 'amount' in transaction:
            amount = self.safe_string(transaction, 'amount')
        elif currency is not None:
            amount = self.safe_string(transaction, currency['id'], amount)
            feeCurrency = currency['code']
        elif (code is not None) and (currencyId is not None):
            amount = self.safe_string(transaction, currencyId, amount)
            feeCurrency = code
        if amount is not None:
            # withdrawals have a negative amount
            amount = Precise.string_abs(amount)
        status = 'ok'
        if 'status' in transaction:
            status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        type = None
        if 'type' in transaction:
            # from fetchDepositsWithdrawals
            rawType = self.safe_string(transaction, 'type')
            if rawType == '0':
                type = 'deposit'
            elif rawType == '1':
                type = 'withdrawal'
        else:
            # from fetchWithdrawals
            type = 'withdrawal'
        tag = None
        address = self.safe_string(transaction, 'address')
        if address is not None:
            # dt(destination tag) is embedded into the address field
            addressParts = address.split('?dt=')
            numParts = len(addressParts)
            if numParts > 1:
                address = addressParts[0]
                tag = addressParts[1]
        fee = {
            'currency': None,
            'cost': None,
            'rate': None,
        }
        if feeCost is not None:
            fee = {
                'currency': feeCurrency,
                'cost': feeCost,
                'rate': None,
            }
        return {
            'info': transaction,
            'id': self.safe_string(transaction, 'id'),
            'txid': self.safe_string(transaction, 'transaction_id'),
            'type': type,
            'currency': code,
            'network': None,
            'amount': self.parse_number(amount),
            'status': status,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'addressFrom': None,
            'addressTo': address,
            'tag': tag,
            'tagFrom': None,
            'tagTo': tag,
            'updated': None,
            'comment': None,
            'internal': None,
            'fee': fee,
        }

    def parse_transaction_status(self, status: Str):
        #
        #   withdrawals:
        #   0(open), 1(in process), 2(finished), 3(canceled) or 4(failed).
        #
        statuses: dict = {
            '0': 'pending',  # Open
            '1': 'pending',  # In process
            '2': 'ok',  # Finished
            '3': 'canceled',  # Canceled
            '4': 'failed',  # Failed
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #   from fetch order:
        #     {status: "Finished",
        #       "id": 731693945,
        #       "client_order_id": '',
        #       "transactions":
        #       [{fee: "0.000019",
        #           "price": "0.00015803",
        #           "datetime": "2018-01-07 10:45:34.132551",
        #           "btc": "0.0079015000000000",
        #           "tid": 42777395,
        #           "type": 2,
        #           "xrp": "50.00000000"}]}
        #
        #   partially filled order:
        #     {"id": 468646390,
        #       "client_order_id": "",
        #       "status": "Canceled",
        #       "transactions": [{
        #           "eth": "0.23000000",
        #           "fee": "0.09",
        #           "tid": 25810126,
        #           "usd": "69.8947000000000000",
        #           "type": 2,
        #           "price": "303.89000000",
        #           "datetime": "2017-11-11 07:22:20.710567"
        #       }]}
        #
        #   from create order response:
        #       {
        #           "price": "0.00008012",
        #           "client_order_id": '',
        #           "currency_pair": "XRP/BTC",
        #           "datetime": "2019-01-31 21:23:36",
        #           "amount": "15.00000000",
        #           "type": "0",
        #           "id": "2814205012"
        #       }
        #
        # cancelOrder
        #
        #    {
        #        "id": 1453282316578816,
        #        "amount": "0.02035278",
        #        "price": "2100.45",
        #        "type": 0,
        #        "market": "BTC/USD"
        #    }
        #
        id = self.safe_string(order, 'id')
        clientOrderId = self.safe_string(order, 'client_order_id')
        side = self.safe_string(order, 'type')
        if side is not None:
            side = 'sell' if (side == '1') else 'buy'
        # there is no timestamp from fetchOrder
        timestamp = self.parse8601(self.safe_string(order, 'datetime'))
        marketId = self.safe_string_lower(order, 'currency_pair')
        symbol = self.safe_symbol(marketId, market, '/')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        amount = self.safe_string(order, 'amount')
        transactions = self.safe_value(order, 'transactions', [])
        price = self.safe_string(order, 'price')
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': None,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'cost': None,
            'amount': amount,
            'filled': None,
            'remaining': None,
            'trades': transactions,
            'fee': None,
            'info': order,
            'average': None,
        }, market)

    def parse_ledger_entry_type(self, type):
        types: dict = {
            '0': 'transaction',
            '1': 'transaction',
            '2': 'trade',
            '14': 'transfer',
        }
        return self.safe_string(types, type, type)

    def parse_ledger_entry(self, item: dict, currency: Currency = None):
        #
        #     [
        #         {
        #             "fee": "0.00000000",
        #             "btc_usd": "0.00",
        #             "id": 1234567894,
        #             "usd": 0,
        #             "btc": 0,
        #             "datetime": "2018-09-08 09:00:31",
        #             "type": "1",
        #             "xrp": "-20.00000000",
        #             "eur": 0,
        #         },
        #         {
        #             "fee": "0.00000000",
        #             "btc_usd": "0.00",
        #             "id": 1134567891,
        #             "usd": 0,
        #             "btc": 0,
        #             "datetime": "2018-09-07 18:47:52",
        #             "type": "0",
        #             "xrp": "20.00000000",
        #             "eur": 0,
        #         },
        #     ]
        #
        type = self.parse_ledger_entry_type(self.safe_string(item, 'type'))
        if type == 'trade':
            parsedTrade = self.parse_trade(item)
            market = None
            keys = list(item.keys())
            for i in range(0, len(keys)):
                if keys[i].find('_') >= 0:
                    marketId = keys[i].replace('_', '')
                    market = self.safe_market(marketId, market)
            # if the market is still not defined
            # try to deduce it from used keys
            if market is None:
                market = self.get_market_from_trade(item)
            direction = 'in' if (parsedTrade['side'] == 'buy') else 'out'
            return {
                'id': parsedTrade['id'],
                'info': item,
                'timestamp': parsedTrade['timestamp'],
                'datetime': parsedTrade['datetime'],
                'direction': direction,
                'account': None,
                'referenceId': parsedTrade['order'],
                'referenceAccount': None,
                'type': type,
                'currency': market['base'],
                'amount': parsedTrade['amount'],
                'before': None,
                'after': None,
                'status': 'ok',
                'fee': parsedTrade['fee'],
            }
        else:
            parsedTransaction = self.parse_transaction(item, currency)
            direction = None
            if 'amount' in item:
                amount = self.safe_string(item, 'amount')
                direction = 'in' if Precise.string_gt(amount, '0') else 'out'
            elif ('currency' in parsedTransaction) and parsedTransaction['currency'] is not None:
                currencyCode = self.safe_string(parsedTransaction, 'currency')
                currency = self.currency(currencyCode)
                amount = self.safe_string(item, currency['id'])
                direction = 'in' if Precise.string_gt(amount, '0') else 'out'
            return {
                'id': parsedTransaction['id'],
                'info': item,
                'timestamp': parsedTransaction['timestamp'],
                'datetime': parsedTransaction['datetime'],
                'direction': direction,
                'account': None,
                'referenceId': parsedTransaction['txid'],
                'referenceAccount': None,
                'type': type,
                'currency': parsedTransaction['currency'],
                'amount': parsedTransaction['amount'],
                'before': None,
                'after': None,
                'status': parsedTransaction['status'],
                'fee': parsedTransaction['fee'],
            }

    async def fetch_ledger(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch the history of changes, actions done by the user or operations that altered balance of the user
        :see: https://www.bitstamp.net/api/#tag/Transactions-private/operation/GetUserTransactions
        :param str code: unified currency code, default is None
        :param int [since]: timestamp in ms of the earliest ledger entry, default is None
        :param int [limit]: max number of ledger entrys to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ledger structure <https://docs.ccxt_zagrava.com/#/?id=ledger-structure>`
        """
        await self.load_markets()
        request: dict = {}
        if limit is not None:
            request['limit'] = limit
        response = await self.privatePostUserTransactions(self.extend(request, params))
        currency = None
        if code is not None:
            currency = self.currency(code)
        return self.parse_ledger(response, currency, since, limit)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/GetAllOpenOrders
        :see: https://www.bitstamp.net/api/#tag/Orders/operation/GetOpenOrdersForMarket
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        market = None
        await self.load_markets()
        if symbol is not None:
            market = self.market(symbol)
        response = await self.privatePostOpenOrdersAll(params)
        #
        #     [
        #         {
        #             "price": "0.00008012",
        #             "currency_pair": "XRP/BTC",
        #             "client_order_id": '',
        #             "datetime": "2019-01-31 21:23:36",
        #             "amount": "15.00000000",
        #             "type": "0",
        #             "id": "2814205012",
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit, {
            'status': 'open',
            'type': 'limit',
        })

    def get_currency_name(self, code):
        """
         * @ignore
        :param str code: Unified currency code
        :returns str: lowercase version of code
        """
        return code.lower()

    def is_fiat(self, code):
        return code == 'USD' or code == 'EUR' or code == 'GBP'

    async def fetch_deposit_address(self, code: str, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :see: https://www.bitstamp.net/api/#tag/Deposits/operation/GetCryptoDepositAddress
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        if self.is_fiat(code):
            raise NotSupported(self.id + ' fiat fetchDepositAddress() for ' + code + ' is not supported!')
        name = self.get_currency_name(code)
        method = 'privatePost' + self.capitalize(name) + 'Address'
        response = await getattr(self, method)(params)
        address = self.safe_string(response, 'address')
        tag = self.safe_string_2(response, 'memo_id', 'destination_tag')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': None,
            'info': response,
        }

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://www.bitstamp.net/api/#tag/Withdrawals/operation/RequestFiatWithdrawal
        :see: https://www.bitstamp.net/api/#tag/Withdrawals/operation/RequestCryptoWithdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        # For fiat withdrawals please provide all required additional parameters in the 'params'
        # Check https://www.bitstamp.net/api/ under 'Open bank withdrawal' for list and description.
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        await self.load_markets()
        self.check_address(address)
        request: dict = {
            'amount': amount,
        }
        currency = None
        method = None
        if not self.is_fiat(code):
            name = self.get_currency_name(code)
            method = 'privatePost' + self.capitalize(name) + 'Withdrawal'
            if code == 'XRP':
                if tag is not None:
                    request['destination_tag'] = tag
            elif code == 'XLM' or code == 'HBAR':
                if tag is not None:
                    request['memo_id'] = tag
            request['address'] = address
        else:
            method = 'privatePostWithdrawalOpen'
            currency = self.currency(code)
            request['iban'] = address
            request['account_currency'] = currency['id']
        response = await getattr(self, method)(self.extend(request, params))
        return self.parse_transaction(response, currency)

    async def transfer(self, code: str, amount: float, fromAccount: str, toAccount: str, params={}) -> TransferEntry:
        """
        transfer currency internally between wallets on the same account
        :see: https://www.bitstamp.net/api/#tag/Sub-account/operation/TransferFromMainToSub
        :see: https://www.bitstamp.net/api/#tag/Sub-account/operation/TransferFromSubToMain
        :param str code: unified currency code
        :param float amount: amount to transfer
        :param str fromAccount: account to transfer from
        :param str toAccount: account to transfer to
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transfer structure <https://docs.ccxt_zagrava.com/#/?id=transfer-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'amount': self.parse_to_numeric(self.currency_to_precision(code, amount)),
            'currency': currency['id'].upper(),
        }
        response = None
        if fromAccount == 'main':
            request['subAccount'] = toAccount
            response = await self.privatePostTransferFromMain(self.extend(request, params))
        elif toAccount == 'main':
            request['subAccount'] = fromAccount
            response = await self.privatePostTransferToMain(self.extend(request, params))
        else:
            raise BadRequest(self.id + ' transfer() only supports from or to main')
        #
        #    {status: 'ok'}
        #
        transfer = self.parse_transfer(response, currency)
        transfer['amount'] = amount
        transfer['fromAccount'] = fromAccount
        transfer['toAccount'] = toAccount
        return transfer

    def parse_transfer(self, transfer, currency=None):
        #
        #    {status: 'ok'}
        #
        status = self.safe_string(transfer, 'status')
        return {
            'info': transfer,
            'id': None,
            'timestamp': None,
            'datetime': None,
            'currency': currency['code'],
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': self.parse_transfer_status(status),
        }

    def parse_transfer_status(self, status: Str) -> Str:
        statuses: dict = {
            'ok': 'ok',
            'error': 'failed',
        }
        return self.safe_string(statuses, status, status)

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api] + '/'
        url += self.version + '/'
        url += self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            xAuth = 'BITSTAMP ' + self.apiKey
            xAuthNonce = self.uuid()
            xAuthTimestamp = str(self.milliseconds())
            xAuthVersion = 'v2'
            contentType = ''
            headers = {
                'X-Auth': xAuth,
                'X-Auth-Nonce': xAuthNonce,
                'X-Auth-Timestamp': xAuthTimestamp,
                'X-Auth-Version': xAuthVersion,
            }
            if method == 'POST':
                if query:
                    body = self.urlencode(query)
                    contentType = 'application/x-www-form-urlencoded'
                    headers['Content-Type'] = contentType
                else:
                    # sending an empty POST request will trigger
                    # an API0020 error returned by the exchange
                    # therefore for empty requests we send a dummy object
                    # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6846
                    body = self.urlencode({'foo': 'bar'})
                    contentType = 'application/x-www-form-urlencoded'
                    headers['Content-Type'] = contentType
            authBody = body if body else ''
            auth = xAuth + method + url.replace('https://', '') + contentType + xAuthNonce + xAuthTimestamp + xAuthVersion + authBody
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256)
            headers['X-Auth-Signature'] = signature
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None
        #
        #     {"error": "No permission found"}  # fetchDepositAddress returns self on apiKeys that don't have the permission required
        #     {"status": "error", "reason": {"__all__": ["Minimum order size is 5.0 EUR."]}}
        #     reuse of a nonce gives: {status: 'error', reason: 'Invalid nonce', code: 'API0004'}
        #
        status = self.safe_string(response, 'status')
        error = self.safe_value(response, 'error')
        if (status == 'error') or (error is not None):
            errors = []
            if isinstance(error, str):
                errors.append(error)
            elif error is not None:
                keys = list(error.keys())
                for i in range(0, len(keys)):
                    key = keys[i]
                    value = self.safe_value(error, key)
                    if isinstance(value, list):
                        errors = self.array_concat(errors, value)
                    else:
                        errors.append(value)
            reasonInner = self.safe_value(response, 'reason', {})
            if isinstance(reasonInner, str):
                errors.append(reasonInner)
            else:
                all = self.safe_value(reasonInner, '__all__', [])
                for i in range(0, len(all)):
                    errors.append(all[i])
            code = self.safe_string(response, 'code')
            if code == 'API0005':
                raise AuthenticationError(self.id + ' invalid signature, use the uid for the main account if you have subaccounts')
            feedback = self.id + ' ' + body
            for i in range(0, len(errors)):
                value = errors[i]
                self.throw_exactly_matched_exception(self.exceptions['exact'], value, feedback)
                self.throw_broadly_matched_exception(self.exceptions['broad'], value, feedback)
            raise ExchangeError(feedback)
        return None
