Repository: ccxt/ccxt
Pull request: huobi - rework for networks
@carlosmiei 
I've been working to redesigning of network-related functionality for huobi, because existing implementation was too much "custom" and based on occasional "if/else" clauses to handle the networks.
I've made a complete rework  and this seems correct (a bit complex though) approach to unify networks  parsing/passing for huobi (which can also be used for other similar exchanges afterwards).

about currencies - in raw response, there were multiple field related to network,  but none of them was suitable except `displayName`:
```
 "chain": "usdterc20",  // unique id, which is actually something like "network-pair-id" of currency name and network id, but they are joined with unorganized manner, i.e. for TRON network, the network name is in the begging `trc20usdt`. moreover, for other coins, this field has very unorganized value
 "fullName": "Ethereum", // in many cases, this field value is just empty
 "baseChain": "ETH",  // this and below key are present only in part of currency response, but are missing from others
 "baseChainProtocol": "ERC20", // as above line says
 "displayName": "ERC20", // this seems existent everywhere and most good one
```
also, our unified definition of `networksById` or generally, network-codes and ids , are meant to be common names, i.e. it's acceptable that `ETH` was  network id, or `Ethereum`, or something, but it's inappropriate that `usdterc20` was network id as it's not common and each coin would have it's 'network-id & network-code' pairs. So, instead of the raw `chain` field we take `displayName` which contain the common name of underlying blockchain(network).
However, as network-related endpoints except that you provide the value that is in  `chain` field, so, to link the network-codes to chain-ids, I've added a few helper methods to seamlessly make links with each other.

This PR might be some kind of urgent to issues like:  fix #15789 and for other upcoming network-related PR.s

below are working & tested responses from unified methods.

Problem statement:
Withdrawal from Huobi problem
Hello. I test withdrawals from Huobi to Kucoin and I got some problems.
First, when I try to do this: `huobi.withdraw("USDT", 15, my_address, tag=None, params={"network": "ERC20"})`
I got
`ccxt.base.errors.BadRequest: huobipro {"status":"error","err-code":"base-currency-chain-error","err-msg":"The current currency chain does not exist","data":null}`
Why?
Ok, then I change network name to "ETH" and got this: `ccxt.base.errors.PermissionDenied: huobipro {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null}`
My questions are:
1) How should the name of the network be given for any exchange? When I withdrawed from MEXC, I filled network params with "ERC20" and "BEP20". It worked. Now on Huobi it doesn't.
2) What temporary addresses mean and how to fix it?

Hints from issue discussion:
And question #3. Now I am trying to withdraw from Kucoin. First, it gives me error Kucoin needs to have a number for withdraw with only 6 decimal numbers after "." Ok, done it, but now with this code:
```
exchangeA = exchangeA
coin = coin
amount = exchangeA.fetch_balance()["free"][coin]
if exchangeA==self.kucoin:
    begining=''
    final = ''
    if '.' in str(amount):
        begining+=str(amount).split(".")[0]+'.'
        if len(str(amount).split(".")[1])>6:
            final+=str(amount).split(".")[1][:5]
        else:
            final+=str(amount).split(".")[1]
    else:
        begining = str(amount)
    amount = float(begining+final)
address = address
exchangeA.withdraw(coin, amount, address, tag=None, params={"network": network})
```
I got this
`ccxt.base.errors.BadRequest: kucoin account.available.Amount`
> And question #3. Now I am trying to withdraw from Kucoin. First, it gives me error Kucoin needs to have a number for withdraw with only 6 decimal numbers after "." Ok, done it, but now with this code:
> 
> ```
> exchangeA = exchangeA
> coin = coin
> amount = exchangeA.fetch_balance()["free"][coin]
> if exchangeA==self.kucoin:
>     begining=''
>     final = ''
>     if '.' in str(amount):
>         begining+=str(amount).split(".")[0]+'.'
>         if len(str(amount).split(".")[1])>6:
>             final+=str(amount).split(".")[1][:5]
>         else:
>             final+=str(amount).split(".")[1]
>     else:
>         begining = str(amount)
>     amount = float(begining+final)
> address = address
> exchangeA.withdraw(coin, amount, address, tag=None, params={"network": network})
> ```
> 
> I got this `ccxt.base.errors.BadRequest: kucoin account.available.Amount`

This error happens when the balance in your account is lower than the amount you are trying to withdraw, note that kucoin has both a main and trading account. The trading account is your spot account

