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

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

from ccxt_zagrava.base.exchange import Exchange
from ccxt_zagrava.abstract.indodax import ImplicitAPI
import hashlib
import math
from ccxt_zagrava.base.types import Balances, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE


class indodax(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(indodax, self).describe(), {
            'id': 'indodax',
            'name': 'INDODAX',
            'countries': ['ID'],  # Indonesia
            # 10 requests per second for making trades => 1000ms / 10 = 100ms
            # 180 requests per minute(public endpoints) = 2 requests per second => cost = (1000ms / rateLimit) / 2 = 5
            'rateLimit': 100,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': False,
                'cancelOrder': True,
                'cancelOrders': False,
                'closeAllPositions': False,
                'closePosition': False,
                'createDepositAddress': False,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': False,
                'createStopMarketOrder': False,
                'createStopOrder': False,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchDeposit': False,
                'fetchDepositAddress': 'emulated',
                'fetchDepositAddresses': True,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': False,
                'fetchDepositsWithdrawals': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTransactionFee': True,
                'fetchTransactionFees': False,
                'fetchTransactions': 'emulated',
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': False,
                'reduceMargin': False,
                'setLeverage': False,
                'setMargin': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': True,
            },
            'version': '2.0',  # 9 April 2018
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87070508-9358c880-c221-11ea-8dc5-5391afbbb422.jpg',
                'api': {
                    'public': 'https://indodax.com',
                    'private': 'https://indodax.com/tapi',
                },
                'www': 'https://www.indodax.com',
                'doc': 'https://github.com/btcid/indodax-official-api-docs',
                'referral': 'https://indodax.com/ref/testbitcoincoid/1',
            },
            'api': {
                'public': {
                    'get': {
                        'api/server_time': 5,
                        'api/pairs': 5,
                        'api/price_increments': 5,
                        'api/summaries': 5,
                        'api/ticker/{pair}': 5,
                        'api/ticker_all': 5,
                        'api/trades/{pair}': 5,
                        'api/depth/{pair}': 5,
                        'tradingview/history_v2': 5,
                    },
                },
                'private': {
                    'post': {
                        'getInfo': 4,
                        'transHistory': 4,
                        'trade': 1,
                        'tradeHistory': 4,  # TODO add fetchMyTrades
                        'openOrders': 4,
                        'orderHistory': 4,
                        'getOrder': 4,
                        'cancelOrder': 4,
                        'withdrawFee': 4,
                        'withdrawCoin': 4,
                        'listDownline': 4,
                        'checkDownline': 4,
                        'createVoucher': 4,  # partner only
                    },
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0,
                    'taker': 0.003,
                },
            },
            'exceptions': {
                'exact': {
                    'invalid_pair': BadSymbol,  # {"error":"invalid_pair","error_description":"Invalid Pair"}
                    'Insufficient balance.': InsufficientFunds,
                    'invalid order.': OrderNotFound,
                    'Invalid credentials. API not found or session has expired.': AuthenticationError,
                    'Invalid credentials. Bad sign.': AuthenticationError,
                },
                'broad': {
                    'Minimum price': InvalidOrder,
                    'Minimum order': InvalidOrder,
                },
            },
            # exchange-specific options
            'options': {
                'recvWindow': 5 * 1000,  # default 5 sec
                'timeDifference': 0,  # the difference between system clock and exchange clock
                'adjustForTimeDifference': False,  # controls the adjustment logic upon instantiation
                'networks': {
                    'XLM': 'Stellar Token',
                    'BSC': 'bep20',
                    'TRC20': 'trc20',
                    'MATIC': 'polygon',
                    # 'BEP2': 'bep2',
                    # 'ARB': 'arb',
                    # 'ERC20': 'erc20',
                    # 'KIP7': 'kip7',
                    # 'MAINNET': 'mainnet',  # TODO: does mainnet just mean the default?
                    # 'OEP4': 'oep4',
                    # 'OP': 'op',
                    # 'SPL': 'spl',
                    # 'TRC10': 'trc10',
                    # 'ZRC2': 'zrc2'
                    # 'ETH': 'eth'
                    # 'BASE': 'base'
                },
                'timeframes': {
                    '1m': '1',
                    '15m': '15',
                    '30m': '30',
                    '1h': '60',
                    '4h': '240',
                    '1d': '1D',
                    '3d': '3D',
                    '1w': '1W',
                },
            },
            'commonCurrencies': {
                'STR': 'XLM',
                'BCHABC': 'BCH',
                'BCHSV': 'BSV',
                'DRK': 'DASH',
                'NEM': 'XEM',
            },
            'precisionMode': TICK_SIZE,
        })

    def nonce(self):
        return self.milliseconds() - self.options['timeDifference']

    def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Public-RestAPI.md#server-time
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.publicGetApiServerTime(params)
        #
        #     {
        #         "timezone": "UTC",
        #         "server_time": 1571205969552
        #     }
        #
        return self.safe_integer(response, 'server_time')

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for indodax
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Public-RestAPI.md#pairs
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.publicGetApiPairs(params)
        #
        #     [
        #         {
        #             "id": "btcidr",
        #             "symbol": "BTCIDR",
        #             "base_currency": "idr",
        #             "traded_currency": "btc",
        #             "traded_currency_unit": "BTC",
        #             "description": "BTC/IDR",
        #             "ticker_id": "btc_idr",
        #             "volume_precision": 0,
        #             "price_precision": 1000,
        #             "price_round": 8,
        #             "pricescale": 1000,
        #             "trade_min_base_currency": 10000,
        #             "trade_min_traded_currency": 0.00007457,
        #             "has_memo": False,
        #             "memo_name": False,
        #             "has_payment_id": False,
        #             "trade_fee_percent": 0.3,
        #             "url_logo": "https://indodax.com/v2/logo/svg/color/btc.svg",
        #             "url_logo_png": "https://indodax.com/v2/logo/png/color/btc.png",
        #             "is_maintenance": 0
        #         }
        #     ]
        #
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'ticker_id')
            baseId = self.safe_string(market, 'traded_currency')
            quoteId = self.safe_string(market, 'base_currency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            isMaintenance = self.safe_integer(market, 'is_maintenance')
            result.append({
                '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': False if isMaintenance else True,
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': self.safe_number(market, 'trade_fee_percent'),
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'percentage': True,
                'precision': {
                    'amount': self.parse_number('1e-8'),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'price_round'))),
                    'cost': self.parse_number(self.parse_precision(self.safe_string(market, 'volume_precision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': self.safe_number(market, 'trade_min_traded_currency'),
                        'max': None,
                    },
                    'price': {
                        'min': self.safe_number(market, 'trade_min_base_currency'),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'created': None,
                'info': market,
            })
        return result

    def parse_balance(self, response) -> Balances:
        balances = self.safe_value(response, 'return', {})
        free = self.safe_value(balances, 'balance', {})
        used = self.safe_value(balances, 'balance_hold', {})
        timestamp = self.safe_timestamp(balances, 'server_time')
        result: dict = {
            'info': response,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
        }
        currencyIds = list(free.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(free, currencyId)
            account['used'] = self.safe_string(used, currencyId)
            result[code] = account
        return self.safe_balance(result)

    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://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#get-info-endpoint
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        self.load_markets()
        response = self.privatePostGetInfo(params)
        #
        #     {
        #         "success":1,
        #         "return":{
        #             "server_time":1619562628,
        #             "balance":{
        #                 "idr":167,
        #                 "btc":"0.00000000",
        #                 "1inch":"0.00000000",
        #             },
        #             "balance_hold":{
        #                 "idr":0,
        #                 "btc":"0.00000000",
        #                 "1inch":"0.00000000",
        #             },
        #             "address":{
        #                 "btc":"1KMntgzvU7iTSgMBWc11nVuJjAyfW3qJyk",
        #                 "1inch":"0x1106c8bb3172625e1f411c221be49161dac19355",
        #                 "xrp":"rwWr7KUZ3ZFwzgaDGjKBysADByzxvohQ3C",
        #                 "zrx":"0x1106c8bb3172625e1f411c221be49161dac19355"
        #             },
        #             "user_id":"276011",
        #             "name":"",
        #             "email":"testbitcoincoid@mailforspam.com",
        #             "profile_picture":null,
        #             "verification_status":"unverified",
        #             "gauth_enable":true
        #         }
        #     }
        #
        return self.parse_balance(response)

    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://github.com/btcid/indodax-official-api-docs/blob/master/Public-RestAPI.md#depth
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['base'] + market['quote'],
        }
        orderbook = self.publicGetApiDepthPair(self.extend(request, params))
        return self.parse_order_book(orderbook, market['symbol'], None, 'buy', 'sell')

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "high":"0.01951",
        #         "low":"0.01877",
        #         "vol_eth":"39.38839319",
        #         "vol_btc":"0.75320886",
        #         "last":"0.01896",
        #         "buy":"0.01896",
        #         "sell":"0.019",
        #         "server_time":1565248908
        #     }
        #
        symbol = self.safe_symbol(None, market)
        timestamp = self.safe_timestamp(ticker, 'server_time')
        baseVolume = 'vol_' + market['baseId'].lower()
        quoteVolume = 'vol_' + market['quoteId'].lower()
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'buy'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'sell'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, baseVolume),
            'quoteVolume': self.safe_string(ticker, quoteVolume),
            'info': ticker,
        }, market)

    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://github.com/btcid/indodax-official-api-docs/blob/master/Public-RestAPI.md#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_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['base'] + market['quote'],
        }
        response = self.publicGetApiTickerPair(self.extend(request, params))
        #
        #     {
        #         "ticker": {
        #             "high":"0.01951",
        #             "low":"0.01877",
        #             "vol_eth":"39.38839319",
        #             "vol_btc":"0.75320886",
        #             "last":"0.01896",
        #             "buy":"0.01896",
        #             "sell":"0.019",
        #             "server_time":1565248908
        #         }
        #     }
        #
        ticker = self.safe_dict(response, 'ticker', {})
        return self.parse_ticker(ticker, market)

    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://github.com/btcid/indodax-official-api-docs/blob/master/Public-RestAPI.md#ticker-all
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        #
        # {
        #     "tickers": {
        #         "btc_idr": {
        #             "high": "120009000",
        #             "low": "116735000",
        #             "vol_btc": "218.13777777",
        #             "vol_idr": "25800033297",
        #             "last": "117088000",
        #             "buy": "117002000",
        #             "sell": "117078000",
        #             "server_time": 1571207881
        #         }
        #     }
        # }
        #
        response = self.publicGetApiTickerAll(params)
        tickers = self.safe_dict(response, 'tickers', {})
        return self.parse_tickers(tickers, symbols)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        timestamp = self.safe_timestamp(trade, 'date')
        return self.safe_trade({
            'id': self.safe_string(trade, 'tid'),
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': self.safe_symbol(None, market),
            'type': None,
            'side': self.safe_string(trade, 'type'),
            'order': None,
            'takerOrMaker': None,
            'price': self.safe_string(trade, 'price'),
            'amount': self.safe_string(trade, 'amount'),
            'cost': None,
            'fee': None,
        }, market)

    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://github.com/btcid/indodax-official-api-docs/blob/master/Public-RestAPI.md#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_zagrava.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['base'] + market['quote'],
        }
        response = self.publicGetApiTradesPair(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        #
        #     {
        #         "Time": 1708416900,
        #         "Open": 51707.52,
        #         "High": 51707.52,
        #         "Low": 51707.52,
        #         "Close": 51707.52,
        #         "Volume": "0"
        #     }
        #
        return [
            self.safe_timestamp(ohlcv, 'Time'),
            self.safe_number(ohlcv, 'Open'),
            self.safe_number(ohlcv, 'High'),
            self.safe_number(ohlcv, 'Low'),
            self.safe_number(ohlcv, 'Close'),
            self.safe_number(ohlcv, 'Volume'),
        ]

    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
        :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 of the latest candle to fetch
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        timeframes = self.options['timeframes']
        selectedTimeframe = self.safe_string(timeframes, timeframe, timeframe)
        now = self.seconds()
        until = self.safe_integer(params, 'until', now)
        params = self.omit(params, ['until'])
        request: dict = {
            'to': until,
            'tf': selectedTimeframe,
            'symbol': market['base'] + market['quote'],
        }
        if limit is None:
            limit = 1000
        if since is not None:
            request['from'] = int(math.floor(since / 1000))
        else:
            duration = self.parse_timeframe(timeframe)
            request['from'] = now - limit * duration - 1
        response = self.publicGetTradingviewHistoryV2(self.extend(request, params))
        #
        #     [
        #         {
        #             "Time": 1708416900,
        #             "Open": 51707.52,
        #             "High": 51707.52,
        #             "Low": 51707.52,
        #             "Close": 51707.52,
        #             "Volume": "0"
        #         }
        #     ]
        #
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            'open': 'open',
            'filled': 'closed',
            'cancelled': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #     {
        #         "order_id": "12345",
        #         "submit_time": "1392228122",
        #         "price": "8000000",
        #         "type": "sell",
        #         "order_ltc": "100000000",
        #         "remain_ltc": "100000000"
        #     }
        #
        # market closed orders - note that the price is very high
        # and does not reflect actual price the order executed at
        #
        #     {
        #       "order_id": "49326856",
        #       "type": "sell",
        #       "price": "1000000000",
        #       "submit_time": "1618314671",
        #       "finish_time": "1618314671",
        #       "status": "filled",
        #       "order_xrp": "30.45000000",
        #       "remain_xrp": "0.00000000"
        #     }
        #
        # cancelOrder
        #
        #    {
        #        "order_id": 666883,
        #        "client_order_id": "clientx-sj82ks82j",
        #        "type": "sell",
        #        "pair": "btc_idr",
        #        "balance": {
        #            "idr": "33605800",
        #            "btc": "0.00000000",
        #            ...
        #            "frozen_idr": "0",
        #            "frozen_btc": "0.00000000",
        #            ...
        #        }
        #    }
        #
        side = None
        if 'type' in order:
            side = order['type']
        status = self.parse_order_status(self.safe_string(order, 'status', 'open'))
        symbol = None
        cost = None
        price = self.safe_string(order, 'price')
        amount = None
        remaining = None
        marketId = self.safe_string(order, 'pair')
        market = self.safe_market(marketId, market)
        if market is not None:
            symbol = market['symbol']
            quoteId = market['quoteId']
            baseId = market['baseId']
            if (market['quoteId'] == 'idr') and ('order_rp' in order):
                quoteId = 'rp'
            if (market['baseId'] == 'idr') and ('remain_rp' in order):
                baseId = 'rp'
            cost = self.safe_string(order, 'order_' + quoteId)
            if not cost:
                amount = self.safe_string(order, 'order_' + baseId)
                remaining = self.safe_string(order, 'remain_' + baseId)
        timestamp = self.safe_integer(order, 'submit_time')
        fee = None
        id = self.safe_string(order, 'order_id')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': self.safe_string(order, 'client_order_id'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': 'limit',
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'cost': cost,
            'average': None,
            'amount': amount,
            'filled': None,
            'remaining': remaining,
            'status': status,
            'fee': fee,
            'trades': None,
        })

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#get-order-endpoints
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'order_id': id,
        }
        response = self.privatePostGetOrder(self.extend(request, params))
        orders = response['return']
        order = self.parse_order(self.extend({'id': id}, orders['order']), market)
        order['info'] = response
        return order

    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://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#open-orders-endpoints
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = None
        request: dict = {}
        if symbol is not None:
            market = self.market(symbol)
            request['pair'] = market['id']
        response = self.privatePostOpenOrders(self.extend(request, params))
        rawOrders = response['return']['orders']
        # {success: 1, return: {orders: null}} if no orders
        if not rawOrders:
            return []
        # {success: 1, return: {orders: [... objects]}} for orders fetched by symbol
        if symbol is not None:
            return self.parse_orders(rawOrders, market, since, limit)
        # {success: 1, return: {orders: {marketid: [... objects]}}} if all orders are fetched
        marketIds = list(rawOrders.keys())
        exchangeOrders = []
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            marketOrders = rawOrders[marketId]
            market = self.safe_market(marketId)
            parsedOrders = self.parse_orders(marketOrders, market, since, limit)
            exchangeOrders = self.array_concat(exchangeOrders, parsedOrders)
        return exchangeOrders

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#order-history
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchClosedOrders() requires a symbol argument')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
        }
        response = self.privatePostOrderHistory(self.extend(request, params))
        orders = self.parse_orders(response['return']['orders'], market)
        orders = self.filter_by(orders, 'status', 'closed')
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#trade-endpoints
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if type != 'limit':
            raise ExchangeError(self.id + ' createOrder() allows limit orders only')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'pair': market['id'],
            'type': side,
            'price': price,
        }
        currency = market['baseId']
        if side == 'buy':
            request[market['quoteId']] = amount * price
        else:
            request[market['baseId']] = amount
        request[currency] = amount
        result = self.privatePostTrade(self.extend(request, params))
        data = self.safe_value(result, 'return', {})
        id = self.safe_string(data, 'order_id')
        return self.safe_order({
            'info': result,
            'id': id,
        }, market)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#cancel-order-endpoints
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires a symbol argument')
        side = self.safe_value(params, 'side')
        if side is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires an extra "side" param')
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'order_id': id,
            'pair': market['id'],
            'type': side,
        }
        response = self.privatePostCancelOrder(self.extend(request, params))
        #
        #    {
        #        "success": 1,
        #        "return": {
        #            "order_id": 666883,
        #            "client_order_id": "clientx-sj82ks82j",
        #            "type": "sell",
        #            "pair": "btc_idr",
        #            "balance": {
        #                "idr": "33605800",
        #                "btc": "0.00000000",
        #                ...
        #                "frozen_idr": "0",
        #                "frozen_btc": "0.00000000",
        #                ...
        #            }
        #        }
        #    }
        #
        data = self.safe_dict(response, 'return')
        return self.parse_order(data)

    def fetch_transaction_fee(self, code: str, params={}):
        """
        fetch the fee for a transaction
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#withdraw-fee-endpoints
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `fee structure <https://docs.ccxt_zagrava.com/#/?id=fee-structure>`
        """
        self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'currency': currency['id'],
        }
        response = self.privatePostWithdrawFee(self.extend(request, params))
        #
        #     {
        #         "success": 1,
        #         "return": {
        #             "server_time": 1607923272,
        #             "withdraw_fee": 0.005,
        #             "currency": "eth"
        #         }
        #     }
        #
        data = self.safe_value(response, 'return', {})
        currencyId = self.safe_string(data, 'currency')
        return {
            'info': response,
            'rate': self.safe_number(data, 'withdraw_fee'),
            'currency': self.safe_currency_code(currencyId, currency),
        }

    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://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#transaction-history-endpoints
        :param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
        :param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
        :param int [limit]: max number of deposit/withdrawals to return, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        request: dict = {}
        if since is not None:
            startTime = self.iso8601(since)[0:10]
            request['start'] = startTime
            request['end'] = self.iso8601(self.milliseconds())[0:10]
        response = self.privatePostTransHistory(self.extend(request, params))
        #
        #     {
        #         "success": 1,
        #         "return": {
        #             "withdraw": {
        #                 "idr": [
        #                     {
        #                         "status": "success",
        #                         "type": "coupon",
        #                         "rp": "115205",
        #                         "fee": "500",
        #                         "amount": "114705",
        #                         "submit_time": "1539844166",
        #                         "success_time": "1539844189",
        #                         "withdraw_id": "1783717",
        #                         "tx": "BTC-IDR-RDTVVO2P-ETD0EVAW-VTNZGMIR-HTNTUAPI-84ULM9OI",
        #                         "sender": "boris",
        #                         "used_by": "viginia88"
        #                     },
        #                     ...
        #                 ],
        #                 "btc": [],
        #                 "abyss": [],
        #                 ...
        #             },
        #             "deposit": {
        #                 "idr": [
        #                     {
        #                         "status": "success",
        #                         "type": "duitku",
        #                         "rp": "393000",
        #                         "fee": "5895",
        #                         "amount": "387105",
        #                         "submit_time": "1576555012",
        #                         "success_time": "1576555012",
        #                         "deposit_id": "3395438",
        #                         "tx": "Duitku OVO Settlement"
        #                     },
        #                     ...
        #                 ],
        #                 "btc": [
        #                     {
        #                         "status": "success",
        #                         "btc": "0.00118769",
        #                         "amount": "0.00118769",
        #                         "success_time": "1539529208",
        #                         "deposit_id": "3602369",
        #                         "tx": "c816aeb35a5b42f389970325a32aff69bb6b2126784dcda8f23b9dd9570d6573"
        #                     },
        #                     ...
        #                 ],
        #                 "abyss": [],
        #                 ...
        #             }
        #         }
        #     }
        #
        data = self.safe_value(response, 'return', {})
        withdraw = self.safe_value(data, 'withdraw', {})
        deposit = self.safe_value(data, 'deposit', {})
        transactions = []
        currency = None
        if code is None:
            keys = list(withdraw.keys())
            for i in range(0, len(keys)):
                key = keys[i]
                transactions = self.array_concat(transactions, withdraw[key])
            keys = list(deposit.keys())
            for i in range(0, len(keys)):
                key = keys[i]
                transactions = self.array_concat(transactions, deposit[key])
        else:
            currency = self.currency(code)
            withdraws = self.safe_value(withdraw, currency['id'], [])
            deposits = self.safe_value(deposit, currency['id'], [])
            transactions = self.array_concat(withdraws, deposits)
        return self.parse_transactions(transactions, currency, since, limit)

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#withdraw-coin-endpoints
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        # Custom string you need to provide to identify each withdrawal.
        # Will be passed to callback URL(assigned via website to the API key)
        # so your system can identify the request and confirm it.
        # Alphanumeric, max length 255.
        requestId = self.milliseconds()
        # Alternatively:
        # requestId = self.uuid()
        request: dict = {
            'currency': currency['id'],
            'withdraw_amount': amount,
            'withdraw_address': address,
            'request_id': str(requestId),
        }
        if tag:
            request['withdraw_memo'] = tag
        response = self.privatePostWithdrawCoin(self.extend(request, params))
        #
        #     {
        #         "success": 1,
        #         "status": "approved",
        #         "withdraw_currency": "xrp",
        #         "withdraw_address": "rwWr7KUZ3ZFwzgaDGjKBysADByzxvohQ3C",
        #         "withdraw_amount": "10000.00000000",
        #         "fee": "2.00000000",
        #         "amount_after_fee": "9998.00000000",
        #         "submit_time": "1509469200",
        #         "withdraw_id": "xrp-12345",
        #         "txid": "",
        #         "withdraw_memo": "123123"
        #     }
        #
        return self.parse_transaction(response, currency)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # withdraw
        #
        #     {
        #         "success": 1,
        #         "status": "approved",
        #         "withdraw_currency": "xrp",
        #         "withdraw_address": "rwWr7KUZ3ZFwzgaDGjKBysADByzxvohQ3C",
        #         "withdraw_amount": "10000.00000000",
        #         "fee": "2.00000000",
        #         "amount_after_fee": "9998.00000000",
        #         "submit_time": "1509469200",
        #         "withdraw_id": "xrp-12345",
        #         "txid": "",
        #         "withdraw_memo": "123123"
        #     }
        #
        # transHistory
        #
        #     {
        #         "status": "success",
        #         "type": "coupon",
        #         "rp": "115205",
        #         "fee": "500",
        #         "amount": "114705",
        #         "submit_time": "1539844166",
        #         "success_time": "1539844189",
        #         "withdraw_id": "1783717",
        #         "tx": "BTC-IDR-RDTVVO2P-ETD0EVAW-VTNZGMIR-HTNTUAPI-84ULM9OI",
        #         "sender": "boris",
        #         "used_by": "viginia88"
        #     }
        #
        #     {
        #         "status": "success",
        #         "btc": "0.00118769",
        #         "amount": "0.00118769",
        #         "success_time": "1539529208",
        #         "deposit_id": "3602369",
        #         "tx": "c816aeb35a5b42f389970325a32aff69bb6b2126784dcda8f23b9dd9570d6573"
        #     },
        status = self.safe_string(transaction, 'status')
        timestamp = self.safe_timestamp_2(transaction, 'success_time', 'submit_time')
        depositId = self.safe_string(transaction, 'deposit_id')
        feeCost = self.safe_number(transaction, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'currency': self.safe_currency_code(None, currency),
                'cost': feeCost,
                'rate': None,
            }
        return {
            'id': self.safe_string_2(transaction, 'withdraw_id', 'deposit_id'),
            'txid': self.safe_string_2(transaction, 'txid', 'tx'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'addressFrom': None,
            'address': self.safe_string(transaction, 'withdraw_address'),
            'addressTo': None,
            'amount': self.safe_number_n(transaction, ['amount', 'withdraw_amount', 'deposit_amount']),
            'type': 'withdraw' if (depositId is None) else 'deposit',
            'currency': self.safe_currency_code(None, currency),
            'status': self.parse_transaction_status(status),
            'updated': None,
            'tagFrom': None,
            'tag': None,
            'tagTo': None,
            'comment': self.safe_string(transaction, 'withdraw_memo'),
            'internal': None,
            'fee': fee,
            'info': transaction,
        }

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'success': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def fetch_deposit_addresses(self, codes: Strings = None, params={}):
        """
        fetch deposit addresses for multiple currencies and chain types
        :see: https://github.com/btcid/indodax-official-api-docs/blob/master/Private-RestAPI.md#general-information-on-endpoints
        :param str[] [codes]: list of unified currency codes, default is None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a list of `address structures <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        self.load_markets()
        response = self.privatePostGetInfo(params)
        #
        #    {
        #        success: '1',
        #        return: {
        #            server_time: '1708031570',
        #            balance: {
        #                idr: '29952',
        #                ...
        #            },
        #            balance_hold: {
        #                idr: '0',
        #                ...
        #            },
        #            address: {
        #                btc: '1KMntgzvU7iTSgMBWc11nVuJjAyfW3qJyk',
        #                ...
        #            },
        #            memo_is_required: {
        #                btc: {mainnet: False},
        #                ...
        #            },
        #            network: {
        #                btc: 'mainnet',
        #                ...
        #            },
        #            user_id: '276011',
        #            name: '',
        #            email: 'testbitcoincoid@mailforspam.com',
        #            profile_picture: null,
        #            verification_status: 'unverified',
        #            gauth_enable: True,
        #            withdraw_status: '0'
        #        }
        #    }
        #
        data = self.safe_dict(response, 'return')
        addresses = self.safe_dict(data, 'address', {})
        networks = self.safe_dict(data, 'network', {})
        addressKeys = list(addresses.keys())
        result: dict = {
            'info': data,
        }
        for i in range(0, len(addressKeys)):
            marketId = addressKeys[i]
            code = self.safe_currency_code(marketId)
            address = self.safe_string(addresses, marketId)
            if (address is not None) and ((codes is None) or (self.in_array(code, codes))):
                self.check_address(address)
                network = None
                if marketId in networks:
                    networkId = self.safe_string(networks, marketId)
                    if networkId.find(',') >= 0:
                        network = []
                        networkIds = networkId.split(',')
                        for j in range(0, len(networkIds)):
                            network.append(self.network_id_to_code(networkIds[j]).upper())
                    else:
                        network = self.network_id_to_code(networkId).upper()
                result[code] = {
                    'info': {},
                    'currency': code,
                    'address': address,
                    'network': network,
                    'tag': None,
                }
        return result

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api'][api]
        if api == 'public':
            query = self.omit(params, self.extract_params(path))
            requestPath = '/' + self.implode_params(path, params)
            url = url + requestPath
            if query:
                url += '?' + self.urlencode_with_array_repeat(query)
        else:
            self.check_required_credentials()
            body = self.urlencode(self.extend({
                'method': path,
                'timestamp': self.nonce(),
                'recvWindow': self.options['recvWindow'],
            }, params))
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Key': self.apiKey,
                'Sign': self.hmac(self.encode(body), self.encode(self.secret), hashlib.sha512),
            }
        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
        # {success: 0, error: "invalid order."}
        # or
        # [{data, ...}, {...}, ...]
        if isinstance(response, list):
            return None  # public endpoints may return []-arrays
        error = self.safe_value(response, 'error', '')
        if not ('success' in response) and error == '':
            return None  # no 'success' property on public responses
        if self.safe_integer(response, 'success', 0) == 1:
            # {success: 1, return: {orders: []}}
            if not ('return' in response):
                raise ExchangeError(self.id + ': malformed response: ' + self.json(response))
            else:
                return None
        feedback = self.id + ' ' + body
        self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
        self.throw_broadly_matched_exception(self.exceptions['broad'], error, feedback)
        raise ExchangeError(feedback)  # unknown message
