UNPKG

ccxt-xigua

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

4,059 lines 179 kB
'use strict';

// ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { BadRequest, BadSymbol, InvalidOrder, InvalidAddress, ExchangeError, ArgumentsRequired, NotSupported, InsufficientFunds } = require ('./base/errors');
const { TICK_SIZE } = require ('./base/functions/number');
const Precise = require ('./base/Precise');

// ---------------------------------------------------------------------------

module.exports = class mexc3 extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'mexc3',
            'name': 'MEXC Global',
            'countries': [ 'SC' ], // Seychelles
            'rateLimit': 50, // default rate limit is 20 times per second
            'version': 'v3',
            'has': {
                'CORS': undefined,
                'spot': undefined,
                'margin': undefined,
                'swap': undefined,
                'future': undefined,
                'option': undefined,
                'addMargin': true,
                'cancelAllOrders': true,
                'cancelOrder': true,
                'cancelOrders': undefined,
                'createDepositAddress': undefined,
                'createLimitOrder': undefined,
                'createMarketOrder': undefined,
                'createOrder': true,
                'deposit': undefined,
                'editOrder': undefined,
                'fetchAccounts': true,
                'fetchBalance': true,
                'fetchBidsAsks': true,
                'fetchBorrowRate': undefined,
                'fetchBorrowRateHistory': undefined,
                'fetchBorrowRates': undefined,
                'fetchBorrowRatesPerSymbol': undefined,
                'fetchCanceledOrders': true,
                'fetchClosedOrder': undefined,
                'fetchClosedOrders': true,
                'fetchCurrencies': true,
                'fetchDeposit': undefined,
                'fetchDepositAddress': true,
                'fetchDepositAddresses': undefined,
                'fetchDepositAddressesByNetwork': true,
                'fetchDeposits': true,
                'fetchFundingHistory': true,
                'fetchFundingRate': true,
                'fetchFundingRateHistory': true,
                'fetchFundingRates': undefined,
                'fetchIndexOHLCV': true,
                'fetchL2OrderBook': true,
                'fetchLedger': undefined,
                'fetchLedgerEntry': undefined,
                'fetchLeverageTiers': true,
                'fetchMarketLeverageTiers': undefined,
                'fetchMarkets': true,
                'fetchMarkOHLCV': true,
                'fetchMyTrades': true,
                'fetchOHLCV': true,
                'fetchOpenOrder': undefined,
                'fetchOpenOrders': true,
                'fetchOrder': true,
                'fetchOrderBook': true,
                'fetchOrderBooks': undefined,
                'fetchOrders': true,
                'fetchOrderTrades': true,
                'fetchPosition': true,
                'fetchPositions': true,
                'fetchPositionsRisk': undefined,
                'fetchPremiumIndexOHLCV': false,
                'fetchStatus': true,
                'fetchTicker': true,
                'fetchTickers': true,
                'fetchTime': true,
                'fetchTrades': true,
                'fetchTradingFee': undefined,
                'fetchTradingFees': true,
                'fetchTradingLimits': undefined,
                'fetchTransactionFee': undefined,
                'fetchTransactionFees': undefined,
                'fetchTransactions': undefined,
                'fetchTransfer': true,
                'fetchTransfers': true,
                'fetchWithdrawal': undefined,
                'fetchWithdrawals': true,
                'loadMarkets': undefined,
                'privateAPI': true,
                'publicAPI': true,
                'reduceMargin': true,
                'setLeverage': true,
                'setMarginMode': undefined,
                'setPositionMode': undefined,
                'signIn': undefined,
                'transfer': undefined,
                'withdraw': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/137283979-8b2a818d-8633-461b-bfca-de89e8c446b2.jpg',
                'api': {
                    'spot': {
                        'public': 'https://api.mexc.com',
                        'private': 'https://api.mexc.com',
                    },
                    'spot2': {
                        'public': 'https://www.mexc.com/open/api/v2',
                        'private': 'https://www.mexc.com/open/api/v2',
                    },
                    'contract': {
                        'public': 'https://contract.mexc.com/api/v1/contract',
                        'private': 'https://contract.mexc.com/api/v1/private',
                    },
                },
                'www': 'https://www.mexc.com/',
                'doc': [
                    'https://mxcdevelop.github.io/apidocs/spot_v3_en/',
                    'https://mxcdevelop.github.io/APIDoc/', // v1 & v2 : soon to be deprecated
                ],
                'fees': [
                    'https://www.mexc.com/fee',
                ],
                'referral': 'https://m.mexc.com/auth/signup?inviteCode=1FQ1G',
            },
            'api': {
                'spot': {
                    'public': {
                        'get': {
                            'ping': 1,
                            'time': 1,
                            'exchangeInfo': 1,
                            'depth': 1,
                            'trades': 1,
                            'historicalTrades': 1,
                            'aggTrades': 1,
                            'klines': 1,
                            'avgPrice': 1,
                            'ticker/24hr': 1,
                            'ticker/price': 1,
                            'ticker/bookTicker': 1,
                            'etf/info': 1,
                        },
                    },
                    'private': {
                        'get': {
                            'order': 1,
                            'openOrders': 1,
                            'allOrders': 1,
                            'account': 1,
                            'myTrades': 1,
                            'sub-account/list': 1,
                            'sub-account/apiKey': 1,
                        },
                        'post': {
                            'order': 1,
                            'order/test': 1,
                            'sub-account/virtualSubAccount': 1,
                            'sub-account/apiKey': 1,
                        },
                        'delete': {
                            'order': 1,
                            'openOrders': 1,
                            'sub-account/apiKey': 1,
                        },
                    },
                },
                'contract': {
                    'public': {
                        'get': {
                            'ping': 2,
                            'detail': 2,
                            'support_currencies': 2, // TODO: should we implement 'fetchCurrencies' solely for swap? because spot doesnt have it atm
                            'depth/{symbol}': 2,
                            'depth_commits/{symbol}/{limit}': 2,
                            'index_price/{symbol}': 2,
                            'fair_price/{symbol}': 2,
                            'funding_rate/{symbol}': 2,
                            'kline/{symbol}': 2,
                            'kline/index_price/{symbol}': 2,
                            'kline/fair_price/{symbol}': 2,
                            'deals/{symbol}': 2,
                            'ticker': 2,
                            'risk_reverse': 2,
                            'risk_reverse/history': 2,
                            'funding_rate/history': 2,
                        },
                    },
                    'private': {
                        'get': {
                            'account/assets': 2,
                            'account/asset/{currency}': 2,
                            'account/transfer_record': 2,
                            'position/list/history_positions': 2,
                            'position/open_positions': 2,
                            'position/funding_records': 2,
                            'order/list/open_orders/{symbol}': 2,
                            'order/list/history_orders': 2,
                            'order/external/{symbol}/{external_oid}': 2,
                            'order/get/{order_id}': 2,
                            'order/batch_query': 8,
                            'order/deal_details/{order_id}': 2,
                            'order/list/order_deals': 2,
                            'planorder/list/orders': 2,
                            'stoporder/list/orders': 2,
                            'stoporder/order_details/{stop_order_id}': 2,
                            'account/risk_limit': 2, // TO_DO: gets max/min position size, allowed sides, leverage, maintenance margin, initial margin, etc...
                            'account/tiered_fee_rate': 2, // TO_DO: taker/maker fees for account
                        },
                        'post': {
                            'position/change_margin': 2,
                            'position/change_leverage': 2,
                            'order/submit': 2,
                            'order/submit_batch': 40,
                            'order/cancel': 2,
                            'order/cancel_with_external': 2,
                            'order/cancel_all': 2,
                            'account/change_risk_level': 2,
                            'planorder/place': 2,
                            'planorder/cancel': 2,
                            'planorder/cancel_all': 2,
                            'stoporder/cancel': 2,
                            'stoporder/cancel_all': 2,
                            'stoporder/change_price': 2,
                            'stoporder/change_plan_price': 2,
                        },
                    },
                },
                'spot2': {
                    'public': {
                        'get': {
                            'market/symbols': 1,
                            'market/coin/list': 2,
                            'common/timestamp': 1,
                            'common/ping': 1,
                            'market/ticker': 1,
                            'market/depth': 1,
                            'market/deals': 1,
                            'market/kline': 1,
                            'market/api_default_symbols': 2,
                        },
                    },
                    'private': {
                        'get': {
                            'account/info': 1,
                            'order/open_orders': 1,
                            'order/list': 1,
                            'order/query': 1,
                            'order/deals': 1,
                            'order/deal_detail': 1,
                            'asset/deposit/address/list': 2,
                            'asset/deposit/list': 2,
                            'asset/address/list': 2,
                            'asset/withdraw/list': 2,
                            'asset/internal/transfer/record': 10,
                            'account/balance': 10,
                            'asset/internal/transfer/info': 10,
                            'market/api_symbols': 2,
                        },
                        'post': {
                            'order/place': 1,
                            'order/place_batch': 1,
                            'asset/withdraw': 2,
                            'asset/internal/transfer': 10,
                        },
                        'delete': {
                            'order/cancel': 1,
                            'order/cancel_by_symbol': 1,
                            'asset/withdraw': 2,
                        },
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'timeframes': {
                '1m': '1m', // spot, swap
                '3m': '3m', // spot
                '5m': '5m', // spot, swap
                '15m': '15m', // spot, swap
                '30m': '30m', // spot, swap
                '1h': '1h', // spot, swap
                '2h': '2h', // spot
                '4h': '4h', // spot, swap
                '6h': '6h', // spot
                '8h': '8h', // spot, swap
                '12h': '12h', // spot
                '1d': '1d', // spot, swap
                '3d': '3d', // spot
                '1w': '1w', // spot, swap
                '1M': '1M', // spot, swap
            },
            'fees': {
                'trading': {
                    'tierBased': false,
                    'percentage': true,
                    'maker': 0.2 / 100, // maker / taker
                    'taker': 0.2 / 100,
                },
            },
            'options': {
                'createMarketBuyOrderRequiresPrice': true,
                'unavailableContracts': {
                    'BTC/USDT:USDT': true,
                    'LTC/USDT:USDT': true,
                    'ETH/USDT:USDT': true,
                },
                'fetchMarkets': {
                    'types': {
                        'spot': true,
                        'future': {
                            'linear': false,
                            'inverse': false,
                        },
                        'swap': {
                            'linear': true,
                            'inverse': false,
                        },
                    },
                },
                'timeframes': {
                    'spot': {
                        '1m': '1m',
                        '3m': '3m',
                        '5m': '5m',
                        '15m': '15m',
                        '30m': '30m',
                        '1h': '1h',
                        '2h': '2h',
                        '4h': '4h',
                        '6h': '6h',
                        '8h': '8h',
                        '12h': '12h',
                        '1d': '1d',
                        '3d': '3d',
                        '1w': '1w',
                        '1M': '1M',
                    },
                    'swap': {
                        '1m': 'Min1',
                        '5m': 'Min5',
                        '15m': 'Min15',
                        '30m': 'Min30',
                        '1h': 'Min60',
                        '4h': 'Hour4',
                        '8h': 'Hour8',
                        '1d': 'Day1',
                        '1w': 'Week1',
                        '1M': 'Month1',
                    },
                },
                'defaultType': 'spot', // spot, swap
                'networks': {
                    'TRX': 'TRC-20',
                    'TRC20': 'TRC-20',
                    'ETH': 'ERC-20',
                    'ERC20': 'ERC-20',
                    'BEP20': 'BEP20(BSC)',
                    'BSC': 'BEP20(BSC)',
                },
                'networkAliases': {
                    'BSC(BEP20)': 'BSC',
                },
                'recvWindow': 5 * 1000, // 5 sec, default
                'maxTimeTillEnd': 90 * 86400 * 1000 - 1, // 90 days
            },
            'commonCurrencies': {
                'BEYONDPROTOCOL': 'BEYOND',
                'BIFI': 'BIFIF',
                'BYN': 'BeyondFi',
                'COFI': 'COFIX', // conflict with CoinFi
                'DFI': 'DfiStarter',
                'DFT': 'dFuture',
                'DRK': 'DRK',
                'EGC': 'Egoras Credit',
                'FLUX1': 'FLUX', // switched places
                'FLUX': 'FLUX1', // switched places
                'FREE': 'FreeRossDAO', // conflict with FREE Coin
                'HERO': 'Step Hero', // conflict with Metahero
                'MIMO': 'Mimosa',
                'PROS': 'Pros.Finance', // conflict with Prosper
                'SIN': 'Sin City Token',
            },
            'exceptions': {
                'exact': {
                    // until mexc migrates fully to v3, it might be worth to note the version & market aside errors, not easily remove obsolete version's exceptions in future
                    '-1128': BadRequest,
                    '-2011': BadRequest,
                    '-1121': BadSymbol,
                    '2009': InvalidOrder, // {"success":false,"code":2009,"message":"Position is not exists or closed."}
                    '2011': BadRequest,
                    '30004': InsufficientFunds,
                    '1002': InvalidOrder,
                    '30019': BadRequest,
                    '30005': InvalidOrder,
                    '2003': InvalidOrder,
                    '2005': InsufficientFunds,
                    '600': BadRequest,
                },
                'broad': {
                    'Order quantity error, please try to modify.': BadRequest, // code:2011
                    'Combination of optional parameters invalid': BadRequest, // code:-2011
                    'api market order is disabled': BadRequest, //
                    'Contract not allow place order!': InvalidOrder, // code:1002
                    'Oversold': InvalidOrder, // code:30005
                    'Insufficient position': InsufficientFunds, // code:30004
                    'Insufficient balance!': InsufficientFunds, // code:2005
                    'Bid price is great than max allow price': InvalidOrder, // code:2003
                    'Invalid symbol.': BadSymbol, // code:-1121
                    'Param error!': BadRequest, // code:600
                },
            },
        });
    }

    async fetchStatus (params = {}) {
        /**
         * @method
         * @name mexc3#fetchStatus
         * @description the latest known information on the availability of the exchange API
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [status structure]{@link https://docs.ccxt.com/en/latest/manual.html#exchange-status-structure}
         */
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchStatus', undefined, params);
        let response = undefined;
        let status = undefined;
        let updated = undefined;
        if (marketType === 'spot') {
            response = await this.spotPublicGetPing (query);
            //
            //     {}
            //
            status = Object.keys (response).length ? this.json (response) : 'ok';
        } else if (marketType === 'swap') {
            response = await this.contractPublicGetPing (query);
            //
            //     {"success":true,"code":"0","data":"1648124374985"}
            //
            status = this.safeValue (response, 'success') ? 'ok' : this.json (response);
            updated = this.safeInteger (response, 'data');
        }
        return {
            'status': status,
            'updated': updated,
            'url': undefined,
            'eta': undefined,
            'info': response,
        };
    }

    async fetchTime (params = {}) {
        /**
         * @method
         * @name mexc3#fetchTime
         * @description fetches the current integer timestamp in milliseconds from the exchange server
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {int} the current integer timestamp in milliseconds from the exchange server
         */
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchTime', undefined, params);
        let response = undefined;
        if (marketType === 'spot') {
            response = await this.spotPublicGetTime (query);
            //
            //     {"serverTime": "1647519277579"}
            //
            return this.safeInteger (response, 'serverTime');
        } else if (marketType === 'swap') {
            response = await this.contractPublicGetPing (query);
            //
            //     {"success":true,"code":"0","data":"1648124374985"}
            //
            return this.safeInteger (response, 'data');
        }
    }

    async fetchCurrencies (params = {}) {
        /**
         * @method
         * @name mexc3#fetchCurrencies
         * @description fetches all available currencies on an exchange
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} an associative dictionary of currencies
         */
        const response = await this.spot2PublicGetMarketCoinList (params);
        //
        //     {
        //         "code":200,
        //         "data":[
        //             {
        //                 "currency":"AGLD",
        //                 "coins":[
        //                     {
        //                         "chain":"ERC20",
        //                         "precision":18,
        //                         "fee":8.09,
        //                         "is_withdraw_enabled":true,
        //                         "is_deposit_enabled":true,
        //                         "deposit_min_confirm":16,
        //                         "withdraw_limit_max":500000.0,
        //                         "withdraw_limit_min":14.0
        //                     }
        //                 ],
        //                 "full_name":"Adventure Gold"
        //             },
        //         ]
        //     }
        //
        const data = this.safeValue (response, 'data', []);
        const result = {};
        for (let i = 0; i < data.length; i++) {
            const currency = data[i];
            const id = this.safeString (currency, 'currency');
            const code = this.safeCurrencyCode (id);
            const name = this.safeString (currency, 'full_name');
            let currencyActive = false;
            let currencyPrecision = undefined;
            let currencyFee = undefined;
            let currencyWithdrawMin = undefined;
            let currencyWithdrawMax = undefined;
            const networks = {};
            const chains = this.safeValue (currency, 'coins', []);
            let depositEnabled = false;
            let withdrawEnabled = false;
            for (let j = 0; j < chains.length; j++) {
                const chain = chains[j];
                const networkId = this.safeString (chain, 'chain');
                const network = this.safeNetwork (networkId);
                const isDepositEnabled = this.safeValue (chain, 'is_deposit_enabled', false);
                const isWithdrawEnabled = this.safeValue (chain, 'is_withdraw_enabled', false);
                const active = (isDepositEnabled && isWithdrawEnabled);
                currencyActive = active || currencyActive;
                const withdrawMin = this.safeString (chain, 'withdraw_limit_min');
                const withdrawMax = this.safeString (chain, 'withdraw_limit_max');
                currencyWithdrawMin = (currencyWithdrawMin === undefined) ? withdrawMin : currencyWithdrawMin;
                currencyWithdrawMax = (currencyWithdrawMax === undefined) ? withdrawMax : currencyWithdrawMax;
                if (Precise.stringGt (currencyWithdrawMin, withdrawMin)) {
                    currencyWithdrawMin = withdrawMin;
                }
                if (Precise.stringLt (currencyWithdrawMax, withdrawMax)) {
                    currencyWithdrawMax = withdrawMax;
                }
                if (isDepositEnabled) {
                    depositEnabled = true;
                }
                if (isWithdrawEnabled) {
                    withdrawEnabled = true;
                }
                networks[network] = {
                    'info': chain,
                    'id': networkId,
                    'network': network,
                    'active': active,
                    'deposit': isDepositEnabled,
                    'withdraw': isWithdrawEnabled,
                    'fee': this.safeNumber (chain, 'fee'),
                    'precision': this.parseNumber (this.parsePrecision (this.safeString (chain, 'precision'))),
                    'limits': {
                        'withdraw': {
                            'min': withdrawMin,
                            'max': withdrawMax,
                        },
                    },
                };
            }
            const networkKeys = Object.keys (networks);
            const networkKeysLength = networkKeys.length;
            if ((networkKeysLength === 1) || ('NONE' in networks)) {
                const defaultNetwork = this.safeValue2 (networks, 'NONE', networkKeysLength - 1);
                if (defaultNetwork !== undefined) {
                    currencyFee = defaultNetwork['fee'];
                    currencyPrecision = defaultNetwork['precision'];
                }
            }
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'active': currencyActive,
                'deposit': depositEnabled,
                'withdraw': withdrawEnabled,
                'fee': currencyFee,
                'precision': currencyPrecision,
                'limits': {
                    'amount': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': currencyWithdrawMin,
                        'max': currencyWithdrawMax,
                    },
                },
                'networks': networks,
            };
        }
        return result;
    }

    safeNetwork (networkId) {
        if (networkId.indexOf ('BSC') >= 0) {
            return 'BEP20';
        }
        const parts = networkId.split (' ');
        networkId = parts.join ('');
        networkId = networkId.replace ('-20', '20');
        const networksById = {
            'ETH': 'ETH',
            'ERC20': 'ERC20',
            'BEP20(BSC)': 'BEP20',
            'TRX': 'TRC20',
        };
        return this.safeString (networksById, networkId, networkId);
    }

    async fetchMarkets (params = {}) {
        /**
         * @method
         * @name mexc3#fetchMarkets
         * @description retrieves data on all markets for mexc3
         * @param {dict} params extra parameters specific to the exchange api endpoint
         * @returns {[dict]} an array of objects representing market data
         */
        const spotMarket = await this.fetchSpotMarkets (params);
        const swapMarket = await this.fetchSwapMarkets (params);
        return this.arrayConcat (spotMarket, swapMarket);
    }

    async fetchSpotMarkets (params = {}) {
        const response = await this.spotPublicGetExchangeInfo (params);
        //
        //     {
        //         "timezone": "CST",
        //         "serverTime": 1647521860402,
        //         "rateLimits": [],
        //         "exchangeFilters": [],
        //         "symbols": [
        //           {
        //                "symbol": "OGNUSDT",
        //                "status": "ENABLED",
        //                "baseAsset": "OGN",
        //                "baseAssetPrecision": "2",
        //                "quoteAsset": "USDT",
        //                "quoteAssetPrecision": "4",
        //                "orderTypes": [
        //                    "LIMIT",
        //                    "LIMIT_MAKER"
        //                ],
        //                "baseCommissionPrecision": "2",
        //                "quoteCommissionPrecision": "4",
        //                "quoteOrderQtyMarketAllowed": false,
        //                "isSpotTradingAllowed": true,
        //                "isMarginTradingAllowed": true,
        //                "permissions": [
        //                    "SPOT",
        //                    "MARGIN"
        //                ],
        //                "filters": [],
        //                "baseSizePrecision": "0.01", // seems to be derived of 'baseAssetPrecision'
        //                "maxQuoteAmount": "5000000",
        //                "makerCommission": "0.002",
        //                "takerCommission": "0.002"
        //                "quoteAmountPrecision": "5", // seem totally unrelated value, as neither quote/base have anything related to this number
        //                "quotePrecision": "4", // deprecated in favor of 'quoteAssetPrecision' ( https://dev.binance.vision/t/what-is-the-difference-between-quoteprecision-and-quoteassetprecision/4333 )
        //                // note, "icebergAllowed" & "ocoAllowed" fields were recently removed
        //            },
        //         ]
        //     }
        //
        // Notes:
        // - 'quoteAssetPrecision' & 'baseAssetPrecision' are not currency's real blockchain precision (to view currency's actual individual precision, refer to fetchCurrencies() method).
        //
        const data = this.safeValue (response, 'symbols', []);
        const result = [];
        for (let i = 0; i < data.length; i++) {
            const market = data[i];
            const id = this.safeString (market, 'symbol');
            const baseId = this.safeString (market, 'baseAsset');
            const quoteId = this.safeString (market, 'quoteAsset');
            const base = this.safeCurrencyCode (baseId);
            const quote = this.safeCurrencyCode (quoteId);
            const status = this.safeString (market, 'status');
            const makerCommission = this.safeNumber (market, 'makerCommission');
            const takerCommission = this.safeNumber (market, 'takerCommission');
            const maxQuoteAmount = this.safeNumber (market, 'maxQuoteAmount');
            result.push ({
                'id': id,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': undefined,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': undefined,
                'type': 'spot',
                'spot': true,
                'margin': false,
                'swap': false,
                'future': false,
                'option': false,
                'active': (status === 'ENABLED'),
                'contract': false,
                'linear': undefined,
                'inverse': undefined,
                'taker': takerCommission,
                'maker': makerCommission,
                'contractSize': undefined,
                'expiry': undefined,
                'expiryDatetime': undefined,
                'strike': undefined,
                'optionType': undefined,
                'precision': {
                    'amount': this.parseNumber (this.parsePrecision (this.safeString (market, 'baseAssetPrecision'))),
                    'price': this.parseNumber (this.parsePrecision (this.safeString (market, 'quoteAssetPrecision'))),
                },
                'limits': {
                    'leverage': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'amount': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'cost': {
                        'min': undefined,
                        'max': maxQuoteAmount,
                    },
                },
                'info': market,
            });
        }
        return result;
    }

    async fetchSwapMarkets (params = {}) {
        const response = await this.contractPublicGetDetail (params);
        //
        //     {
        //         "success":true,
        //         "code":0,
        //         "data":[
        //             {
        //                 "symbol":"BTC_USDT",
        //                 "displayName":"BTC_USDT永续",
        //                 "displayNameEn":"BTC_USDT SWAP",
        //                 "positionOpenType":3,
        //                 "baseCoin":"BTC",
        //                 "quoteCoin":"USDT",
        //                 "settleCoin":"USDT",
        //                 "contractSize":0.0001,
        //                 "minLeverage":1,
        //                 "maxLeverage":125,
        //                 "priceScale":2, // seems useless atm, as it's just how UI shows the price, i.e. 29583.50 for BTC/USDT:USDT, while price ticksize is 0.5
        //                 "volScale":0, // probably: contract amount precision
        //                 "amountScale":4, // probably: quote currency precision
        //                 "priceUnit":0.5, // price tick size
        //                 "volUnit":1, // probably: contract tick size
        //                 "minVol":1,
        //                 "maxVol":1000000,
        //                 "bidLimitPriceRate":0.1,
        //                 "askLimitPriceRate":0.1,
        //                 "takerFeeRate":0.0006,
        //                 "makerFeeRate":0.0002,
        //                 "maintenanceMarginRate":0.004,
        //                 "initialMarginRate":0.008,
        //                 "riskBaseVol":10000,
        //                 "riskIncrVol":200000,
        //                 "riskIncrMmr":0.004,
        //                 "riskIncrImr":0.004,
        //                 "riskLevelLimit":5,
        //                 "priceCoefficientVariation":0.1,
        //                 "indexOrigin":["BINANCE","GATEIO","HUOBI","MXC"],
        //                 "state":0, // 0 enabled, 1 delivery, 2 completed, 3 offline, 4 pause
        //                 "isNew":false,
        //                 "isHot":true,
        //                 "isHidden":false
        //             },
        //         ]
        //     }
        //
        const data = this.safeValue (response, 'data', []);
        const result = [];
        for (let i = 0; i < data.length; i++) {
            const market = data[i];
            const id = this.safeString (market, 'symbol');
            const baseId = this.safeString (market, 'baseCoin');
            const quoteId = this.safeString (market, 'quoteCoin');
            const settleId = this.safeString (market, 'settleCoin');
            const base = this.safeCurrencyCode (baseId);
            const quote = this.safeCurrencyCode (quoteId);
            const settle = this.safeCurrencyCode (settleId);
            const state = this.safeString (market, 'state');
            result.push ({
                'id': id,
                'symbol': base + '/' + quote + ':' + settle,
                'base': base,
                'quote': quote,
                'settle': settle,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': settleId,
                'type': 'swap',
                'spot': false,
                'margin': false,
                'swap': true,
                'future': false,
                'option': false,
                'active': (state === '0'),
                'contract': true,
                'linear': true,
                'inverse': false,
                'taker': this.safeNumber (market, 'takerFeeRate'),
                'maker': this.safeNumber (market, 'makerFeeRate'),
                'contractSize': this.safeNumber (market, 'contractSize'),
                'expiry': undefined,
                'expiryDatetime': undefined,
                'strike': undefined,
                'optionType': undefined,
                'precision': {
                    'amount': this.safeNumber (market, 'volUnit'),
                    'price': this.safeNumber (market, 'priceUnit'),
                },
                'limits': {
                    'leverage': {
                        'min': this.safeNumber (market, 'minLeverage'),
                        'max': this.safeNumber (market, 'maxLeverage'),
                    },
                    'amount': {
                        'min': this.safeNumber (market, 'minVol'),
                        'max': this.safeNumber (market, 'maxVol'),
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
                'info': market,
            });
        }
        return result;
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchOrderBook
         * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         * @param {str} symbol unified symbol of the market to fetch the order book for
         * @param {int|undefined} limit the maximum amount of order book entries to return
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        let orderbook = undefined;
        if (market['spot']) {
            const response = await this.spotPublicGetDepth (this.extend (request, params));
            //
            //     {
            //         "lastUpdateId": "744267132",
            //         "bids": [
            //             ["40838.50","0.387864"],
            //             ["40837.95","0.008400"],
            //         ],
            //         "asks": [
            //             ["40838.61","6.544908"],
            //             ["40838.88","0.498000"],
            //         ]
            //     }
            //
            orderbook = this.parseOrderBook (response, symbol);
            orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId');
        } else if (market['swap']) {
            const response = await this.contractPublicGetDepthSymbol (this.extend (request, params));
            //
            //     {
            //         "success":true,
            //         "code":0,
            //         "data":{
            //             "asks":[
            //                 [3445.72,48379,1],
            //                 [3445.75,34994,1],
            //             ],
            //             "bids":[
            //                 [3445.55,44081,1],
            //                 [3445.51,24857,1],
            //             ],
            //             "version":2827730444,
            //             "timestamp":1634117846232
            //         }
            //     }
            //
            const data = this.safeValue (response, 'data');
            const timestamp = this.safeInteger (data, 'timestamp');
            orderbook = this.parseOrderBook (data, symbol, timestamp);
            orderbook['nonce'] = this.safeInteger (data, 'version');
        }
        return orderbook;
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchTrades
         * @description get the list of most recent trades for a particular symbol
         * @param {str} symbol unified symbol of the market to fetch trades for
         * @param {int|undefined} since timestamp in ms of the earliest trade to fetch
         * @param {int|undefined} limit the maximum amount of trades to fetch
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        // if (since !== undefined) {
        //     request['startTime'] = since; bug in api, waiting for fix
        // }
        let trades = undefined;
        if (market['spot']) {
            let method = this.safeString (this.options, 'fetchTradesMethod', 'spotPublicGetAggTrades');
            method = this.safeString (params, 'method', method); // AggTrades, HistoricalTrades, Trades
            trades = await this[method] (this.extend (request, params));
            //
            //     /trades, /historicalTrades
            //
            //     [
            //         {
            //             "id": null,
            //             "price": "40798.94",
            //             "qty": "0.000508",
            //             "quoteQty": "20.72586152",
            //             "time": "1647546934374",
            //             "isBuyerMaker": true,
            //             "isBestMatch": true
            //         },
            //     ]
            //
            //     /aggrTrades
            //
            //     [
            //         {
            //           "a": null,
            //           "f": null,
            //           "l": null,
            //           "p": "40679",
            //           "q": "0.001309",
            //           "T": 1647551328000,
            //           "m": true,
            //           "M": true
            //         },
            //     ]
            //
        } else if (market['swap']) {
            const response = await this.contractPublicGetDealsSymbol (this.extend (request, params));
            //
            //     {
            //         "success": true,
            //         "code": 0,
            //         "data": [
            //             {
            //                 "p": 31199,
            //                 "v": 18,
            //                 "T": 1,
            //                 "O": 3,
            //                 "M": 2,
            //                 "t": 1609831235985
            //             },
            //         ]
            //     }
            //
            trades = this.safeValue (response, 'data');
        }
        return this.parseTrades (trades, market, since, limit);
    }

    parseTrade (trade, market = undefined) {
        let id = undefined;
        let timestamp = undefined;
        let orderId = undefined;
        let symbol = undefined;
        let fee = undefined;
        const type = undefined;
        let side = undefined;
        let takerOrMaker = undefined;
        let priceString = undefined;
        let amountString = undefined;
        let costString = undefined;
        // if swap
        if ('v' in trade) {
            //
            // swap: fetchTrades
            //
            //     {
            //         "p": 31199,
            //         "v": 18,
            //         "T": 1,
            //         "O": 3,
            //         "M": 2,
            //         "t": 1609831235985
            //     }
            //
            timestamp = this.safeInteger (trade, 't');
            market = this.safeMarket (undefined, market);
            symbol = market['symbol'];
            priceString = this.safeString (trade, 'p');
            amountString = this.safeString (trade, 'v');
            side = this.parseOrderSide (this.safeString (trade, 'T'));
            takerOrMaker = 'taker';
        } else {
            //
            // spot: fetchTrades (for aggTrades)
            //
            //         {
            //             "a": null,
            //             "f": null,
            //             "l": null,
            //             "p": "40679",
            //             "q": "0.001309",
            //             "T": 1647551328000,
            //             "m": true,
            //             "M": true
            //         }
            //
            // spot: fetchMyTrades, fetchOrderTrades
            //
            //         {
            //             "symbol": "BTCUSDT",
            //             "id": "133948532984922113",
            //             "orderId": "133948532531949568",
            //             "orderListId": "-1",
            //             "price": "41995.51",
            //             "qty": "0.0002",
            //             "quoteQty": "8.399102",
            //             "commission": "0.016798204",
            //             "commissionAsset": "USDT",
            //             "time": "1647718055000",
            //             "isBuyer": true,
            //             "isMaker": false,
            //             "isBestMatch": true
            //         }
            //
            // swap: fetchMyTrades, fetchOrderTrades
            //
            //         {
            //             "id": "299444585",
            //             "symbol": "STEPN_USDT",
            //             "side": "1",
            //             "vol": "1",
            //             "price": "2.45455",
            //             "feeCurrency": "USDT",
            //             "fee": "0.00147273",
            //             "timestamp": "1648924557000",
            //             "profit": "0",
            //             "category": "1",
            //             "orderId": "265307163526610432",
            //             "positionMode": "1",
            //             "taker": true
            //         }
            //
            const marketId = this.safeString (trade, 'symbol');
            market = this.safeMarket (marketId, market);
            symbol = market['symbol'];
            id = this.safeString2 (trade, 'id', 'a');
            priceString = this.safeString2 (trade, 'price', 'p');
            orderId = this.safeString (trade, 'orderId');
            // if swap
            if ('positionMode' in trade) {
                timestamp = this.safeInteger (trade, 'timestamp');
                amountString = this.safeString (trade, 'vol');
                side = this.parseOrderSide (this.safeString (trade, 'side'));
                fee = {
                    'cost': this.safeNumber (trade, 'fee'),
                    'currency': this.safeCurrencyCode (this.safeString (trade, 'feeCurrency')),
                };
                takerOrMaker = this.safeValue (trade, 'taker') ? 'taker' : 'maker';
            } else {
                timestamp = this.safeInteger2 (trade, 'time', 'T');
                amountString = this.safeString2 (trade, 'qty', 'q');
                costString = this.safeString (trade, 'quoteQty');
                const isBuyer = this.safeValue (trade, 'isBuyer');
                const isMaker = this.safeValue (trade, 'isMaker');
                const buyerMaker = this.safeString2 (trade, 'isBuyerMaker', 'm');
                if (isMaker !== undefined) {
                    takerOrMaker = isMaker ? 'maker' : 'taker';
                }
                if (isBuyer !== undefined) {
                    side = isBuyer ? 'buy' : 'sell';
                }
                if (buyerMaker !== undefined) {
                    side = buyerMaker ? 'sell' : 'buy';
                    takerOrMaker = 'taker';
                }
                const feeAsset = this.safeString (trade, 'commissionAsset');
                if (feeAsset !== undefined) {
                    fee = {
                        'cost': this.safeNumber (trade, 'commission'),
                        'currency': this.safeCurrencyCode (feeAsset),
                    };
                }
            }
        }
        if (id === undefined) {
            id = this.syntheticTradeId (market, timestamp, side, amountString, priceString, type, takerOrMaker);
        }
        return this.safeTrade ({
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'type': type,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
            'info': trade,
        }, market);
    }

    syntheticTradeId (market = undefined, timestamp = undefined, side = undefined, amount = undefined, price = undefined, orderType = undefined, takerOrMaker = undefined) {
        // TODO: can be unified method? this approach is being used by multiple exchanges (mexc, woo-coinsbit, dydx, ...)
        let id = '';
        if (timestamp !== undefined) {
            id = this.numberToString (timestamp) + '-' + this.safeString (market, 'id', '_');
            if (side !== undefined) {
                id += '-' + side;
            }
            if (amount !== undefined) {
                id += '-' + this.numberToString (amount);
            }
            if (price !== undefined) {
                id += '-' + this.numberToString (price);
            }
            if (takerOrMaker !== undefined) {
                id += '-' + takerOrMaker;
            }
            if (orderType !== undefined) {
                id += '-' + orderType;
            }
        }
        return id;
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchOHLCV
         * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
         * @param {str} symbol unified symbol of the market to fetch OHLCV data for
         * @param {str} timeframe the length of time each candle represents
         * @param {int|undefined} since timestamp in ms of the earliest candle to fetch
         * @param {int|undefined} limit the maximum amount of candles to fetch
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const options = this.safeValue (this.options, 'timeframes', {});
        const timeframes = this.safeValue (options, market['type'], {});
        const timeframeValue = this.safeString (timeframes, timeframe);
        const request = {
            'symbol': market['id'],
            'interval': timeframeValue,
        };
        let candles = undefined;
        if (market['spot']) {
            if (since !== undefined) {
                request['startTime'] = since;
            }
            if (limit !== undefined) {
                request['limit'] = limit;
            }
            const response = await this.spotPublicGetKlines (this.extend (request, params));
            //
            //     [
            //       [
            //         1640804880000,
            //         "47482.36",
            //         "47482.36",
            //         "47416.57",
            //         "47436.1",
            //         "3.550717",
            //         1640804940000,
            //         "168387.3"
            //       ],
            //     ]
            //
            candles = response;
        } else if (market['swap']) {
            if (since !== undefined) {
                request['start'] = parseInt (since / 1000);
            }
            const priceType = this.safeString (params, 'price', 'default');
            params = this.omit (params, 'price');
            const method = this.getSupportedMapping (priceType, {
                'default': 'contractPublicGetKlineSymbol',
                'index': 'contractPublicGetKlineIndexPriceSymbol',
                'mark': 'contractPublicGetKlineFairPriceSymbol',
            });
            const response = await this[method] (this.extend (request, params));
            //
            //     {
            //         "success":true,
            //         "code":0,
            //         "data":{
            //             "time":[1634052300,1634052360,1634052420],
            //             "open":[3492.2,3491.3,3495.65],
            //             "close":[3491.3,3495.65,3495.2],
            //             "high":[3495.85,3496.55,3499.4],
            //             "low":[3491.15,3490.9,3494.2],
            //             "vol":[1740.0,351.0,314.0],
            //             "amount":[60793.623,12260.4885,10983.1375],
            //         }
            //     }
            //
            const data = this.safeValue (response, 'data');
            candles = this.convertTradingViewToOHLCV (data, 'time', 'open', 'high', 'low', 'close', 'vol');
        }
        return this.parseOHLCVs (candles, market, timeframe, since, limit);
    }

    parseOHLCV (ohlcv, market = undefined) {
        return [
            this.safeInteger (ohlcv, 0),
            this.safeNumber (ohlcv, 1),
            this.safeNumber (ohlcv, 2),
            this.safeNumber (ohlcv, 3),
            this.safeNumber (ohlcv, 4),
            this.safeNumber (ohlcv, 5),
        ];
    }

    async fetchTickers (symbols = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchTickers
         * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
         * @param {[str]|undefined} 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 mexc3 api endpoint
         * @returns {dict} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
         */
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        let isSingularMarket = false;
        if (symbols !== undefined) {
            const length = symbols.length;
            isSingularMarket = length === 1;
            const firstSymbol = this.safeString (symbols, 0);
            market = this.market (firstSymbol);
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchTickers', market, params);
        let tickers = undefined;
        if (isSingularMarket) {
            request['symbol'] = market['id'];
        }
        if (marketType === 'spot') {
            tickers = await this.spotPublicGetTicker24hr (this.extend (request, query));
            //
            //     [
            //         {
            //             "symbol": "BTCUSDT",
            //             "priceChange": "184.34",
            //             "priceChangePercent": "0.00400048",
            //             "prevClosePrice": "46079.37",
            //             "lastPrice": "46263.71",
            //             "lastQty": "",
            //             "bidPrice": "46260.38",
            //             "bidQty": "",
            //             "askPrice": "46260.41",
            //             "askQty": "",
            //             "openPrice": "46079.37",
            //             "highPrice": "47550.01",
            //             "lowPrice": "45555.5",
            //             "volume": "1732.461487",
            //             "quoteVolume": null,
            //             "openTime": 1641349500000,
            //             "closeTime": 1641349582808,
            //             "count": null
            //         }
            //     ]
            //
        } else if (marketType === 'swap') {
            const response = await this.contractPublicGetTicker (this.extend (request, query));
            //
            //     {
            //         "success":true,
            //         "code":0,
            //         "data":[
            //             {
            //                 "symbol":"ETH_USDT",
            //                 "lastPrice":3581.3,
            //                 "bid1":3581.25,
            //                 "ask1":3581.5,
            //                 "volume24":4045530,
            //                 "amount24":141331823.5755,
            //                 "holdVol":5832946,
            //                 "lower24Price":3413.4,
            //                 "high24Price":3588.7,
            //                 "riseFallRate":0.0275,
            //                 "riseFallValue":95.95,
            //                 "indexPrice":3580.7852,
            //                 "fairPrice":3581.08,
            //                 "fundingRate":0.000063,
            //                 "maxBidPrice":3938.85,
            //                 "minAskPrice":3222.7,
            //                 "timestamp":1634162885016
            //             },
            //         ]
            //     }
            //
            tickers = this.safeValue (response, 'data', []);
        }
        // when it's single symbol request, the returned structure is different (singular object) for both spot & swap, thus we need to wrap inside array
        if (isSingularMarket) {
            tickers = [ tickers ];
        }
        return this.parseTickers (tickers, symbols);
    }

    async fetchTicker (symbol, params = {}) {
        /**
         * @method
         * @name mexc3#fetchTicker
         * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
         * @param {str} symbol unified symbol of the market to fetch the ticker for
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchTicker', market, params);
        let ticker = undefined;
        const request = {
            'symbol': market['id'],
        };
        if (marketType === 'spot') {
            ticker = await this.spotPublicGetTicker24hr (this.extend (request, query));
            //
            //     {
            //         "symbol": "BTCUSDT",
            //         "priceChange": "184.34",
            //         "priceChangePercent": "0.00400048",
            //         "prevClosePrice": "46079.37",
            //         "lastPrice": "46263.71",
            //         "lastQty": "",
            //         "bidPrice": "46260.38",
            //         "bidQty": "",
            //         "askPrice": "46260.41",
            //         "askQty": "",
            //         "openPrice": "46079.37",
            //         "highPrice": "47550.01",
            //         "lowPrice": "45555.5",
            //         "volume": "1732.461487",
            //         "quoteVolume": null,
            //         "openTime": 1641349500000,
            //         "closeTime": 1641349582808,
            //         "count": null
            //     }
            //
        } else if (marketType === 'swap') {
            const response = await this.contractPublicGetTicker (this.extend (request, query));
            //
            //     {
            //         "success":true,
            //         "code":0,
            //         "data":{
            //             "symbol":"ETH_USDT",
            //             "lastPrice":3581.3,
            //             "bid1":3581.25,
            //             "ask1":3581.5,
            //             "volume24":4045530,
            //             "amount24":141331823.5755,
            //             "holdVol":5832946,
            //             "lower24Price":3413.4,
            //             "high24Price":3588.7,
            //             "riseFallRate":0.0275,
            //             "riseFallValue":95.95,
            //             "indexPrice":3580.7852,
            //             "fairPrice":3581.08,
            //             "fundingRate":0.000063,
            //             "maxBidPrice":3938.85,
            //             "minAskPrice":3222.7,
            //             "timestamp":1634162885016
            //         }
            //     }
            //
            ticker = this.safeValue (response, 'data', {});
        }
        // when it's single symbol request, the returned structure is different (singular object) for both spot & swap, thus we need to wrap inside array
        return this.parseTicker (ticker, symbol);
    }

    parseTicker (ticker, market = undefined) {
        const marketId = this.safeString (ticker, 'symbol');
        market = this.safeMarket (marketId, market);
        let timestamp = undefined;
        let bid = undefined;
        let ask = undefined;
        let bidVolume = undefined;
        let askVolume = undefined;
        let baseVolume = undefined;
        let quoteVolume = undefined;
        let open = undefined;
        let high = undefined;
        let low = undefined;
        let changePcnt = undefined;
        let changeValue = undefined;
        let prevClose = undefined;
        const isSwap = this.safeValue (market, 'swap');
        // if swap
        if (isSwap || ('timestamp' in ticker)) {
            //
            //     {
            //         "symbol":"ETH_USDT",
            //         "lastPrice":3581.3,
            //         "bid1":3581.25,
            //         "ask1":3581.5,
            //         "volume24":4045530,
            //         "amount24":141331823.5755,
            //         "holdVol":5832946,
            //         "lower24Price":3413.4,
            //         "high24Price":3588.7,
            //         "riseFallRate":0.0275,
            //         "riseFallValue":95.95,
            //         "indexPrice":3580.7852,
            //         "fairPrice":3581.08,
            //         "fundingRate":0.000063,
            //         "maxBidPrice":3938.85,
            //         "minAskPrice":3222.7,
            //         "timestamp":1634162885016
            //     }
            //
            timestamp = this.safeInteger (ticker, 'timestamp');
            bid = this.safeNumber (ticker, 'bid1');
            ask = this.safeNumber (ticker, 'ask1');
            baseVolume = this.safeString (ticker, 'volume24');
            quoteVolume = this.safeString (ticker, 'amount24');
            high = this.safeNumber (ticker, 'high24Price');
            low = this.safeNumber (ticker, 'lower24Price');
            changeValue = this.safeString (ticker, 'riseFallValue');
            changePcnt = this.safeString (ticker, 'riseFallRate');
            changePcnt = this.parseNumber (Precise.stringMul (changePcnt, '100'));
        } else {
            //
            //     {
            //         "symbol": "BTCUSDT",
            //         "priceChange": "184.34",
            //         "priceChangePercent": "0.00400048",
            //         "prevClosePrice": "46079.37",
            //         "lastPrice": "46263.71",
            //         "lastQty": "",
            //         "bidPrice": "46260.38",
            //         "bidQty": "",
            //         "askPrice": "46260.41",
            //         "askQty": "",
            //         "openPrice": "46079.37",
            //         "highPrice": "47550.01",
            //         "lowPrice": "45555.5",
            //         "volume": "1732.461487",
            //         "quoteVolume": null,
            //         "openTime": 1641349500000,
            //         "closeTime": 1641349582808,
            //         "count": null
            //     }
            //
            timestamp = this.safeInteger (ticker, 'closeTime');
            bid = this.safeNumber (ticker, 'bidPrice');
            ask = this.safeNumber (ticker, 'askPrice');
            bidVolume = this.safeNumber (ticker, 'bidQty');
            askVolume = this.safeNumber (ticker, 'askQty');
            if (bidVolume === 0) {
                bidVolume = undefined;
            }
            if (askVolume === 0) {
                askVolume = undefined;
            }
            baseVolume = this.safeString (ticker, 'volume');
            quoteVolume = this.safeString (ticker, 'quoteVolume');
            open = this.safeString (ticker, 'openPrice');
            high = this.safeNumber (ticker, 'highPrice');
            low = this.safeNumber (ticker, 'lowPrice');
            prevClose = this.safeString (ticker, 'prevClosePrice');
            changeValue = this.safeString (ticker, 'priceChange');
            changePcnt = this.safeString (ticker, 'priceChangePercent');
            changePcnt = this.parseNumber (Precise.stringMul (changePcnt, '100'));
        }
        return this.safeTicker ({
            'symbol': market['symbol'],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'open': open,
            'high': high,
            'low': low,
            'close': this.safeString (ticker, 'lastPrice'),
            'bid': bid,
            'bidVolume': bidVolume,
            'ask': ask,
            'askVolume': askVolume,
            'vwap': undefined,
            'previousClose': prevClose,
            'change': changeValue,
            'percentage': changePcnt,
            'average': undefined,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market);
    }

    async fetchBidsAsks (symbols = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchBidsAsks
         * @description fetches the bid and ask price and volume for multiple markets
         * @param {[str]|undefined} symbols unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
         */
        await this.loadMarkets ();
        let market = undefined;
        let isSingularMarket = false;
        if (symbols !== undefined) {
            const length = symbols.length;
            isSingularMarket = length === 1;
            market = this.market (symbols[0]);
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchBidsAsks', market, params);
        let tickers = undefined;
        if (marketType === 'spot') {
            tickers = await this.spotPublicGetTickerBookTicker (query);
            //
            //     [
            //       {
            //         "symbol": "AEUSDT",
            //         "bidPrice": "0.11001",
            //         "bidQty": "115.59",
            //         "askPrice": "0.11127",
            //         "askQty": "215.48"
            //       },
            //     ]
            //
        } else if (marketType === 'swap') {
            throw new NotSupported (this.id + ' fetchBidsAsks() is not available for ' + marketType + ' markets');
        }
        // when it's single symbol request, the returned structure is different (singular object) for both spot & swap, thus we need to wrap inside array
        if (isSingularMarket) {
            tickers = [ tickers ];
        }
        return this.parseTickers (tickers, symbols);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#createOrder
         * @description create a trade order
         * @param {str} symbol unified symbol of the market to create an order in
         * @param {str} type 'market' or 'limit'
         * @param {str} side 'buy' or 'sell'
         * @param {float} amount how much of currency you want to trade in units of base currency
         * @param {float|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} an [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        if (market['spot']) {
            return await this.createSpotOrder (market, type, side, amount, price, params);
        } else if (market['swap']) {
            return await this.createSwapOrder (market, type, side, amount, price, params);
        }
    }

    async createSpotOrder (market, type, side, amount, price = undefined, params = {}) {
        const symbol = market['symbol'];
        const orderSide = (side === 'buy') ? 'BUY' : 'SELL';
        const request = {
            'symbol': market['id'],
            'side': orderSide,
            'type': type.toUpperCase (),
        };
        if (orderSide === 'BUY' && type === 'market') {
            const quoteOrderQty = this.safeNumber (params, 'quoteOrderQty');
            if (quoteOrderQty !== undefined) {
                amount = quoteOrderQty;
            } else if (this.options['createMarketBuyOrderRequiresPrice']) {
                if (price === undefined) {
                    throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)");
                } else {
                    amount = amount * price;
                }
            }
            request['quoteOrderQty'] = amount;
        } else {
            request['quantity'] = this.amountToPrecision (symbol, amount);
        }
        if (price !== undefined) {
            request['price'] = this.priceToPrecision (symbol, price);
        }
        const clientOrderId = this.safeString (params, 'clientOrderId');
        if (clientOrderId !== undefined) {
            request['newClientOrderId'] = clientOrderId;
            params = this.omit (params, [ 'type', 'clientOrderId' ]);
        }
        const response = await this.spotPrivatePostOrder (this.extend (request, params));
        //
        // spot
        //
        //     {
        //         "symbol": "BTCUSDT",
        //         "orderId": "123738410679123456",
        //         "orderListId": -1
        //     }
        //
        return this.extend (this.parseOrder (response, market), {
            'side': side,
            'type': type,
            'price': price,
            'amount': amount,
        });
    }

    async createSwapOrder (market, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        const symbol = market['symbol'];
        const unavailableContracts = this.safeValue (this.options, 'unavailableContracts', {});
        const isContractUnavaiable = this.safeValue (unavailableContracts, symbol, false);
        if (isContractUnavaiable) {
            throw new NotSupported (this.id + ' createSwapOrder() does not support yet this symbol:' + symbol);
        }
        let openType = undefined;
        const marginType = this.safeStringLower (params, 'margin');
        if (marginType !== undefined) {
            if (marginType === 'cross') {
                openType = 2;
            } else if (marginType === 'isolated') {
                openType = 1;
            } else {
                throw new ArgumentsRequired (this.id + ' createSwapOrder() margin parameter should be either "cross" or "isolated"');
            }
        } else {
            openType = this.safeInteger (params, 'openType', 2); // defaulting to cross margin
        }
        if ((type !== 'limit') && (type !== 'market') && (type !== 1) && (type !== 2) && (type !== 3) && (type !== 4) && (type !== 5) && (type !== 6)) {
            throw new InvalidOrder (this.id + ' createSwapOrder() order type must either limit, market, or 1 for limit orders, 2 for post-only orders, 3 for IOC orders, 4 for FOK orders, 5 for market orders or 6 to convert market price to current price');
        }
        const postOnly = this.safeValue (params, 'postOnly', false);
        if (postOnly) {
            type = 2;
        } else if (type === 'limit') {
            type = 1;
        } else if (type === 'market') {
            type = 6;
        }
        // TODO: side not unified
        if ((side !== 1) && (side !== 2) && (side !== 3) && (side !== 4)) {
            throw new InvalidOrder (this.id + ' createSwapOrder() order side must be 1 open long, 2 close short, 3 open short or 4 close long');
        }
        const request = {
            'symbol': market['id'],
            // 'price': parseFloat (this.priceToPrecision (symbol, price)),
            'vol': parseFloat (this.amountToPrecision (symbol, amount)),
            // 'leverage': int, // required for isolated margin
            'side': side, // 1 open long, 2 close short, 3 open short, 4 close long
            //
            // supported order types
            //
            //     1 limit
            //     2 post only maker (PO)
            //     3 transact or cancel instantly (IOC)
            //     4 transact completely or cancel completely (FOK)
            //     5 market orders
            //     6 convert market price to current price
            //
            'type': type,
            'openType': openType, // 1 isolated, 2 cross
            // 'positionId': 1394650, // long, filling in this parameter when closing a position is recommended
            // 'externalOid': clientOrderId,
            // 'triggerPrice': 10.0, // Required for trigger order
            // 'triggerType': 1, // Required for trigger order 1: more than or equal, 2: less than or equal
            // 'executeCycle': 1, // Required for trigger order 1: 24 hours,2: 7 days
            // 'trend': 1, // Required for trigger order 1: latest price, 2: fair price, 3: index price
            // 'orderType': 1, // Required for trigger order 1: limit order,2:Post Only Maker,3: close or cancel instantly ,4: close or cancel completely,5: Market order
        };
        let method = 'contractPrivatePostOrderSubmit';
        const stopPrice = this.safeNumber2 (params, 'triggerPrice', 'stopPrice');
        params = this.omit (params, [ 'stopPrice', 'triggerPrice' ]);
        if (stopPrice) {
            method = 'contractPrivatePostPlanorderPlace';
            request['triggerPrice'] = this.priceToPrecision (symbol, stopPrice);
            request['triggerType'] = this.safeInteger (params, 'triggerType', 1);
            request['executeCycle'] = this.safeInteger (params, 'executeCycle', 1);
            request['trend'] = this.safeInteger (params, 'trend', 1);
            request['orderType'] = this.safeInteger (params, 'orderType', 1);
        }
        if ((type !== 5) && (type !== 6) && (type !== 'market')) {
            request['price'] = parseFloat (this.priceToPrecision (symbol, price));
        }
        if (openType === 1) {
            const leverage = this.safeInteger (params, 'leverage');
            if (leverage === undefined) {
                throw new ArgumentsRequired (this.id + ' createSwapOrder() requires a leverage parameter for isolated margin orders');
            }
        }
        const clientOrderId = this.safeString2 (params, 'clientOrderId', 'externalOid');
        if (clientOrderId !== undefined) {
            request['externalOid'] = clientOrderId;
        }
        params = this.omit (params, [ 'clientOrderId', 'externalOid', 'postOnly' ]);
        const response = await this[method] (this.extend (request, params));
        //
        // Swap
        //     {"code":200,"data":"2ff3163e8617443cb9c6fc19d42b1ca4"}
        //
        // Trigger
        //     {"success":true,"code":0,"data":259208506303929856}
        //
        const data = this.safeString (response, 'data');
        return this.parseOrder (data, market);
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchOrder
         * @description fetches information on an order made by the user
         * @param {str} symbol unified symbol of the market the order was made in
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        let data = undefined;
        if (market['spot']) {
            const clientOrderId = this.safeString (params, 'clientOrderId');
            if (clientOrderId !== undefined) {
                params = this.omit (params, 'clientOrderId');
                request['origClientOrderId'] = clientOrderId;
            } else {
                request['orderId'] = id;
            }
            data = await this.spotPrivateGetOrder (this.extend (request, params));
            //
            //     {
            //         "symbol": "BTCUSDT",
            //         "orderId": "133734823834147272",
            //         "orderListId": "-1",
            //         "clientOrderId": null,
            //         "price": "30000",
            //         "origQty": "0.0002",
            //         "executedQty": "0",
            //         "cummulativeQuoteQty": "0",
            //         "status": "CANCELED",
            //         "timeInForce": null,
            //         "type": "LIMIT",
            //         "side": "BUY",
            //         "stopPrice": null,
            //         "icebergQty": null,
            //         "time": "1647667102000",
            //         "updateTime": "1647708567000",
            //         "isWorking": true,
            //         "origQuoteOrderQty": "6"
            //     }
            //
        } else if (market['swap']) {
            request['order_id'] = id;
            const response = await this.contractPrivateGetOrderGetOrderId (this.extend (request, params));
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": {
            //             "orderId": "264995729269765120",
            //             "symbol": "STEPN_USDT",
            //             "positionId": "0",
            //             "price": "2.2",
            //             "vol": "15",
            //             "leverage": "20",
            //             "side": "1",
            //             "category": "1",
            //             "orderType": "1",
            //             "dealAvgPrice": "0",
            //             "dealVol": "0",
            //             "orderMargin": "2.2528",
            //             "takerFee": "0",
            //             "makerFee": "0",
            //             "profit": "0",
            //             "feeCurrency": "USDT",
            //             "openType": "1",
            //             "state": "2",
            //             "externalOid": "_m_0e9520c256744d64b942985189026d20",
            //             "errorCode": "0",
            //             "usedMargin": "0",
            //             "createTime": "1648850305236",
            //             "updateTime": "1648850305245",
            //             "positionMode": "1"
            //         }
            //     }
            //
            data = this.safeValue (response, 'data');
        }
        return this.parseOrder (data, market);
    }

    async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchOrders
         * @description fetches information on multiple orders made by the user
         * @param {str|undefined} symbol unified market symbol of the market orders were made in
         * @param {int|undefined} since the earliest time in ms to fetch orders for
         * @param {int|undefined} limit the maximum number of  orde structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
         */
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchOrders', market, params);
        if (marketType === 'spot') {
            if (symbol === undefined) {
                throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument for spot market');
            }
            if (since !== undefined) {
                request['startTime'] = since;
            }
            if (limit !== undefined) {
                request['limit'] = limit;
            }
            const response = await this.spotPrivateGetAllOrders (this.extend (request, query));
            //
            //     [
            //         {
            //             "symbol": "BTCUSDT",
            //             "orderId": "133949373632483328",
            //             "orderListId": "-1",
            //             "clientOrderId": null,
            //             "price": "45000",
            //             "origQty": "0.0002",
            //             "executedQty": "0",
            //             "cummulativeQuoteQty": "0",
            //             "status": "NEW",
            //             "timeInForce": null,
            //             "type": "LIMIT",
            //             "side": "SELL",
            //             "stopPrice": null,
            //             "icebergQty": null,
            //             "time": "1647718255000",
            //             "updateTime": "1647718255000",
            //             "isWorking": true,
            //             "origQuoteOrderQty": "9"
            //         },
            //     ]
            //
            return this.parseOrders (response, market, since, limit);
        } else {
            if (since !== undefined) {
                request['start_time'] = since;
                const end = this.safeInteger (params, 'end_time');
                if (end === undefined) {
                    request['end_time'] = this.sum (since, this.options['maxTimeTillEnd']);
                }
            }
            if (limit !== undefined) {
                request['page_size'] = limit;
            }
            let method = this.safeString (this.options, 'fetchOrders', 'contractPrivateGetOrderListHistoryOrders');
            method = this.safeString (query, 'method', method);
            let ordersOfRegular = [];
            let ordersOfTrigger = [];
            if (method === 'contractPrivateGetOrderListHistoryOrders') {
                const response = await this.contractPrivateGetOrderListHistoryOrders (this.extend (request, query));
                //
                //     {
                //         "success": true,
                //         "code": "0",
                //         "data": [
                //             {
                //                 "orderId": "265230764677709315",
                //                 "symbol": "STEPN_USDT",
                //                 "positionId": "0",
                //                 "price": "2.1",
                //                 "vol": "102",
                //                 "leverage": "20",
                //                 "side": "1",
                //                 "category": "1",
                //                 "orderType": "1",
                //                 "dealAvgPrice": "0",
                //                 "dealVol": "0",
                //                 "orderMargin": "10.96704",
                //                 "takerFee": "0",
                //                 "makerFee": "0",
                //                 "profit": "0",
                //                 "feeCurrency": "USDT",
                //                 "openType": "1",
                //                 "state": "2",
                //                 "externalOid": "_m_7e42f8df6b324c869e4e200397e2b00f",
                //                 "errorCode": "0",
                //                 "usedMargin": "0",
                //                 "createTime": "1648906342000",
                //                 "updateTime": "1648906342000",
                //                 "positionMode": "1"
                //             },
                //          ]
                //     }
                //
                ordersOfRegular = this.safeValue (response, 'data');
            } else {
                // the Planorder endpoints work not only for stop-market orders, but also for stop-limit orders that were supposed to have a separate endpoint
                const response = await this.contractPrivateGetPlanorderListOrders (this.extend (request, query));
                //
                //     {
                //         "success": true,
                //         "code": "0",
                //         "data": [
                //             {
                //                 "symbol": "STEPN_USDT",
                //                 "leverage": "20",
                //                 "side": "1",
                //                 "vol": "13",
                //                 "openType": "1",
                //                 "state": "1",
                //                 "orderType": "1",
                //                 "errorCode": "0",
                //                 "createTime": "1648984276000",
                //                 "updateTime": "1648984276000",
                //                 "id": "265557643326564352",
                //                 "triggerType": "1",
                //                 "triggerPrice": "3",
                //                 "price": "2.9", // not present in stop-market, but in stop-limit order
                //                 "executeCycle": "87600",
                //                 "trend": "1",
                //             },
                //         ]
                //     }
                //
                ordersOfTrigger = this.safeValue (response, 'data');
            }
            const merged = this.arrayConcat (ordersOfTrigger, ordersOfRegular);
            return this.parseOrders (merged, market, since, limit, params);
        }
    }

    async fetchOrdersByIds (ids, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchOrdersByIds', market, params);
        if (marketType === 'spot') {
            throw new BadRequest (this.id + ' fetchOrdersByIds() is not supported for ' + marketType);
        } else {
            request['order_ids'] = ids.join (',');
            const response = await this.contractPrivateGetOrderBatchQuery (this.extend (request, query));
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": [
            //             {
            //                 "orderId": "265230764677709315",
            //                 "symbol": "STEPN_USDT",
            //                 "positionId": "0",
            //                 "price": "2.1",
            //                 "vol": "102",
            //                 "leverage": "20",
            //                 "side": "1",
            //                 "category": "1",
            //                 "orderType": "1",
            //                 "dealAvgPrice": "0",
            //                 "dealVol": "0",
            //                 "orderMargin": "10.96704",
            //                 "takerFee": "0",
            //                 "makerFee": "0",
            //                 "profit": "0",
            //                 "feeCurrency": "USDT",
            //                 "openType": "1",
            //                 "state": "2",
            //                 "externalOid": "_m_7e42f8df6b324c869e4e200397e2b00f",
            //                 "errorCode": "0",
            //                 "usedMargin": "0",
            //                 "createTime": "1648906342000",
            //                 "updateTime": "1648906342000",
            //                 "positionMode": "1"
            //             }
            //         ]
            //     }
            //
            const data = this.safeValue (response, 'data');
            return this.parseOrders (data, market);
        }
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchOpenOrders
         * @description fetch all unfilled currently open orders
         * @param {str|undefined} symbol unified market symbol
         * @param {int|undefined} since the earliest time in ms to fetch open orders for
         * @param {int|undefined} limit the maximum number of  open orders structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchOpenOrders', market, params);
        if (marketType === 'spot') {
            if (symbol === undefined) {
                throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument for spot market');
            }
            const response = await this.spotPrivateGetOpenOrders (this.extend (request, query));
            //
            // spot
            //
            //     [
            //         {
            //             "symbol": "BTCUSDT",
            //             "orderId": "133949373632483328",
            //             "orderListId": "-1",
            //             "clientOrderId": "",
            //             "price": "45000",
            //             "origQty": "0.0002",
            //             "executedQty": "0",
            //             "cummulativeQuoteQty": "0",
            //             "status": "NEW",
            //             "timeInForce": null,
            //             "type": "LIMIT",
            //             "side": "SELL",
            //             "stopPrice": null,
            //             "icebergQty": null,
            //             "time": "1647718255199",
            //             "updateTime": null,
            //             "isWorking": true,
            //             "origQuoteOrderQty": "9"
            //         }
            //     ]
            //
            return this.parseOrders (response, market, since, limit);
        } else {
            // TO_DO: another possible way is through: open_orders/{symbol}, but as they have same ratelimits, and less granularity, i think historical orders are more convenient, as it supports more params (however, theoretically, open-orders endpoint might be sligthly fast)
            return await this.fetchOrdersByState (2, symbol, since, limit, params);
        }
    }

    async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchClosedOrders
         * @description fetches information on multiple closed orders made by the user
         * @param {str|undefined} symbol unified market symbol of the market orders were made in
         * @param {int|undefined} since the earliest time in ms to fetch orders for
         * @param {int|undefined} limit the maximum number of  orde structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure
         */
        return await this.fetchOrdersByState (3, symbol, since, limit, params);
    }

    async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchCanceledOrders
         * @description fetches information on multiple canceled orders made by the user
         * @param {str|undefined} symbol unified market symbol of the market orders were made in
         * @param {int|undefined} since timestamp in ms of the earliest order, default is undefined
         * @param {int|undefined} limit max number of orders to return, default is undefined
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        return await this.fetchOrdersByState (4, symbol, since, limit, params);
    }

    async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        }
        const [ marketType ] = this.handleMarketTypeAndParams ('fetchOrdersByState', market, params);
        if (marketType === 'spot') {
            throw new BadRequest (this.id + ' fetchOrdersByState() is not supported for ' + marketType);
        } else {
            params['states'] = state;
            return this.fetchOrders (symbol, since, limit, params);
        }
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#cancelOrder
         * @description cancels an open order
         * @param {str} id order id
         * @param {str|undefined} symbol unified symbol of the market the order was made in
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('cancelOrder', market, params);
        let data = undefined;
        if (marketType === 'spot') {
            if (symbol === undefined) {
                throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument');
            }
            const request = {
                'symbol': market['id'],
            };
            const clientOrderId = this.safeString (params, 'clientOrderId');
            if (clientOrderId !== undefined) {
                params = this.omit (query, 'clientOrderId');
                request['origClientOrderId'] = clientOrderId;
            } else {
                request['orderId'] = id;
            }
            data = await this.spotPrivateDeleteOrder (this.extend (request, params));
            //
            //     {
            //         "symbol": "BTCUSDT",
            //         "orderId": "133734823834447872",
            //         "price": "30000",
            //         "origQty": "0.0002",
            //         "type": "LIMIT",
            //         "side": "BUY"
            //     }
            //
        } else {
            // TODO: PlanorderCancel endpoint has bug atm. waiting for fix.
            let method = this.safeString (this.options, 'cancelOrder', 'contractPrivatePostOrderCancel'); // contractPrivatePostOrderCancel, contractPrivatePostPlanorderCancel
            method = this.safeString (query, 'method', method);
            const response = await this[method] ([ id ]); // the request cannot be changed or extended. This is the only way to send.
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": [
            //             {
            //                 "orderId": "264995729269765120",
            //                 "errorCode": "0",         // if already canceled: "2041"; if doesn't exist: "2040"
            //                 "errorMsg": "success",    // if already canceled: "order state cannot be cancelled"; if doesn't exist: "order not exist"
            //             }
            //         ]
            //     }
            //
            data = this.safeValue (response, 'data');
            const order = this.safeValue (data, 0);
            const errorMsg = this.safeValue (order, 'errorMsg', '');
            if (errorMsg !== 'success') {
                throw new InvalidOrder (this.id + ' cancelOrder() the order with id ' + id + ' cannot be cancelled: ' + errorMsg);
            }
        }
        return this.parseOrder (data, market);
    }

    async cancelOrders (ids, symbol = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#cancelOrders
         * @description cancel multiple orders
         * @param {[str]} ids order ids
         * @param {str|undefined} symbol unified market symbol, default is undefined
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} an list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        const market = symbol !== undefined ? this.market (symbol) : undefined;
        const [ marketType ] = this.handleMarketTypeAndParams ('cancelOrders', market, params);
        if (marketType === 'spot') {
            throw new BadRequest (this.id + ' cancelOrders() is not supported for ' + marketType);
        } else {
            const response = await this.contractPrivatePostOrderCancel (ids); // the request cannot be changed or extended. The only way to send.
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": [
            //             {
            //                 "orderId": "264995729269765120",
            //                 "errorCode": "0",         // if already canceled: "2041"
            //                 "errorMsg": "success",    // if already canceled: "order state cannot be cancelled"
            //             },
            //         ]
            //     }
            //
            const data = this.safeValue (response, 'data');
            return this.parseOrders (data, market);
        }
    }

    async cancelAllOrders (symbol = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#cancelAllOrders
         * @description cancel all open orders
         * @param {str|undefined} symbol unified market symbol, only orders in the market of this symbol are cancelled when symbol is not undefined
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
         */
        await this.loadMarkets ();
        const market = symbol !== undefined ? this.market (symbol) : undefined;
        const request = {};
        const [ marketType, query ] = this.handleMarketTypeAndParams ('cancelAllOrders', market, params);
        if (marketType === 'spot') {
            if (symbol === undefined) {
                throw new ArgumentsRequired (this.id + ' cancelAllOrders() requires a symbol argument on spot');
            }
            request['symbol'] = market['id'];
            const response = await this.spotPrivateDeleteOpenOrders (this.extend (request, query));
            //
            //     [
            //         {
            //             "symbol": "BTCUSDT",
            //             "orderId": "133926492139692032",
            //             "price": "30000",
            //             "origQty": "0.0002",
            //             "type": "LIMIT",
            //             "side": "BUY"
            //         },
            //     ]
            //
            return this.parseOrders (response, market);
        } else {
            if (symbol !== undefined) {
                request['symbol'] = market['id'];
            }
            // method can be either: contractPrivatePostOrderCancelAll or contractPrivatePostPlanorderCancelAll
            // the Planorder endpoints work not only for stop-market orders but also for stop-limit orders that are supposed to have separate endpoint
            let method = this.safeString (this.options, 'cancelAllOrders', 'contractPrivatePostOrderCancelAll');
            method = this.safeString (query, 'method', method);
            const response = await this[method] (this.extend (request, query));
            //
            //     {
            //         "success": true,
            //         "code": "0"
            //     }
            //
            const data = this.safeValue (response, 'data', []);
            return this.parseOrders (data, market);
        }
    }

    parseOrder (order, market = undefined) {
        //
        // spot: createOrder
        //
        //     { "symbol": "BTCUSDT", "orderId": "123738410679123456", "orderListId": -1 }
        //
        // spot: cancelOrder, cancelAllOrders
        //
        //     {
        //         "symbol": "BTCUSDT",
        //         "orderId": "133926441921286144",
        //         "price": "30000",
        //         "origQty": "0.0002",
        //         "type": "LIMIT",
        //         "side": "BUY"
        //     }
        //
        // spot: fetchOrder, fetchOpenOrders, fetchOrders
        //     {
        //         "symbol": "BTCUSDT",
        //         "orderId": "133734823834147272",
        //         "orderListId": "-1",
        //         "clientOrderId": null,
        //         "price": "30000",
        //         "origQty": "0.0002",
        //         "executedQty": "0",
        //         "cummulativeQuoteQty": "0",
        //         "status": "CANCELED",
        //         "timeInForce": null,
        //         "type": "LIMIT",
        //         "side": "BUY",
        //         "stopPrice": null,
        //         "icebergQty": null,
        //         "time": "1647667102000",
        //         "updateTime": "1647708567000",
        //         "isWorking": true,
        //         "origQuoteOrderQty": "6"
        //     }
        //
        // swap: createOrder
        //
        //     2ff3163e8617443cb9c6fc19d42b1ca4
        //
        // swap: fetchOrder, fetchOrders
        //
        //     regular
        //     {
        //         "orderId": "264995729269765120",
        //         "symbol": "STEPN_USDT",
        //         "positionId": "0",
        //         "price": "2.2",
        //         "vol": "15",
        //         "leverage": "20",
        //         "side": "1", // TODO: not unified
        //         "category": "1",
        //         "orderType": "1", // TODO: not unified
        //         "dealAvgPrice": "0",
        //         "dealVol": "0",
        //         "orderMargin": "2.2528",
        //         "takerFee": "0",
        //         "makerFee": "0",
        //         "profit": "0",
        //         "feeCurrency": "USDT",
        //         "openType": "1",
        //         "state": "2", // TODO
        //         "externalOid": "_m_0e9520c256744d64b942985189026d20",
        //         "errorCode": "0",
        //         "usedMargin": "0",
        //         "createTime": "1648850305236",
        //         "updateTime": "1648850305245",
        //         "positionMode": "1"
        //     }
        //
        //     stop
        //     {
        //         "id": "265557643326564352",
        //         "triggerType": "1",
        //         "triggerPrice": "3",
        //         "price": "2.9", // not present in stop-market, but in stop-limit order
        //         "executeCycle": "87600",
        //         "trend": "1",
        //          // below keys are same as in regular order structure
        //         "symbol": "STEPN_USDT",
        //         "leverage": "20",
        //         "side": "1",
        //         "vol": "13",
        //         "openType": "1",
        //         "state": "1",
        //         "orderType": "1",
        //         "errorCode": "0",
        //         "createTime": "1648984276000",
        //         "updateTime": "1648984276000",
        //     }
        //
        let id = undefined;
        if (typeof order === 'string') {
            id = order;
        } else {
            id = this.safeString2 (order, 'orderId', 'id');
        }
        const marketId = this.safeString (order, 'symbol');
        market = this.safeMarket (marketId, market);
        const timestamp = this.safeInteger2 (order, 'time', 'createTime');
        let fee = undefined;
        const feeCurrency = this.safeString (order, 'feeCurrency');
        if (feeCurrency !== undefined) {
            const takerFee = this.safeString (order, 'takerFee');
            const makerFee = this.safeString (order, 'makerFee');
            const feeSum = Precise.stringAdd (takerFee, makerFee);
            fee = {
                'currency': feeCurrency,
                'cost': this.parseNumber (feeSum),
            };
        }
        return this.safeOrder ({
            'id': id,
            'clientOrderId': this.safeString (order, 'clientOrderId'),
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'lastTradeTimestamp': undefined, // TODO: this might be 'updateTime' if order-status is filled, otherwise cancellation time. needs to be checked
            'status': this.parseOrderStatus (this.safeString2 (order, 'status', 'state')),
            'symbol': market['symbol'],
            'type': this.parseOrderType (this.safeString (order, 'type')),
            'timeInForce': this.parseOrderTimeInForce (this.safeString (order, 'timeInForce')),
            'side': this.parseOrderSide (this.safeString (order, 'side')),
            'price': this.safeNumber (order, 'price'),
            'stopPrice': this.safeNumber2 (order, 'stopPrice', 'triggerPrice'),
            'average': this.safeNumber (order, 'dealAvgPrice'),
            'amount': this.safeNumber2 (order, 'origQty', 'vol'),
            'cost': this.safeNumber (order, 'cummulativeQuoteQty'),  // 'cummulativeQuoteQty' vs 'origQuoteOrderQty'
            'filled': this.safeNumber2 (order, 'executedQty', 'dealVol'),
            'remaining': undefined,
            'fee': fee,
            'trades': undefined,
            'info': order,
        }, market);
    }

    parseOrderSide (status) {
        const statuses = {
            'BUY': 'buy',
            'SELL': 'sell',
            // contracts v1 : TODO
        };
        return this.safeString (statuses, status, status);
    }

    parseOrderType (status) {
        const statuses = {
            'MARKET': 'market',
            'LIMIT': 'limit',
            'LIMIT_MAKER': 'limit',
        };
        return this.safeString (statuses, status, status);
    }

    parseOrderStatus (status) {
        const statuses = {
            'NEW': 'open',
            'FILLED': 'closed',
            'CANCELED': 'canceled',
            'PARTIALLY_FILLED': 'open',
            'PARTIALLY_CANCELED': 'canceled',
            // contracts v1
            // '1': 'uninformed', // TODO: wt?
            '2': 'open',
            '3': 'closed',
            '4': 'canceled',
            // '5': 'invalid', //  TODO: wt?
        };
        return this.safeString (statuses, status, status);
    }

    parseOrderTimeInForce (status) {
        const statuses = {
            'GTC': 'GTC',
            'FOK': 'FOK',
            'IOC': 'IOC',
        };
        return this.safeString (statuses, status, status);
    }

    async fetchAccountHelper (type, params) {
        if (type === 'spot') {
            return await this.spotPrivateGetAccount (params);
            //
            //     {
            //         "makerCommission": "20",
            //         "takerCommission": "20",
            //         "buyerCommission": "0",
            //         "sellerCommission": "0",
            //         "canTrade": true,
            //         "canWithdraw": true,
            //         "canDeposit": true,
            //         "updateTime": null,
            //         "accountType": "SPOT",
            //         "balances": [
            //             {
            //                 "asset": "BTC",
            //                 "free": "0.002",
            //                 "locked": "0"
            //             },
            //             {
            //                 "asset": "USDT",
            //                 "free": "88.120131350620957006",
            //                 "locked": "0"
            //             },
            //         ],
            //         "permissions": [
            //             "SPOT"
            //         ]
            //     }
            //
        } else if (type === 'swap') {
            const response = this.contractPrivateGetAccountAssets (params);
            //
            //     {
            //         "success":true,
            //         "code":0,
            //         "data":[
            //            {
            //              "currency":"BSV",
            //              "positionMargin":0,
            //              "availableBalance":0,
            //              "cashBalance":0,
            //              "frozenBalance":0,
            //              "equity":0,
            //              "unrealized":0,
            //              "bonus":0
            //           },
            //         ]
            //     }
            //
            return this.safeValue (response, 'data');
        }
    }

    async fetchAccounts (params = {}) {
        /**
         * @method
         * @name mexc3#fetchAccounts
         * @description fetch all the accounts associated with a profile
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a dictionary of [account structures]{@link https://docs.ccxt.com/en/latest/manual.html#account-structure} indexed by the account type
         */
        // TODO: is the below endpoints suitable for fetchAccounts?
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchAccounts', undefined, params);
        await this.loadMarkets ();
        const response = await this.fetchAccountHelper (marketType, query);
        const data = this.safeValue (response, 'balances', []);
        const result = [];
        for (let i = 0; i < data.length; i++) {
            const account = data[i];
            const currencyId = this.safeString2 (account, 'asset', 'currency');
            const code = this.safeCurrencyCode (currencyId);
            result.push ({
                'id': this.safeString (account, 'id'),
                'type': this.safeString (account, 'type'),
                'code': code,
                'info': account,
            });
        }
        return result;
    }

    async fetchTradingFees (params = {}) {
        /**
         * @method
         * @name mexc3#fetchTradingFees
         * @description fetch the trading fees for multiple markets
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a dictionary of [fee structures]{@link https://docs.ccxt.com/en/latest/manual.html#fee-structure} indexed by market symbols
         */
        await this.loadMarkets ();
        const response = await this.fetchAccountHelper ('spot', params);
        let makerFee = this.safeString (response, 'makerCommission');
        let takerFee = this.safeString (response, 'takerCommission');
        makerFee = Precise.stringDiv (makerFee, '1000');
        takerFee = Precise.stringDiv (takerFee, '1000');
        const result = {};
        for (let i = 0; i < this.symbols.length; i++) {
            const symbol = this.symbols[i];
            result[symbol] = {
                'symbol': symbol,
                'maker': this.parseNumber (makerFee),
                'taker': this.parseNumber (takerFee),
                'percentage': true,
                'tierBased': false,
                'info': response,
            };
        }
        return result;
    }

    async fetchBalance (params = {}) {
        /**
         * @method
         * @name mexc3#fetchBalance
         * @description query for balance and get the amount of funds available for trading or funds locked in orders
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure}
         */
        await this.loadMarkets ();
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchBalance', undefined, params);
        const result = {};
        let response = undefined;
        if (marketType === 'spot') {
            response = await this.fetchAccountHelper ('spot', query);
            const balances = this.safeValue (response, 'balances', []);
            for (let i = 0; i < balances.length; i++) {
                const entry = balances[i];
                const currencyId = this.safeString (entry, 'asset');
                const code = this.safeCurrencyCode (currencyId);
                const account = this.account ();
                account['free'] = this.safeString (entry, 'free');
                account['used'] = this.safeString (entry, 'locked');
                result[code] = account;
            }
        } else if (marketType === 'swap') {
            response = await this.contractPrivateGetAccountAssets (query);
            //
            //     {
            //         "success":true,
            //         "code":0,
            //         "data":[
            //             {"currency":"BSV","positionMargin":0,"availableBalance":0,"cashBalance":0,"frozenBalance":0,"equity":0,"unrealized":0,"bonus":0},
            //             {"currency":"BCH","positionMargin":0,"availableBalance":0,"cashBalance":0,"frozenBalance":0,"equity":0,"unrealized":0,"bonus":0},
            //             {"currency":"CRV","positionMargin":0,"availableBalance":0,"cashBalance":0,"frozenBalance":0,"equity":0,"unrealized":0,"bonus":0},
            //         ]
            //     }
            //
            const data = this.safeValue (response, 'data', []);
            for (let i = 0; i < data.length; i++) {
                const balance = data[i];
                const currencyId = this.safeString (balance, 'currency');
                const code = this.safeCurrencyCode (currencyId);
                const account = this.account ();
                account['free'] = this.safeString (balance, 'availableBalance');
                account['used'] = this.safeString (balance, 'frozenBalance');
                result[code] = account;
            }
        }
        result['info'] = response;
        return this.safeBalance (result);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchMyTrades
         * @description fetch all trades made by the user
         * @param {str} symbol unified market symbol
         * @param {int|undefined} since the earliest time in ms to fetch trades for
         * @param {int|undefined} limit the maximum number of trades structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html#trade-structure}
         */
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchMyTrades', market, params);
        const request = {
            'symbol': market['id'],
        };
        let trades = undefined;
        if (marketType === 'spot') {
            if (since !== undefined) {
                request['start_time'] = since;
            }
            if (limit !== undefined) {
                request['limit'] = limit;
            }
            trades = await this.spotPrivateGetMyTrades (this.extend (request, query));
            //
            // spot
            //
            //     [
            //         {
            //             "symbol": "BTCUSDT",
            //             "id": "133948532984922113",
            //             "orderId": "133948532531949568",
            //             "orderListId": "-1",
            //             "price": "41995.51",
            //             "qty": "0.0002",
            //             "quoteQty": "8.399102",
            //             "commission": "0.016798204",
            //             "commissionAsset": "USDT",
            //             "time": "1647718055000",
            //             "isBuyer": true,
            //             "isMaker": false,
            //             "isBestMatch": true
            //         }
            //     ]
            //
        } else {
            if (since !== undefined) {
                request['start_time'] = since;
                const end = this.safeInteger (params, 'end_time');
                if (end === undefined) {
                    request['end_time'] = this.sum (since, this.options['maxTimeTillEnd']);
                }
            }
            if (limit !== undefined) {
                request['page_size'] = limit;
            }
            const response = await this.contractPrivateGetOrderListOrderDeals (this.extend (request, query));
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": [
            //             {
            //                 "id": "299444585",
            //                 "symbol": "STEPN_USDT",
            //                 "side": "1",
            //                 "vol": "1",
            //                 "price": "2.45455",
            //                 "feeCurrency": "USDT",
            //                 "fee": "0.00147273",
            //                 "timestamp": "1648924557000",
            //                 "profit": "0",
            //                 "category": "1",
            //                 "orderId": "265307163526610432",
            //                 "positionMode": "1",
            //                 "taker": true
            //             }
            //         ]
            //     }
            //
            trades = this.safeValue (response, 'data');
        }
        return this.parseTrades (trades, market, since, limit);
    }

    async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchOrderTrades
         * @description fetch all the trades made from a single order
         * @param {str} id order id
         * @param {str|undefined} symbol unified market symbol
         * @param {int|undefined} since the earliest time in ms to fetch trades for
         * @param {int|undefined} limit the maximum number of trades to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html#trade-structure}
         */
        await this.loadMarkets ();
        const request = {};
        let market = undefined;
        if (symbol !== undefined) {
            market = this.market (symbol);
        }
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchOrderTrades', market, params);
        let trades = undefined;
        if (marketType === 'spot') {
            if (symbol === undefined) {
                throw new ArgumentsRequired (this.id + ' fetchOrderTrades() requires a symbol argument');
            }
            request['symbol'] = market['id'];
            request['orderId'] = id;
            trades = await this.spotPrivateGetMyTrades (this.extend (request, query));
            //
            // spot
            //
            //     [
            //         {
            //             "symbol": "BTCUSDT",
            //             "id": "133948532984922113",
            //             "orderId": "133948532531949568",
            //             "orderListId": "-1",
            //             "price": "41995.51",
            //             "qty": "0.0002",
            //             "quoteQty": "8.399102",
            //             "commission": "0.016798204",
            //             "commissionAsset": "USDT",
            //             "time": "1647718055000",
            //             "isBuyer": true,
            //             "isMaker": false,
            //             "isBestMatch": true
            //         }
            //     ]
            //
        } else {
            request['order_id'] = id;
            const response = await this.contractPrivateGetOrderDealDetailsOrderId (this.extend (request, query));
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": [
            //             {
            //                 "id": "299444585",
            //                 "symbol": "STEPN_USDT",
            //                 "side": "1",
            //                 "vol": "1",
            //                 "price": "2.45455",
            //                 "feeCurrency": "USDT",
            //                 "fee": "0.00147273",
            //                 "timestamp": "1648924557000",
            //                 "profit": "0",
            //                 "category": "1",
            //                 "orderId": "265307163526610432",
            //                 "positionMode": "1",
            //                 "taker": true
            //             }
            //         ]
            //     }
            //
            trades = this.safeValue (response, 'data');
        }
        return this.parseTrades (trades, market, since, limit, query);
    }

    async modifyMarginHelper (symbol, amount, addOrReduce, params = {}) {
        const positionId = this.safeInteger (params, 'positionId');
        if (positionId === undefined) {
            throw new ArgumentsRequired (this.id + ' modifyMarginHelper() requires a positionId parameter');
        }
        await this.loadMarkets ();
        const request = {
            'positionId': positionId,
            'amount': amount,
            'type': addOrReduce,
        };
        const response = await this.contractPrivatePostPositionChangeMargin (this.extend (request, params));
        //
        //     {
        //         "success": true,
        //         "code": 0
        //     }
        return response;
    }

    async reduceMargin (symbol, amount, params = {}) {
        /**
         * @method
         * @name mexc3#reduceMargin
         * @description remove margin from a position
         * @param {str} symbol unified market symbol
         * @param {float} amount the amount of margin to remove
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [margin structure]{@link https://docs.ccxt.com/en/latest/manual.html#reduce-margin-structure}
         */
        return await this.modifyMarginHelper (symbol, amount, 'SUB', params);
    }

    async addMargin (symbol, amount, params = {}) {
        /**
         * @method
         * @name mexc3#addMargin
         * @description add margin
         * @param {str} symbol unified market symbol
         * @param {float} amount amount of margin to add
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [margin structure]{@link https://docs.ccxt.com/en/latest/manual.html#add-margin-structure}
         */
        return await this.modifyMarginHelper (symbol, amount, 'ADD', params);
    }

    async setLeverage (leverage, symbol = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#setLeverage
         * @description set the level of leverage for a market
         * @param {float} leverage the rate of leverage
         * @param {str|undefined} symbol unified market symbol
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} response from the exchange
         */
        await this.loadMarkets ();
        const request = {
            'leverage': leverage,
        };
        const positionId = this.safeInteger (params, 'positionId');
        if (positionId === undefined) {
            const openType = this.safeNumber (params, 'openType'); // 1 or 2
            const positionType = this.safeNumber (params, 'positionType'); // 1 or 2
            const market = (symbol !== undefined) ? this.market (symbol) : undefined;
            if ((openType === undefined) || (positionType === undefined) || (market === undefined)) {
                throw new ArgumentsRequired (this.id + ' setLeverage() requires a positionId parameter or a symbol argument with openType and positionType parameters, use openType 1 or 2 for isolated or cross margin respectively, use positionType 1 or 2 for long or short positions');
            } else {
                request['openType'] = openType;
                request['symbol'] = market['symbol'];
                request['positionType'] = positionType;
            }
        } else {
            request['positionId'] = positionId;
        }
        return await this.contractPrivatePostPositionChangeLeverage (this.extend (request, params));
    }

    async fetchFundingHistory (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchFundingHistory
         * @description fetch the history of funding payments paid and received on this account
         * @param {str|undefined} symbol unified market symbol
         * @param {int|undefined} since the earliest time in ms to fetch funding history for
         * @param {int|undefined} limit the maximum number of funding history structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [funding history structure]{@link https://docs.ccxt.com/en/latest/manual.html#funding-history-structure}
         */
        await this.loadMarkets ();
        let market = undefined;
        const request = {
            // 'symbol': market['id'],
            // 'position_id': positionId,
            // 'page_num': 1,
            // 'page_size': limit, // default 20, max 100
        };
        if (symbol !== undefined) {
            market = this.market (symbol);
            request['symbol'] = market['id'];
        }
        if (limit !== undefined) {
            request['page_size'] = limit;
        }
        const response = await this.contractPrivateGetPositionFundingRecords (this.extend (request, params));
        //
        //     {
        //         "success": true,
        //         "code": 0,
        //         "data": {
        //             "pageSize": 20,
        //             "totalCount": 2,
        //             "totalPage": 1,
        //             "currentPage": 1,
        //             "resultList": [
        //                 {
        //                     "id": 7423910,
        //                     "symbol": "BTC_USDT",
        //                     "positionType": 1,
        //                     "positionValue": 29.30024,
        //                     "funding": 0.00076180624,
        //                     "rate": -0.000026,
        //                     "settleTime": 1643299200000
        //                 },
        //                 {
        //                     "id": 7416473,
        //                     "symbol": "BTC_USDT",
        //                     "positionType": 1,
        //                     "positionValue": 28.9188,
        //                     "funding": 0.0014748588,
        //                     "rate": -0.000051,
        //                     "settleTime": 1643270400000
        //                 }
        //             ]
        //         }
        //     }
        //
        const data = this.safeValue (response, 'data', {});
        const resultList = this.safeValue (data, 'resultList', []);
        const result = [];
        for (let i = 0; i < resultList.length; i++) {
            const entry = resultList[i];
            const timestamp = this.safeInteger (entry, 'settleTime');
            result.push ({
                'info': entry,
                'symbol': symbol,
                'code': undefined,
                'timestamp': timestamp,
                'datetime': this.iso8601 (timestamp),
                'id': this.safeNumber (entry, 'id'),
                'amount': this.safeNumber (entry, 'funding'),
            });
        }
        return result;
    }

    parseFundingRate (contract, market = undefined) {
        //
        //     {
        //         "symbol": "BTC_USDT",
        //         "fundingRate": 0.000014,
        //         "maxFundingRate": 0.003,
        //         "minFundingRate": -0.003,
        //         "collectCycle": 8,
        //         "nextSettleTime": 1643241600000,
        //         "timestamp": 1643240373359
        //     }
        //
        const nextFundingRate = this.safeNumber (contract, 'fundingRate');
        const nextFundingTimestamp = this.safeInteger (contract, 'nextSettleTime');
        const marketId = this.safeString (contract, 'symbol');
        const symbol = this.safeSymbol (marketId, market);
        const timestamp = this.safeInteger (contract, 'timestamp');
        const datetime = this.iso8601 (timestamp);
        return {
            'info': contract,
            'symbol': symbol,
            'markPrice': undefined,
            'indexPrice': undefined,
            'interestRate': undefined,
            'estimatedSettlePrice': undefined,
            'timestamp': timestamp,
            'datetime': datetime,
            'fundingRate': undefined,
            'fundingTimestamp': undefined,
            'fundingDatetime': undefined,
            'nextFundingRate': nextFundingRate,
            'nextFundingTimestamp': nextFundingTimestamp,
            'nextFundingDatetime': this.iso8601 (nextFundingTimestamp),
            'previousFundingRate': undefined,
            'previousFundingTimestamp': undefined,
            'previousFundingDatetime': undefined,
        };
    }

    async fetchFundingRate (symbol, params = {}) {
        /**
         * @method
         * @name mexc3#fetchFundingRate
         * @description fetch the current funding rate
         * @param {str} symbol unified market symbol
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [funding rate structure]{@link https://docs.ccxt.com/en/latest/manual.html#funding-rate-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        const response = await this.contractPublicGetFundingRateSymbol (this.extend (request, params));
        //
        //     {
        //         "success": true,
        //         "code": 0,
        //         "data": {
        //             "symbol": "BTC_USDT",
        //             "fundingRate": 0.000014,
        //             "maxFundingRate": 0.003,
        //             "minFundingRate": -0.003,
        //             "collectCycle": 8,
        //             "nextSettleTime": 1643241600000,
        //             "timestamp": 1643240373359
        //         }
        //     }
        //
        const result = this.safeValue (response, 'data', {});
        return this.parseFundingRate (result, market);
    }

    async fetchFundingRateHistory (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc#fetchFundingRateHistory
         * @description fetches historical funding rate prices
         * @param {str|undefined} symbol unified symbol of the market to fetch the funding rate history for
         * @param {int|undefined} since not used by mexc, but filtered internally by ccxt
         * @param {int|undefined} limit mexc limit is page_size default 20, maximum is 100
         * @param {dict} params extra parameters specific to the mexc api endpoint
         * @returns {[dict]} a list of [funding rate structures]{@link https://docs.ccxt.com/en/latest/manual.html?#funding-rate-history-structure}
         */
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchFundingRateHistory() requires a symbol argument');
        }
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
            // 'page_size': limit, // optional
            // 'page_num': 1, // optional, current page number, default is 1
        };
        if (limit !== undefined) {
            request['page_size'] = limit;
        }
        const response = await this.contractPublicGetFundingRateHistory (this.extend (request, params));
        //
        //    {
        //        "success": true,
        //        "code": 0,
        //        "data": {
        //            "pageSize": 2,
        //            "totalCount": 21,
        //            "totalPage": 11,
        //            "currentPage": 1,
        //            "resultList": [
        //                {
        //                    "symbol": "BTC_USDT",
        //                    "fundingRate": 0.000266,
        //                    "settleTime": 1609804800000
        //                },
        //                {
        //                    "symbol": "BTC_USDT",
        //                    "fundingRate": 0.00029,
        //                    "settleTime": 1609776000000
        //                }
        //            ]
        //        }
        //    }
        //
        const data = this.safeValue (response, 'data');
        const result = this.safeValue (data, 'resultList', []);
        const rates = [];
        for (let i = 0; i < result.length; i++) {
            const entry = result[i];
            const marketId = this.safeString (entry, 'symbol');
            const symbol = this.safeSymbol (marketId);
            const timestamp = this.safeInteger (entry, 'settleTime');
            rates.push ({
                'info': entry,
                'symbol': symbol,
                'fundingRate': this.safeNumber (entry, 'fundingRate'),
                'timestamp': timestamp,
                'datetime': this.iso8601 (timestamp),
            });
        }
        const sorted = this.sortBy (rates, 'timestamp');
        return this.filterBySymbolSinceLimit (sorted, market['symbol'], since, limit);
    }

    async fetchLeverageTiers (symbols = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchLeverageTiers
         * @description retrieve information on the maximum leverage, and maintenance margin for trades of varying trade sizes
         * @param {[str]|undefined} symbols list of unified market symbols
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a dictionary of [leverage tiers structures]{@link https://docs.ccxt.com/en/latest/manual.html#leverage-tiers-structure}, indexed by market symbols
         */
        await this.loadMarkets ();
        const response = await this.contractPublicGetDetail (params);
        //
        //     {
        //         "success":true,
        //         "code":0,
        //         "data":[
        //             {
        //                 "symbol": "BTC_USDT",
        //                 "displayName": "BTC_USDT永续",
        //                 "displayNameEn": "BTC_USDT SWAP",
        //                 "positionOpenType": 3,
        //                 "baseCoin": "BTC",
        //                 "quoteCoin": "USDT",
        //                 "settleCoin": "USDT",
        //                 "contractSize": 0.0001,
        //                 "minLeverage": 1,
        //                 "maxLeverage": 125,
        //                 "priceScale": 2,
        //                 "volScale": 0,
        //                 "amountScale": 4,
        //                 "priceUnit": 0.5,
        //                 "volUnit": 1,
        //                 "minVol": 1,
        //                 "maxVol": 1000000,
        //                 "bidLimitPriceRate": 0.1,
        //                 "askLimitPriceRate": 0.1,
        //                 "takerFeeRate": 0.0006,
        //                 "makerFeeRate": 0.0002,
        //                 "maintenanceMarginRate": 0.004,
        //                 "initialMarginRate": 0.008,
        //                 "riskBaseVol": 10000,
        //                 "riskIncrVol": 200000,
        //                 "riskIncrMmr": 0.004,
        //                 "riskIncrImr": 0.004,
        //                 "riskLevelLimit": 5,
        //                 "priceCoefficientVariation": 0.1,
        //                 "indexOrigin": ["BINANCE","GATEIO","HUOBI","MXC"],
        //                 "state": 0, // 0 enabled, 1 delivery, 2 completed, 3 offline, 4 pause
        //                 "isNew": false,
        //                 "isHot": true,
        //                 "isHidden": false
        //             },
        //             ...
        //         ]
        //     }
        //
        const data = this.safeValue (response, 'data');
        return this.parseLeverageTiers (data, symbols, 'symbol');
    }

    parseMarketLeverageTiers (info, market) {
        /**
            @param info: Exchange response for 1 market
            {
                "symbol": "BTC_USDT",
                "displayName": "BTC_USDT永续",
                "displayNameEn": "BTC_USDT SWAP",
                "positionOpenType": 3,
                "baseCoin": "BTC",
                "quoteCoin": "USDT",
                "settleCoin": "USDT",
                "contractSize": 0.0001,
                "minLeverage": 1,
                "maxLeverage": 125,
                "priceScale": 2,
                "volScale": 0,
                "amountScale": 4,
                "priceUnit": 0.5,
                "volUnit": 1,
                "minVol": 1,
                "maxVol": 1000000,
                "bidLimitPriceRate": 0.1,
                "askLimitPriceRate": 0.1,
                "takerFeeRate": 0.0006,
                "makerFeeRate": 0.0002,
                "maintenanceMarginRate": 0.004,
                "initialMarginRate": 0.008,
                "riskBaseVol": 10000,
                "riskIncrVol": 200000,
                "riskIncrMmr": 0.004,
                "riskIncrImr": 0.004,
                "riskLevelLimit": 5,
                "priceCoefficientVariation": 0.1,
                "indexOrigin": ["BINANCE","GATEIO","HUOBI","MXC"],
                "state": 0, // 0 enabled, 1 delivery, 2 completed, 3 offline, 4 pause
                "isNew": false,
                "isHot": true,
                "isHidden": false
            }
            @param market: CCXT market
         */
        let maintenanceMarginRate = this.safeString (info, 'maintenanceMarginRate');
        let initialMarginRate = this.safeString (info, 'initialMarginRate');
        const maxVol = this.safeString (info, 'maxVol');
        const riskIncrVol = this.safeString (info, 'riskIncrVol');
        const riskIncrMmr = this.safeString (info, 'riskIncrMmr');
        const riskIncrImr = this.safeString (info, 'riskIncrImr');
        let floor = '0';
        const tiers = [];
        const quoteId = this.safeString (info, 'quoteCoin');
        while (Precise.stringLt (floor, maxVol)) {
            const cap = Precise.stringAdd (floor, riskIncrVol);
            tiers.push ({
                'tier': this.parseNumber (Precise.stringDiv (cap, riskIncrVol)),
                'currency': this.safeCurrencyCode (quoteId),
                'notionalFloor': this.parseNumber (floor),
                'notionalCap': this.parseNumber (cap),
                'maintenanceMarginRate': this.parseNumber (maintenanceMarginRate),
                'maxLeverage': this.parseNumber (Precise.stringDiv ('1', initialMarginRate)),
                'info': info,
            });
            initialMarginRate = Precise.stringAdd (initialMarginRate, riskIncrImr);
            maintenanceMarginRate = Precise.stringAdd (maintenanceMarginRate, riskIncrMmr);
            floor = cap;
        }
        return tiers;
    }

    parseDepositAddress (depositAddress, currency = undefined) {
        //
        //     {"chain":"ERC-20","address":"0x55cbd73db24eafcca97369e3f2db74b2490586e6"},
        //     {"chain":"MATIC","address":"0x05aa3236f1970eae0f8feb17ec19435b39574d74"},
        //     {"chain":"TRC20","address":"TGaPfhW41EXD3sAfs1grLF6DKfugfqANNw"},
        //     {"chain":"SOL","address":"5FSpUKuh2gjw4mF89T2e7sEjzUA1SkRKjBChFqP43KhV"},
        //     {"chain":"ALGO","address":"B3XTZND2JJTSYR7R2TQVCUDT4QSSYVAIZYDPWVBX34DGAYATBU3AUV43VU"}
        //
        //
        const address = this.safeString (depositAddress, 'address');
        const code = this.safeCurrencyCode (undefined, currency);
        const networkId = this.safeString (depositAddress, 'chain');
        const network = this.safeNetwork (networkId);
        this.checkAddress (address);
        return {
            'currency': code,
            'address': address,
            'tag': undefined,
            'network': network,
            'info': depositAddress,
        };
    }

    async fetchDepositAddressesByNetwork (code, params = {}) {
        /**
         * @method
         * @name mexc3#fetchDepositAddressesByNetwork
         * @description fetch a dictionary of addresses for a currency, indexed by network
         * @param {str} code unified currency code of the currency for the deposit address
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a dictionary of [address structures]{@link https://docs.ccxt.com/en/latest/manual.html#address-structure} indexed by the network
         */
        await this.loadMarkets ();
        const currency = this.currency (code);
        const request = {
            'currency': currency['id'],
        };
        const response = await this.spot2PrivateGetAssetDepositAddressList (this.extend (request, params));
        //
        //     {
        //         "code":200,
        //         "data":{
        //             "currency":"USDC",
        //             "chains":[
        //                 {"chain":"ERC-20","address":"0x55cbd73db24eafcca97369e3f2db74b2490586e6"},
        //                 {"chain":"MATIC","address":"0x05aa3236f1970eae0f8feb17ec19435b39574d74"},
        //                 {"chain":"TRC20","address":"TGaPfhW41EXD3sAfs1grLF6DKfugfqANNw"},
        //                 {"chain":"SOL","address":"5FSpUKuh2gjw4mF89T2e7sEjzUA1SkRKjBChFqP43KhV"},
        //                 {"chain":"ALGO","address":"B3XTZND2JJTSYR7R2TQVCUDT4QSSYVAIZYDPWVBX34DGAYATBU3AUV43VU"}
        //             ]
        //         }
        //     }
        //
        const data = this.safeValue (response, 'data', {});
        const chains = this.safeValue (data, 'chains', []);
        const depositAddresses = [];
        for (let i = 0; i < chains.length; i++) {
            const depositAddress = this.parseDepositAddress (chains[i], currency);
            depositAddresses.push (depositAddress);
        }
        return this.indexBy (depositAddresses, 'network');
    }

    async fetchDepositAddress (code, params = {}) {
        /**
         * @method
         * @name mexc3#fetchDepositAddress
         * @description fetch the deposit address for a currency associated with this account
         * @param {str} code unified currency code
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} an [address structure]{@link https://docs.ccxt.com/en/latest/manual.html#address-structure}
         */
        const rawNetwork = this.safeStringUpper (params, 'network');
        params = this.omit (params, 'network');
        const response = await this.fetchDepositAddressesByNetwork (code, params);
        const networks = this.safeValue (this.options, 'networks', {});
        const network = this.safeString (networks, rawNetwork, rawNetwork);
        let result = undefined;
        if (network === undefined) {
            result = this.safeValue (response, code);
            if (result === undefined) {
                const alias = this.safeString (networks, code, code);
                result = this.safeValue (response, alias);
                if (result === undefined) {
                    const defaultNetwork = this.safeString (this.options, 'defaultNetwork', 'ERC20');
                    result = this.safeValue (response, defaultNetwork);
                    if (result === undefined) {
                        const values = Object.values (response);
                        result = this.safeValue (values, 0);
                        if (result === undefined) {
                            throw new InvalidAddress (this.id + ' fetchDepositAddress() cannot find deposit address for ' + code);
                        }
                    }
                }
            }
            return result;
        }
        // TODO: add support for all aliases here
        result = this.safeValue (response, rawNetwork);
        if (result === undefined) {
            throw new InvalidAddress (this.id + ' fetchDepositAddress() cannot find ' + network + ' deposit address for ' + code);
        }
        return result;
    }

    async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchDeposits
         * @description fetch all deposits made to an account
         * @param {str|undefined} code unified currency code
         * @param {int|undefined} since the earliest time in ms to fetch deposits for
         * @param {int|undefined} limit the maximum number of deposits structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [transaction structures]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure}
         */
        await this.loadMarkets ();
        const request = {
            // 'currency': currency['id'] + network example: USDT-TRX,
            // 'state': 'state',
            // 'start_time': since, // default 1 day
            // 'end_time': this.milliseconds (),
            // 'page_num': 1,
            // 'page_size': limit, // default 20, maximum 50
        };
        let currency = undefined;
        if (code !== undefined) {
            const rawNetwork = this.safeString (params, 'network');
            params = this.omit (params, 'network');
            if (rawNetwork === undefined) {
                throw new ArgumentsRequired (this.id + ' fetchDeposits() requires a network parameter when the currency is specified');
            }
            // currently mexc does not have network names unified so for certain things we might need TRX or TRC-20
            // due to that I'm applying the network parameter directly so the user can control it on its side
            currency = this.currency (code);
            request['currency'] = currency['id'] + '-' + rawNetwork;
        }
        if (since !== undefined) {
            request['start_time'] = since;
        }
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        const response = await this.spot2PrivateGetAssetDepositList (this.extend (request, params));
        //
        //     {
        //         "code":200,
        //         "data":{
        //             "page_size":20,
        //             "total_page":1,
        //             "total_size":1,
        //             "page_num":1,
        //             "result_list":[
        //                 {
        //                     "currency":"USDC",
        //                     "amount":150.0,
        //                     "fee":0.0,
        //                     "confirmations":19,
        //                     "address":"0x55cbd73db24eafcca97369e3f2db74b2490586e6",
        //                     "state":"SUCCESS",
        //                     "tx_id":"0xc65a9b09e1b71def81bf8bb3ec724c0c1b2b4c82200c8c142e4ea4c1469fd789:0",
        //                     "require_confirmations":12,
        //                     "create_time":"2021-10-11T18:58:25.000+00:00",
        //                     "update_time":"2021-10-11T19:01:06.000+00:00"
        //                 }
        //             ]
        //         }
        //     }
        //
        const data = this.safeValue (response, 'data', {});
        const resultList = this.safeValue (data, 'result_list', []);
        return this.parseTransactions (resultList, code, since, limit);
    }

    async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchWithdrawals
         * @description fetch all withdrawals made from an account
         * @param {str|undefined} code unified currency code
         * @param {int|undefined} since the earliest time in ms to fetch withdrawals for
         * @param {int|undefined} limit the maximum number of withdrawals structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [transaction structures]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure}
         */
        await this.loadMarkets ();
        const request = {
            // 'withdrawal_id': '4b450616042a48c99dd45cacb4b092a7', // string
            // 'currency': currency['id'],
            // 'state': 'state',
            // 'start_time': since, // default 1 day
            // 'end_time': this.milliseconds (),
            // 'page_num': 1,
            // 'page_size': limit, // default 20, maximum 50
        };
        let currency = undefined;
        if (code !== undefined) {
            currency = this.currency (code);
            request['currency'] = currency['id'];
        }
        if (since !== undefined) {
            request['start_time'] = since;
        }
        if (limit !== undefined) {
            request['limit'] = limit;
        }
        const response = await this.spot2PrivateGetAssetWithdrawList (this.extend (request, params));
        //
        //     {
        //         "code":200,
        //         "data":{
        //             "page_size":20,
        //             "total_page":1,
        //             "total_size":1,
        //             "page_num":1,
        //             "result_list":[
        //                 {
        //                     "id":"4b450616042a48c99dd45cacb4b092a7",
        //                     "currency":"USDT-TRX",
        //                     "address":"TRHKnx74Gb8UVcpDCMwzZVe4NqXfkdtPak",
        //                     "amount":30.0,
        //                     "fee":1.0,
        //                     "remark":"this is my first withdrawal remark",
        //                     "state":"WAIT",
        //                     "create_time":"2021-10-11T20:45:08.000+00:00"
        //                 }
        //             ]
        //         }
        //     }
        //
        const data = this.safeValue (response, 'data', {});
        const resultList = this.safeValue (data, 'result_list', []);
        return this.parseTransactions (resultList, code, since, limit);
    }

    parseTransaction (transaction, currency = undefined) {
        //
        // fetchDeposits
        //
        //     {
        //         "currency":"USDC",
        //         "amount":150.0,
        //         "fee":0.0,
        //         "confirmations":19,
        //         "address":"0x55cbd73db24eafcca97369e3f2db74b2490586e6",
        //         "state":"SUCCESS",
        //         "tx_id":"0xc65a9b09e1b71def81bf8bb3ec724c0c1b2b4c82200c8c142e4ea4c1469fd789:0",
        //         "require_confirmations":12,
        //         "create_time":"2021-10-11T18:58:25.000+00:00",
        //         "update_time":"2021-10-11T19:01:06.000+00:00"
        //     }
        //
        // fetchWithdrawals
        //
        //     {
        //         "id":"4b450616042a48c99dd45cacb4b092a7",
        //         "currency":"USDT-TRX",
        //         "address":"TRHKnx74Gb8UVcpDCMwzZVe4NqXfkdtPak",
        //         "amount":30.0,
        //         "fee":1.0,
        //         "remark":"this is my first withdrawal remark",
        //         "state":"WAIT",
        //         "create_time":"2021-10-11T20:45:08.000+00:00"
        //     }
        //
        // withdraw
        //
        //     {
        //         "withdrawId":"25fb2831fb6d4fc7aa4094612a26c81d"
        //     }
        //
        const id = this.safeString2 (transaction, 'id', 'withdrawId');
        const type = (id === undefined) ? 'deposit' : 'withdrawal';
        const timestamp = this.parse8601 (this.safeString (transaction, 'create_time'));
        const updated = this.parse8601 (this.safeString (transaction, 'update_time'));
        let currencyId = this.safeString (transaction, 'currency');
        let network = undefined;
        if ((currencyId !== undefined) && (currencyId.indexOf ('-') >= 0)) {
            const parts = currencyId.split ('-');
            currencyId = this.safeString (parts, 0);
            const networkId = this.safeString (parts, 1);
            network = this.safeNetwork (networkId);
        }
        const code = this.safeCurrencyCode (currencyId, currency);
        const status = this.parseTransactionStatus (this.safeString (transaction, 'state'));
        let amountString = this.safeString (transaction, 'amount');
        const address = this.safeString (transaction, 'address');
        const txid = this.safeString (transaction, 'tx_id');
        let fee = undefined;
        const feeCostString = this.safeString (transaction, 'fee');
        if (feeCostString !== undefined) {
            fee = {
                'cost': this.parseNumber (feeCostString),
                'currency': code,
            };
        }
        if (type === 'withdrawal') {
            // mexc withdrawal amount includes the fee
            amountString = Precise.stringSub (amountString, feeCostString);
        }
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'network': network,
            'address': address,
            'addressTo': undefined,
            'addressFrom': undefined,
            'tag': undefined,
            'tagTo': undefined,
            'tagFrom': undefined,
            'type': type,
            'amount': this.parseNumber (amountString),
            'currency': code,
            'status': status,
            'updated': updated,
            'fee': fee,
        };
    }

    parseTransactionStatus (status) {
        const statuses = {
            'WAIT': 'pending',
            'WAIT_PACKAGING': 'pending',
            'SUCCESS': 'ok',
        };
        return this.safeString (statuses, status, status);
    }

    async fetchPosition (symbol, params = {}) {
        /**
         * @method
         * @name mexc3#fetchPosition
         * @description fetch data on a single open contract trade position
         * @param {str} symbol unified market symbol of the market the position is held in, default is undefined
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
         */
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'symbol': market['id'],
        };
        const response = await this.fetchPositions (undefined, this.extend (request, params));
        return this.safeValue (response, 0);
    }

    async fetchPositions (symbols = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchPositions
         * @description fetch all open positions
         * @param {[str]|undefined} symbols list of unified market symbols
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [position structure]{@link https://docs.ccxt.com/en/latest/manual.html#position-structure}
         */
        await this.loadMarkets ();
        const response = await this.contractPrivateGetPositionOpenPositions (params);
        //
        //     {
        //         "success": true,
        //         "code": 0,
        //         "data": [
        //             {
        //                 "positionId": 1394650,
        //                 "symbol": "ETH_USDT",
        //                 "positionType": 1,
        //                 "openType": 1,
        //                 "state": 1,
        //                 "holdVol": 1,
        //                 "frozenVol": 0,
        //                 "closeVol": 0,
        //                 "holdAvgPrice": 1217.3,
        //                 "openAvgPrice": 1217.3,
        //                 "closeAvgPrice": 0,
        //                 "liquidatePrice": 1211.2,
        //                 "oim": 0.1290338,
        //                 "im": 0.1290338,
        //                 "holdFee": 0,
        //                 "realised": -0.0073,
        //                 "leverage": 100,
        //                 "createTime": 1609991676000,
        //                 "updateTime": 1609991676000,
        //                 "autoAddIm": false
        //             }
        //         ]
        //     }
        //
        const data = this.safeValue (response, 'data', []);
        return this.parsePositions (data, symbols);
    }

    parsePosition (position, market = undefined) {
        //
        //     {
        //         "positionId": 1394650,
        //         "symbol": "ETH_USDT",
        //         "positionType": 1,
        //         "openType": 1,
        //         "state": 1,
        //         "holdVol": 1,
        //         "frozenVol": 0,
        //         "closeVol": 0,
        //         "holdAvgPrice": 1217.3,
        //         "openAvgPrice": 1217.3,
        //         "closeAvgPrice": 0,
        //         "liquidatePrice": 1211.2,
        //         "oim": 0.1290338,
        //         "im": 0.1290338,
        //         "holdFee": 0,
        //         "realised": -0.0073,
        //         "leverage": 100,
        //         "createTime": 1609991676000,
        //         "updateTime": 1609991676000,
        //         "autoAddIm": false
        //     }
        //
        market = this.safeMarket (this.safeString (position, 'symbol'), market);
        const symbol = market['symbol'];
        const contracts = this.safeString (position, 'holdVol');
        const entryPrice = this.safeNumber (position, 'openAvgPrice');
        const initialMargin = this.safeString (position, 'im');
        const rawSide = this.safeString (position, 'positionType');
        const side = (rawSide === '1') ? 'long' : 'short';
        const openType = this.safeString (position, 'margin_mode');
        const marginType = (openType === '1') ? 'isolated' : 'cross';
        const leverage = this.safeNumber (position, 'leverage');
        const liquidationPrice = this.safeNumber (position, 'liquidatePrice');
        const timestamp = this.safeNumber (position, 'updateTime');
        return {
            'info': position,
            'symbol': symbol,
            'contracts': this.parseNumber (contracts),
            'contractSize': undefined,
            'entryPrice': entryPrice,
            'collateral': undefined,
            'side': side,
            'unrealizedProfit': undefined,
            'leverage': this.parseNumber (leverage),
            'percentage': undefined,
            'marginType': marginType,
            'notional': undefined,
            'markPrice': undefined,
            'liquidationPrice': liquidationPrice,
            'initialMargin': this.parseNumber (initialMargin),
            'initialMarginPercentage': undefined,
            'maintenanceMargin': undefined,
            'maintenanceMarginPercentage': undefined,
            'marginRatio': undefined,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
        };
    }

    async fetchTransfer (id, since = undefined, limit = undefined, params = {}) {
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchTransfer', undefined, params);
        await this.loadMarkets ();
        if (marketType === 'spot') {
            const request = {
                'transact_id': id,
            };
            const response = await this.spot2PrivateGetAssetInternalTransferInfo (this.extend (request, query));
            //
            //     {
            //         code: '200',
            //         data: {
            //             currency: 'USDT',
            //             amount: '1',
            //             transact_id: '954877a2ef54499db9b28a7cf9ebcf41',
            //             from: 'MAIN',
            //             to: 'CONTRACT',
            //             transact_state: 'SUCCESS'
            //         }
            //     }
            //
            const data = this.safeValue (response, 'data', {});
            return this.parseTransfer (data);
        } else if (marketType === 'swap') {
            throw new BadRequest (this.id + ' fetchTransfer() is not supported for ' + marketType);
        }
    }

    async fetchTransfers (code = undefined, since = undefined, limit = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#fetchTransfers
         * @description fetch a history of internal transfers made on an account
         * @param {str|undefined} code unified currency code of the currency transferred
         * @param {int|undefined} since the earliest time in ms to fetch transfers for
         * @param {int|undefined} limit the maximum number of  transfers structures to retrieve
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {[dict]} a list of [transfer structures]{@link https://docs.ccxt.com/en/latest/manual.html#transfer-structure}
         */
        const [ marketType, query ] = this.handleMarketTypeAndParams ('fetchTransfers', undefined, params);
        await this.loadMarkets ();
        const request = {};
        let currency = undefined;
        let resultList = undefined;
        if (code !== undefined) {
            currency = this.currency (code);
            request['currency'] = currency['id'];
        }
        if (marketType === 'spot') {
            if (since !== undefined) {
                request['start_time'] = since;
            }
            if (limit !== undefined) {
                if (limit > 50) {
                    throw new ExchangeError ('This exchange supports a maximum limit of 50');
                }
                request['page-size'] = limit;
            }
            const response = await this.spot2PrivateGetAssetInternalTransferRecord (this.extend (request, query));
            //
            //     {
            //         code: '200',
            //         data: {
            //             total_page: '1',
            //             total_size: '5',
            //             result_list: [{
            //                     currency: 'USDT',
            //                     amount: '1',
            //                     transact_id: '954877a2ef54499db9b28a7cf9ebcf41',
            //                     from: 'MAIN',
            //                     to: 'CONTRACT',
            //                     transact_state: 'SUCCESS'
            //                 },
            //                 ...
            //             ]
            //         }
            //     }
            //
            const data = this.safeValue (response, 'data', {});
            resultList = this.safeValue (data, 'result_list', []);
        } else if (marketType === 'swap') {
            if (limit !== undefined) {
                request['page_size'] = limit;
            }
            const response = await this.contractPrivateGetAccountTransferRecord (this.extend (request, query));
            const data = this.safeValue (response, 'data');
            resultList = this.safeValue (data, 'resultList');
            //
            //     {
            //         "success": true,
            //         "code": "0",
            //         "data": {
            //             "pageSize": "20",
            //             "totalCount": "10",
            //             "totalPage": "1",
            //             "currentPage": "1",
            //             "resultList": [
            //                 {
            //                     "id": "2980812",
            //                     "txid": "fa8a1e7bf05940a3b7025856dc48d025",
            //                     "currency": "USDT",
            //                     "amount": "22.90213135",
            //                     "type": "IN",
            //                     "state": "SUCCESS",
            //                     "createTime": "1648849076000",
            //                     "updateTime": "1648849076000"
            //                 },
            //             ]
            //         }
            //     }
            //
        }
        return this.parseTransfers (resultList, currency, since, limit);
    }

    async transfer (code, amount, fromAccount, toAccount, params = {}) {
        /**
         * @method
         * @name mexc3#transfer
         * @description transfer currency internally between wallets on the same account
         * @param {str} code unified currency code
         * @param {float} amount amount to transfer
         * @param {str} fromAccount account to transfer from
         * @param {str} toAccount account to transfer to
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [transfer structure]{@link https://docs.ccxt.com/en/latest/manual.html#transfer-structure}
         */
        await this.loadMarkets ();
        const currency = this.currency (code);
        const accounts = {
            'spot': 'MAIN',
            'swap': 'CONTRACT',
        };
        const fromId = this.safeString (accounts, fromAccount);
        const toId = this.safeString (accounts, toAccount);
        if (fromId === undefined) {
            const keys = Object.keys (accounts);
            throw new ExchangeError (this.id + ' fromAccount must be one of ' + keys.join (', '));
        }
        if (toId === undefined) {
            const keys = Object.keys (accounts);
            throw new ExchangeError (this.id + ' toAccount must be one of ' + keys.join (', '));
        }
        const request = {
            'currency': currency['id'],
            'amount': amount,
            'from': fromId,
            'to': toId,
        };
        const response = await this.spot2PrivatePostAssetInternalTransfer (this.extend (request, params));
        //
        //     {
        //         code: '200',
        //         data: {
        //             currency: 'USDT',
        //             amount: '1',
        //             transact_id: 'b60c1df8e7b24b268858003f374ecb75',
        //             from: 'MAIN',
        //             to: 'CONTRACT',
        //             transact_state: 'WAIT'
        //         }
        //     }
        //
        const data = this.safeValue (response, 'data', {});
        return this.parseTransfer (data, currency);
    }

    parseTransfer (transfer, currency = undefined) {
        //
        // spot:
        //
        //     {
        //         currency: 'USDT',
        //         amount: '1',
        //         transact_id: 'b60c1df8e7b24b268858003f374ecb75',
        //         from: 'MAIN',
        //         to: 'CONTRACT',
        //         transact_state: 'WAIT'
        //     }
        //
        // swap
        //
        //     {
        //         "currency": "USDT",
        //         "amount": "22.90213135",
        //         "txid": "fa8a1e7bf05940a3b7025856dc48d025",
        //         "id": "2980812",
        //         "type": "IN",
        //         "state": "SUCCESS",
        //         "createTime": "1648849076000",
        //         "updateTime": "1648849076000"
        //     }
        //
        const currencyId = this.safeString (transfer, 'currency');
        const id = this.safeString2 (transfer, 'transact_id', 'txid');
        const timestamp = this.safeInteger (transfer, 'createTime');
        const datetime = (timestamp !== undefined) ? this.iso8601 (timestamp) : undefined;
        const direction = this.safeString (transfer, 'type');
        let accountFrom = undefined;
        let accountTo = undefined;
        if (direction !== undefined) {
            accountFrom = (direction === 'IN') ? 'MAIN' : 'CONTRACT';
            accountTo = (direction === 'IN') ? 'CONTRACT' : 'MAIN';
        } else {
            accountFrom = this.safeString (transfer, 'from');
            accountTo = this.safeString (transfer, 'to');
        }
        return {
            'info': transfer,
            'id': id,
            'timestamp': timestamp,
            'datetime': datetime,
            'currency': this.safeCurrencyCode (currencyId, currency),
            'amount': this.safeNumber (transfer, 'amount'),
            'fromAccount': this.parseAccountId (accountFrom),
            'toAccount': this.parseAccountId (accountTo),
            'status': this.parseTransferStatus (this.safeString2 (transfer, 'transact_state', 'state')),
        };
    }

    parseAccountId (status) {
        const statuses = {
            'MAIN': 'spot',
            'CONTRACT': 'swap',
        };
        return this.safeString (statuses, status, status);
    }

    parseTransferStatus (status) {
        const statuses = {
            'SUCCESS': 'ok',
            'FAILED': 'failed',
            'WAIT': 'pending',
        };
        return this.safeString (statuses, status, status);
    }

    async withdraw (code, amount, address, tag = undefined, params = {}) {
        /**
         * @method
         * @name mexc3#withdraw
         * @description make a withdrawal
         * @param {str} code unified currency code
         * @param {float} amount the amount to withdraw
         * @param {str} address the address to withdraw to
         * @param {str|undefined} tag
         * @param {dict} params extra parameters specific to the mexc3 api endpoint
         * @returns {dict} a [transaction structure]{@link https://docs.ccxt.com/en/latest/manual.html#transaction-structure}
         */
        [ tag, params ] = this.handleWithdrawTagAndParams (tag, params);
        const networks = this.safeValue (this.options, 'networks', {});
        let network = this.safeString2 (params, 'network', 'chain'); // this line allows the user to specify either ERC20 or ETH
        network = this.safeString (networks, network, network); // handle ETH > ERC-20 alias
        this.checkAddress (address);
        await this.loadMarkets ();
        const currency = this.currency (code);
        if (tag !== undefined) {
            address += ':' + tag;
        }
        const request = {
            'currency': currency['id'],
            'address': address,
            'amount': amount,
        };
        if (network !== undefined) {
            request['chain'] = network;
            params = this.omit (params, [ 'network', 'chain' ]);
        }
        const response = await this.spot2PrivatePostAssetWithdraw (this.extend (request, params));
        //
        //     {
        //         "code":200,
        //         "data": {
        //             "withdrawId":"25fb2831fb6d4fc7aa4094612a26c81d"
        //         }
        //     }
        //
        const data = this.safeValue (response, 'data', {});
        return this.parseTransaction (data, currency);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        const [ section, access ] = api;
        [ path, params ] = this.resolvePath (path, params);
        let url = undefined;
        if (section === 'spot') {
            url = this.urls['api'][section][access] + '/api/' + this.version + '/' + path;
            let paramsEncoded = '';
            if (access === 'private') {
                params['timestamp'] = this.milliseconds ();
                params['recvWindow'] = this.safeInteger (this.options, 'recvWindow', 5000);
            }
            if (Object.keys (params).length) {
                paramsEncoded = this.urlencode (params);
                url += '?' + paramsEncoded;
            }
            if (access === 'private') {
                this.checkRequiredCredentials ();
                const signature = this.hmac (this.encode (paramsEncoded), this.encode (this.secret), 'sha256');
                url += '&' + 'signature=' + signature;
                headers = {
                    'X-MEXC-APIKEY': this.apiKey,
                };
            }
            if (method === 'POST') {
                headers['Content-Type'] = 'application/json';
            }
        } else if (section === 'contract' || section === 'spot2') {
            url = this.urls['api'][section][access] + '/' + this.implodeParams (path, params);
            params = this.omit (params, this.extractParams (path));
            if (access === 'public') {
                if (Object.keys (params).length) {
                    url += '?' + this.urlencode (params);
                }
            } else {
                this.checkRequiredCredentials ();
                const timestamp = this.milliseconds ().toString ();
                let auth = '';
                headers = {
                    'ApiKey': this.apiKey,
                    'Request-Time': timestamp,
                    'Content-Type': 'application/json',
                };
                if (method === 'POST') {
                    auth = this.json (params);
                    body = auth;
                } else {
                    params = this.keysort (params);
                    if (Object.keys (params).length) {
                        auth += this.urlencode (params);
                        url += '?' + auth;
                    }
                }
                auth = this.apiKey + timestamp + auth;
                const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256');
                headers['Signature'] = signature;
            }
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        if (response === undefined) {
            return;
        }
        // spot
        //     {"code":-1128,"msg":"Combination of optional parameters invalid.","_extend":null}
        //     {"success":false,"code":123456,"message":"Order quantity error...."}
        //
        // contract
        //
        //     {"code":10232,"msg":"The currency not exist"}
        //     {"code":10216,"msg":"No available deposit address"}
        //     {"success":true, "code":0, "data":1634095541710}
        //
        const success = this.safeValue (response, 'success', false); // v1
        if (success === true) {
            return;
        }
        const responseCode = this.safeString (response, 'code', undefined);
        if ((responseCode !== undefined) && (responseCode !== '200') && (responseCode !== '0')) {
            const feedback = this.id + ' ' + body;
            this.throwBroadlyMatchedException (this.exceptions['broad'], body, feedback);
            this.throwExactlyMatchedException (this.exceptions['exact'], responseCode, feedback);
            throw new ExchangeError (feedback);
        }
    }
};