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

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

from ccxt.base.exchange import Exchange
from ccxt.abstract.cube import ImplicitAPI
import hashlib
import math
import json
from ccxt.base.types import Balances, Currencies, Currency, IndexType, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFeeInterface, Transaction
from typing import List
from ccxt.base.errors import AuthenticationError
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.decimal_to_precision import DECIMAL_PLACES


class cube(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(cube, self).describe(), {
            'id': 'cube',
            'name': 'cube',
            'countries': [],
            'urls': {
                'referral': '',
                'logo': 'https://www.cube.exchange/assets/cube-logo-180x180.png',
                'api': {
                    'rest': {
                        'production': {
                            'iridium': 'https://api.cube.exchange/ir/v0',
                            'mendelev': 'https://api.cube.exchange/md/v0',
                            'osmium': 'https://api.cube.exchange/os/v0',
                        },
                        'staging': {
                            'iridium': 'https://staging.cube.exchange/ir/v0',
                            'mendelev': 'https://staging.cube.exchange/md/v0',
                            'osmium': 'https://staging.cube.exchange/os/v0',
                        },
                    },
                    'ws': {
                        'production': {
                            'iridium': 'wss://api.cube.exchange/ir',
                            'mendelev': 'wss://api.cube.exchange/md',
                            'osmium': 'wss://api.cube.exchange/os',
                        },
                        'staging': {
                            'iridium': 'wss://staging.cube.exchange/ir',
                            'mendelev': 'wss://staging.cube.exchange/md',
                            'osmium': 'wss://staging.cube.exchange/os',
                        },
                    },
                },
                'www': 'https://www.cube.exchange/',
                'doc': 'https://cubexch.gitbook.io/cube-api',
                'fees': 'https://www.cube.exchange/fees',
            },
            'version': 'v0',
            'api': {
                'rest': {
                    'iridium': {
                        'public': {
                            'get': {
                                '/markets': 1,
                                '/history/klines': 1,
                                '/points/loyalty-leaderboard': 1,
                                '/points/referral-leaderboard': 1,
                                '/points/blocks-leaderboard': 1,
                            },
                        },
                        'private': {
                            'get': {
                                '/users/check': 1,
                                '/users/info': 1,
                                '/users/subaccounts': 1,
                                '/users/subaccount/{subaccountId}': 1,
                                '/users/subaccount/{subaccountId}/positions': 1,
                                '/users/subaccount/{subaccountId}/transfers': 1,
                                '/users/subaccount/{subaccountId}/deposits': 1,
                                '/users/subaccount/{subaccountId}/withdrawals': 1,
                                '/users/subaccount/{subaccountId}/orders': 1,
                                '/users/subaccount/{subaccountId}/fills': 1,
                                '/users/fee-estimate/{market_id}': 1,
                                '/users/address': 1,
                                '/users/address/settings': 1,
                                '/users/loot-boxes': 1,
                                '/users/invites': 1,
                                '/users/daily-loyalty': 1,
                                '/users/user-tier': 1,
                            },
                            'post': {
                                '/users/withdraw': 1,
                                '/users/subaccounts': 1,
                            },
                            'patch': {
                                '/users/subaccount/{subaccountId}': 1,
                            },
                        },
                    },
                    'mendelev': {
                        'public': {
                            'get': {
                                '/book/{market_id}/snapshot': 1,
                                '/parsed/book/{market_symbol}/snapshot': 1,
                                '/book/{market_id}/recent-trades': 1,
                                '/parsed/book/{market_symbol}/recent-trades': 1,
                                '/tickers/snapshot': 1,
                                '/parsed/tickers': 1,
                            },
                        },
                    },
                    'osmium': {
                        'private': {
                            'get': {
                                '/orders': 1,
                                '/positions': 1,
                            },
                            'delete': {
                                '/orders': 1,
                                '/order': 1,
                            },
                            'post': {
                                '/order': 1,
                            },
                            'patch': {
                                '/order': 1,
                            },
                        },
                    },
                },
            },
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': True,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': False,
                'closeAllPositions': False,
                'closePosition': False,
                'createDepositAddress': False,
                'createMarketOrder': False,
                'createOrder': True,
                'createOrders': False,
                'createPostOnlyOrder': False,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': False,
                'createStopMarketOrder': False,
                'createStopOrder': False,
                'fetchAccounts': False,
                'fetchBalance': True,
                'fetchBorrowInterest': False,
                'fetchBorrowRateHistory': False,
                'fetchCanceledOrders': False,
                'fetchClosedOrders': 'emulated',
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDeposit': False,
                'fetchDepositAddress': False,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': True,
                'fetchDepositsWithdrawals': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLedger': False,
                'fetchLedgerEntry': False,
                'fetchLeverageTiers': False,
                'fetchMarketLeverageTiers': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': 'emulated',
                'fetchOHLCV': True,
                'fetchOpenInterest': False,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrders': True,
                'fetchOrderTrades': False,
                'fetchPermissions': False,
                'fetchPosition': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTradingLimits': False,
                'fetchTransactionFee': False,
                'fetchTransactionFees': False,
                'fetchTransactions': False,
                'fetchTransfers': False,
                'fetchWithdrawAddresses': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'setLeverage': False,
                'setMargin': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'signIn': False,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '1s': '1s',
                '1m': '1m',
                '15m': '15m',
                '1h': '1h',
                '4h': '4h',
                '1d': '1d',
                '0': '1s',
                '1': '1m',
                '2': '15m',
                '3': '1h',
                '4': '4h',
                '5': '1d',
                'S1': '1s',
                'M1': '1m',
                'M15': '15m',
                'H1': '1h',
                'H4': '4h',
                'D1': '1d',
            },
            'timeout': 10000,
            'rateLimit': 100,
            'userAgent': False,
            'verbose': False,
            'markets': None,
            'symbols': None,
            'currencies': None,
            'markets_by_id': None,
            'currencies_by_id': None,
            'apiKey': None,
            'secret': None,
            'password': None,
            'uid': '',
            'options': {
                'environment': 'production',
                'subaccountId': None,
                'networks': {
                    'BTC': '1',
                    'ERC20': '2',
                    'SPL': '3',
                    'DOGE': '4',
                    'TAO': '5',
                    'LTC': '6',
                    'tBTC': '7',
                    'tETH': '8',
                },
                'impliedNetworks': {
                    'ETH': {'ERC20': '2'},
                    'SOL': {'SPL': '3'},
                },
                'legalMoney': {
                    'USD': True,
                },
                'mappings': {
                    'rawMarketsIdsToMarkets': {},
                    'rawCurrenciesIdsToCurrencies': {},
                },
            },
            'pro': True,
            'fees': {
                'trading': {
                    'maker': self.parse_number('0.0004'),
                    'taker': self.parse_number('0.0008'),
                },
            },
            'commonCurrencies': None,
            'precisionMode': DECIMAL_PLACES,
            'exceptions': {
                'exact': {
                    'Must be authorized': AuthenticationError,
                    'Market not found': BadRequest,
                    'Insufficient funds': InsufficientFunds,
                    'Order not found': BadRequest,
                },
            },
        })

    def remove_non_base16_chars(self, input: str) -> str:
        base16Chars = '0123456789abcdefABCDEF'
        result = ''
        for i in range(0, self.count_items(input)):
            if base16Chars.find(input[i]) != -1:
                result += input[i]
        return result

    def generate_signature(self):
        timestamp = self.seconds()
        timestampBytes = self.number_to_le(timestamp, 8)
        secretKeyBytes = self.base16_to_binary(self.remove_non_base16_chars(self.secret))
        message = self.binary_concat(self.encode('cube.xyz'), timestampBytes)
        signature = self.hmac(message, secretKeyBytes, hashlib.sha256, 'base64')
        return [signature, timestamp]

    def generate_authentication_headers(self):
        signature, timestamp = self.generate_signature()
        return {
            'x-api-key': self.apiKey,
            'x-api-signature': signature,
            'x-api-timestamp': str(timestamp),
        }

    def authenticate_request(self, request):
        headers = self.safe_dict(request, 'headers', {})
        request['headers'] = self.extend(headers, self.generate_authentication_headers())
        return request

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        environment = self.options['environment']
        endpoint = None
        apiArray = None
        if isinstance(api, str):
            apiArray = api.split(',')
        else:
            apiArray = api
        for i in range(0, len(apiArray)):
            if api[i] == 'iridium':
                endpoint = 'iridium'
                break
            elif api[i] == 'mendelev':
                endpoint = 'mendelev'
                break
            elif api[i] == 'osmium':
                endpoint = 'osmium'
                break
        baseUrl = self.urls['api']['rest'][environment][endpoint]
        url = baseUrl + self.implode_params(path, params)
        params = self.omit(params, self.extract_params(path))
        methods = ['GET', 'HEAD']
        found = False
        for i in range(0, len(methods)):
            if methods[i] == method:
                if self.count_items(params) > 0:
                    url += '?' + self.urlencode(params)
                found = True
                break
        if not found:
            body = json.dumps(params)
        found = False
        for i in range(0, len(apiArray)):
            if apiArray[i] == 'private':
                found = True
                break
        if found:
            request = {
                'headers': {
                    'Content-Type': 'application/json',
                    'Referer': 'CCXT',
                },
            }
            request = self.authenticate_request(request)
            headers = request['headers']
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def set_sandbox_mode(self, enable):
        if enable is True:
            self.options['environment'] = 'staging'
        else:
            self.options['environment'] = 'production'

    def fetch_market_meta(self, symbolOrSymbols=None):
        symbol = None
        marketId = None
        market = None
        symbols = None
        marketIds = None
        markets = None
        self.load_markets()
        if symbolOrSymbols is not None:
            if isinstance(symbolOrSymbols, str):
                marketId = symbolOrSymbols.upper().replace('/', '')
                market = self.market(marketId)
                marketId = market['id']
                symbolOrSymbols = self.safe_symbol(marketId, market)
                symbol = symbolOrSymbols
                return {
                    'symbol': symbol,
                    'marketId': marketId,
                    'market': market,
                    'symbols': symbols,
                    'marketIds': marketIds,
                    'markets': markets,
                }
            elif isinstance(symbolOrSymbols, list):
                marketIds = []
                markets = []
                for i in range(0, len(symbolOrSymbols)):
                    marketId = symbolOrSymbols[i].upper().replace('/', '')
                    market = self.market(marketId)
                    marketId = market['id']
                    symbolOrSymbols[i] = self.safe_symbol(marketId, market)
                    marketIds.append(marketId)
                    markets.append(market)
                symbolOrSymbols = self.market_symbols(symbolOrSymbols)
                symbols = symbolOrSymbols
                return {
                    'symbol': symbol,
                    'marketId': marketId,
                    'market': market,
                    'symbols': symbols,
                    'marketIds': marketIds,
                    'markets': markets,
                }
            else:
                raise BadSymbol(self.id + ' symbolOrSymbols must be a string or an array of strings')
        return {
            'symbol': symbol,
            'marketId': marketId,
            'market': market,
            'symbols': symbols,
            'marketIds': marketIds,
            'markets': markets,
        }

    def inject_sub_account_id(self, request, params):
        if self.safe_integer(params, 'subaccountId') is not None:
            request['subaccountId'] = self.safe_integer(params, 'subaccountId')
        elif self.safe_integer(params, 'subAccountId') is not None:
            request['subaccountId'] = self.safe_integer(params, 'subAccountId')
        elif self.safe_integer(self.options, 'subaccountId') is not None:
            request['subaccountId'] = self.safe_integer(self.options, 'subaccountId')
        elif self.safe_integer(self.options, 'subAccountId') is not None:
            request['subaccountId'] = self.safe_integer(self.options, 'subAccountId')

    def fetch_currencies(self, params={}):
        """
        fetches all available currencies on an exchange
        :see: https://cubexch.gitbook.io/cube-api/rest-iridium-api#markets
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.restIridiumPublicGetMarkets(params)
        # {
        #     "result": {
        #         "assets": [
        #             {
        #                 "assetId": 1,
        #                 "symbol": "BTC",
        #                 "decimals": 8,
        #                 "displayDecimals": 5,
        #                 "settles": True,
        #                 "assetType": "Crypto",
        #                 "sourceId": 1,
        #                 "metadata": {
        #                     "dustAmount": 3000
        #                 },
        #                 "status": 1
        #             },
        #             ...
        #         ],
        #         ...
        #     }
        # }
        assets = self.safe_list(self.safe_dict(response, 'result'), 'assets')
        return self.parse_currencies(assets)

    def parse_currencies(self, assets) -> Currencies:
        self.options['mappings']['rawCurrenciesIdsToCurrencies'] = {}
        result = {}
        for i in range(0, len(assets)):
            rawCurrency = assets[i]
            id = self.safe_string(rawCurrency, 'symbol').lower()
            code = id.upper()
            name = self.safe_string(self.safe_dict(rawCurrency, 'metadata'), 'currencyName')
            networkId = self.safe_string(rawCurrency, 'sourceId')
            networks = {}
            networks[networkId] = {
                'id': networkId,
            }
            currency = self.safe_currency_structure({
                'info': rawCurrency,
                'id': id,
                'numericId': self.safe_integer(rawCurrency, 'assetId'),
                'code': code,
                'precision': self.safe_integer(rawCurrency, 'decimals'),
                'type': self.safe_string_lower(rawCurrency, 'assetType'),
                'name': name,
                'active': self.safe_integer(rawCurrency, 'status') == 1,
                'deposit': True,
                'withdraw': True,
                'fee': None,  # TODO Check if it is possible to fill a withdraw feenot !!
                'fees': {},  # TODO Check if it is possible to fill a withdraw feenot !!
                'networks': networks,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                    'deposit': {
                        'min': None,
                        'max': None,
                    },
                },
            })
            result[code] = currency
            self.options['mappings']['rawCurrenciesIdsToCurrencies'][self.safe_integer(currency, 'numericId')] = currency
        return result

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for cube
        :see: https://cubexch.gitbook.io/cube-api/rest-iridium-api#markets
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.restIridiumPublicGetMarkets(params)
        # {
        #     "result": {
        #         "assets": [
        #             {
        #                 "assetId": 1,
        #                 "symbol": "BTC",
        #                 "decimals": 8,
        #                 "displayDecimals": 5,
        #                 "settles": True,
        #                 "assetType": "Crypto",
        #                 "sourceId": 1,
        #                 "metadata": {
        #                     "dustAmount": 3000
        #                 },
        #                 "status": 1
        #             },
        #             ...
        #         ],
        #         "markets": [
        #             {
        #                 "marketId": 100004,
        #                 "symbol": "BTCUSDC",
        #                 "baseAssetId": 1,
        #                 "baseLotSize": "1000",
        #                 "quoteAssetId": 7,
        #                 "quoteLotSize": "1",
        #                 "priceDisplayDecimals": 2,
        #                 "protectionPriceLevels": 3000,
        #                 "priceBandBidPct": 25,
        #                 "priceBandAskPct": 400,
        #                 "priceTickSize": "0.1",
        #                 "quantityTickSize": "0.00001",
        #                 "status": 1,
        #                 "feeTableId": 2
        #             },
        #             ...
        #         ],
        #         "feeTables": [
        #             {
        #                 "feeTableId": 1,
        #                 "feeTiers": [
        #                     {
        #                         "priority": 0,
        #                         "makerFeeRatio": 0.0,
        #                         "takerFeeRatio": 0.0
        #                     }
        #                 ]
        #             },
        #             {
        #                 "feeTableId": 2,
        #                 "feeTiers": [
        #                     {
        #                         "priority": 0,
        #                         "makerFeeRatio": 0.0004,
        #                         "takerFeeRatio": 0.0008
        #                     }
        #                 ]
        #             }
        #         ]
        #     }
        # }
        rawMarkets = self.safe_list(self.safe_dict(response, 'result'), 'markets')
        rawAssets = self.safe_list(self.safe_dict(response, 'result'), 'assets')
        self.currencies = self.parse_currencies(rawAssets)
        return self.parse_markets(rawMarkets)

    def parse_markets(self, markets) -> List[Market]:
        self.options['mappings']['rawMarketsIdsToMarkets'] = {}
        result = []
        for i in range(0, len(markets)):
            if self.safe_string(markets[i], 'status') != '1':
                continue
            market = self.parse_market(markets[i])
            result.append(market)
            self.options['mappings']['rawMarketsIdsToMarkets'][self.safe_integer(self.safe_dict(market, 'info'), 'marketId')] = market
        return result

    def parse_market(self, market) -> Market:
        id = self.safe_string(market, 'symbol').upper()
        # TODO Expose self object globally for the exchange so the currencies can be retrieved in O(1) time
        currenciesByNumericId = {}
        for i in range(0, self.count_items(self.currencies)):
            currenciesKeysArray = list(self.currencies.keys())
            targetCurrency = self.safe_value(self.currencies, currenciesKeysArray[i])
            targetCurrencyNumericId = self.safe_integer(targetCurrency, 'numericId')
            currenciesByNumericId[targetCurrencyNumericId] = targetCurrency
        baseAsset = currenciesByNumericId[self.safe_integer(market, 'baseAssetId')]
        quoteAsset = currenciesByNumericId[self.safe_integer(market, 'quoteAssetId')]
        baseSymbol = self.safe_string(self.safe_dict(baseAsset, 'info'), 'symbol')
        quoteSymbol = self.safe_string(self.safe_dict(quoteAsset, 'info'), 'symbol')
        marketSymbol = baseSymbol + quoteSymbol
        return self.safe_market_structure({
            'id': id,
            'lowercaseId': id.lower(),
            'symbol': marketSymbol,
            'base': self.safe_string(baseAsset, 'code'),
            'quote': self.safe_string(quoteAsset, 'code'),
            'settle': None,
            'baseId': self.safe_string(baseAsset, 'id'),
            'quoteId': self.safe_string(quoteAsset, 'id'),
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'active': self.safe_integer(market, 'status') == 1,
            'contract': False,
            'linear': None,
            'inverse': None,
            'contractSize': None,
            'taker': self.safe_number(self.safe_dict(self.fees, 'trading'), 'taker'),
            'maker': self.safe_number(self.safe_dict(self.fees, 'trading'), 'maker'),
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'precision': {
                'amount': self.precision_from_string(self.safe_string(market, 'quantityTickSize')),
                'price': self.precision_from_string(self.safe_string(market, 'priceTickSize')),
                'cost': None,
                'base': None,
                'quote': None,
            },
            'limits': {
                'leverage': {
                    'min': None,
                    'max': None,
                },
                'amount': {
                    'min': None,
                    'max': None,
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            },
            'created': None,
            'info': market,
        })

    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://cubexch.gitbook.io/cube-api/rest-mendelev-api#book-market_id-snapshot
        :see: https://cubexch.gitbook.io/cube-api/rest-mendelev-api#parsed-book-market_symbol-snapshot
        :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
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        request = {'market_symbol': symbol}
        response = self.restMendelevPublicGetParsedBookMarketSymbolSnapshot(self.extend(request, params))
        #
        # {
        #   "result":{
        #       "ticker_id":"BTCUSDC",
        #       "timestamp":1711544655331,
        #       "bids":[
        #           [
        #               70635.6,
        #               0.01
        #           ]
        #       ],
        #       "asks":[
        #           [
        #               70661.8,
        #               0.1421
        #           ]
        #       ]
        #   }
        # }
        #
        rawBids = self.safe_list(self.safe_dict(response, 'result'), 'bids', [])
        rawAsks = self.safe_list(self.safe_dict(response, 'result'), 'asks', [])
        bids = []
        for i in range(0, self.count_items(rawBids)):
            if not (self.parse_to_numeric(rawBids[i][0]) <= 0 or self.parse_to_numeric(rawBids[i][1]) <= 0):
                bids.append(rawBids[i])
        asks = []
        for i in range(0, self.count_items(rawAsks)):
            if not (self.parse_to_numeric(rawAsks[i][0]) <= 0 or self.parse_to_numeric(rawAsks[i][1]) <= 0):
                asks.append(rawAsks[i])
        rawOrderbook = {
            'bids': bids,
            'asks': asks,
        }
        timestamp = self.safe_integer(self.safe_dict(response, 'result'), 'timestamp')  # Don't use self.safe_timestamp()
        return self.parse_order_book(rawOrderbook, symbol, timestamp, 'bids', 'asks')

    def parse_bids_asks(self, bidasks, priceKey: IndexType = 0, amountKey: IndexType = 1, countOrIdKey: IndexType = 2):
        return bidasks

    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://cubexch.gitbook.io/cube-api/rest-mendelev-api#parsed-tickers
        :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>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        tickers = self.fetch_tickers([symbol], params)
        ticker = self.safe_value(tickers, symbol, None)
        if ticker is None:
            raise BadSymbol(self.id + ' fetchTicker() symbol ' + symbol + ' not found')
        return ticker

    def parse_ticker(self, ticker, market: Market = None) -> Ticker:
        #
        #       {
        #         ticker_id: "JTOUSDC",
        #         base_currency: "JTO",
        #         quote_currency: "USDC",
        #         timestamp: 1713217334960,
        #         last_price: 2.6624,
        #         base_volume: 337.12,
        #         quote_volume: 961.614166,
        #         bid: 2.6627,
        #         ask: 2.6715,
        #         high: 3.0515,
        #         low: 2.6272,
        #         open: 2.8051,
        #       }
        #
        timestamp = self.safe_integer(ticker, 'timestamp')
        return self.safe_ticker({
            'symbol': self.safe_string(market, 'symbol'),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'high'),
            'low': self.safe_number(ticker, 'low'),
            'bid': self.safe_number(ticker, 'bid'),
            'bidVolume': self.safe_number(ticker, 'base_volume'),
            'ask': self.safe_number(ticker, 'ask'),
            'askVolume': self.safe_number(ticker, 'quote_volume'),
            'vwap': None,
            'open': self.safe_number(ticker, 'open'),
            'close': None,
            'last': self.safe_number(ticker, 'last_price'),
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_number(ticker, 'base_volume'),
            'quoteVolume': self.safe_number(ticker, 'quote_volume'),
            'info': ticker,
        }, market)

    def fetch_tickers(self, symbols: List[str] = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://cubexch.gitbook.io/cube-api/rest-mendelev-api#parsed-tickers
        :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>`
        """
        meta = self.fetch_market_meta(symbols)
        symbols = self.safe_list(meta, 'symbols')
        response = self.restMendelevPublicGetParsedTickers(params)
        #
        #  {
        #     result: [
        #       {
        #     ticker_id: "JTOUSDC",
        #     base_currency: "JTO",
        #     quote_currency: "USDC",
        #     timestamp: 1713216571697,
        #     last_price: 2.6731,
        #     base_volume: 333.66,
        #     quote_volume: 953.635304,
        #     bid: 2.6653,
        #     ask: 2.6761,
        #     high: 3.0515,
        #     low: 2.6272,
        #     open: 2.8231,
        #      },
        #    ],
        #  }
        #
        rawTickers = self.safe_list(response, 'result', [])
        result = {}
        for i in range(0, len(rawTickers)):
            rawTicker = rawTickers[i]
            rawTickerId = self.safe_string(rawTicker, 'ticker_id').upper().replace('/', '')
            if symbols is not None:
                for j in range(0, len(symbols)):
                    if symbols[j].upper() == rawTickerId:
                        break
            marketId = None
            try:
                marketId = self.market_id(rawTickerId)
            except Exception as _exception:
                continue
            market = self.market(marketId)
            symbol = self.safe_string(market, 'symbol')
            ticker = self.parse_ticker(rawTicker, market)
            result[symbol] = ticker
        return self.filter_by_array_tickers(result, 'symbol', symbols)

    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://cubexch.gitbook.io/cube-api/rest-mendelev-api#parsed-tickers
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        market = self.safe_dict(meta, 'market')
        marketNumericId = self.safe_integer(self.safe_dict(market, 'info'), 'marketId')
        selectedTimeframe = self.timeframes[timeframe]
        request = {
            'interval': selectedTimeframe,
        }
        if marketNumericId is not None:
            request['marketId'] = marketNumericId
        if since is not None:
            request['start_time'] = since  # The unix nanosecond timestamp that self kline covers.
        response = self.restIridiumPublicGetHistoryKlines(self.extend(request, params))
        data = self.safe_value(response, 'result', [])
        #
        #  {
        #    "result": [
        #      [
        #        1715280300,
        #        14736,
        #        14736,
        #        14736,
        #        14736,
        #        "7299"
        #      ],
        #      [
        #        1715279400,
        #        14734,
        #        14768,
        #        14720,
        #        14720,
        #        "14242"
        #      ]
        #    ]
        #  }
        #
        return self.parse_ohlcvs(data, market, str(selectedTimeframe), since, limit)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        # Cube KLine format
        # [
        #     1715278500,  # start_time           |   ohlcv[0]
        #     14695,       # Kline open price.    |   ohlcv[1]
        #     14695,       # Kline close price.   |   ohlcv[2]
        #     14695,       # Kline high price.    |   ohlcv[3]
        #     14695,       # Kline low price.     |   ohlcv[4]
        #     "5784"       # volume_hi            |   ohlcv[5]
        # ]
        #
        # CCXT KLine format
        # [
        #     1504541580000,  # UTC timestamp in milliseconds, integer |   ohlcv[0]
        #     4235.4,        #(O)pen price, float                    |   ohlcv[1]
        #     4240.6,        #(H)ighest price, float                 |   ohlcv[3]
        #     4230.0,        #(L)owest price, float                  |   ohlcv[4]
        #     4230.7,        #(C)losing price, float                 |   ohlcv[2]
        #     37.72941911    #(V)olume, float                        |   ohlcv[5]
        # ],
        normalizer = math.pow(10, self.safe_integer(self.safe_dict(market, 'precision'), 'price'))
        return [
            self.parse_to_numeric(ohlcv[0]),
            self.parse_to_numeric(ohlcv[1]) / normalizer,
            self.parse_to_numeric(ohlcv[3]) / normalizer,
            self.parse_to_numeric(ohlcv[4]) / normalizer,
            self.parse_to_numeric(ohlcv[2]) / normalizer,
            self.parse_to_numeric(ohlcv[5]),
        ]

    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://cubexch.gitbook.io/cube-api/rest-iridium-api#users-positions
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://github.com/ccxt/ccxt/wiki/Manual#order-structure>`
        """
        self.fetch_market_meta()
        request = {}
        self.inject_sub_account_id(request, params)
        response = self.restIridiumPrivateGetUsersSubaccountSubaccountIdPositions(self.extend(request, params))
        subaccountId = self.safe_string(self.options, 'subaccountId')
        allOrders = self.fetch_raw_orders()
        result = self.safe_list(self.safe_dict(self.safe_dict(response, 'result'), subaccountId), 'inner')
        return self.parse_balance({'result': result, 'allOrders': allOrders})

    def parse_balance(self, response) -> Balances:
        result = self.safe_value(response, 'result')
        allOrders = self.safe_value(response, 'allOrders')
        openOrders = []
        filledUnsettledOrders = []
        allMarketsByNumericId = {}
        for i in range(0, self.count_items(self.markets_by_id)):
            marketArrayItem = list(self.markets_by_id.values())[i]
            market = marketArrayItem[0]
            marketInfo = self.safe_dict(market, 'info')
            marketNumericId = self.safe_string(marketInfo, 'marketId')
            allMarketsByNumericId[marketNumericId] = market
        free = {}
        used = {}
        total = {}
        currenciesByNumericId = {}
        for i in range(0, self.count_items(self.currencies)):
            currenciesKeysArray = list(self.currencies.keys())
            targetCurrency = self.safe_value(self.currencies, currenciesKeysArray[i])
            targetCurrencyNumericId = self.safe_integer(targetCurrency, 'numericId')
            currenciesByNumericId[targetCurrencyNumericId] = targetCurrency
        for i in range(0, self.count_items(result)):
            asset = result[i]
            assetAmount = int(self.safe_string(asset, 'amount'))
            if assetAmount > 0:
                assetNumericId = self.parse_to_int(self.safe_string(asset, 'assetId'))
                currency = currenciesByNumericId[assetNumericId]
                currencyPrecision = self.safe_integer(currency, 'precision')
                assetSymbol = self.safe_string(currency, 'code')
                total[assetSymbol] = assetAmount / math.pow(10, currencyPrecision)
                used[assetSymbol] = 0  # To prevent the 'parser' from adding 'null' when there are no orders holding an asset.
                free[assetSymbol] = 0  # To prevent the 'parser' from adding 'null' when there are no orders holding an asset.
        for i in range(0, self.count_items(allOrders)):
            order = allOrders[i]
            orderStatus = self.safe_string(order, 'status')
            if orderStatus == 'open':
                openOrders.append(order)
            if orderStatus == 'filled':
                isSettled = self.safe_string(order, 'settled')
                if not isSettled:
                    filledUnsettledOrders.append(order)
        for i in range(0, self.count_items(openOrders)):
            order = openOrders[i]
            orderMarketId = self.safe_string(order, 'marketId')
            orderMarket = self.safe_dict(allMarketsByNumericId, orderMarketId)
            orderSide = self.safe_string(order, 'side')
            orderBaseToken = self.safe_string(orderMarket, 'base')
            orderQuoteToken = self.safe_string(orderMarket, 'quote')
            orderAmount = self.safe_integer(order, 'qty')
            orderPrice = self.safe_integer(order, 'price')
            targetToken = ''
            lotSize = 0
            if orderSide == 'Ask':
                targetToken = orderBaseToken
                lotSize = self.safe_integer(self.safe_dict(orderMarket, 'info'), 'baseLotSize')
            elif orderSide == 'Bid':
                targetToken = orderQuoteToken
                lotSize = self.safe_integer(self.safe_dict(orderMarket, 'info'), 'quoteLotSize')
            targetCurrency = self.currency(targetToken)
            targetCurrencyPrecision = self.safe_integer(targetCurrency, 'precision')
            orderLockedAmount = 0
            if orderSide == 'Ask':
                orderLockedAmount = orderAmount * lotSize / math.pow(10, targetCurrencyPrecision)
            elif orderSide == 'Bid':
                orderLockedAmount = orderAmount * orderPrice * lotSize / math.pow(10, targetCurrencyPrecision)
            used[targetToken] += orderLockedAmount
            free[targetToken] = total[targetToken] - used[targetToken]
        for i in range(0, self.count_items(total)):  # For when an asset does not have any values locked in orders.
            targetToken = list(total.keys())[i]
            if self.safe_value(free, targetToken) == 0:
                targetTokenTotalAmount = self.safe_value(total, targetToken)
                free[targetToken] = targetTokenTotalAmount
        timestamp = self.milliseconds()
        balanceResult = {
            'info': {
                'balances': result,
                'openOrders': openOrders,
                'filledUnsettledOrders': filledUnsettledOrders,
            },
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'free': free,
            'used': used,
            'total': total,
        }
        for i in range(0, self.count_items(total)):
            assetSymbol = list(total.keys())[i]
            assetBalances = {
                'free': self.safe_number(free, assetSymbol),
                'used': self.safe_number(used, assetSymbol),
                'total': self.safe_number(total, assetSymbol),
            }
            balanceResult[assetSymbol] = assetBalances
        return self.safe_balance(balanceResult)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}) -> Order:
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        marketId = self.safe_string(meta, 'marketId')
        market = self.safe_dict(meta, 'market')
        rawMarketId = self.safe_integer(self.safe_dict(market, 'info'), 'marketId')
        quantityTickSize = self.safe_number(self.safe_dict(market, 'info'), 'quantityTickSize')
        exchangeAmount = None
        if quantityTickSize and quantityTickSize != 0:
            exchangeAmount = self.parse_to_int(amount / quantityTickSize)
        exchangeOrderType = None
        if type == 'limit':
            exchangeOrderType = 0
        elif type == 'market':
            exchangeOrderType = 1
        elif type == 'MARKET_WITH_PROTECTION':
            exchangeOrderType = 2
        else:
            raise InvalidOrder('OrderType was not recognized: ' + type)
        exchangeOrderSide = None
        if side == 'buy':
            exchangeOrderSide = 0
        elif side == 'sell':
            exchangeOrderSide = 1
        else:
            raise InvalidOrder('OrderSide was not recognized: ' + side)
        timestamp = self.milliseconds()
        clientOrderId = self.safe_integer(params, 'clientOrderId', timestamp)
        request = {
            'clientOrderId': clientOrderId,
            'requestId': self.safe_integer(params, 'requestId', 1),
            'marketId': rawMarketId,
            'quantity': exchangeAmount,
            'side': exchangeOrderSide,
            'timeInForce': self.safe_integer(params, 'timeInForce', 1),
            'orderType': exchangeOrderType,
            'selfTradePrevention': self.safe_integer(params, 'selfTradePrevention', 0),
            'postOnly': self.safe_integer(params, 'postOnly', 0),
            'cancelOnDisconnect': self.safe_bool(params, 'cancelOnDisconnect', False),
        }
        priceTickSize = self.parse_to_numeric(self.safe_value(self.safe_dict(market, 'info'), 'priceTickSize'))
        if price is not None:
            lamportPrice = None
            if priceTickSize and priceTickSize != 0:
                lamportPrice = self.parse_to_int(price / priceTickSize)
            request['price'] = lamportPrice
        self.inject_sub_account_id(request, params)
        response = self.restOsmiumPrivatePostOrder(self.extend(request, params))
        self.validate_create_order_response(response)
        order = self.safe_dict(self.safe_dict(response, 'result'), 'Ack')
        exchangeOrderId = self.safe_string(order, 'exchangeOrderId')
        fetchedOrder = self.fetch_raw_order(exchangeOrderId, marketId)
        orderStatus = 'open'
        if not fetchedOrder:
            orderStatus = 'filled' if order else 'rejected'
        return self.parse_order({
            'order': order,
            'fetchedOrder': fetchedOrder,
            'orderStatus': orderStatus,
            'transactionType': 'creation',
        }, market)

    def validate_create_order_response(self, response: object):
        result = self.safe_dict(response, 'result')
        if 'Ack' in result:
            return
        rejection = self.safe_dict(result, 'Rej')
        if rejection is not None:
            rejectReason = self.safe_string(rejection, 'reason')
            if rejectReason is not None:
                self.handle_create_order_reject(rejectReason, rejection)
        raise InvalidOrder('Order response is invalid: No Ack or Rej found.')

    def handle_create_order_reject(self, reason: str, order: object):
        clientOrderId = self.safe_string(order, 'clientOrderId')
        errorMessage = 'Order rejected for clientOrderId ' + clientOrderId + '. Reason: '
        if reason == '0':
            raise InvalidOrder(errorMessage + 'Unclassified error occurred.')
        elif reason == '1':
            raise InvalidOrder(errorMessage + 'Invalid quantity: Quantity was zero.')
        elif reason == '2':
            raise InvalidOrder(errorMessage + 'Invalid market ID: The specified market ID does not exist.')
        elif reason == '3':
            raise InvalidOrder(errorMessage + 'Duplicate order ID: The specified client order ID was not unique among open orders for self subaccount.')
        elif reason == '4':
            raise InvalidOrder(errorMessage + 'Invalid side specified.')
        elif reason == '5':
            raise InvalidOrder(errorMessage + 'Invalid time in force specified.')
        elif reason == '6':
            raise InvalidOrder(errorMessage + 'Invalid order type specified.')
        elif reason == '7':
            raise InvalidOrder(errorMessage + 'Invalid post-only flag specified.')
        elif reason == '8':
            raise InvalidOrder(errorMessage + 'Invalid self-trade prevention specified.')
        elif reason == '9':
            raise InvalidOrder(errorMessage + 'Unknown trader: Internal error with subaccount positions.')
        elif reason == '10':
            raise InvalidOrder(errorMessage + 'Price should not be specified for market or market limit orders.')
        elif reason == '11':
            raise InvalidOrder(errorMessage + 'Post-only with market order is not allowed.')
        elif reason == '12':
            raise InvalidOrder(errorMessage + 'Post-only with invalid time in force.')
        elif reason == '13':
            raise InvalidOrder(errorMessage + 'Exceeded spot position limits.')
        elif reason == '14':
            raise InvalidOrder(errorMessage + 'No opposing resting orders to trade against.')
        elif reason == '15':
            raise InvalidOrder(errorMessage + 'Post-only order would have crossed and traded.')
        elif reason == '16':
            raise InvalidOrder(errorMessage + 'Fill or kill(FOK) order was not fully fillable.')
        elif reason == '17':
            raise InvalidOrder(errorMessage + 'Only order cancelations are accepted at self time.')
        elif reason == '18':
            raise InvalidOrder(errorMessage + 'Protection price would not trade for market-with-protection orders.')
        elif reason == '19':
            raise InvalidOrder(errorMessage + 'Market orders cannot be placed because there is no internal reference price.')
        elif reason == '20':
            raise InvalidOrder(errorMessage + 'Slippage too high: The order would trade beyond allowed protection levels.')
        elif reason == '21':
            raise InvalidOrder(errorMessage + 'Outside price band: Bid price is too low or ask price is too high.')
        else:
            raise InvalidOrder(errorMessage + 'Unknown reason code: ' + reason + '.')

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        marketId = self.safe_string(meta, 'marketId')
        market = self.safe_dict(meta, 'market')
        rawMarketId = self.safe_integer(self.safe_dict(market, 'info'), 'marketId')
        fetchedOrder = self.fetch_raw_order(id, marketId)
        if not fetchedOrder:
            fetchedOrder = {}
        clientOrderId = self.safe_integer(fetchedOrder, 'clientOrderId')
        request = {
            'clientOrderId': clientOrderId,
            'requestId': self.safe_integer(params, 'requestId', 1),
            'marketId': rawMarketId,
        }
        self.inject_sub_account_id(request, params)
        response = self.restOsmiumPrivateDeleteOrder(self.extend(request, params))
        self.validate_cancel_order_response(response, fetchedOrder)
        return self.parse_order({
            'cancellationResponse': response,
            'fetchedOrder': fetchedOrder,
            'transactionType': 'cancellation',
        }, market)

    def validate_cancel_order_response(self, response: object, order: object):
        result = self.safe_dict(response, 'result')
        if 'Ack' in result:
            ack = self.safe_dict(result, 'Ack')
            reason = self.safe_string(ack, 'reason')
            if reason is not None:
                self.handle_cancel_order_ack(reason, ack)
            return
        rejection = self.safe_dict(result, 'Rej')
        if rejection:
            rejectReason = self.safe_string(rejection, 'reason')
            if rejectReason is not None:
                self.handle_cancel_order_reject(rejectReason, order)
        raise InvalidOrder('Cancel order response is invalid: No Ack or Rej found.')

    def handle_cancel_order_reject(self, reason: str, order: object):
        clientOrderId = self.safe_string(order, 'clientOrderId')
        errorMessage = 'Order cancellation rejected for clientOrderId' + clientOrderId + 'Reason: '
        if reason == '0':
            raise InvalidOrder(errorMessage + 'Unclassified error occurred.')
        elif reason == '1':
            raise InvalidOrder(errorMessage + 'Invalid market ID: The specified market ID does not exist.')
        elif reason == '2':
            raise InvalidOrder(errorMessage + 'Order not found: The specified client order ID does not exist for the corresponding market ID and subaccount ID.')
        else:
            raise InvalidOrder(errorMessage + 'Unknown reason code: ' + reason + '.')

    def handle_cancel_order_ack(self, reason: str, ack: object):
        clientOrderId = self.safe_string(ack, 'clientOrderId')
        errorMessage = 'Order rejected for clientOrderId ' + clientOrderId + '. Reason: '
        if reason == '0':
            raise InvalidOrder(errorMessage + 'Unclassified acknowledgment.')
        elif reason == '1':
            raise InvalidOrder(errorMessage + 'Order canceled due to disconnection.')
        elif reason == '2':
            raise InvalidOrder(errorMessage + 'Order was requested to be canceled.')
        elif reason == '3':
            raise InvalidOrder(errorMessage + 'Immediate or cancel(IOC) order was not fully filled.')
        elif reason == '4':
            raise InvalidOrder(errorMessage + 'A resting order was canceled due to self-trade prevention(STP).')
        elif reason == '5':
            raise InvalidOrder(errorMessage + 'An aggressing order was canceled due to self-trade prevention(STP).')
        elif reason == '6':
            raise InvalidOrder(errorMessage + 'Order was covered by a mass-cancel request.')
        elif reason == '7':
            raise InvalidOrder(errorMessage + 'Order was canceled because asset position limits would be otherwise breached.')
        else:
            raise InvalidOrder(errorMessage + 'Unknown acknowledgment reason code:' + reason + '.')

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders
        :see: https://cubexch.gitbook.io/cube-api/rest-osmium-api#orders-1
        :param str symbol: cube cancelAllOrders cannot setting symbol, it will cancel all open orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        market = self.safe_dict(meta, 'market')
        rawMarkeId = self.safe_integer(self.safe_dict(market, 'info'), 'marketId')
        request = {
            'marketId': rawMarkeId,
            'requestId': self.safe_integer(params, 'requestId', 1),
            'side': self.safe_integer(params, 'side', None),
        }
        self.inject_sub_account_id(request, params)
        response = self.restOsmiumPrivateDeleteOrders(self.extend(request, params))
        return {
            'info': self.safe_dict(response, 'result'),
            'market': market,
        }

    def fetch_order(self, id: str, symbol: Str = None, params={}) -> Order:
        """
        fetches information on an order made by the user
        :see: https://cubexch.gitbook.io/cube-api/rest-osmium-api#orders
        :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.com/#/?id=order-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        market = self.safe_dict(meta, 'market')
        order = self.fetch_raw_order(id, symbol, params)
        #
        #  {
        #      "result": {
        #          "orders": [
        #              {
        #                  "clientOrderId": 1713422528124,
        #                  "exchangeOrderId": 1295024967,
        #                  "marketId": 100006,
        #                  "price": 11000,
        #                  "orderQuantity": 1,
        #                  "side": 0,
        #                  "timeInForce": 1,
        #                  "orderType": 0,
        #                  "remainingQuantity": 1,
        #                  "restTime": 1713422528222471490,
        #                  "subaccountId": 38393,
        #                  "cumulativeQuantity": 0,
        #                  "cancelOnDisconnect": False
        #              },
        #              ...
        #          ]
        #      }
        #  }
        #
        return self.parse_order(
            {
                'fetchedOrder': order,
                'transactionType': 'fetching',
            },
            market
        )

    def fetch_raw_order(self, id: str, symbol=None, params={}):
        """
        fetches information on an order made by the user
        :see: https://cubexch.gitbook.io/cube-api/rest-osmium-api#orders
        :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.com/#/?id=order-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        request = {}
        self.inject_sub_account_id(request, params)
        rawResponse = self.restOsmiumPrivateGetOrders(self.extend(request, params))
        #
        # {
        #    "result": {
        #        "orders": [
        #            {
        #                "clientOrderId": 1713422528124,
        #                "exchangeOrderId": 1295024967,
        #                "marketId": 100006,
        #                "price": 11000,
        #                "orderQuantity": 1,
        #                "side": 0,
        #                "timeInForce": 1,
        #                "orderType": 0,
        #                "remainingQuantity": 1,
        #                "restTime": 1713422528222471490,
        #                "subaccountId": 38393,
        #                "cumulativeQuantity": 0,
        #                "cancelOnDisconnect": False
        #            },
        #            ...
        #        ]
        #    }
        # }
        #
        result = self.safe_list(self.safe_dict(rawResponse, 'result'), 'orders')
        order = None
        for i in range(0, self.count_items(result)):
            clientOrderId = self.safe_string(result[i], 'clientOrderId')
            exchangeOrderId = self.safe_string(result[i], 'exchangeOrderId')
            if id == clientOrderId or id == exchangeOrderId:
                order = result[i]
                break
        return order

    def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :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.com/#/?id=order-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        market = self.safe_market(self.safe_string(meta, 'marketId'), self.safe_dict(meta, 'market'), '/')
        rawOrders = self.fetch_raw_orders()
        return self.parse_orders(rawOrders, market, since, limit)

    def parse_orders(self, orders: object, market: Market = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        #
        # the value of orders is either a dict or a list
        #
        # dict
        #
        #     {
        #         'id1': {...},
        #         'id2': {...},
        #         'id3': {...},
        #         ...
        #     }
        #
        # list
        #
        #     [
        #         {'id': 'id1', ...},
        #         {'id': 'id2', ...},
        #         {'id': 'id3', ...},
        #         ...
        #     ]
        #
        results = []
        if isinstance(orders, list):
            for i in range(0, len(orders)):
                order = self.extend(self.parse_order({'fetchedOrder': orders[i], 'transactionType': 'fetching_all'}, market), params)
                results.append(order)
        else:
            ids = list(orders.keys())
            for i in range(0, len(ids)):
                id = ids[i]
                order = self.extend(self.parse_order({'fetchedOrder': orders[id], 'transactionType': 'fetching_all'}, market), params)
                results.append(order)
        results = self.sort_by(results, 'timestamp')
        symbol = None
        if market is not None:
            symbol = market['symbol']
        return self.filter_by_symbol_since_limit(results, symbol, since, limit)

    def parse_order(self, order, market: Market = None):
        transactionType = self.safe_string(order, 'transactionType')
        fetchedOrder = self.safe_dict(order, 'fetchedOrder')
        orderStatus = None
        if transactionType == 'creation':
            orderStatus = self.safe_string(order, 'orderStatus')
            if orderStatus == 'rejected':
                raise InvalidOrder('Order was rejected')
            if orderStatus == 'filled':
                fetchedOrder = self.safe_dict(order, 'order')
        elif transactionType == 'cancellation':
            orderStatus = 'canceled'
        elif transactionType == 'fetching':
            orderStatus = 'open'  # If the order is fetched, it is open
        elif transactionType == 'fetching_all':
            orderStatus = self.safe_string(fetchedOrder, 'status')  # The order status is present in the order body when fetching the endpoint of all orders
        if fetchedOrder is not None:
            if not market:
                market = self.options['mappings']['rawMarketsIdsToMarkets'][self.parse_to_numeric(fetchedOrder['marketId'])]
            exchangeOrderId = self.safe_string(fetchedOrder, 'exchangeOrderId')
            clientOrderId = self.safe_string(fetchedOrder, 'clientOrderId')
            timestampInNanoseconds = None
            timestampInNanoseconds = self.safe_integer(fetchedOrder, 'restTime')
            if timestampInNanoseconds is None:
                timestampInNanoseconds = self.safe_integer(fetchedOrder, 'transactTime')
            if timestampInNanoseconds is None:
                timestampInNanoseconds = self.safe_integer(fetchedOrder, 'createdAt')
            if timestampInNanoseconds is None:
                timestampInNanoseconds = self.safe_integer(fetchedOrder, 'filledAt')
            if timestampInNanoseconds is None:
                timestampInNanoseconds = self.safe_integer(fetchedOrder, 'canceledAt')
            timestampInMilliseconds = self.parse_to_int(timestampInNanoseconds / 1000000)
            symbol = self.safe_string(market, 'symbol')
            orderSideRaw = self.safe_integer(fetchedOrder, 'side')
            orderSide = None
            if orderSideRaw == 0:
                orderSide = 'buy'
            else:
                orderSide = 'sell'
            currency = None
            if orderSide == 'buy':
                currency = self.safe_string(market, 'base')
            else:
                currency = self.safe_string(market, 'quote')
            orderTypeRaw = self.safe_integer(fetchedOrder, 'orderType')
            orderType = None
            if orderTypeRaw == 0:
                orderType = 'limit'
            elif orderTypeRaw == 1:
                orderType = 'market'
            elif orderTypeRaw == 2:
                orderType = 'MARKET_WITH_PROTECTION'
            timeInForce = None
            timeInForceRaw = self.safe_integer(fetchedOrder, 'timeInForce')
            if timeInForceRaw == 0:
                timeInForce = 'IOC'
            elif timeInForceRaw == 1:
                timeInForce = 'GTC'
            elif timeInForceRaw == 2:
                timeInForce = 'FOK'
            priceTickSize = self.parse_to_numeric(self.safe_value(self.safe_dict(market, 'info'), 'priceTickSize'))
            rawPrice = self.safe_integer(fetchedOrder, 'price')
            price = None
            if rawPrice is None or orderType == 'market':
                price = 0
            else:
                if priceTickSize and priceTickSize != 0:
                    price = rawPrice * priceTickSize
            amount = None
            amount = self.safe_integer(fetchedOrder, 'quantity')
            if amount is None:
                amount = self.safe_integer(fetchedOrder, 'qty')
            if amount is None:
                amount = self.safe_integer(fetchedOrder, 'orderQuantity')
            remainingAmount = None
            remainingAmount = self.safe_integer(fetchedOrder, 'remainingQuantity')
            if remainingAmount is None and (orderStatus == 'canceled' or orderStatus == 'filled'):
                remainingAmount = amount
            if remainingAmount is None:
                remainingAmount = 0
            filledAmount = amount - remainingAmount
            tradeFeeRatios = self.safe_dict(self.fees, 'trading')
            rate = None
            if orderSide == 'buy':
                rate = self.safe_number(tradeFeeRatios, 'maker')
            elif orderSide == 'sell':
                rate = self.safe_number(tradeFeeRatios, 'taker')
            quantityTickSize = self.parse_to_numeric(self.safe_value(self.safe_dict(market, 'info'), 'quantityTickSize'))
            decimalAmount = 0
            decimalFilledAmount = 0
            decimalRemainingAmount = 0
            if quantityTickSize and quantityTickSize != 0:
                decimalAmount = amount * quantityTickSize
                decimalFilledAmount = filledAmount * quantityTickSize
                decimalRemainingAmount = remainingAmount * quantityTickSize
            cost = decimalFilledAmount * price
            feeCost = decimalAmount * rate
            # average = None
            # if price is not None and str(price).split(len('.')) == 1:
            #     average = str(self.parse_to_numeric(price) + '.0000001')
            # else:
            #     average = price
            # }
            finalOrder = {
                'id': exchangeOrderId,
                'clientOrderId': clientOrderId,
                'datetime': self.iso8601(timestampInMilliseconds),
                'timestamp': timestampInMilliseconds,
                'lastTradeTimestamp': timestampInMilliseconds,
                'status': orderStatus,
                'symbol': symbol,
                'type': orderType,
                'timeInForce': timeInForce,
                'side': orderSide,
                'price': price,
                'average': None,
                'amount': decimalAmount,
                'filled': decimalFilledAmount,
                'remaining': decimalRemainingAmount,
                'cost': cost,
                'trades': [],
                'fee': {
                    'currency': currency,  # a deduction from the asset hasattr(self, received) trade
                    'cost': feeCost,
                    'rate': rate,
                },
                'info': {
                    'fetchedOrder': fetchedOrder,
                },
            }
            finalOrder['fees'] = self.safe_dict(finalOrder, 'fee')
            return self.safe_order(finalOrder)
        else:
            raise OrderNotFound('Order not found')

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all unfilled currently open orders
        :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.com/#/?id=order-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        market = self.safe_dict(meta, 'market')
        request = {}
        self.inject_sub_account_id(request, params)
        response = self.restOsmiumPrivateGetOrders(self.extend(request, params))
        rawOrders = self.safe_list(self.safe_dict(response, 'result'), 'orders')
        return self.parse_orders(rawOrders, market, since, limit)

    def fetch_raw_orders(self):
        """
        fetch all orders from all markets
        :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.com/#/?id=order-structure>`
        """
        request = {}
        self.inject_sub_account_id(request, {})
        response = self.restIridiumPrivateGetUsersSubaccountSubaccountIdOrders(self.extend(request))
        rawOrders = self.safe_list(self.safe_dict(response, 'result'), 'orders')
        return rawOrders

    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://cubexch.gitbook.io/cube-api/rest-mendelev-api#book-market_id-recent-trades
        :see: https://cubexch.gitbook.io/cube-api/rest-mendelev-api#parsed-book-market_symbol-recent-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 number of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int params['lastId']: order id
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        market = self.safe_dict(meta, 'market')
        rawMarketId = self.safe_string(self.safe_dict(market, 'info'), 'marketId')
        rawMarketSymbol = self.safe_string(self.safe_dict(market, 'info'), 'symbol')
        request = None
        request = {
            'market_id': rawMarketId,
        }
        recentTradesResponse = self.restMendelevPublicGetBookMarketIdRecentTrades(self.extend(request, params))
        #
        # {
        #     "result":{
        #         "trades":[
        #             {
        #                 "tradeId":1192726,
        #                 "price":25730,
        #                 "aggressingSide":1,
        #                 "restingExchangeOrderId":775000423,
        #                 "fillQuantity":2048,
        #                 "transactTime":1710261845127064300,
        #                 "aggressingExchangeOrderId":775000298
        #             },
        #             {
        #                 "tradeId":1192723,
        #                 "price":25730,
        #                 "aggressingSide":0,
        #                 "restingExchangeOrderId":775000298,
        #                 "fillQuantity":5000,
        #                 "transactTime":1710261844303742500,
        #                 "aggressingExchangeOrderId":774996895
        #             }
        #         ]
        #     }
        # }
        #
        request = {
            'market_symbol': rawMarketSymbol,
        }
        parsedRecentTradesResponse = self.restMendelevPublicGetParsedBookMarketSymbolRecentTrades(self.extend(request, params))
        #
        # {
        #     "result":{
        #         "ticker_id":"BTCUSDC",
        #         "trades":[
        #             {
        #                 "id":1106939,
        #                 "p":63565.6,
        #                 "q":0.01,
        #                 "side":"Ask",
        #                 "ts":1711153560907
        #             },
        #             {
        #                 "id":1107084,
        #                 "p":63852.9,
        #                 "q":0.01,
        #                 "side":"Bid",
        #                 "ts":1711156552440
        #             }
        #         ]
        #     }
        # }
        #
        tradesAndParsedTrades = {
            'trades': self.safe_list(self.safe_dict(recentTradesResponse, 'result'), 'trades'),
            'parsedTrades': self.safe_list(self.safe_dict(parsedRecentTradesResponse, 'result'), 'trades'),
        }
        rawTrades = [tradesAndParsedTrades]
        parsedTrades = self.parse_trades(rawTrades, market)
        return self.filter_by_symbol_since_limit(parsedTrades, symbol, since, limit)

    def parse_trades(self, trades, market: Market = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        parsedTrades = self.safe_value(trades[0], 'parsedTrades')
        finalTrades = []
        if parsedTrades is not None and self.count_items(parsedTrades) > 0:
            for i in range(0, self.count_items(parsedTrades)):
                trade = parsedTrades[i]
                finalTrades.append(self.parse_trade(trade, market))
        return finalTrades

    def parse_trade(self, trade, market=None):
        timestampSeconds = 0
        if trade['ts'] is not None:
            timestampSeconds = self.safe_integer(trade, 'ts')
        elif trade['transactTime'] is not None:
            timestampNanoseconds = trade['transactTime']
            timestampSeconds = timestampNanoseconds / 1000000
        datetime = self.iso8601(timestampSeconds)
        tradeSide = self.safe_string(trade, 'side')
        side = ''
        if tradeSide == 'Bid':
            side = 'buy'
        elif tradeSide == 'Ask':
            side = 'sell'
        marketSymbol = self.safe_string(market, 'symbol')
        price = float(self.safe_string(trade, 'p'))
        amount = float(self.safe_string(trade, 'q'))
        return self.safe_trade({
            'info': trade,
            'timestamp': timestampSeconds,
            'datetime': datetime,
            'symbol': marketSymbol,
            'id': self.safe_string(trade, 'id'),
            'order': None,
            'type': None,
            'takerOrMaker': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': None,
            'fees': [
                {
                    'cost': None,
                    'currency': None,
                    'rate': None,
                },
            ],
        }, market)

    def fetch_trading_fee(self, symbol: str, params={}) -> TradingFeeInterface:
        """
        fetch the trading fees for a market
        :see: https://cubexch.gitbook.io/cube-api/rest-iridium-api#users-fee-estimate-market-id
        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `fee structure <https://docs.ccxt.com/#/?id=fee-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        market = self.safe_dict(meta, 'market')
        rawMarketId = self.safe_integer(self.safe_dict(market, 'info'), 'marketId')
        request = {
            'market_id': rawMarketId,
        }
        response = self.restIridiumPrivateGetUsersFeeEstimateMarketId(self.extend(request, params))
        # {
        #     "result": {
        #         "userKey": "123e4567-e89b-12d3-a456-426614174000",
        #         "makerFeeRatio": 0,
        #         "takerFeeRatio": 0
        #     }
        # }
        return {
            'info': response,
            'symbol': symbol,
            'maker': self.safe_number(self.safe_dict(response, 'result'), 'makerFeeRatio'),
            'taker': self.safe_number(self.safe_dict(response, 'result'), 'takerFeeRatio'),
            'percentage': None,
            'tierBased': None,
        }

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        fetch all trades made by the user
        :see: https://cubexch.gitbook.io/cube-api/rest-iridium-api#users-subaccount-subaccount_id-fills
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        meta = self.fetch_market_meta(symbol)
        symbol = self.safe_string(meta, 'symbol')
        allOrders = self.fetch_orders(symbol, since, limit, params)
        myTrades = []
        for i in range(0, self.count_items(allOrders)):
            orderStatus = self.safe_string(allOrders[i], 'status')
            if orderStatus == 'filled':
                orderFills = self.safe_list(self.safe_dict(self.safe_dict(allOrders[i], 'info'), 'fetchedOrder'), 'fills')
                fillsLength = self.count_items(orderFills)
                for j in range(0, fillsLength):
                    trade = orderFills[j]
                    parsedTrade = self.parse_my_trade(trade, allOrders[i])
                    myTrades.append(parsedTrade)
        return self.filter_by_symbol_since_limit(myTrades, symbol, since, limit)

    def parse_my_trade(self, trade, order):
        tradeId = self.safe_string(trade, 'tradeId')
        timestampInNanoseconds = self.safe_integer(trade, 'filledAt')
        timestampInMilliseconds = self.parse_to_int(timestampInNanoseconds / 1000000)
        datetime = self.iso8601(timestampInMilliseconds)
        meta = self.fetch_market_meta(self.safe_string(order, 'symbol'))
        marketSymbol = self.safe_string(meta, 'symbol')
        orderType = self.safe_string(order, 'type')
        orderId = None
        if orderType == 'limit':
            orderId = self.safe_string(order, 'id')
        elif orderType == 'market':
            orderId = self.safe_string(order, 'clientOrderId')
        orderSide = self.safe_string(order, 'side')
        timeInForce = self.safe_string(order, 'timeInForce')
        takerOrMaker = None
        if orderType == 'market' or timeInForce == 'IOC' or timeInForce == 'FOK':
            takerOrMaker = 'taker'
        else:
            takerOrMaker = 'maker'
        orderPrice = self.safe_number(order, 'price')
        orderAmount = self.safe_number(order, 'amount')
        cost = None
        if orderPrice is not None:
            cost = orderPrice * orderAmount
        fee = self.safe_dict(order, 'fee')
        fees = self.safe_list(order, 'fees')
        return {
            'id': tradeId,
            'timestamp': timestampInMilliseconds,
            'datetime': datetime,
            'symbol': marketSymbol,
            'order': orderId,
            'type': orderType,
            'side': orderSide,
            'takerOrMaker': takerOrMaker,
            'price': orderPrice,
            'amount': orderAmount,
            'cost': cost,
            'fee': fee,
            'fees': fees,
            'info': order,
        }

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches a list of closed(or canceled) orders
        :see: https://github.com/ccxt/ccxt/wiki/Manual#understanding-the-orders-api-design
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        allOrders = self.fetch_orders(symbol, since, limit, params)
        closedOrders = []
        for i in range(0, self.count_items(allOrders)):
            orderStatus = self.safe_string(allOrders[i], 'status')
            if orderStatus == 'canceled' or orderStatus == 'closed':
                closedOrders.append(allOrders[i])
        return closedOrders

    def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API
        :see: https://binance-docs.github.io/apidocs/spot/en/#system-status-system
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `status structure <https://docs.ccxt.com/#/?id=exchange-status-structure>`
        """
        response = self.restIridiumPublicGetMarkets(params)
        keys = list(response.keys())
        keysLength = len(keys)
        formattedStatus = None
        if keysLength:
            formattedStatus = 'ok'
        else:
            formattedStatus = 'maintenance'
        return {
            'status': formattedStatus,
            'updated': None,
            'eta': None,
            'url': None,
            'info': None,
        }

    def fetch_deposits(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :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>`
        """
        self.fetch_market_meta()
        request = {}
        currency = None
        if symbol is not None:
            currency = self.currency(symbol)
            request['asset_symbol'] = self.safe_string(self.safe_dict(currency, 'info'), 'assetId')
        if limit is not None:
            request['limit'] = limit
        self.inject_sub_account_id(request, params)
        subAccountId = self.safe_string(request, 'subaccountId')
        response = self.restIridiumPrivateGetUsersSubaccountSubaccountIdDeposits(self.extend(request, params))
        #
        # result: {
        #     "161": {
        #       name: "primary",
        #       inner: [
        #         {
        #           assetId: 80005,
        #           amount: "5000000000",
        #           txnHash: "5E8xrkpCdwsczNDqGcezQ6agxDoFjXN9YVQFE4ZDk7vcdmdQHbPRSw7z3F769kkg4F57Vh4HsAsaKeFt8Z7qHhjZ",
        #           txnIndex: 1,
        #           createdAt: "2024-03-27T23:51:14.933108Z",
        #           updatedAt: "2024-03-27T23:51:28.93706Z",
        #           txnState: "confirmed",
        #           kytStatus: "accept",
        #           address: "79xoQgxNgKbjDrwp3Gb6t1oc1NmcgZ3PQFE7i1XCrk5x",
        #           fiatToCrypto: False,
        #         },
        #       ],
        #     },
        #   },
        #
        deposits = self.safe_list(self.safe_dict(self.safe_dict(response, 'result'), subAccountId), 'inner', [])
        for i in range(0, len(deposits)):
            deposits[i]['type'] = 'deposit'
        return self.parse_transactions(deposits, currency, since, limit, params)

    def fetch_deposit_addresses(self, codes: Strings = None, params={}):
        """
        fetch deposit addresses for multiple currencies and chain types
        :see: https://cubexch.gitbook.io/cube-api/rest-iridium-api#users-info
        :param str[]|None 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 structure <https://docs.ccxt.com/#/?id=address-structure>`
        """
        self.fetch_market_meta()
        rawUsersInfoResponse = self.restIridiumPrivateGetUsersInfo(params)
        rawMarketsResponse = self.restIridiumPublicGetMarkets(params)
        #
        # getUsersInfo
        # {
        #     ...
        #     "subaccounts": [
        #         {
        #             "id": 161,
        #             "name": "primary",
        #             "addresses": {
        #                 "101": "tb1p74t28ne95z0rptyhqfwzx8xh2xclw4t2ua46fyzqr6x76km7sp8qdy2n3e",
        #                 "103": "Cz3BnXPhYudZscqHHh5eDusYzVn4Bpb5EKBvrG4Zvaf3",
        #                 "105": "nf8FjTqGW4B7Bx5UZhkRUhaGGnMjZ7LUPw",
        #                 "106": "5G6AHKi87NCNkFvPfXx4aX3qLcoKzWRTK4KcHNPeVPB6qZyT",
        #                 "107": "tltc1qwlky3uxaygw4g3yr39cw0xvzw323xynmunuca3",
        #                 "108": "cosmos1wlky3uxaygw4g3yr39cw0xvzw323xynme8m7jx"
        #             },
        #             "hasOrderHistory": True
        #         }
        #     ]
        #     ...
        # }
        #
        # getMarkets
        # {
        #     ...
        #     sources: [
        #        {
        #            sourceId: 1,
        #            name: "bitcoin",
        #            transactionExplorer: "https://mempool.space/tx/{}",
        #            addressExplorer: "https://mempool.space/address/{}",
        #            metadata: {
        #              network: "Mainnet",
        #              scope: "bitcoin",
        #              type: "mainnet",
        #           },
        #        },
        #     ]
        #     ...
        # }
        if codes is None:
            codes = []
        newCodes = []
        for i in range(0, len(codes)):
            newCodes[i] = codes[i].upper()
        codes = newCodes
        sourcesByIds = {}
        sources = self.safe_list(rawMarketsResponse, 'sources', [])
        for i in range(0, len(sources)):
            source = sources[i]
            sourceId = self.safe_string(source, 'sourceId')
            sourcesByIds[sourceId] = source
        subAccounts = self.safe_list(rawUsersInfoResponse, 'subaccounts', [])
        result = {
            'info': {
                'subaccounts': subAccounts,
                'sources': sources,
            },
        }
        for i in range(0, len(subAccounts)):
            subAccount = subAccounts[i]
            subAccountId = self.safe_string(subAccount, 'id')
            addresses = self.safe_list(subAccount, 'addresses', [])
            sourcesIds = list(addresses.keys())
            for j in range(0, len(sourcesIds)):
                sourceId = sourcesIds[j]
                address = addresses[sourceId]
                self.check_address(address)
                source = self.safe_string(sourcesByIds, sourceId)
                currency = self.currency(self.safe_string(source, 'name'))
                sourceMetaData = self.safe_dict(source, 'metadata')
                network = self.safe_string(sourceMetaData, 'scope') + '-' + self.safe_string(sourceMetaData, 'type')
                currencyCode = self.safe_string(currency, 'code')
                if not self.in_array(currencyCode, codes):
                    continue
                result[currencyCode] = {
                    'info': {
                        'subaccount': subAccount,
                        'source': source,
                    },
                    'currency': currencyCode,
                    'address': address,
                    'network': network,
                    'tag': subAccountId,
                }
        return result

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}) -> Transaction:
        """
        make a withdrawal
        :see: https://cubexch.gitbook.io/cube-api/rest-iridium-api#users-withdraw
        :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.fetch_market_meta()
        currency = self.currency(code)
        currencyPrecision = self.safe_integer(currency, 'precision')
        exchangeAmount = int(round(amount * math.pow(10, currencyPrecision)))
        request = {
            'amount': self.number_to_string(exchangeAmount),
            'destination': address,
            'assetId': self.safe_integer(currency, 'numericId'),
        }
        self.inject_sub_account_id(request, params)
        response = self.restIridiumPrivatePostUsersWithdraw(self.extend(request, params))
        #
        # {
        #     "result": {
        #       "status": "pending",
        #       "approved": False,
        #       "reason": "text"
        #     }
        #     "result": {
        #     "status": "accept",
        #     "approved": True
        #    }
        # }
        #
        return response

    def fetch_withdrawals(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :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>`
        """
        self.fetch_market_meta()
        request = {}
        currency = None
        if symbol is not None:
            currency = self.currency(symbol)
            request['assetId'] = currency['id']
        if limit is not None:
            request['limit'] = limit
        self.inject_sub_account_id(request, params)
        subAccountId = self.safe_string(request, 'subaccountId')
        response = self.restIridiumPrivateGetUsersSubaccountSubaccountIdWithdrawals(self.extend(request, params))
        #
        # result: {
        #     "161": {
        #       name: "primary",
        #       inner: [
        #         {
        #           assetId: 80005,
        #           amount: "100000000",
        #           createdAt: "2024-05-02T18:03:36.779453Z",
        #           updatedAt: "2024-05-02T18:03:37.941902Z",
        #           attemptId: 208,
        #           address: "6khUqefutr3xA6fEUnZfRMRGwER8BBTZZFFgBPhuUyyp",
        #           kytStatus: "accept",
        #           approved: True,
        #         },
        #       ],
        #     },
        #   },
        #
        withdrawals = self.safe_list(self.safe_dict(self.safe_dict(response, 'result'), subAccountId), 'inner', [])
        for i in range(0, len(withdrawals)):
            withdrawals[i]['type'] = 'withdrawal'
        return self.parse_transactions(withdrawals, currency, since, limit, params)

    def parse_transaction(self, transaction, currency: Currency = None) -> Transaction:
        #
        # fetchDeposits
        #
        # result: {
        #     "161": {
        #       name: "primary",
        #       inner: [
        #         {
        #           assetId: 80005,
        #           amount: "5000000000",
        #           txnHash: "5E8xrkpCdwsczNDqGcezQ6agxDoFjXN9YVQFE4ZDk7vcdmdQHbPRSw7z3F769kkg4F57Vh4HsAsaKeFt8Z7qHhjZ",
        #           txnIndex: 1,
        #           createdAt: "2024-03-27T23:51:14.933108Z",
        #           updatedAt: "2024-03-27T23:51:28.93706Z",
        #           txnState: "confirmed",
        #           kytStatus: "accept",
        #           address: "79xoQgxNgKbjDrwp3Gb6t1oc1NmcgZ3PQFE7i1XCrk5x",
        #           fiatToCrypto: False,
        #         },
        #       ],
        #     },
        #   },
        #
        # fetchWithdrawals
        #
        # result: {
        #     "161": {
        #       name: "primary",
        #       inner: [
        #         {
        #           assetId: 80005,
        #           amount: "100000000",
        #           createdAt: "2024-05-02T18:03:36.779453Z",
        #           updatedAt: "2024-05-02T18:03:37.941902Z",
        #           attemptId: 208,
        #           address: "6khUqefutr3xA6fEUnZfRMRGwER8BBTZZFFgBPhuUyyp",
        #           kytStatus: "accept",
        #           approved: True,
        #         },
        #       ],
        #     },
        #   },
        #
        # TODO Expose self object globally for the exchange so the currencies can be retrieved in O(1) timenot !!
        currenciesByNumericId = {}
        for i in range(0, self.count_items(self.currencies)):
            currenciesKeysArray = list(self.currencies.keys())
            targetCurrency = self.safe_value(self.currencies, currenciesKeysArray[i])
            targetCurrencyNumericId = self.safe_integer(targetCurrency, 'numericId')
            currenciesByNumericId[targetCurrencyNumericId] = targetCurrency
        id = self.safe_string(transaction, 'attemptId')
        txId = self.safe_string(transaction, 'txnHash')
        code = self.safe_string(currenciesByNumericId[self.safe_integer(transaction, 'assetId')], 'code')
        timestamp = self.parse8601(self.safe_string(transaction, 'createdAt'))
        updated = self.parse8601(self.safe_string(transaction, 'updatedAt'))
        status = self.parse_transaction_status(self.safe_string(transaction, 'kytStatus'))
        address = self.safe_string(transaction, 'address')
        type = self.safe_string(transaction, 'type', None)
        assetAmount = self.parse_to_numeric(self.safe_string(transaction, 'amount'))
        assetNumericId = self.parse_to_int(self.safe_string(transaction, 'assetId'))
        currency = currenciesByNumericId[assetNumericId]
        currencyPrecision = self.safe_integer(currency, 'precision')
        amount = assetAmount / math.pow(10, currencyPrecision)
        return {
            'info': transaction,
            'id': id,
            'txid': txId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'network': None,
            'addressFrom': None,
            'address': address,
            'addressTo': address,
            'tagFrom': None,
            'tag': None,
            'tagTo': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': None,
            'comment': None,
            'internal': None,
        }

    def parse_transaction_status(self, status):
        statuses = {
            # what are other statuses here?
            'WITHHOLD': 'ok',
            'UNCONFIRMED': 'pending',
            'CONFIRMED': 'ok',
            'COMPLETED': 'ok',
            'PENDING': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def count_with_loop(self, items):
        counter = 0
        for i in range(0, len(items)):
            counter += 1
        return counter

    def count_items(self, input):
        counter = 0
        if isinstance(input, list):
            counter = self.count_with_loop(input)
        elif isinstance(input, dict) and input is not None:
            keys = list(input.keys())
            counter = self.count_with_loop(keys)
        elif isinstance(input, str):
            counter = self.count_with_loop(self.string_to_chars_array(input))
        return counter
