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

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

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


class poloniex(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(poloniex, self).describe(), {
            'id': 'poloniex',
            'name': 'Poloniex',
            'countries': ['US'],
            # 200 requests per second for some unauthenticated market endpoints => 1000ms / 200 = 5ms between requests
            'rateLimit': 5,
            'certified': False,
            'pro': True,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': None,  # has but not fully implemented
                'swap': False,
                'future': False,
                'option': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'createDepositAddress': True,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrder': False,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': True,
                'fetchDepositsWithdrawals': True,
                'fetchDepositWithdrawFee': 'emulated',
                'fetchDepositWithdrawFees': True,
                'fetchFundingRate': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrder': False,
                'fetchOpenOrders': True,  # True endpoint for open orders
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrderTrades': True,  # True endpoint for trades of a single open or closed order
                'fetchPosition': False,
                'fetchPositionMode': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactions': 'emulated',
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawals': True,
                'sandbox': True,
                'transfer': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': 'MINUTE_1',
                '5m': 'MINUTE_5',
                '10m': 'MINUTE_10',
                '15m': 'MINUTE_15',
                '30m': 'MINUTE_30',
                '1h': 'HOUR_1',
                '2h': 'HOUR_2',
                '4h': 'HOUR_4',
                '6h': 'HOUR_6',
                '12h': 'HOUR_12',
                '1d': 'DAY_1',
                '3d': 'DAY_3',
                '1w': 'WEEK_1',
                '1M': 'MONTH_1',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766817-e9456312-5ee6-11e7-9b3c-b628ca5626a5.jpg',
                'api': {
                    'rest': 'https://api.poloniex.com',
                },
                'test': {
                    'rest': 'https://sand-spot-api-gateway.poloniex.com',
                },
                'www': 'https://www.poloniex.com',
                'doc': 'https://docs.poloniex.com',
                'fees': 'https://poloniex.com/fees',
                'referral': 'https://poloniex.com/signup?c=UBFZJRPJ',
            },
            'api': {
                'public': {
                    'get': {
                        'markets': 20,
                        'markets/{symbol}': 1,
                        'currencies': 20,
                        'currencies/{currency}': 20,
                        'v2/currencies': 20,
                        'v2/currencies/{currency}': 20,
                        'timestamp': 1,
                        'markets/price': 1,
                        'markets/{symbol}/price': 1,
                        'markets/markPrice': 1,
                        'markets/{symbol}/markPrice': 1,
                        'markets/{symbol}/markPriceComponents': 1,
                        'markets/{symbol}/orderBook': 1,
                        'markets/{symbol}/candles': 1,
                        'markets/{symbol}/trades': 20,
                        'markets/ticker24h': 20,
                        'markets/{symbol}/ticker24h': 20,
                        'markets/collateralInfo': 1,
                        'markets/{currency}/collateralInfo': 1,
                        'markets/borrowRatesInfo': 1,
                    },
                },
                'private': {
                    'get': {
                        'accounts': 4,
                        'accounts/balances': 4,
                        'accounts/{id}/balances': 4,
                        'accounts/activity': 20,
                        'accounts/transfer': 20,
                        'accounts/transfer/{id}': 4,
                        'feeinfo': 20,
                        'accounts/interest/history': 1,
                        'subaccounts': 4,
                        'subaccounts/balances': 20,
                        'subaccounts/{id}/balances': 4,
                        'subaccounts/transfer': 20,
                        'subaccounts/transfer/{id}': 4,
                        'wallets/addresses': 20,
                        'wallets/addresses/{currency}': 20,
                        'wallets/activity': 20,
                        'margin/accountMargin': 4,
                        'margin/borrowStatus': 4,
                        'margin/maxSize': 4,
                        'orders': 20,
                        'orders/{id}': 4,
                        'orders/killSwitchStatus': 4,
                        'smartorders': 20,
                        'smartorders/{id}': 4,
                        'orders/history': 20,
                        'smartorders/history': 20,
                        'trades': 20,
                        'orders/{id}/trades': 4,
                    },
                    'post': {
                        'accounts/transfer': 4,
                        'subaccounts/transfer': 20,
                        'wallets/address': 20,
                        'wallets/withdraw': 20,
                        'v2/wallets/withdraw': 20,
                        'orders': 4,
                        'orders/batch': 20,
                        'orders/killSwitch': 4,
                        'smartorders': 4,
                    },
                    'delete': {
                        'orders/{id}': 4,
                        'orders/cancelByIds': 20,
                        'orders': 20,
                        'smartorders/{id}': 4,
                        'smartorders/cancelByIds': 20,
                        'smartorders': 20,
                    },
                    'put': {
                        'orders/{id}': 20,
                        'smartorders/{id}': 20,
                    },
                },
            },
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    # starting from Jan 8 2020
                    'maker': self.parse_number('0.0009'),
                    'taker': self.parse_number('0.0009'),
                },
                'funding': {},
            },
            'commonCurrencies': {
                'AIR': 'AirCoin',
                'APH': 'AphroditeCoin',
                'BCC': 'BTCtalkcoin',
                'BCHABC': 'BCHABC',
                'BDG': 'Badgercoin',
                'BTM': 'Bitmark',
                'CON': 'Coino',
                'ETHTRON': 'ETH',
                'GOLD': 'GoldEagles',
                'GPUC': 'GPU',
                'HOT': 'Hotcoin',
                'ITC': 'Information Coin',
                'KEY': 'KEYCoin',
                'MASK': 'NFTX Hashmasks Index',  # conflict with Mask Network
                'MEME': 'Degenerator Meme',  # Degenerator Meme migrated to Meme Inu, self exchange still has the old price
                'PLX': 'ParallaxCoin',
                'REPV2': 'REP',
                'STR': 'XLM',
                'SOC': 'SOCC',
                'TRADE': 'Unitrade',
                'TRXETH': 'TRX',
                'XAP': 'API Coin',
                # self is not documented in the API docs for Poloniex
                # https://github.com/ccxt/ccxt/issues/7084
                # when the user calls withdraw('USDT', amount, address, tag, params)
                # with params = {'currencyToWithdrawAs': 'USDTTRON'}
                # or params = {'currencyToWithdrawAs': 'USDTETH'}
                # fetchWithdrawals('USDT') returns the corresponding withdrawals
                # with a USDTTRON or a USDTETH currency id, respectfully
                # therefore we have map them back to the original code USDT
                # otherwise the returned withdrawals are filtered out
                'USDTBSC': 'USDT',
                'USDTTRON': 'USDT',
                'USDTETH': 'USDT',
                'UST': 'USTC',
            },
            'options': {
                'createMarketBuyOrderRequiresPrice': True,
                'networks': {
                    'BEP20': 'BSC',
                    'ERC20': 'ETH',
                    'TRC20': 'TRON',
                },
                'limits': {
                    'cost': {
                        'min': {
                            'BTC': 0.0001,
                            'ETH': 0.0001,
                            'USDT': 1.0,
                            'TRX': 100,
                            'BNB': 0.06,
                            'USDC': 1.0,
                            'USDJ': 1.0,
                            'TUSD': 0.0001,
                            'DAI': 1.0,
                            'PAX': 1.0,
                            'BUSD': 1.0,
                        },
                    },
                },
                'accountsByType': {
                    'spot': 'spot',
                    'future': 'futures',
                },
                'accountsById': {
                    'exchange': 'spot',
                    'futures': 'future',
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                'exact': {
                    # General
                    '500': ExchangeNotAvailable,  # Internal System Error
                    '603': RequestTimeout,  # Internal Request Timeout
                    '601': BadRequest,  # Invalid Parameter
                    '415': ExchangeError,  # System Error
                    '602': ArgumentsRequired,  # Missing Required Parameters
                    # Accounts
                    '21604': BadRequest,  # Invalid UserId
                    '21600': AuthenticationError,  # Account Not Found
                    '21605': AuthenticationError,  # Invalid Account Type
                    '21102': ExchangeError,  # Invalid Currency
                    '21100': AuthenticationError,  # Invalid account
                    '21704': AuthenticationError,  # Missing UserId and/or AccountId
                    '21700': BadRequest,  # Error updating accounts
                    '21705': BadRequest,  # Invalid currency type
                    '21707': ExchangeError,  # Internal accounts Error
                    '21708': BadRequest,  # Currency not available to User
                    '21601': AccountSuspended,  # Account locked. Contact support
                    '21711': ExchangeError,  # Currency locked. Contact support
                    '21709': InsufficientFunds,  # Insufficient balance
                    '250000': ExchangeError,  # Transfer error. Try again later
                    '250001': BadRequest,  # Invalid toAccount for transfer
                    '250002': BadRequest,  # Invalid fromAccount for transfer
                    '250003': BadRequest,  # Invalid transfer amount
                    '250004': BadRequest,  # Transfer is not supported
                    '250005': InsufficientFunds,  # Insufficient transfer balance
                    '250008': BadRequest,  # Invalid transfer currency
                    '250012': ExchangeError,  # Futures account is not valid
                    # Trading
                    '21110': BadRequest,  # Invalid quote currency
                    '10040': BadSymbol,  # Invalid symbol
                    '10060': ExchangeError,  # Symbol setup error
                    '10020': BadSymbol,  # Invalid currency
                    '10041': BadSymbol,  # Symbol frozen for trading
                    '21340': OnMaintenance,  # No order creation/cancelation is allowed is in Maintenane Mode
                    '21341': InvalidOrder,  # Post-only orders type allowed is in Post Only Mode
                    '21342': InvalidOrder,  # Price is higher than highest bid is in Maintenance Mode
                    '21343': InvalidOrder,  # Price is lower than lowest bid is in Maintenance Mode
                    '21351': AccountSuspended,  # Trading for self account is frozen. Contact support
                    '21352': BadSymbol,  # Trading for self currency is frozen
                    '21353': PermissionDenied,  # Trading for US customers is not supported
                    '21354': PermissionDenied,  # Account needs to be verified via email before trading is enabled. Contact support
                    '21359': OrderNotFound,  # {"code" : 21359, "message" : "Order was already canceled or filled."}
                    '21360': InvalidOrder,  # {"code" : 21360, "message" : "Order size exceeds the limit.Please enter a smaller amount and try again."}
                    '24106': BadRequest,  # Invalid market depth
                    '24201': ExchangeNotAvailable,  # Service busy. Try again later
                    # Orders
                    '21301': OrderNotFound,  # Order not found
                    '21302': ExchangeError,  # Batch cancel order error
                    '21304': ExchangeError,  # Order is filled
                    '21305': OrderNotFound,  # Order is canceled
                    '21307': ExchangeError,  # Error during Order Cancelation
                    '21309': InvalidOrder,  # Order price must be greater than 0
                    '21310': InvalidOrder,  # Order price must be less than max price
                    '21311': InvalidOrder,  # Order price must be greater than min price
                    '21312': InvalidOrder,  # Client orderId already exists
                    '21314': InvalidOrder,  # Max limit of open orders(2000) exceeded
                    '21315': InvalidOrder,  # Client orderId exceeded max length of 17 digits
                    '21317': InvalidOrder,  # Amount must be greater than 0
                    '21319': InvalidOrder,  # Invalid order side
                    '21320': InvalidOrder,  # Invalid order type
                    '21321': InvalidOrder,  # Invalid timeInForce value
                    '21322': InvalidOrder,  # Amount is less than minAmount trade limit
                    '21324': BadRequest,  # Invalid account type
                    '21327': InvalidOrder,  # Order pice must be greater than 0
                    '21328': InvalidOrder,  # Order quantity must be greater than 0
                    '21330': InvalidOrder,  # Quantity is less than minQuantity trade limit
                    '21335': InvalidOrder,  # Invalid priceScale for self symbol
                    '21336': InvalidOrder,  # Invalid quantityScale for self symbol
                    '21337': InvalidOrder,  # Invalid amountScale for self symbol
                    '21344': InvalidOrder,  # Value of limit param is greater than max value of 100
                    '21345': InvalidOrder,  # Value of limit param value must be greater than 0
                    '21346': InvalidOrder,  # Order Id must be of type Long
                    '21348': InvalidOrder,  # Order type must be LIMIT_MAKER
                    '21347': InvalidOrder,  # Stop price must be greater than 0
                    '21349': InvalidOrder,  # Order value is too large
                    '21350': InvalidOrder,  # Amount must be greater than 1 USDT
                    '21355': ExchangeError,  # Interval between startTime and endTime in trade/order history has exceeded 7 day limit
                    '21356': BadRequest,  # Order size would cause too much price movement. Reduce order size.
                    '24101': BadSymbol,  # Invalid symbol
                    '24102': InvalidOrder,  # Invalid K-line type
                    '24103': InvalidOrder,  # Invalid endTime
                    '24104': InvalidOrder,  # Invalid amount
                    '24105': InvalidOrder,  # Invalid startTime
                    '25020': InvalidOrder,  # No active kill switch
                    # Smartorders
                    '25000': InvalidOrder,  # Invalid userId
                    '25001': InvalidOrder,  # Invalid parameter
                    '25002': InvalidOrder,  # Invalid userId.
                    '25003': ExchangeError,  # Unable to place order
                    '25004': InvalidOrder,  # Client orderId already exists
                    '25005': ExchangeError,  # Unable to place smart order
                    '25006': InvalidOrder,  # OrderId and clientOrderId already exists
                    '25007': InvalidOrder,  # Invalid orderid
                    '25008': InvalidOrder,  # Both orderId and clientOrderId are required
                    '25009': ExchangeError,  # Failed to cancel order
                    '25010': PermissionDenied,  # Unauthorized to cancel order
                    '25011': InvalidOrder,  # Failed to cancel due to invalid paramters
                    '25012': ExchangeError,  # Failed to cancel
                    '25013': OrderNotFound,  # Failed to cancel were not found
                    '25014': OrderNotFound,  # Failed to cancel were not found
                    '25015': OrderNotFound,  # Failed to cancel orders exist
                    '25016': ExchangeError,  # Failed to cancel to release funds
                    '25017': ExchangeError,  # No orders were canceled
                    '25018': BadRequest,  # Invalid accountType
                    '25019': BadSymbol,  # Invalid symbol
                },
                'broad': {
                },
            },
        })

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     [
        #         [
        #             "22814.01",
        #             "22937.42",
        #             "22832.57",
        #             "22937.42",
        #             "3916.58764051",
        #             "0.171199",
        #             "2982.64647063",
        #             "0.130295",
        #             33,
        #             0,
        #             "22877.449915304470460711",
        #             "MINUTE_5",
        #             1659664800000,
        #             1659665099999
        #         ]
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 12),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 0),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 5),
        ]

    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://docs.poloniex.com/#public-endpoints-market-data-candles
        :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
        :param int [params.until]: timestamp in ms
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchOHLCV', 'paginate', False)
        if paginate:
            return await self.fetch_paginated_call_deterministic('fetchOHLCV', symbol, since, limit, timeframe, params, 500)
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            'interval': self.safe_string(self.timeframes, timeframe, timeframe),
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            # limit should in between 100 and 500
            request['limit'] = limit
        request, params = self.handle_until_option('endTime', request, params)
        response = await self.publicGetMarketsSymbolCandles(self.extend(request, params))
        #
        #     [
        #         [
        #             "22814.01",
        #             "22937.42",
        #             "22832.57",
        #             "22937.42",
        #             "3916.58764051",
        #             "0.171199",
        #             "2982.64647063",
        #             "0.130295",
        #             33,
        #             0,
        #             "22877.449915304470460711",
        #             "MINUTE_5",
        #             1659664800000,
        #             1659665099999
        #         ]
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    async def load_markets(self, reload=False, params={}):
        markets = await super(poloniex, self).load_markets(reload, params)
        currenciesByNumericId = self.safe_value(self.options, 'currenciesByNumericId')
        if (currenciesByNumericId is None) or reload:
            self.options['currenciesByNumericId'] = self.index_by(self.currencies, 'numericId')
        return markets

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for poloniex
        :see: https://docs.poloniex.com/#public-endpoints-reference-data-symbol-information
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        markets = await self.publicGetMarkets(params)
        #
        #     [
        #         {
        #             "symbol" : "BTS_BTC",
        #             "baseCurrencyName" : "BTS",
        #             "quoteCurrencyName" : "BTC",
        #             "displayName" : "BTS/BTC",
        #             "state" : "NORMAL",
        #             "visibleStartTime" : 1659018816626,
        #             "tradableStartTime" : 1659018816626,
        #             "symbolTradeLimit" : {
        #                 "symbol" : "BTS_BTC",
        #                 "priceScale" : 10,
        #                 "quantityScale" : 0,
        #                 "amountScale" : 8,
        #                 "minQuantity" : "100",
        #                 "minAmount" : "0.00001",
        #                 "highestBid" : "0",
        #                 "lowestAsk" : "0"
        #             }
        #         }
        #     ]
        #
        return self.parse_markets(markets)

    def parse_market(self, market: dict) -> Market:
        id = self.safe_string(market, 'symbol')
        baseId = self.safe_string(market, 'baseCurrencyName')
        quoteId = self.safe_string(market, 'quoteCurrencyName')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        state = self.safe_string(market, 'state')
        active = state == 'NORMAL'
        symbolTradeLimit = self.safe_value(market, 'symbolTradeLimit')
        # these are known defaults
        return {
            'id': id,
            'symbol': base + '/' + quote,
            'base': base,
            'quote': quote,
            'settle': None,
            'baseId': baseId,
            'quoteId': quoteId,
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'active': active,
            '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(symbolTradeLimit, 'quantityScale'))),
                'price': self.parse_number(self.parse_precision(self.safe_string(symbolTradeLimit, 'priceScale'))),
            },
            'limits': {
                'amount': {
                    'min': self.safe_number(symbolTradeLimit, 'minQuantity'),
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': self.safe_number(symbolTradeLimit, 'minAmount'),
                    'max': None,
                },
            },
            'created': self.safe_integer(market, 'tradableStartTime'),
            'info': market,
        }

    async def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :see: https://docs.poloniex.com/#public-endpoints-reference-data-system-timestamp
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = await self.publicGetTimestamp(params)
        return self.safe_integer(response, 'serverTime')

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "symbol" : "BTC_USDT",
        #         "open" : "26053.33",
        #         "low" : "26053.33",
        #         "high" : "26798.02",
        #         "close" : "26447.58",
        #         "quantity" : "6116.210188",
        #         "amount" : "161082122.88450926",
        #         "tradeCount" : "134709",
        #         "startTime" : "1692784440000",
        #         "closeTime" : "1692870839630",
        #         "displayName" : "BTC/USDT",
        #         "dailyChange" : "0.0151",
        #         "bid" : "26447.57",
        #         "bidQuantity" : "0.016313",
        #         "ask" : "26447.58",
        #         "askQuantity" : "0.068307",
        #         "ts" : "1692870845446",
        #         "markPrice" : "26444.11"
        #     }
        #
        timestamp = self.safe_integer(ticker, 'ts')
        marketId = self.safe_string(ticker, 'symbol')
        market = self.safe_market(marketId)
        close = self.safe_string(ticker, 'close')
        relativeChange = self.safe_string(ticker, 'dailyChange')
        percentage = Precise.string_mul(relativeChange, '100')
        bidVolume = self.safe_string(ticker, 'bidQuantity')
        askVolume = self.safe_string(ticker, 'askQuantity')
        return self.safe_ticker({
            'id': marketId,
            'symbol': market['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': bidVolume,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': askVolume,
            'vwap': None,
            'open': self.safe_string(ticker, 'open'),
            'close': close,
            'last': close,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'quantity'),
            'quoteVolume': self.safe_string(ticker, 'amount'),
            'info': 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://docs.poloniex.com/#public-endpoints-market-data-ticker
        :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.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        response = await self.publicGetMarketsTicker24h(params)
        #
        #     [
        #         {
        #              "symbol" : "BTC_USDT",
        #              "open" : "26053.33",
        #              "low" : "26053.33",
        #              "high" : "26798.02",
        #              "close" : "26447.58",
        #              "quantity" : "6116.210188",
        #              "amount" : "161082122.88450926",
        #              "tradeCount" : "134709",
        #              "startTime" : "1692784440000",
        #              "closeTime" : "1692870839630",
        #              "displayName" : "BTC/USDT",
        #              "dailyChange" : "0.0151",
        #              "bid" : "26447.57",
        #              "bidQuantity" : "0.016313",
        #              "ask" : "26447.58",
        #              "askQuantity" : "0.068307",
        #              "ts" : "1692870845446",
        #              "markPrice" : "26444.11"
        #         }
        #     ]
        #
        return self.parse_tickers(response, symbols)

    async def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://docs.poloniex.com/#public-endpoints-reference-data-currency-information
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = await self.publicGetCurrencies(self.extend(params, {'includeMultiChainCurrencies': True}))
        #
        #     [
        #         {
        #             "1CR": {
        #                 "id": 1,
        #                 "name": "1CRedit",
        #                 "description": "BTC Clone",
        #                 "type": "address",
        #                 "withdrawalFee": "0.01000000",
        #                 "minConf": 10000,
        #                 "depositAddress": null,
        #                 "blockchain": "1CR",
        #                 "delisted": False,
        #                 "tradingState": "NORMAL",
        #                 "walletState": "DISABLED",
        #                 "walletDepositState": "DISABLED",
        #                 "walletWithdrawalState": "DISABLED",
        #                 "parentChain": null,
        #                 "isMultiChain": False,
        #                 "isChildChain": False,
        #                 "childChains": []
        #             }
        #         }
        #     ]
        #
        result: dict = {}
        for i in range(0, len(response)):
            item = self.safe_value(response, i)
            ids = list(item.keys())
            id = self.safe_value(ids, 0)
            currency = self.safe_value(item, id)
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'name')
            networkId = self.safe_string(currency, 'blockchain')
            networkCode = None
            if networkId is not None:
                networkCode = self.network_id_to_code(networkId, code)
            delisted = self.safe_value(currency, 'delisted')
            walletEnabled = self.safe_string(currency, 'walletState') == 'ENABLED'
            depositEnabled = self.safe_string(currency, 'walletDepositState') == 'ENABLED'
            withdrawEnabled = self.safe_string(currency, 'walletWithdrawalState') == 'ENABLED'
            active = not delisted and walletEnabled and depositEnabled and withdrawEnabled
            numericId = self.safe_integer(currency, 'id')
            feeString = self.safe_string(currency, 'withdrawalFee')
            parentChain = self.safe_value(currency, 'parentChain')
            noParentChain = parentChain is None
            if self.safe_value(result, code) is None:
                result[code] = {
                    'id': id,
                    'code': code,
                    'info': None,
                    'name': name,
                    'active': active,
                    'deposit': depositEnabled,
                    'withdraw': withdrawEnabled,
                    'fee': self.parse_number(feeString),
                    'precision': None,
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'deposit': {
                            'min': None,
                            'max': None,
                        },
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
            minFeeString = self.safe_string(result[code], 'fee')
            if feeString is not None:
                minFeeString = feeString if (minFeeString is None) else Precise.string_min(feeString, minFeeString)
            depositAvailable = self.safe_value(result[code], 'deposit')
            depositAvailable = depositEnabled if (depositEnabled) else depositAvailable
            withdrawAvailable = self.safe_value(result[code], 'withdraw')
            withdrawAvailable = withdrawEnabled if (withdrawEnabled) else withdrawAvailable
            networks = self.safe_value(result[code], 'networks', {})
            if networkCode is not None:
                networks[networkCode] = {
                    'info': currency,
                    'id': networkId,
                    'network': networkCode,
                    'currencyId': id,
                    'numericId': numericId,
                    'deposit': depositEnabled,
                    'withdraw': withdrawEnabled,
                    'active': active,
                    'fee': self.parse_number(feeString),
                    'precision': None,
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                        'deposit': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
            result[code]['networks'] = networks
            info = self.safe_value(result[code], 'info', [])
            rawInfo: dict = {}
            rawInfo[id] = currency
            info.append(rawInfo)
            result[code]['info'] = info
            if noParentChain:
                result[code]['id'] = id
                result[code]['name'] = name
            result[code]['active'] = depositAvailable and withdrawAvailable
            result[code]['deposit'] = depositAvailable
            result[code]['withdraw'] = withdrawAvailable
            result[code]['fee'] = self.parse_number(minFeeString)
        return result

    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://docs.poloniex.com/#public-endpoints-market-data-ticker
        :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.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        response = await self.publicGetMarketsSymbolTicker24h(self.extend(request, params))
        #
        #     {
        #         "symbol" : "BTC_USDT",
        #         "open" : "26053.33",
        #         "low" : "26053.33",
        #         "high" : "26798.02",
        #         "close" : "26447.58",
        #         "quantity" : "6116.210188",
        #         "amount" : "161082122.88450926",
        #         "tradeCount" : "134709",
        #         "startTime" : "1692784440000",
        #         "closeTime" : "1692870839630",
        #         "displayName" : "BTC/USDT",
        #         "dailyChange" : "0.0151",
        #         "bid" : "26447.57",
        #         "bidQuantity" : "0.016313",
        #         "ask" : "26447.58",
        #         "askQuantity" : "0.068307",
        #         "ts" : "1692870845446",
        #         "markPrice" : "26444.11"
        #     }
        #
        return self.parse_ticker(response, market)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades
        #
        #     {
        #         "id" : "60014521",
        #         "price" : "23162.94",
        #         "quantity" : "0.00009",
        #         "amount" : "2.0846646",
        #         "takerSide" : "SELL",
        #         "ts" : 1659684602042,
        #         "createTime" : 1659684602036
        #     }
        #
        # fetchMyTrades
        #
        #     {
        #         "id": "32164924331503616",
        #         "symbol": "LINK_USDT",
        #         "accountType": "SPOT",
        #         "orderId": "32164923987566592",
        #         "side": "SELL",
        #         "type": "MARKET",
        #         "matchRole": "TAKER",
        #         "createTime": 1648635115525,
        #         "price": "11",
        #         "quantity": "0.5",
        #         "amount": "5.5",
        #         "feeCurrency": "USDT",
        #         "feeAmount": "0.007975",
        #         "pageId": "32164924331503616",
        #         "clientOrderId": "myOwnId-321"
        #     }
        #
        # fetchOrderTrades(taker trades)
        #
        #     {
        #         "id": "30341456333942784",
        #         "symbol": "LINK_USDT",
        #         "accountType": "SPOT",
        #         "orderId": "30249408733945856",
        #         "side": "BUY",
        #         "type": "LIMIT",
        #         "matchRole": "MAKER",
        #         "createTime": 1648200366864,
        #         "price": "3.1",
        #         "quantity": "1",
        #         "amount": "3.1",
        #         "feeCurrency": "LINK",
        #         "feeAmount": "0.00145",
        #         "pageId": "30341456333942784",
        #         "clientOrderId": ""
        #     }
        #
        #
        id = self.safe_string_2(trade, 'id', 'tradeID')
        orderId = self.safe_string(trade, 'orderId')
        timestamp = self.safe_integer_2(trade, 'ts', 'createTime')
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        side = self.safe_string_lower_2(trade, 'side', 'takerSide')
        fee = None
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'quantity')
        costString = self.safe_string(trade, 'amount')
        feeCurrencyId = self.safe_string(trade, 'feeCurrency')
        feeCostString = self.safe_string(trade, 'feeAmount')
        if feeCostString is not None:
            feeCurrencyCode = self.safe_currency_code(feeCurrencyId)
            fee = {
                'cost': feeCostString,
                'currency': feeCurrencyCode,
            }
        return self.safe_trade({
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': self.safe_string_lower(trade, 'type'),
            'side': side,
            'takerOrMaker': self.safe_string_lower(trade, 'matchRole'),
            '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://docs.poloniex.com/#public-endpoints-market-data-trades
        :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.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit
        trades = await self.publicGetMarketsSymbolTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id" : "60014521",
        #             "price" : "23162.94",
        #             "quantity" : "0.00009",
        #             "amount" : "2.0846646",
        #             "takerSide" : "SELL",
        #             "ts" : 1659684602042,
        #             "createTime" : 1659684602036
        #         }
        #     ]
        #
        return self.parse_trades(trades, market, since, limit)

    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://docs.poloniex.com/#authenticated-endpoints-trades-trade-history
        :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
        :param int [params.until]: the latest time in ms to fetch entries for
        :param boolean [params.paginate]: default False, when True will automatically paginate by calling self endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-params)
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        paginate = False
        paginate, params = self.handle_option_and_params(params, 'fetchMyTrades', 'paginate')
        if paginate:
            return await self.fetch_paginated_call_dynamic('fetchMyTrades', symbol, since, limit, params)
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
        request: dict = {
            # 'from': 12345678,  # A 'trade Id'. The query begins at ‘from'.
            # 'direction': 'PRE',  # PRE, NEXT The direction before or after ‘from'.
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        request, params = self.handle_until_option('endTime', request, params)
        response = await self.privateGetTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "32164924331503616",
        #             "symbol": "LINK_USDT",
        #             "accountType": "SPOT",
        #             "orderId": "32164923987566592",
        #             "side": "SELL",
        #             "type": "MARKET",
        #             "matchRole": "TAKER",
        #             "createTime": 1648635115525,
        #             "price": "11",
        #             "quantity": "0.5",
        #             "amount": "5.5",
        #             "feeCurrency": "USDT",
        #             "feeAmount": "0.007975",
        #             "pageId": "32164924331503616",
        #             "clientOrderId": "myOwnId-321"
        #         }
        #     ]
        #
        result = self.parse_trades(response, market, since, limit)
        return result

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'NEW': 'open',
            'PARTIALLY_FILLED': 'open',
            'FILLED': 'closed',
            'PENDING_CANCEL': 'canceled',
            'PARTIALLY_CANCELED': 'canceled',
            'CANCELED': 'canceled',
            'FAILED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # fetchOpenOrder
        #
        #     {
        #         "id" : "7xxxxxxxxxxxxxxx6",
        #         "clientOrderId" : "",
        #         "symbol" : "ETH_USDT",
        #         "state" : "NEW",
        #         "accountType" : "SPOT",
        #         "side" : "BUY",
        #         "type" : "LIMIT",
        #         "timeInForce" : "GTC",
        #         "quantity" : "0.001",
        #         "price" : "1600",
        #         "avgPrice" : "0",
        #         "amount" : "0",
        #         "filledQuantity" : "0",
        #         "filledAmount" : "0",
        #         "createTime" : 16xxxxxxxxx26,
        #         "updateTime" : 16xxxxxxxxx36
        #     }
        #
        # fetchOpenOrders
        #
        #     {
        #         "id": "24993088082542592",
        #         "clientOrderId": "",
        #         "symbol": "ELON_USDC",
        #         "state": "NEW",
        #         "accountType": "SPOT",
        #         "side": "SELL",
        #         "type": "MARKET",
        #         "timeInForce": "GTC",
        #         "quantity": "1.00",
        #         "price": "0.00",
        #         "avgPrice": "0.00",
        #         "amount": "0.00",
        #         "filledQuantity": "0.00",
        #         "filledAmount": "0.00",
        #         "createTime": 1646925216548,
        #         "updateTime": 1646925216548
        #     }
        #
        # createOrder, editOrder
        #
        #     {
        #         "id": "29772698821328896",
        #         "clientOrderId": "1234Abc"
        #     }
        #
        timestamp = self.safe_integer_2(order, 'timestamp', 'createTime')
        if timestamp is None:
            timestamp = self.parse8601(self.safe_string(order, 'date'))
        marketId = self.safe_string(order, 'symbol')
        market = self.safe_market(marketId, market, '_')
        symbol = market['symbol']
        resultingTrades = self.safe_value(order, 'resultingTrades')
        if not isinstance(resultingTrades, list):
            resultingTrades = self.safe_value(resultingTrades, self.safe_string(market, 'id', marketId))
        price = self.safe_string_2(order, 'price', 'rate')
        amount = self.safe_string(order, 'quantity')
        filled = self.safe_string(order, 'filledQuantity')
        status = self.parse_order_status(self.safe_string(order, 'state'))
        side = self.safe_string_lower(order, 'side')
        rawType = self.safe_string(order, 'type')
        type = self.parse_order_type(rawType)
        id = self.safe_string_n(order, ['orderNumber', 'id', 'orderId'])
        fee = None
        feeCurrency = self.safe_string(order, 'tokenFeeCurrency')
        feeCost: Str = None
        feeCurrencyCode: Str = None
        rate = self.safe_string(order, 'fee')
        if feeCurrency is None:
            feeCurrencyCode = market['base'] if (side == 'buy') else market['quote']
        else:
            # poloniex accepts a 30% discount to pay fees in TRX
            feeCurrencyCode = self.safe_currency_code(feeCurrency)
            feeCost = self.safe_string(order, 'tokenFee')
        if feeCost is not None:
            fee = {
                'rate': rate,
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        clientOrderId = self.safe_string(order, 'clientOrderId')
        triggerPrice = self.safe_string_2(order, 'triggerPrice', 'stopPrice')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': self.safe_integer(order, 'updateTime'),
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': self.safe_string(order, 'timeInForce'),
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': triggerPrice,
            'triggerPrice': triggerPrice,
            'cost': None,
            'average': self.safe_string(order, 'avgPrice'),
            'amount': amount,
            'filled': filled,
            'remaining': None,
            'trades': resultingTrades,
            'fee': fee,
        }, market)

    def parse_order_type(self, status):
        statuses: dict = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'STOP-LIMIT': 'limit',
            'STOP-MARKET': 'market',
        }
        return self.safe_string(statuses, status, status)

    def parse_open_orders(self, orders, market, result):
        for i in range(0, len(orders)):
            order = orders[i]
            extended = self.extend(order, {
                'status': 'open',
                'type': 'limit',
                'side': order['type'],
                'price': order['rate'],
            })
            result.append(self.parse_order(extended, market))
        return result

    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://docs.poloniex.com/#authenticated-endpoints-orders-open-orders
        :see: https://docs.poloniex.com/#authenticated-endpoints-smart-orders-open-orders  # trigger orders
        :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
        :param boolean [params.stop]: set True to fetch trigger orders instead of regular orders
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market: Market = None
        request: dict = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        response = None
        if isTrigger:
            response = await self.privateGetSmartorders(self.extend(request, params))
        else:
            response = await self.privateGetOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "id" : "7xxxxxxxxxxxxxxx6",
        #             "clientOrderId" : "",
        #             "symbol" : "ETH_USDT",
        #             "state" : "NEW",
        #             "accountType" : "SPOT",
        #             "side" : "BUY",
        #             "type" : "LIMIT",
        #             "timeInForce" : "GTC",
        #             "quantity" : "0.001",
        #             "price" : "1600",
        #             "avgPrice" : "0",
        #             "amount" : "0",
        #             "filledQuantity" : "0",
        #             "filledAmount" : "0",
        #             "stopPrice": "3750.00",              # for trigger orders
        #             "createTime" : 16xxxxxxxxx26,
        #             "updateTime" : 16xxxxxxxxx36
        #         }
        #     ]
        #
        extension: dict = {'status': 'open'}
        return self.parse_orders(response, market, since, limit, extension)

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://docs.poloniex.com/#authenticated-endpoints-orders-create-order
        :see: https://docs.poloniex.com/#authenticated-endpoints-smart-orders-create-order  # trigger orders
        :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 fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: *spot only* The price at which a trigger order is triggered at
        :param float [params.cost]: *spot market buy only* the quote quantity that can be used alternative for the amount
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' createOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request: dict = {
            'symbol': market['id'],
            'side': side,
            # 'timeInForce': timeInForce,
            # 'accountType': 'SPOT',
            # 'amount': amount,
        }
        triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
        request, params = self.order_request(symbol, type, side, amount, request, price, params)
        response = None
        if triggerPrice is not None:
            response = await self.privatePostSmartorders(self.extend(request, params))
        else:
            response = await self.privatePostOrders(self.extend(request, params))
        #
        #     {
        #         "id" : "78923648051920896",
        #         "clientOrderId" : ""
        #     }
        #
        response = self.extend(response, {
            'type': side,
        })
        return self.parse_order(response, market)

    def order_request(self, symbol, type, side, amount, request, price=None, params={}):
        upperCaseType = type.upper()
        isMarket = upperCaseType == 'MARKET'
        isPostOnly = self.is_post_only(isMarket, upperCaseType == 'LIMIT_MAKER', params)
        triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
        params = self.omit(params, ['postOnly', 'triggerPrice', 'stopPrice'])
        if triggerPrice is not None:
            upperCaseType = 'STOP' if (price is None) else 'STOP_LIMIT'
            request['stopPrice'] = triggerPrice
        elif isPostOnly:
            upperCaseType = 'LIMIT_MAKER'
        request['type'] = upperCaseType
        if isMarket:
            if side == 'buy':
                quoteAmount = None
                createMarketBuyOrderRequiresPrice = True
                createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
                cost = self.safe_number(params, 'cost')
                params = self.omit(params, 'cost')
                if cost is not None:
                    quoteAmount = self.cost_to_precision(symbol, cost)
                elif createMarketBuyOrderRequiresPrice:
                    if price is None:
                        raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
                    else:
                        amountString = self.number_to_string(amount)
                        priceString = self.number_to_string(price)
                        costRequest = Precise.string_mul(amountString, priceString)
                        quoteAmount = self.cost_to_precision(symbol, costRequest)
                else:
                    quoteAmount = self.cost_to_precision(symbol, amount)
                request['amount'] = quoteAmount
            else:
                request['quantity'] = self.amount_to_precision(symbol, amount)
        else:
            request['quantity'] = self.amount_to_precision(symbol, amount)
            request['price'] = self.price_to_precision(symbol, price)
        clientOrderId = self.safe_string(params, 'clientOrderId')
        if clientOrderId is not None:
            request['clientOrderId'] = clientOrderId
            params = self.omit(params, 'clientOrderId')
        # remember the timestamp before issuing the request
        return [request, params]

    async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
        """
        edit a trade order
        :see: https://docs.poloniex.com/#authenticated-endpoints-orders-cancel-replace-order
        :see: https://docs.poloniex.com/#authenticated-endpoints-smart-orders-cancel-replace-order
        :param str id: order id
        :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 the currency you want to trade in units of the base currency
        :param float [price]: the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: The price at which a trigger order is triggered at
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        if not market['spot']:
            raise NotSupported(self.id + ' editOrder() does not support ' + market['type'] + ' orders, only spot orders are accepted')
        request: dict = {
            'id': id,
            # 'timeInForce': timeInForce,
        }
        triggerPrice = self.safe_number_2(params, 'stopPrice', 'triggerPrice')
        request, params = self.order_request(symbol, type, side, amount, request, price, params)
        response = None
        if triggerPrice is not None:
            response = await self.privatePutSmartordersId(self.extend(request, params))
        else:
            response = await self.privatePutOrdersId(self.extend(request, params))
        #
        #     {
        #         "id" : "78923648051920896",
        #         "clientOrderId" : ""
        #     }
        #
        response = self.extend(response, {
            'type': side,
        })
        return self.parse_order(response, market)

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        #
        # @method
        # @name poloniex#cancelOrder
        # @description cancels an open order
        # @see https://docs.poloniex.com/#authenticated-endpoints-orders-cancel-order-by-id
        # @see https://docs.poloniex.com/#authenticated-endpoints-smart-orders-cancel-order-by-id  # trigger orders
        # @param {string} id order id
        # @param {string} symbol unified symbol of the market the order was made in
        # @param {object} [params] extra parameters specific to the exchange API endpoint
        # @param {boolean} [params.trigger] True if canceling a trigger order
        # @returns {object} An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        #
        await self.load_markets()
        request: dict = {}
        clientOrderId = self.safe_value(params, 'clientOrderId')
        if clientOrderId is not None:
            id = clientOrderId
        request['id'] = id
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['clientOrderId', 'trigger', 'stop'])
        response = None
        if isTrigger:
            response = await self.privateDeleteSmartordersId(self.extend(request, params))
        else:
            response = await self.privateDeleteOrdersId(self.extend(request, params))
        #
        #   {
        #       "orderId":"210832697138888704",
        #       "clientOrderId":"",
        #       "state":"PENDING_CANCEL",
        #       "code":200,
        #       "message":""
        #   }
        #
        return self.parse_order(response)

    async def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders
        :see: https://docs.poloniex.com/#authenticated-endpoints-orders-cancel-all-orders
        :see: https://docs.poloniex.com/#authenticated-endpoints-smart-orders-cancel-all-orders  # trigger orders
        :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
        :param boolean [params.trigger]: True if canceling trigger orders
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        request: dict = {
            # 'accountTypes': 'SPOT',
            'symbols': [],
        }
        market: Market = None
        if symbol is not None:
            market = self.market(symbol)
            request['symbols'] = [
                market['id'],
            ]
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        response = None
        if isTrigger:
            response = await self.privateDeleteSmartorders(self.extend(request, params))
        else:
            response = await self.privateDeleteOrders(self.extend(request, params))
        #
        #     [
        #         {
        #             "orderId" : "78xxxxxxxx80",
        #             "clientOrderId" : "",
        #             "state" : "NEW",
        #             "code" : 200,
        #             "message" : ""
        #         }, {
        #             "orderId" : "78xxxxxxxxx80",
        #             "clientOrderId" : "",
        #             "state" : "NEW",
        #             "code" : 200,
        #             "message" : ""
        #         }
        #     ]
        #
        return self.parse_orders(response, market)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetch an order by it's id
        :see: https://docs.poloniex.com/#authenticated-endpoints-orders-order-details
        :see: https://docs.poloniex.com/#authenticated-endpoints-smart-orders-open-orders  # trigger orders
        :param str id: order id
        :param str symbol: unified market symbol, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param boolean [params.trigger]: True if fetching a trigger order
        :returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        id = str(id)
        request: dict = {
            'id': id,
        }
        isTrigger = self.safe_value_2(params, 'trigger', 'stop')
        params = self.omit(params, ['trigger', 'stop'])
        response = None
        if isTrigger:
            response = await self.privateGetSmartordersId(self.extend(request, params))
            response = self.safe_value(response, 0)
        else:
            response = await self.privateGetOrdersId(self.extend(request, params))
        #
        #     {
        #         "id": "21934611974062080",
        #         "clientOrderId": "123",
        #         "symbol": "TRX_USDC",
        #         "state": "NEW",
        #         "accountType": "SPOT",
        #         "side": "SELL",
        #         "type": "LIMIT",
        #         "timeInForce": "GTC",
        #         "quantity": "1.00",
        #         "price": "10.00",
        #         "avgPrice": "0.00",
        #         "amount": "0.00",
        #         "filledQuantity": "0.00",
        #         "filledAmount": "0.00",
        #         "stopPrice": "3750.00",              # for trigger orders
        #         "createTime": 1646196019020,
        #         "updateTime": 1646196019020
        #     }
        #
        order = self.parse_order(response)
        order['id'] = id
        return order

    async def fetch_order_status(self, id: str, symbol: Str = None, params={}):
        await self.load_markets()
        orders = await self.fetch_open_orders(symbol, None, None, params)
        indexed = self.index_by(orders, 'id')
        return 'open' if (id in indexed) else 'closed'

    async def fetch_order_trades(self, id: str, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all the trades made from a single order
        :see: https://docs.poloniex.com/#authenticated-endpoints-trades-trades-by-order-id
        :param str id: order id
        :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 to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        await self.load_markets()
        request: dict = {
            'id': id,
        }
        trades = await self.privateGetOrdersIdTrades(self.extend(request, params))
        #
        #     [
        #         {
        #             "id": "30341456333942784",
        #             "symbol": "LINK_USDT",
        #             "accountType": "SPOT",
        #             "orderId": "30249408733945856",
        #             "side": "BUY",
        #             "type": "LIMIT",
        #             "matchRole": "MAKER",
        #             "createTime": 1648200366864,
        #             "price": "3.1",
        #             "quantity": "1",
        #             "amount": "3.1",
        #             "feeCurrency": "LINK",
        #             "feeAmount": "0.00145",
        #             "pageId": "30341456333942784",
        #             "clientOrderId": ""
        #         }
        #     ]
        #
        return self.parse_trades(trades)

    def parse_balance(self, response) -> Balances:
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        for i in range(0, len(response)):
            account = self.safe_value(response, i, {})
            balances = self.safe_value(account, 'balances')
            for j in range(0, len(balances)):
                balance = self.safe_value(balances, j)
                currencyId = self.safe_string(balance, 'currency')
                code = self.safe_currency_code(currencyId)
                newAccount = self.account()
                newAccount['free'] = self.safe_string(balance, 'available')
                newAccount['used'] = self.safe_string(balance, 'hold')
                result[code] = newAccount
        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://docs.poloniex.com/#authenticated-endpoints-accounts-all-account-balances
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        request: dict = {
            'accountType': 'SPOT',
        }
        response = await self.privateGetAccountsBalances(self.extend(request, params))
        #
        #     [
        #         {
        #             "accountId" : "7xxxxxxxxxx8",
        #             "accountType" : "SPOT",
        #             "balances" : [
        #                 {
        #                     "currencyId" : "214",
        #                     "currency" : "USDT",
        #                     "available" : "2.00",
        #                     "hold" : "0.00"
        #                 }
        #             ]
        #         }
        #     ]
        #
        return self.parse_balance(response)

    async def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets
        :see: https://docs.poloniex.com/#authenticated-endpoints-accounts-fee-info
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt.com/#/?id=fee-structure>` indexed by market symbols
        """
        await self.load_markets()
        response = await self.privateGetFeeinfo(params)
        #
        #     {
        #         "trxDiscount" : False,
        #         "makerRate" : "0.00145",
        #         "takerRate" : "0.00155",
        #         "volume30D" : "0.00"
        #     }
        #
        result: dict = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': self.safe_number(response, 'makerRate'),
                'taker': self.safe_number(response, 'takerRate'),
                'percentage': True,
                'tierBased': True,
            }
        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://docs.poloniex.com/#public-endpoints-market-data-order-book
        :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.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # The default value of limit is 10. Valid limit values are: 5, 10, 20, 50, 100, 150.
        response = await self.publicGetMarketsSymbolOrderBook(self.extend(request, params))
        #
        #     {
        #         "time" : 1659695219507,
        #         "scale" : "-1",
        #         "asks" : ["23139.82", "0.317981", "23140", "0.191091", "23170.06", "0.01", "23200", "0.107758", "23230.55", "0.01", "23247.2", "0.154", "23254", "0.005121", "23263", "0.038", "23285.4", "0.308", "23300", "0.108896"],
        #         "bids" : ["23139.74", "0.432092", "23139.73", "0.198592", "23123.21", "0.000886", "23123.2", "0.308", "23121.4", "0.154", "23105", "0.000789", "23100", "0.078175", "23069.1", "0.026276", "23068.83", "0.001329", "23051", "0.000048"],
        #         "ts" : 1659695219513
        #     }
        #
        timestamp = self.safe_integer(response, 'time')
        asks = self.safe_value(response, 'asks')
        bids = self.safe_value(response, 'bids')
        asksResult = []
        bidsResult = []
        for i in range(0, len(asks)):
            if (i % 2) < 1:
                price = self.safe_number(asks, i)
                amount = self.safe_number(asks, self.sum(i, 1))
                asksResult.append([price, amount])
        for i in range(0, len(bids)):
            if (i % 2) < 1:
                price = self.safe_number(bids, i)
                amount = self.safe_number(bids, self.sum(i, 1))
                bidsResult.append([price, amount])
        return {
            'symbol': market['symbol'],
            'bids': self.sort_by(bidsResult, 0, True),
            'asks': self.sort_by(asksResult, 0),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'nonce': None,
        }

    async def create_deposit_address(self, code: str, params={}):
        """
        create a currency deposit address
        :see: https://docs.poloniex.com/#authenticated-endpoints-wallets-deposit-addresses
        :param str code: unified currency code of the currency for the deposit address
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['currency'] += network  # when network the currency need to be changed to currency+network https://docs.poloniex.com/#withdraw on MultiChain Currencies section
            params = self.omit(params, 'network')
        else:
            if currency['id'] == 'USDT':
                raise ArgumentsRequired(self.id + ' createDepositAddress requires a network parameter for ' + code + '.')
        response = await self.privatePostWalletsAddress(self.extend(request, params))
        #
        #     {
        #         "address" : "0xfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxf"
        #     }
        #
        address = self.safe_string(response, 'address')
        tag: Str = None
        self.check_address(address)
        if currency is not None:
            depositAddress = self.safe_string(currency['info'], 'depositAddress')
            if depositAddress is not None:
                tag = address
                address = depositAddress
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': response,
        }

    async def fetch_deposit_address(self, code: str, params={}):
        """
        fetch the deposit address for a currency associated with self account
        :see: https://docs.poloniex.com/#authenticated-endpoints-wallets-deposit-addresses
        :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.com/#/?id=address-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['currency'] += network  # when network the currency need to be changed to currency+network https://docs.poloniex.com/#withdraw on MultiChain Currencies section
            params = self.omit(params, 'network')
        else:
            if currency['id'] == 'USDT':
                raise ArgumentsRequired(self.id + ' fetchDepositAddress requires a network parameter for ' + code + '.')
        response = await self.privateGetWalletsAddresses(self.extend(request, params))
        #
        #     {
        #         "USDTTRON" : "Txxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxp"
        #     }
        #
        address = self.safe_string(response, request['currency'])
        tag: Str = None
        self.check_address(address)
        if currency is not None:
            depositAddress = self.safe_string(currency['info'], 'depositAddress')
            if depositAddress is not None:
                tag = address
                address = depositAddress
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': network,
            'info': response,
        }

    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://docs.poloniex.com/#authenticated-endpoints-accounts-accounts-transfer
        :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.com/#/?id=transfer-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, fromAccount)
        request: dict = {
            'amount': self.currency_to_precision(code, amount),
            'currency': currency['id'],
            'fromAccount': fromId,
            'toAccount': toId,
        }
        response = await self.privatePostAccountsTransfer(self.extend(request, params))
        #
        #    {
        #        "transferId" : "168041074"
        #    }
        #
        return self.parse_transfer(response, currency)

    def parse_transfer(self, transfer: dict, currency: Currency = None) -> TransferEntry:
        #
        #    {
        #        "transferId" : "168041074"
        #    }
        #
        return {
            'info': transfer,
            'id': self.safe_string(transfer, 'transferId'),
            'timestamp': None,
            'datetime': None,
            'currency': self.safe_string(currency, 'id'),
            'amount': None,
            'fromAccount': None,
            'toAccount': None,
            'status': None,
        }

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://docs.poloniex.com/#authenticated-endpoints-wallets-withdraw-currency
        :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.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
            'amount': amount,
            'address': address,
        }
        if tag is not None:
            request['paymentId'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['currency'] += network  # when network the currency need to be changed to currency+network https://docs.poloniex.com/#withdraw on MultiChain Currencies section
            params = self.omit(params, 'network')
        response = await self.privatePostWalletsWithdraw(self.extend(request, params))
        #
        #     {
        #         "response": "Withdrew 1.00000000 USDT.",
        #         "email2FA": False,
        #         "withdrawalNumber": 13449869
        #     }
        #
        return self.parse_transaction(response, currency)

    async def fetch_transactions_helper(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        await self.load_markets()
        year = 31104000  # 60 * 60 * 24 * 30 * 12 = one year of history, why not
        now = self.seconds()
        start = self.parse_to_int(since / 1000) if (since is not None) else now - 10 * year
        request: dict = {
            'start': start,  # UNIX timestamp, required
            'end': now,  # UNIX timestamp, required
        }
        response = await self.privateGetWalletsActivity(self.extend(request, params))
        #
        #     {
        #         "adjustments":[],
        #         "deposits":[
        #             {
        #                 "currency": "BTC",
        #                 "address": "1MEtiqJWru53FhhHrfJPPvd2tC3TPDVcmW",
        #                 "amount": "0.01063000",
        #                 "confirmations":  1,
        #                 "txid": "952b0e1888d6d491591facc0d37b5ebec540ac1efb241fdbc22bcc20d1822fb6",
        #                 "timestamp":  1507916888,
        #                 "status": "COMPLETE"
        #             },
        #             {
        #                 "currency": "ETH",
        #                 "address": "0x20108ba20b65c04d82909e91df06618107460197",
        #                 "amount": "4.00000000",
        #                 "confirmations": 38,
        #                 "txid": "0x4be260073491fe63935e9e0da42bd71138fdeb803732f41501015a2d46eb479d",
        #                 "timestamp": 1525060430,
        #                 "status": "COMPLETE"
        #             }
        #         ],
        #         "withdrawals":[
        #             {
        #                 "withdrawalNumber":13449869,
        #                 "currency":"USDTTRON",  # not documented in API docs, see commonCurrencies in describe()
        #                 "address":"TXGaqPW23JdRWhsVwS2mRsGsegbdnAd3Rw",
        #                 "amount":"1.00000000",
        #                 "fee":"0.00000000",
        #                 "timestamp":1591573420,
        #                 "status":"COMPLETE: dadf427224b3d44b38a2c13caa4395e4666152556ca0b2f67dbd86a95655150f",
        #                 "ipAddress":"x.x.x.x",
        #                 "canCancel":0,
        #                 "canResendEmail":0,
        #                 "paymentID":null,
        #                 "scope":"crypto"
        #             },
        #             {
        #                 "withdrawalNumber": 8224394,
        #                 "currency": "EMC2",
        #                 "address": "EYEKyCrqTNmVCpdDV8w49XvSKRP9N3EUyF",
        #                 "amount": "63.10796020",
        #                 "fee": "0.01000000",
        #                 "timestamp": 1510819838,
        #                 "status": "COMPLETE: d37354f9d02cb24d98c8c4fc17aa42f475530b5727effdf668ee5a43ce667fd6",
        #                 "ipAddress": "x.x.x.x"
        #             },
        #             {
        #                 "withdrawalNumber": 9290444,
        #                 "currency": "ETH",
        #                 "address": "0x191015ff2e75261d50433fbd05bd57e942336149",
        #                 "amount": "0.15500000",
        #                 "fee": "0.00500000",
        #                 "timestamp": 1514099289,
        #                 "status": "COMPLETE: 0x12d444493b4bca668992021fd9e54b5292b8e71d9927af1f076f554e4bea5b2d",
        #                 "ipAddress": "x.x.x.x"
        #             },
        #             {
        #                 "withdrawalNumber": 11518260,
        #                 "currency": "BTC",
        #                 "address": "8JoDXAmE1GY2LRK8jD1gmAmgRPq54kXJ4t",
        #                 "amount": "0.20000000",
        #                 "fee": "0.00050000",
        #                 "timestamp": 1527918155,
        #                 "status": "COMPLETE: 1864f4ebb277d90b0b1ff53259b36b97fa1990edc7ad2be47c5e0ab41916b5ff",
        #                 "ipAddress": "x.x.x.x"
        #             }
        #         ]
        #     }
        #
        return response

    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://docs.poloniex.com/#authenticated-endpoints-wallets-wallets-activity-records
        :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.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        response = await self.fetch_transactions_helper(code, since, limit, params)
        currency: Currency = None
        if code is not None:
            currency = self.currency(code)
        withdrawals = self.safe_value(response, 'withdrawals', [])
        deposits = self.safe_value(response, 'deposits', [])
        withdrawalTransactions = self.parse_transactions(withdrawals, currency, since, limit)
        depositTransactions = self.parse_transactions(deposits, currency, since, limit)
        transactions = self.array_concat(depositTransactions, withdrawalTransactions)
        return self.filter_by_currency_since_limit(self.sort_by(transactions, 'timestamp'), code, 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://docs.poloniex.com/#authenticated-endpoints-wallets-wallets-activity-records
        :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.com/#/?id=transaction-structure>`
        """
        response = await self.fetch_transactions_helper(code, since, limit, params)
        currency: Currency = None
        if code is not None:
            currency = self.currency(code)
        withdrawals = self.safe_value(response, 'withdrawals', [])
        transactions = self.parse_transactions(withdrawals, currency, since, limit)
        return self.filter_by_currency_since_limit(transactions, code, since, limit)

    async def fetch_deposit_withdraw_fees(self, codes: Strings = None, params={}):
        """
        fetch deposit and withdraw fees
        :see: https://docs.poloniex.com/#public-endpoints-reference-data-currency-information
        :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 `fees structures <https://docs.ccxt.com/#/?id=fee-structure>`
        """
        await self.load_markets()
        response = await self.publicGetCurrencies(self.extend(params, {'includeMultiChainCurrencies': True}))
        #
        #     [
        #         {
        #             "1CR": {
        #                 "id": 1,
        #                 "name": "1CRedit",
        #                 "description": "BTC Clone",
        #                 "type": "address",
        #                 "withdrawalFee": "0.01000000",
        #                 "minConf": 10000,
        #                 "depositAddress": null,
        #                 "blockchain": "1CR",
        #                 "delisted": False,
        #                 "tradingState": "NORMAL",
        #                 "walletState": "DISABLED",
        #                 "parentChain": null,
        #                 "isMultiChain": False,
        #                 "isChildChain": False,
        #                 "childChains": []
        #             }
        #         }
        #     ]
        #
        data: dict = {}
        for i in range(0, len(response)):
            entry = response[i]
            currencies = list(entry.keys())
            currencyId = self.safe_string(currencies, 0)
            data[currencyId] = entry[currencyId]
        return self.parse_deposit_withdraw_fees(data, codes)

    def parse_deposit_withdraw_fees(self, response, codes=None, currencyIdKey=None):
        #
        #         {
        #             "1CR": {
        #                 "id": 1,
        #                 "name": "1CRedit",
        #                 "description": "BTC Clone",
        #                 "type": "address",
        #                 "withdrawalFee": "0.01000000",
        #                 "minConf": 10000,
        #                 "depositAddress": null,
        #                 "blockchain": "1CR",
        #                 "delisted": False,
        #                 "tradingState": "NORMAL",
        #                 "walletState": "DISABLED",
        #                 "parentChain": null,
        #                 "isMultiChain": False,
        #                 "isChildChain": False,
        #                 "childChains": []
        #             },
        #         }
        #
        depositWithdrawFees: dict = {}
        codes = self.market_codes(codes)
        responseKeys = list(response.keys())
        for i in range(0, len(responseKeys)):
            currencyId = responseKeys[i]
            code = self.safe_currency_code(currencyId)
            feeInfo = response[currencyId]
            if (codes is None) or (self.in_array(code, codes)):
                currency = self.currency(code)
                depositWithdrawFees[code] = self.parse_deposit_withdraw_fee(feeInfo, currency)
                childChains = self.safe_value(feeInfo, 'childChains')
                chainsLength = len(childChains)
                if chainsLength > 0:
                    for j in range(0, len(childChains)):
                        networkId = childChains[j]
                        networkId = networkId.replace(code, '')
                        networkCode = self.network_id_to_code(networkId)
                        networkInfo = self.safe_value(response, networkId)
                        networkObject: dict = {}
                        withdrawFee = self.safe_number(networkInfo, 'withdrawalFee')
                        networkObject[networkCode] = {
                            'withdraw': {
                                'fee': withdrawFee,
                                'percentage': False if (withdrawFee is not None) else None,
                            },
                            'deposit': {
                                'fee': None,
                                'percentage': None,
                            },
                        }
                        depositWithdrawFees[code]['networks'] = self.extend(depositWithdrawFees[code]['networks'], networkObject)
        return depositWithdrawFees

    def parse_deposit_withdraw_fee(self, fee, currency: Currency = None):
        depositWithdrawFee = self.deposit_withdraw_fee({})
        depositWithdrawFee['info'][currency['code']] = fee
        networkId = self.safe_string(fee, 'blockchain')
        withdrawFee = self.safe_number(fee, 'withdrawalFee')
        withdrawResult: dict = {
            'fee': withdrawFee,
            'percentage': False if (withdrawFee is not None) else None,
        }
        depositResult: dict = {
            'fee': None,
            'percentage': None,
        }
        depositWithdrawFee['withdraw'] = withdrawResult
        depositWithdrawFee['deposit'] = depositResult
        networkCode = self.network_id_to_code(networkId)
        depositWithdrawFee['networks'][networkCode] = {
            'withdraw': withdrawResult,
            'deposit': depositResult,
        }
        return depositWithdrawFee

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :see: https://docs.poloniex.com/#authenticated-endpoints-wallets-wallets-activity-records
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
        """
        response = await self.fetch_transactions_helper(code, since, limit, params)
        currency = None
        if code is not None:
            currency = self.currency(code)
        deposits = self.safe_value(response, 'deposits', [])
        transactions = self.parse_transactions(deposits, currency, since, limit)
        return self.filter_by_currency_since_limit(transactions, code, since, limit)

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'COMPLETE': 'ok',
            'COMPLETED': 'ok',
            'AWAITING APPROVAL': 'pending',
            'AWAITING_APPROVAL': 'pending',
            'PENDING': 'pending',
            'PROCESSING': 'pending',
            'COMPLETE ERROR': 'failed',
            'COMPLETE_ERROR': 'failed',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # deposits
        #
        #     {
        #         "txid": "f49d489616911db44b740612d19464521179c76ebe9021af85b6de1e2f8d68cd",
        #         "amount": "49798.01987021",
        #         "status": "COMPLETE",
        #         "address": "DJVJZ58tJC8UeUv9Tqcdtn6uhWobouxFLT",
        #         "currency": "DOGE",
        #         "timestamp": 1524321838,
        #         "confirmations": 3371,
        #         "depositNumber": 134587098
        #     }
        #
        # withdrawals
        #
        #     {
        #         "withdrawalRequestsId": 7397527,
        #         "currency": "ETC",
        #         "address": "0x26419a62055af459d2cd69bb7392f5100b75e304",
        #         "amount": "13.19951600",
        #         "fee": "0.01000000",
        #         "timestamp": 1506010932,
        #         "status": "COMPLETED",
        #         "txid": "343346392f82ac16e8c2604f2a604b7b2382d0e9d8030f673821f8de4b5f5bk",
        #         "ipAddress": "1.2.3.4",
        #         "paymentID": null
        #     }
        #
        # withdraw
        #
        #     {
        #         "withdrawalRequestsId": 33485231
        #     }
        #
        timestamp = self.safe_timestamp(transaction, 'timestamp')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        status = self.safe_string(transaction, 'status', 'pending')
        status = self.parse_transaction_status(status)
        txid = self.safe_string(transaction, 'txid')
        type = 'withdrawal' if ('withdrawalRequestsId' in transaction) else 'deposit'
        id = self.safe_string_2(transaction, 'withdrawalRequestsId', 'depositNumber')
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'paymentID')
        amountString = self.safe_string(transaction, 'amount')
        feeCostString = self.safe_string(transaction, 'fee')
        if type == 'withdrawal':
            amountString = Precise.string_sub(amountString, feeCostString)
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': self.parse_number(amountString),
            'network': None,
            'address': address,
            'addressTo': None,
            'addressFrom': None,
            'tag': tag,
            'tagTo': None,
            'tagFrom': None,
            'status': status,
            'type': type,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'comment': None,
            'internal': None,
            'fee': {
                'currency': code,
                'cost': self.parse_number(feeCostString),
                'rate': None,
            },
        }

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api']['rest']
        query = self.omit(params, self.extract_params(path))
        implodedPath = self.implode_params(path, params)
        if api == 'public':
            url += '/' + implodedPath
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            timestamp = str(self.nonce())
            auth = method + "\n"  # eslint-disable-line quotes
            url += '/' + implodedPath
            auth += '/' + implodedPath
            if (method == 'POST') or (method == 'PUT') or (method == 'DELETE'):
                auth += "\n"  # eslint-disable-line quotes
                if query:
                    body = self.json(query)
                    auth += 'requestBody=' + body + '&'
                auth += 'signTimestamp=' + timestamp
            else:
                sortedQuery = self.extend({'signTimestamp': timestamp}, query)
                sortedQuery = self.keysort(sortedQuery)
                auth += "\n" + self.urlencode(sortedQuery)  # eslint-disable-line quotes
                if query:
                    url += '?' + self.urlencode(query)
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha256, 'base64')
            headers = {
                'Content-Type': 'application/json',
                'key': self.apiKey,
                'signTimestamp': timestamp,
                'signature': signature,
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None
        #
        #     {
        #         "code" : 21709,
        #         "message" : "Low available balance"
        #     }
        #
        responseCode = self.safe_string(response, 'code')
        if (responseCode is not None) and (responseCode != '200'):
            codeInner = response['code']
            message = self.safe_string(response, 'message')
            feedback = self.id + ' ' + body
            self.throw_exactly_matched_exception(self.exceptions['exact'], codeInner, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)  # unknown message
        return None