![](https://i.imgur.com/LXMLY4l.png)

> Ok, then I change network name to "ETH" and got this: ccxt.base.errors.PermissionDenied: huobipro {"status":"error","err-code":"api-not-support-temp-addr","err-msg":"API withdrawal does not support temporary addresses","data":null}

I'm not exactly sure what this error means, it's from the huobi exchange, but because it's `PermissionDenied` I think it may be referring to your ip address, so I think you may be trying to withdraw from a non-approved ip address

@ttodua @pcriadoperez @sc0Vu Can one of you try to reproduce this error? The api key I'm using has no withdraw permissions, and I am unable to create a new key or edit my key

> `ccxt.base.errors.BadRequest: huobipro {"status":"error","err-code":"base-currency-chain-error","err-msg":"The current currency chain does not exist","data":null}`

This is a bug, I will push a PR to fix it
@samgermain can you help with Kucoin? I'm trying to withdraw BUSD from Kucoin to MEXC. Give "BEP20" - error `ccxt.base.errors.BadRequest: kucoin Invalid chainId` . 
Give "BSC" - same error `ccxt.base.errors.BadRequest: kucoin Invalid chainId`
Why does it happen? Before it I tested withdraw with USDT TRC20 and everything worked. Kucoin allows to withdraw BUSD by BEP20 and MEXC allows to deposit BUSD with BEP20
@samgermain as I see you have already grasped the problem and as you self-assigned, I suppose that you'll push the PR and after that, I'll test it with real withdrawals. the problem seems just the need of `this.networkCodeToId` base method , with huobi and kucoin too.
> @samgermain as I see you have already grasped the problem and as you self-assigned, I suppose that you'll push the PR and after that, I'll test it with real withdrawals. the problem seems just the need of `this.networkCodeToId` base method , with huobi and kucoin too.

I believe it has to do with the default value of safeStringUpper and safeStringLower not being returned as uppercase and lowercase. See https://github.com/ccxt/ccxt/pull/15812
also `ccxt.base.errors.BadRequest: kucoin account.available.Amount` needs to be `InsufficientFunds` instead of `BadRequest`
@ttodua @samgermain can you notify me here that bugs are fixed, when you made it?
> @ttodua @samgermain can you notify me here that bugs are fixed, when you made it?

The issue will be closed when the bugs are fixed
@samgermain well, I hope this will be fixed soon, because I have a very important project that is ready in everything except withdrawing funds from Huobi and Kucoin
> @samgermain well, I hope this will be fixed soon, because I have a very important project that is ready in everything except withdrawing funds from Huobi and Kucoin

Try pestering @carlosmiei on the discord server

Commit message: lint

Patch to review:
diff --git a/js/base/Exchange.js b/js/base/Exchange.js
index b8f113358ece0..da847c550042a 100644
--- a/js/base/Exchange.js
+++ b/js/base/Exchange.js
@@ -126,6 +126,7 @@ module.exports = class Exchange {
                 'fetchTradingLimits': undefined,
                 'fetchTransactions': undefined,
                 'fetchTransfers': undefined,
+                'fetchWithdrawAddresses': undefined,
                 'fetchWithdrawal': undefined,
                 'fetchWithdrawals': undefined,
                 'reduceMargin': undefined,
diff --git a/js/huobi.js b/js/huobi.js
index 3570111ad0970..05fa074ea5f6f 100644
--- a/js/huobi.js
+++ b/js/huobi.js
@@ -3,7 +3,7 @@
 //  ---------------------------------------------------------------------------
 
 const Exchange = require ('./base/Exchange');
-const { AccountNotEnabled, ArgumentsRequired, AuthenticationError, ExchangeError, PermissionDenied, ExchangeNotAvailable, OnMaintenance, InvalidOrder, OrderNotFound, InsufficientFunds, BadSymbol, BadRequest, RateLimitExceeded, RequestTimeout, NetworkError, InvalidAddress, NotSupported } = require ('./base/errors');
+const { AccountNotEnabled, ArgumentsRequired, AuthenticationError, ExchangeError, PermissionDenied, ExchangeNotAvailable, OnMaintenance, InvalidOrder, OrderNotFound, InsufficientFunds, BadSymbol, BadRequest, RateLimitExceeded, RequestTimeout, NetworkError, NotSupported } = require ('./base/errors');
 const { TICK_SIZE, TRUNCATE } = require ('./base/functions/number');
 const Precise = require ('./base/Precise');
 
@@ -100,7 +100,7 @@ module.exports = class huobi extends Exchange {
                 'fetchTransactionFees': undefined,
                 'fetchTransactions': undefined,
                 'fetchTransfers': undefined,
-                'fetchWithdrawAddressesByNetwork': true,
+                'fetchWithdrawAddresses': true,
                 'fetchWithdrawal': undefined,
                 'fetchWithdrawals': true,
                 'fetchWithdrawalWhitelist': undefined,
@@ -908,14 +908,46 @@ module.exports = class huobi extends Exchange {
                 'defaultType': 'spot', // spot, future, swap
                 'defaultSubType': 'linear', // inverse, linear
                 'defaultNetwork': 'ERC20',
+                'defaultNetworks': {
+                    'ETH': 'ERC20',
+                },
                 'networks': {
-                    'ETH': 'erc20',
-                    'TRX': 'trc20',
-                    'HRC20': 'hrc20',
-                    'HECO': 'hrc20',
-                    'HT': 'hrc20',
-                    'ALGO': 'algo',
-                    'OMNI': '',
+                    // by displaynames
+                    'ALGO': 'ALGO',
+                    'ALGORAND': 'ALGO',
+                    'BEP20': 'BEP20',
+                    'BSC': 'BEP20',
+                    'ERC20': 'ERC20',
+                    'ETH': 'ERC20',
+                    'AVALANCHE': 'AVAXCCHAIN',
+                    'AVAX': 'AVAXCCHAIN',
+                    'HRC20': 'HECO',
+                    'HECO': 'HECO',
+                    // 'HT': 'HECO', // HT is not acceptable networkcode for unification
+                    'TRC20': 'TRC20',
+                    'TRX': 'TRC20',
+                    'BTC': 'BTC',
+                    'BITCOIN': 'BTC',
+                    'ARBITRUM': 'ARB',
+                    'ARB': 'ARB',
+                    'SOLANA': 'SOL',
+                    'SOL': 'SOL',
+                    'SPL': 'SOL',
+                    'PRC20': 'PRC20',
+                    'POLYGON': 'PRC20',
+                    'MATIC': 'PRC20',
+                },
+                'networksById': {
+                    'ALGO': 'ALGO',
+                    'BEP20': 'BEP20',
+                    'ERC20': 'ERC20',
+                    'AVAXCCHAIN': 'AVALANCHE',
+                    'HECO': 'HRC20',
+                    'TRC20': 'TRC20',
+                    'BTC': 'BTC',
+                    'ARB': 'ARBITRUM',
+                    'SOL': 'SOLANA',
+                    'PRC20': 'POLYGON',
                 },
                 // https://github.com/ccxt/ccxt/issues/5376
                 'fetchOrdersByStatesMethod': 'spot_private_get_v1_order_orders', // 'spot_private_get_v1_order_history' // https://github.com/ccxt/ccxt/pull/5392
@@ -2751,41 +2783,38 @@ module.exports = class huobi extends Exchange {
         //
         const data = this.safeValue (response, 'data', []);
         const result = {};
+        this.networkJunctionsByTitles = {};
+        this.networkTitlesByJunctions = {};
         for (let i = 0; i < data.length; i++) {
             const entry = data[i];
             const currencyId = this.safeString (entry, 'currency');
             const code = this.safeCurrencyCode (currencyId);
+            this.networkJunctionsByTitles[code] = {};
+            this.networkTitlesByJunctions[code] = {};
             const chains = this.safeValue (entry, 'chains', []);
             const networks = {};
             const instStatus = this.safeString (entry, 'instStatus');
             const currencyActive = instStatus === 'normal';
-            let fee = undefined;
             let minPrecision = undefined;
             let minWithdraw = undefined;
             let maxWithdraw = undefined;
             let deposit = undefined;
             let withdraw = undefined;
             for (let j = 0; j < chains.length; j++) {
-                const chain = chains[j];
-                const networkId = this.safeString (chain, 'chain');
-                let baseChainProtocol = this.safeString (chain, 'baseChainProtocol');
-                const huobiToken = 'h' + currencyId;
-                if (baseChainProtocol === undefined) {
-                    if (huobiToken === networkId) {
-                        baseChainProtocol = 'ERC20';
-                    } else {
-                        baseChainProtocol = this.safeString (chain, 'displayName');
-                    }
-                }
-                const network = this.safeNetwork (baseChainProtocol);
-                minWithdraw = this.safeNumber (chain, 'minWithdrawAmt');
-                maxWithdraw = this.safeNumber (chain, 'maxWithdrawAmt');
-                const withdrawStatus = this.safeString (chain, 'withdrawStatus');
-                const depositStatus = this.safeString (chain, 'depositStatus');
+                const chainEntry = chains[j];
+                const uniqueChainId = this.safeString (chainEntry, 'chain'); // i.e. usdterc20, trc20usdt ...
+                const title = this.safeString (chainEntry, 'displayName');
+                this.networkJunctionsByTitles[code][title] = uniqueChainId;
+                this.networkTitlesByJunctions[code][uniqueChainId] = title;
+                const networkCode = this.networkIdToCode (title, code);
+                minWithdraw = this.safeNumber (chainEntry, 'minWithdrawAmt');
+                maxWithdraw = this.safeNumber (chainEntry, 'maxWithdrawAmt');
+                const withdrawStatus = this.safeString (chainEntry, 'withdrawStatus');
+                const depositStatus = this.safeString (chainEntry, 'depositStatus');
                 const withdrawEnabled = (withdrawStatus === 'allowed');
                 const depositEnabled = (depositStatus === 'allowed');
                 const active = withdrawEnabled && depositEnabled;
-                const precision = this.parsePrecision (this.safeString (chain, 'withdrawPrecision'));
+                const precision = this.parsePrecision (this.safeString (chainEntry, 'withdrawPrecision'));
                 if (precision !== undefined) {
                     minPrecision = (minPrecision === undefined) ? precision : Precise.stringMin (precision, minPrecision);
                 }
@@ -2799,11 +2828,11 @@ module.exports = class huobi extends Exchange {
                 } else if (!depositEnabled) {
                     deposit = false;
                 }
-                fee = this.safeNumber (chain, 'transactFeeWithdraw');
-                networks[network] = {
-                    'info': chain,
-                    'id': networkId,
-                    'network': network,
+                const fee = this.safeNumber (chainEntry, 'transactFeeWithdraw');
+                networks[networkCode] = {
+                    'info': chainEntry,
+                    'id': uniqueChainId,
+                    'network': networkCode,
                     'limits': {
                         'withdraw': {
                             'min': minWithdraw,
@@ -2817,8 +2846,6 @@ module.exports = class huobi extends Exchange {
                     'precision': this.parseNumber (precision),
                 };
             }
-            const networksKeys = Object.keys (networks);
-            const networkLength = networksKeys.length;
             result[code] = {
                 'info': entry,
                 'code': code,
@@ -2826,7 +2853,7 @@ module.exports = class huobi extends Exchange {
                 'active': currencyActive,
                 'deposit': deposit,
                 'withdraw': withdraw,
-                'fee': (networkLength <= 1) ? fee : undefined,
+                'fee': undefined,
                 'name': undefined,
                 'limits': {
                     'amount': {
@@ -2834,8 +2861,8 @@ module.exports = class huobi extends Exchange {
                         'max': undefined,
                     },
                     'withdraw': {
-                        'min': (networkLength <= 1) ? minWithdraw : undefined,
-                        'max': (networkLength <= 1) ? maxWithdraw : undefined,
+                        'min': minWithdraw,
+                        'max': maxWithdraw,
                     },
                 },
                 'precision': this.parseNumber (minPrecision),
@@ -2845,6 +2872,33 @@ module.exports = class huobi extends Exchange {
         return result;
     }
 
+    networkIdToCode (networkId, currencyCode = undefined) {
+        // here network-id is provided as a pair of currency & chain (i.e. trc20usdt)
+        if (currencyCode === undefined) {
+            throw new ArgumentsRequired (this.id + ' networkIdToCode() requires a currencyCode argument');
+        }
+        const keysLength = (Object.keys (this.networkTitlesByJunctions)).length;
+        if (keysLength === 0) {
+            throw new ExchangeError (this.id + ' networkIdToCode() - markets need to be loaded at first');
+        }
+        const networkTitles = this.safeValue (this.networkTitlesByJunctions, currencyCode, {});
+        const networkTitle = this.safeValue (networkTitles, networkId, networkId);
+        return super.networkIdToCode (networkTitle);
+    }
+
+    networkCodeToId (networkCode, currencyCode = undefined) { // here network-id is provided as a pair of currency & chain (i.e. trc20usdt)
+        if (currencyCode === undefined) {
+            throw new ArgumentsRequired (this.id + ' networkIdToCode() requires a currencyCode argument');
+        }
+        const keysLength = (Object.keys (this.networkJunctionsByTitles)).length;
+        if (keysLength === 0) {
+            throw new ExchangeError (this.id + ' networkCodeToId() - markets need to be loaded at first');
+        }
+        const uniqueNetworkIds = this.safeValue (this.networkJunctionsByTitles, currencyCode, {});
+        const networkTitle = super.networkCodeToId (networkCode);
+        return this.safeValue (uniqueNetworkIds, networkTitle, networkTitle);
+    }
+
     async fetchBalance (params = {}) {
         /**
          * @method
@@ -4615,16 +4669,6 @@ module.exports = class huobi extends Exchange {
         return response;
     }
 
-    safeNetwork (networkId) {
-        const lastCharacterIndex = networkId.length - 1;
-        const lastCharacter = networkId[lastCharacterIndex];
-        if (lastCharacter === '1') {
-            networkId = networkId.slice (0, lastCharacterIndex);
-        }
-        const networksById = {};
-        return this.safeString (networksById, networkId, networkId);
-    }
-
     parseDepositAddress (depositAddress, currency = undefined) {
         //
         //     {
@@ -4639,18 +4683,14 @@ module.exports = class huobi extends Exchange {
         const currencyId = this.safeString (depositAddress, 'currency');
         currency = this.safeCurrency (currencyId, currency);
         const code = this.safeCurrencyCode (currencyId, currency);
-        const networkId = this.safeString (depositAddress, 'chain');
-        const networks = this.safeValue (currency, 'networks', {});
-        const networksById = this.indexBy (networks, 'id');
-        const networkValue = this.safeValue (networksById, networkId, networkId);
-        const network = this.safeStringUpper (networkValue, 'network');
         const note = this.safeString (depositAddress, 'note');
+        const networkId = this.safeString (depositAddress, 'chain');
         this.checkAddress (address);
         return {
             'currency': code,
             'address': address,
             'tag': tag,
-            'network': network,
+            'network': this.networkIdToCode (networkId, code),
             'note': note,
             'info': depositAddress,
         };
@@ -4698,39 +4738,13 @@ module.exports = class huobi extends Exchange {
          * @param {object} params extra parameters specific to the huobi api endpoint
          * @returns {object} an [address structure]{@link https://docs.ccxt.com/en/latest/manual.html#address-structure}
          */
-        const rawNetwork = this.safeStringUpper (params, 'network');
-        const networks = this.safeValue (this.options, 'networks', {});
-        const network = this.safeStringUpper (networks, rawNetwork, rawNetwork);
-        params = this.omit (params, 'network');
-        const response = await this.fetchDepositAddressesByNetwork (code, params);
-        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;
-        }
-        result = this.safeValue (response, network);
-        if (result === undefined) {
-            throw new InvalidAddress (this.id + ' fetchDepositAddress() cannot find ' + network + ' deposit address for ' + code);
-        }
-        return result;
+        const [ networkCode, paramsOmited ] = this.handleNetworkCodeAndParams (params);
+        const indexedAddresses = await this.fetchDepositAddressesByNetwork (code, paramsOmited);
+        const selectedNetworkId = this.selectNetworkIdFromAvailableNetworks (code, networkCode, indexedAddresses);
+        return indexedAddresses[selectedNetworkId];
     }
 
-    async fetchWithdrawAddressesByNetwork (code, params = {}) {
+    async fetchWithdrawAddresses (code, note = undefined, networkCode = undefined, params = {}) {
         await this.loadMarkets ();
         const currency = this.currency (code);
         const request = {
@@ -4752,41 +4766,17 @@ module.exports = class huobi extends Exchange {
         //     }
         //
         const data = this.safeValue (response, 'data', []);
-        const parsed = this.parseDepositAddresses (data, [ code ], false);
-        return this.indexBy (parsed, 'network');
-    }
-
-    async fetchWithdrawAddress (code, params = {}) {
-        const rawNetwork = this.safeStringUpper (params, 'network');
-        const networks = this.safeValue (this.options, 'networks', {});
-        const network = this.safeStringUpper (networks, rawNetwork, rawNetwork);
-        params = this.omit (params, 'network');
-        const response = await this.fetchWithdrawAddressesByNetwork (code, params);
-        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 + ' fetchWithdrawAddress() cannot find withdraw address for ' + code);
-                        }
-                    }
-                }
+        const allAddresses = this.parseDepositAddresses (data, [ code ], false);
+        const addresses = [];
+        for (let i = 0; i < allAddresses.length; i++) {
+            const address = allAddresses[i];
+            const noteMatch = (note === undefined) || (address['note'] === note);
+            const networkMatch = (networkCode === undefined) || (address['network'] === networkCode);
+            if (noteMatch && networkMatch) {
+                addresses.push (address);
             }
-            return result;
-        }
-        result = this.safeValue (response, network);
-        if (result === undefined) {
-            throw new InvalidAddress (this.id + ' fetchWithdrawAddress() cannot find ' + network + ' withdraw address for ' + code);
         }
-        return result;
+        return addresses;
     }
 
     async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
@@ -4819,7 +4809,32 @@ module.exports = class huobi extends Exchange {
             request['size'] = limit; // max 100
         }
         const response = await this.spotPrivateGetV1QueryDepositWithdraw (this.extend (request, params));
-        // return response
+        //
+        //    {
+        //         "status": "ok",
+        //         "data": [
+        //             {
+        //                 "id": "75115912",
+        //                 "type": "deposit",
+        //                 "sub-type": "NORMAL",
+        //                 "request-id": "trc20usdt-a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff-200",
+        //                 "currency": "usdt",
+        //                 "chain": "trc20usdt",
+        //                 "tx-hash": "a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff",
+        //                 "amount": "12.000000000000000000",
+        //                 "from-addr-tag": "",
+        //                 "address-id": "0",
+        //                 "address": "TRFTd1FxepQE6CnpwzUEMEbFaLm5bJK67s",
+        //                 "address-tag": "",
+        //                 "fee": "0",
+        //                 "state": "safe",
+        //                 "wallet-confirm": "2",
+        //                 "created-at": "1621843808662",
+        //                 "updated-at": "1621843857137"
+        //             },
+        //         ]
+        //     }
+        //
         return this.parseTransactions (response['data'], currency, since, limit);
     }
 
@@ -4853,7 +4868,30 @@ module.exports = class huobi extends Exchange {
             request['size'] = limit; // max 100
         }
         const response = await this.spotPrivateGetV1QueryDepositWithdraw (this.extend (request, params));
-        // return response
+        //
+        //    {
+        //         "status": "ok",
+        //         "data": [
+        //             {
+        //                 "id": "61335312",
+        //                 "type": "withdraw",
+        //                 "sub-type": "NORMAL",
+        //                 "currency": "usdt",
+        //                 "chain": "trc20usdt",
+        //                 "tx-hash": "30a3111f2fead74fae45c6218ca3150fc33cab2aa59cfe41526b96aae79ce4ec",
+        //                 "amount": "12.000000000000000000",
+        //                 "from-addr-tag": "",
+        //                 "address-id": "27321591",
+        //                 "address": "TRf5JacJQRsF4Nm2zu11W6maDGeiEWQu9e",
+        //                 "address-tag": "",
+        //                 "fee": "1.000000000000000000",
+        //                 "state": "confirmed",
+        //                 "created-at": "1621852316553",
+        //                 "updated-at": "1621852467041"
+        //             },
+        //         ]
+        //     }
+        //
         return this.parseTransactions (response['data'], currency, since, limit);
     }
 
@@ -4862,35 +4900,43 @@ module.exports = class huobi extends Exchange {
         // fetchDeposits
         //
         //     {
-        //         'id': 8211029,
-        //         'type': 'deposit',
-        //         'currency': 'eth',
-        //         'chain': 'eth',
-        //         'tx-hash': 'bd315....',
-        //         'amount': 0.81162421,
-        //         'address': '4b8b....',
-        //         'address-tag': '',
-        //         'fee': 0,
-        //         'state': 'safe',
-        //         'created-at': 1542180380965,
-        //         'updated-at': 1542180788077
-        //     }
+        //         "id": "75115912",
+        //         "type": "deposit",
+        //         "sub-type": "NORMAL",
+        //         "request-id": "trc20usdt-a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff-200",
+        //         "currency": "usdt",
+        //         "chain": "trc20usdt",
+        //         "tx-hash": "a2e229a44ef2a948c874366230bb56aa73631cc0a03d177bd8b4c9d38262d7ff",
+        //         "amount": "2849.000000000000000000",
+        //         "from-addr-tag": "",
+        //         "address-id": "0",
+        //         "address": "TRFTd1FxepQE6CnpwzUEMEbFaLm5bJK67s",
+        //         "address-tag": "",
+        //         "fee": "0",
+        //         "state": "safe",
+        //         "wallet-confirm": "2",
+        //         "created-at": "1621843808662",
+        //         "updated-at": "1621843857137"
+        //     },
         //
         // fetchWithdrawals
         //
         //     {
-        //         'id': 6908275,
-        //         'type': 'withdraw',
-        //         'currency': 'btc',
-        //         'chain': 'btc',
-        //         'tx-hash': 'c1a1a....',
-        //         'amount': 0.80257005,
-        //         'address': '1QR....',
-        //         'address-tag': '',
-        //         'fee': 0.0005,
-        //         'state': 'confirmed',
-        //         'created-at': 1552107295685,
-        //         'updated-at': 1552108032859
+        //         "id": "61335312",
+        //         "type": "withdraw",
+        //         "sub-type": "NORMAL",
+        //         "currency": "usdt",
+        //         "chain": "trc20usdt",
+        //         "tx-hash": "30a3111f2fead74fae45c6218ca3150fc33cab2aa59cfe41526b96aae79ce4ec",
+        //         "amount": "12.000000000000000000",
+        //         "from-addr-tag": "",
+        //         "address-id": "27321591",
+        //         "address": "TRf5JacJQRsF4Nm2zu11W6maDGeiEWQu9e",
+        //         "address-tag": "",
+        //         "fee": "1.000000000000000000",
+        //         "state": "confirmed",
+        //         "created-at": "1621852316553",
+        //         "updated-at": "1621852467041"
         //     }
         //
         // withdraw
@@ -4910,13 +4956,14 @@ module.exports = class huobi extends Exchange {
         if (feeCost !== undefined) {
             feeCost = Precise.stringAbs (feeCost);
         }
+        const networkId = this.safeString (transaction, 'chain');
         return {
             'info': transaction,
             'id': this.safeString2 (transaction, 'id', 'data'),
             'txid': this.safeString (transaction, 'tx-hash'),
             'timestamp': timestamp,
             'datetime': this.iso8601 (timestamp),
-            'network': this.safeStringUpper (transaction, 'chain'),
+            'network': this.networkIdToCode (networkId, code),
             'address': this.safeString (transaction, 'address'),
             'addressTo': undefined,
             'addressFrom': undefined,
@@ -4984,19 +5031,11 @@ module.exports = class huobi extends Exchange {
         if (tag !== undefined) {
             request['addr-tag'] = tag; // only for XRP?
         }
-        const networks = this.safeValue (this.options, 'networks', {});
-        let network = this.safeStringUpper (params, 'network'); // this line allows the user to specify either ERC20 or ETH
-        network = this.safeStringLower (networks, network, network); // handle ETH>ERC20 alias
-        if (network !== undefined) {
-            // possible chains - usdterc20, trc20usdt, hrc20usdt, usdt, algousdt
-            if (network === 'erc20') {
-                request['chain'] = currency['id'] + network;
-            } else {
-                request['chain'] = network + currency['id'];
-            }
-            params = this.omit (params, 'network');
+        const [ networkCode, paramsOmited ] = this.handleNetworkCodeAndParams (params);
+        if (networkCode !== undefined) {
+            request['chain'] = this.networkPairIdFromCode (code, networkCode);
         }
-        const response = await this.spotPrivatePostV1DwWithdrawApiCreate (this.extend (request, params));
+        const response = await this.spotPrivatePostV1DwWithdrawApiCreate (this.extend (request, paramsOmited));
         //
         //     {
         //         "status": "ok",


Code review comments:
diff_hunk: @@ -2845,6 +2872,33 @@ module.exports = class huobi extends Exchange {
         return result;
     }
 
+    networkIdToCode (networkId, currencyCode = undefined) {
+        // here network-id is provided as a pair of currency & chain (i.e. trc20usdt)
+        if (currencyCode === undefined) {
path: js/huobi.js
text: @user1:
@author can't we change `networkTitlesByJunctions` to be a direct map between networkId -> Unified Code?

It would be something like this:
```Javascript

{
    "bep20usdt", "BEP20",
    (...)
}
```
⬆️ I don't see any benefit in indexing by the currency, but I might be missing something. Additionally, by doing this, we can remove the `currencyCode` requirement from here, because if we only want to convert networkCode Id to unified (that does not hold any reference to the currency) we should not need the currency either I think

@author:
I have thought of that in the beginning too, but I would cause discrepancy in the logic of `networkCodeToId` and `networkIdToCode`. 
for example, to use `networkCodeToId` you need to input `currencyCode` too which is mandatory for huobi: 
`networkCodeToId ('BEP20', 'USDT') ----> bep20usdt`
so, to make it working in reverse, i thought we should samely do:
`networkIdToCode  ('bep20usdt', 'USDT') ----> BEP20 `

but you are right, we can remove `currency` requirement from id->chain bindings.

@author:
Sorry, I think I have incorrectly explained in above comment, let me explain in different way, how I build unified network junctions for huobi.


@author:
I was thinking that I had explained this somewhere a few days ago, but I don't find my such explanation here. So, I will write-down. 
First, we should define the `networks` and `networksById` options, which should link exchange-specific networks to unified networks. That junction, in most cases of our existing implementations, are like `exchange_specific_id -> unified_network_code` (where `exchange_specific_id` is the id that is used in raw request to api endpoints (for withdraw, depositAddress, etc).  however...

CASE 1
=

 if we made the same approach for huobi, it will not be viable, because huobi's "network_specific_id" does not have not generic phrase, like `BEP20, ETH, ERC20, TRX`... or whatever, instead their in-use "network_specific_ids" are "variable", constructed using currency-code and network-title, i.e. `bep20usdt`, `bep20bnb`, `erc20bnb` and etc (_which is very unintelligent design ofc)_ . So, if we don't fix that garbage junctions, we would not be able to define these below properties 
```
'networksById':{ 
     'bep20usdt': 'BEP20',
     'bep20bnb': 'BEP20',
     'bep20usdt': 'BEP20',
     .... 
}
```
`networksById` can be somehow defined (without conflict) but it will need to have populated with thousands of such currency-specific junction names under our `options`. we shouldnt do that, but even if you say that we should do that, then ok, but beside it we have more problem - it's impossible to define `networks` property at all:
````
'networks':{
     'BEP20': '...what should be here.. ?  bep20usdt , bep20bnb, .. or which one from hundreds of bep20 based junctions
     ....
},
````

CASE 2
=
so, to stay in normal regime, I constructed the following approach : 
1) as mentioned in top post of this PR, each currency objects (in raw response) has additional "displayName" (not network-specific-id-what-is-used-in-request-for-withraw-deposit endpoint), like:
```
USDT: {
    'chain_id': 'bep20usdt',
    'displayName': 'Bep20',
},
BNB: {
    'chain_id': 'bep20bnb',
    'displayName': 'Bep20',
},
```
so, as you see in that example, `displayName` is same format (i.e. `Bep20`)  for "that-network-related" currencies too (as opposed to "id" junction which is variable for each currency). So, I've used that `displayName` to call them 'exchange-specific-network-titles' and defined them in options:
````
'networks': {
    'BEP20', 'Bep20',
    'ERC20', 'Erc20',
    ... //so, quite simple job to list our 10 unified network codes-to-id linkings
},
'networksById': {
    'Bep20': 'BEP20',
    'Erc20': 'ERC20',
    ... // so, as you see, as opposed to the `CASE-1`, we dont have to defined hundreds of network-id-to-code junctions here,  like `bep20usdt : BEP20` etc.
}
```
and that looks more aligned to our whole library. 

However, as already clear for you, huobi endpoints require not such network "title" (i.e. Bep20), but the awkward junction-ids, like `bep20usdt`. so, we had to override n`IdToCode` and n`CodeToId` methods, because:

A) we dont want `networkCodeToId ('BEP20', 'USDT')` returned network "title" (i.e. `Bep20`), but instead it should return `bep20usdt`. that will be the correct behavior, even though in options we had said `networks -> BEP20` has `Bep20` id (I know that might be non-ideal, but that's only way still, let me continue...)
B) we also dont want `networkIdToCode ('Bep20'...)` to use anywhere, because for response parsing, we everywhere get i.e. `bep20usdt` like values from raw responses, so we will be handling `networkIdToCode ('bep20usdt'...)` cases actually. so, again, we don't have `networksById->bep20usdt..` properties defined, and thus, we instead need to have some container which will tell us what network-code is for `bep20usdt`.

So, to address A & B cases, we needed those two exchange-wide properties (`networkJunctionsByTitles`,) and it will sort our all these burden from users sight (without defining those awkward currency+chainid lists in implementations) and I'm using overrided n`CodeToId / IdToCode` methods in huobi, which handles such scenarios, while `super.networkCodeToId/IdToCode` is also used in their body, but `super.n-code-id` methods just do a basic lookup from `options->networks/networksById` properties, which are meant to be 'default way' for most exchanges.

hope this explanation clarifies the case. 

(last note - you are right, I might simplify the specific technical thing you mentioned in above comment, however, the whole above logic will still hold true)

@user1:
@author I understand your point, but as I said, I think we are overcomplicating it in the networkdID->networkCode translation; it is better to spend a few more bytes and keep things more transparent IMO

One more thing, can't we keep everything inside the "standard" options?

`networksById` -> would store the flat map as we discussed before (current `networkTitlesByJunctions`).

`networks` -> would store a map indexed by the currency (current `networkJunctionsByTitles` )

⬆️ since we override the methods using them, it should be ok to handle the different format of `networks` right?  

@author wdyt?



@author:
> @author I understand your point, but as I said, I think we are overcomplicating it in the networkdID->networkCode translation; it is better to spend a few more bytes and keep things more transparent IMO

ok 👍 in networkId->networkCode translation I will strip out the need of currencyCode, as `networkId`'s are unique across while data.

> One more thing, can't we keep everything inside the "standard" options?
> 
> `networksById` -> would store the flat map as we discussed before (current `networkTitlesByJunctions`).
> 
> `networks` -> would store a map indexed by the currency (current `networkJunctionsByTitles` )
> 
> ⬆️ since we override the methods using them, it should be ok to handle the different format of `networks` right?

for better understanding of me, can you please give me an example of how you mean it to be?

@user1:
@author 
>  for better understanding of me, can you please give me an example of how you mean it to be?

For instance usually, the `networks` map is something like this right?

```
networks: {
    "TRC20": "TRX"
}
```

But at huobi would be something like this ⬇️  (basically the current `networkJunctionsByTitles`)

```
networks: {
    "USDT": {
        "BEP20": "bep20usdt"
    }
}
```

diff_hunk: @@ -2751,41 +2783,38 @@ module.exports = class huobi extends Exchange {
         //
         const data = this.safeValue (response, 'data', []);
         const result = {};
+        this.networkJunctionsByTitles = {};
path: js/huobi.js
text: @user1:
I think we should avoid defining them as class props. Derived class properties are not transpilable, so we would need to add them to the base which is not desirable because this is specific to huobi. 

Even though this might be working now, it won't work after we migrate to TS or another language so can't we add them to options? 

@author:
> Derived class properties are not transpilable

I fear that I don't fully understand, what means are not transpilable? do you mean that they are not being un_camel_cased? otherwise, i see that they are being transpiled, i.e. huobi.py , there are these properties present.

@user1:
@author no, in a properly typed language you need to declare the property before using it, and we're not doing it here and we can't really because class properties are not transpilable rn

@author:
ah, i understand what you mean. you mean that the declarations (of properties) are not being transpiled, right, ofc (i though that `this.whateverProperty` occasions in method bodies were not being transpiled : ) 

and right, in ideal cases they should be declared in class. but populating `this.options` with 1MB data doesn't seem good to me. can we implement something in base, like `this.instanceOptions` or whatever?  which will take that functionality, so `options` will stay purely "read-only" like or be much less mutated.

@user1:
@author 
>  but populating this.options with 1MB data doesn't seem good to me

why would be different than storing it in a property? 

@author:
The reason is just a readability. I believe that `options` should be a property which (should) hold the "pure options" (settings) set by user/exchange which manage instance's functionality, but all other variables and data that live just during lifespan of instance, should be held in other instance property. However, that is just my optimal design, and as you say, in this case, I'll do as you suggest and will move them in options.

diff_hunk: @@ -2751,41 +2783,38 @@ module.exports = class huobi extends Exchange {
         //
         const data = this.safeValue (response, 'data', []);
         const result = {};
+        this.networkJunctionsByTitles = {};
+        this.networkTitlesByJunctions = {};
path: js/huobi.js
text: @user1:
Also these names are too ambiguous, I can't tell what they store just by looking at the names