Source: core/CrossInvoker.js

'use strict'
let ccUtil = require('../api/ccUtil');

let {
  CrossChainBtcLock,
  CrossChainBtcRedeem,
  CrossChainBtcRevoke,
  CrossChainEthLock,
  CrossChainEthRedeem,
  CrossChainEthRevoke,
  CrossChainE20Approve,
  CrossChainE20Lock,
  CrossChainE20Revoke,
  CrossChainE20Redeem
} = require('../trans/CrossChain');

let {
  NormalChainBtc,
  NormalChainE20,
  NormalChainEth
} = require('../trans/NormalChain');

/**
 * @class
 * @classdesc Class representing a class to handle cross chain.
 * SDK users can finish transfer coin or token from source chain to destination chain using this class.
 * SDK users only provide source chain info., destination chain info., action (approve,lock,redeem,revoke)
 * and the input(such as  gas, gas limit, from address, to address, amount...),SDK can draw all the configuration used
 * by system automatically
 */
class CrossInvoker {
  /**
   * @constructs
   * @param {Object} config   - The merged config from user's configuration and sdk configuration.Users' configuration
   * will override the configuration of SDK.
   *
   */
  constructor(config){
    /**
     * The merged config from user's configuration and sdk configuration.</br>
     * <pre>
     {
       port: 8545,
       useLocalNode: false,
       logPathPrex: '',
       databasePathPrex: '',
       loglevel: 'info',
       network: 'testnet',
       socketUrl: 'wss://apitest.wanchain.info',
       ethTokenAddressOnWan: '0x46397994a7e1e926ea0de95557a4806d38f10b0d',
       wanTokenAddress: 'WAN',
       ethTokenAddress: 'ETH',
       ethHtlcAddr: '0x358b18d9dfa4cce042f2926d014643d4b3742b31',
       wanHtlcAddr: '0xfbaffb655906424d501144eefe35e28753dea037',
       HtlcETHAbi:
       ethHtlcAddrE20: '0x4a8f5dd531e4cd1993b79b23dbda21faacb9c731',
       wanHtlcAddrE20: '0xfc0eba261b49763decb6c911146e3cf524fa7ebc',
       ethAbiE20:
       wanAbiE20:
       orgEthAbiE20:
       orgWanAbiE20:
       ethHtlcAddrBtc: '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2',
       wanHtlcAddrBtc: '0x5d1dd99ebaa6ee3289d9cd3369948e4ce96736c3',
       ethAbiBtc:
       wanAbiBtc:
       orgEthAbiBtc:
       orgWanAbiBtc:
       inStgLockEvent: 'ETH2WETHLock(address,address,bytes32,uint256)',
       outStgLockEvent: 'WETH2ETHLock(address,address,bytes32,uint256)',
       inStgLockEventE20: 'InboundLockLogger(address,address,bytes32,uint256,address)',
       outStgLockEventE20: 'OutboundLockLogger(address,address,bytes32,uint256,address)',
       dataName: 'testnet',
       rpcIpcPath: '/home/jacob/.wanchain/gwan.ipc',
       keyStorePath: '/home/jacob/.wanchain/testnet/keystore/',
       ethkeyStorePath: '/home/jacob/.ethereum/testnet/keystore/',
       databasePath: '/home/jacob/LocalDb',
       crossDbname: 'wanchainDb',
       crossCollection: 'crossTrans',
       crossCollectionBtc: 'crossTransBtc',
       normalCollection: 'normalTrans',
       wanKeyStorePath: '/home/jacob/.wanchain/testnet/keystore/',
       ethKeyStorePath: '/home/jacob/.ethereum/testnet/keystore/',
       btcKeyStorePath: '',
       confirmBlocks: 2,
       tryTimes: 3,
       consoleColor:
        { COLOR_FgRed: '\u001b[31m',
          COLOR_FgYellow: '\u001b[33m',
          COLOR_FgGreen: '\u001b[32m' },
       ccLog: 'logs/crossChainLog.log',
       ccErr: 'logs/crossChainErr.log',
       mrLog: 'logs/ccMonitorLog.log',
       mrErr: 'logs/ccMonitorErr.log',
       mrLogNormal: 'logs/ccMonitorLogN.log',
       mrErrNormal: 'logs/ccMonitorErrN.log',
       logfileName: 'logs/crossChainLog.log',
       errfileName: 'logs/crossChainErr.log',
       logfileNameMR: 'logs/ccMonitorLog.log',
       errfileNameMR: 'logs/ccMonitorErr.log',
       logfileNameMRN: 'logs/ccMonitorLogN.log',
       errfileNameMRN: 'logs/ccMonitorErrN.log'
     }
     </pre>
     * @type {Object}
     */
    this.config                 = config;
    this.tokensE20              = [];
    /**
     * All coin and token's info. including wan coin on WAN chain.
     * <pre>
     {
       'ETH' => Map {
                           'ETH' =>

                           {
                                       tokenSymbol: 'ETH',
                                       tokenStand: 'ETH',
                                       tokenType: 'ETH',
                                       buddy: '0x46397994a7e1e926ea0de95557a4806d38f10b0d',
                                       storemenGroup: [Array],
                                       token2WanRatio: 0,
                                       tokenDecimals: 18
                           },

                           '0x54950025d1854808b09277fe082b54682b11a50b' =>
                           {
                                       tokenSymbol: 'MKR',
                                       tokenStand: 'E20',
                                       tokenType: 'ETH',
                                       buddy: '0x29204554d51b6d8e7b477fe0fa4769b47f2a00ef',
                                       storemenGroup: [Array],
                                       token2WanRatio: '6000000',
                                       tokenDecimals: '18'

                           },

                           '0xdbf193627ee704d38495c2f5eb3afc3512eafa4c' =>
                           {
                                       tokenSymbol: 'DAI',
                                       tokenStand: 'E20',
                                       tokenType: 'ETH',
                                       buddy: '0xcc0ac621653faae13dae742ebb34f6e459218ff6',
                                       storemenGroup: [Array],
                                       token2WanRatio: '5000',
                                       tokenDecimals: '18'
                           },

                           '0x00f58d6d585f84b2d7267940cede30ce2fe6eae8' =>
                           {
                                       tokenSymbol: 'ZRX',
                                       tokenStand: 'E20',
                                       tokenType: 'ETH',
                                       buddy: '0xe7d648256543d2467ca722b7560a92c1dcb654bb',
                                       storemenGroup: [Array],
                                       token2WanRatio: '3000',
                                       tokenDecimals: '18'
                           },

                           '0x87271f3df675f13e8ceffa6e426d18a787267e9e' =>
                           {
                                       tokenSymbol: 'WCT',
                                       tokenStand: 'E20',
                                       tokenType: 'ETH',
                                       buddy: '0xe9585620239e4eca4f906cb0382ae9eb57d3ba3b',
                                       storemenGroup: [Array],
                                       token2WanRatio: '10000',
                                       tokenDecimals: '13'
                           }
                    },

       'BTC' => Map {
                       '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2' =>
                           {
                                       tokenSymbol: 'BTC',
                                       tokenStand: 'BTC',
                                       tokenType: 'BTC',
                                       buddy: '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2',
                                       storemenGroup: [],
                                       token2WanRatio: 0,
                                       tokenDecimals: 18
                           }
                    },

       'WAN' => Map {
                       'WAN' =>
                       {
                                       tokenSymbol: 'WAN',
                                       tokenStand: 'WAN',
                                       tokenType: 'WAN',
                                       buddy: 'WAN',
                                       storemenGroup: [Array],
                                       token2WanRatio: 0,
                                       tokenDecimals: 18
                       }
                    }

     }
     </pre>
     * @type {Map<any, any>}
     */
    this.chainsNameMap          = new Map();
    /**
     * Source chain's information  including both coin info. and configuration of cross chain.</br>
     * <pre>
     Map {
  'ETH' =>
	  Map {
				  'ETH' =>
				  {
					srcChain: 'ETH',
				  dstChain: 'WAN',
				  tokenSymbol: 'ETH',
				  tokenStand: 'ETH',
				  useLocalNode: false,
				  tokenDecimals: 18,
				  srcSCAddr: '0x358b18d9dfa4cce042f2926d014643d4b3742b31',
				  srcSCAddrKey: 'ETH',
				  midSCAddr: '0x358b18d9dfa4cce042f2926d014643d4b3742b31',
				  dstSCAddr: '0xfbaffb655906424d501144eefe35e28753dea037',
				  dstSCAddrKey: 'WAN',
				  srcAbi: [Array],
				  midSCAbi: [Array],
				  dstAbi: [Array],
				  srcKeystorePath: '/home/jacob/.ethereum/testnet/keystore/',
				  dstKeyStorePath: '/home/jacob/.wanchain/testnet/keystore/',
				  lockClass: 'CrossChainEthLock',
				  redeemClass: 'CrossChainEthRedeem',
				  revokeClass: 'CrossChainEthRevoke',
				  normalTransClass: 'NormalChainEth',
				  approveScFunc: 'approve',
				  lockScFunc: 'eth2wethLock',
				  redeemScFunc: 'eth2wethRefund',
				  revokeScFunc: 'eth2wethRevoke',
				  srcChainType: 'ETH',
				  dstChainType: 'WAN',
				  crossCollection: 'crossTrans',
				  normalCollection: 'normalTrans'
				  },
				  '0x54950025d1854808b09277fe082b54682b11a50b' =>
					{
				  srcChain: 'MKR',
				  dstChain: 'WAN',
				  tokenSymbol: 'MKR',
				  tokenStand: 'E20',
				  useLocalNode: false,
				  tokenDecimals: '18',
				  srcSCAddr: '0x54950025d1854808b09277fe082b54682b11a50b',
				  srcSCAddrKey: '0x54950025d1854808b09277fe082b54682b11a50b',
				  midSCAddr: '0x4a8f5dd531e4cd1993b79b23dbda21faacb9c731',
				  dstSCAddr: '0xfc0eba261b49763decb6c911146e3cf524fa7ebc',
				  dstSCAddrKey: 'WAN',
				  srcAbi: [Array],								// token abi
				  midSCAbi: [Array],							// HTLCETH abi
				  dstAbi: [Array],								// HTLCWAN abi
				  srcKeystorePath: '/home/jacob/.ethereum/testnet/keystore/',
				  dstKeyStorePath: '/home/jacob/.wanchain/testnet/keystore/',
				  approveClass: 'CrossChainE20Approve',
				  lockClass: 'CrossChainE20Lock',
				  redeemClass: 'CrossChainE20Redeem',
				  revokeClass: 'CrossChainE20Revoke',
				  normalTransClass: 'NormalChainE20',
				  approveScFunc: 'approve',
				  transferScFunc: 'transfer',
				  lockScFunc: 'inboundLock',
				  redeemScFunc: 'inboundRedeem',
				  revokeScFunc: 'inboundRevoke',
				  srcChainType: 'ETH',
				  dstChainType: 'WAN',
				  crossCollection: 'crossTrans',
				  normalCollection: 'normalTrans',
				  token2WanRatio: '6000000'
					},

				  '0x87271f3df675f13e8ceffa6e426d18a787267e9e' =>
				  {
				  srcChain: 'WCT',
				  dstChain: 'WAN',
				  tokenSymbol: 'WCT',
				  tokenStand: 'E20',
				  useLocalNode: false,
				  tokenDecimals: '13',
				  srcSCAddr: '0x87271f3df675f13e8ceffa6e426d18a787267e9e',
				  srcSCAddrKey: '0x87271f3df675f13e8ceffa6e426d18a787267e9e',
				  midSCAddr: '0x4a8f5dd531e4cd1993b79b23dbda21faacb9c731',
				  dstSCAddr: '0xfc0eba261b49763decb6c911146e3cf524fa7ebc',
				  dstSCAddrKey: 'WAN',
				  srcAbi: [Array],
				  midSCAbi: [Array],
				  dstAbi: [Array],
				  srcKeystorePath: '/home/jacob/.ethereum/testnet/keystore/',
				  dstKeyStorePath: '/home/jacob/.wanchain/testnet/keystore/',
				  approveClass: 'CrossChainE20Approve',
				  lockClass: 'CrossChainE20Lock',
				  redeemClass: 'CrossChainE20Redeem',
				  revokeClass: 'CrossChainE20Revoke',
				  normalTransClass: 'NormalChainE20',
				  approveScFunc: 'approve',
				  transferScFunc: 'transfer',
				  lockScFunc: 'inboundLock',
				  redeemScFunc: 'inboundRedeem',
				  revokeScFunc: 'inboundRevoke',
				  srcChainType: 'ETH',
				  dstChainType: 'WAN',
				  crossCollection: 'crossTrans',
				  normalCollection: 'normalTrans',
				  token2WanRatio: '10000'
				  }

				},

	'BTC' =>
				Map
				{
				  '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2' =>
				  {
				  srcChain: 'BTC',
				  dstChain: 'WAN',
				  tokenSymbol: 'BTC',
				  tokenStand: 'BTC',
				  useLocalNode: false,
				  tokenDecimals: 18,
				  srcSCAddr: '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2',
				  srcSCAddrKey: '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2',
				  midSCAddr: '0xcdc96fea7e2a6ce584df5dc22d9211e53a5b18b2',
				  dstSCAddr: '0x5d1dd99ebaa6ee3289d9cd3369948e4ce96736c3',
				  dstSCAddrKey: 'WAN',
				  srcAbi: [Array],
				  midSCAbi: [Array],
				  dstAbi: [Array],
				  srcKeystorePath: '',
				  dstKeyStorePath: '/home/jacob/.wanchain/testnet/keystore/',
				  approveClass: 'CrossChainE20Approve',
				  lockClass: 'CrossChainBtcLock',
				  redeemClass: 'CrossChainBtcRedeem',
				  revokeClass: 'CrossChainBtcRevoke',
				  normalTransClass: 'NormalChainBtc',
				  approveScFunc: 'approve',
				  lockScFunc: 'inboundLock',
				  redeemScFunc: 'inboundRedeem',
				  revokeScFunc: 'inboundRevoke',
				  srcChainType: 'BTC',
				  dstChainType: 'WAN',
				  crossCollection: 'crossTransBtc',
				  normalCollection: 'normalTrans'
				  }
				}

  }
     </pre>
     * @type {Map<any, any>}
     */
    this.srcChainsMap           = new Map();
    /**
     * Destination chain's information  including both coin info. and configuration of cross chain.</br>
     * It is similar to {@link CrossInvoker#srcChainsMap  [Destination chains info.]}
     * @type {Map<any, any>}
     */
    this.dstChainsMap           = new Map();
  };

  /**
   * Init all the configuration used for cross chain.
   * step1: get tokens info. from api server.</br>
   * step2: init all chains info.</br>
   * step3: update token or coin symbol info. and the storemengroup related to this token or coin.</br>
   * step4: init all the token or coin info. in source chains Map</br>
   * step5: init all the token or coin info. in destination chains Map</br>
   * @returns {Promise<void>}
   */
  async  init() {
    global.logger.debug("CrossInvoker init");
    try{
      global.logger.debug("getTokensE20 start>>>>>>>>>>");
      this.tokensE20              = await this.getTokensE20();
      global.logger.debug("getTokensE20 done<<<<<<<<<<");

      global.logger.debug("initChainsNameMap start>>>>>>>>>>");
      this.chainsNameMap          = this.initChainsNameMap();
      global.logger.debug("initChainsNameMap done<<<<<<<<<<");

      global.logger.debug("initChainsSymbol&&initChainsStoremenGroup start>>>>>>>>>>");
      await Promise.all(this.initChainsSymbol().concat(this.initChainsStoremenGroup()));
      global.logger.debug("initChainsSymbol&&initChainsStoremenGroup done<<<<<<<<<<");

      global.logger.debug("initSrcChainsMap start>>>>>>>>>>");
      this.srcChainsMap           = this.initSrcChainsMap();
      global.logger.debug("initSrcChainsMap done<<<<<<<<<<");

      global.logger.debug("initDstChainsMap start>>>>>>>>>>");
      this.dstChainsMap           = this.initDstChainsMap();
      global.logger.debug("initDstChainsMap done<<<<<<<<<<");

      global.logger.info("this.chainsNameMap");
      global.logger.info(this.chainsNameMap);

      global.logger.info("this.srcChainsMap");
      global.logger.info(this.srcChainsMap);

      global.logger.info("this.dstChainsMap");
      global.logger.info(this.dstChainsMap);


    }catch(error){
      global.logger.error("CrossInvoker init error: ",error);
      process.exit();
    }
  };

  /**
   * get ERC20 tokens from API server, the configuration of API server is located in ../../../../conf/config.js
   * @returns {Promise<void>}
   */
  async getTokensE20(){
    let tokensE20 = await ccUtil.getRegErc20Tokens();
    return tokensE20;
  };

  /**
   * Build all the coins and tokens information, this information is two-layer map structure.</br>
   * first layer: key is chains name(such as 'ETH', 'WAN','BTC'), value is all the tokens and coins info. on this chain.
   * second layer: key is the token unique address(currently, system use contract address of the tokens.</br>
   * second layer: value is the info. about the token or coin.</br>
   * Below is an example.
   * {@link CrossInvoker#chainsNameMap [example for chainsName]}
   * @returns {Map<any, any>} - Two layers Map including all the tokens and coins chain information.
   */
  initChainsNameMap(){
    let chainsNameMap     = new Map();
    let chainsNameMapEth  = new Map();
    let chainsNameMapBtc  = new Map();
    let chainsNameMapWan  = new Map();
    // init ETH
    let keyTemp;
    keyTemp               = this.config.ethTokenAddress;
    let valueTemp         = {};
    valueTemp.tokenSymbol = 'ETH';
    valueTemp.tokenStand  = 'ETH';
    valueTemp.tokenType   = 'ETH';
    valueTemp.buddy       = this.config.ethTokenAddressOnWan;
    valueTemp.storemenGroup = [];
    valueTemp.token2WanRatio = 0;
    valueTemp.tokenDecimals   = 18;
    chainsNameMapEth.set(keyTemp,valueTemp);

    // init E20
    for(let token of this.tokensE20){
      /**
       * key of coin or token's chain info., contract address of coin or token.
       * @member {string}  - key of the token or coin's chain info., contract address
       */
      let keyTemp;
      /**
       * value of coin or token's chain info.
       * @type {Object}   - value of the token or coin's chain info.
       */
      let valueTemp           = {};

      keyTemp                 = token.tokenOrigAddr;
      valueTemp.tokenSymbol   = '';
      valueTemp.tokenStand    = 'E20';
      valueTemp.tokenType     = 'ETH';
      valueTemp.buddy         = token.tokenWanAddr;
      valueTemp.storemenGroup = [];
      valueTemp.token2WanRatio = token.ratio;
      valueTemp.tokenDecimals   = 18;
      chainsNameMapEth.set(keyTemp, valueTemp);
    }
    chainsNameMap.set('ETH',chainsNameMapEth);

    // init BTC
    keyTemp                 = this.config.ethHtlcAddrBtc;
    valueTemp               = {};
    valueTemp.tokenSymbol   = 'BTC';
    valueTemp.tokenStand    = 'BTC';
    valueTemp.tokenType     = 'BTC';
    valueTemp.buddy         = this.config.ethHtlcAddrBtc;
    valueTemp.storemenGroup = [];
    valueTemp.token2WanRatio = 0;
    valueTemp.tokenDecimals   = 18;
    chainsNameMapBtc.set(keyTemp,valueTemp);

    chainsNameMap.set('BTC',chainsNameMapBtc);

    // init WAN
    keyTemp                 = this.config.wanTokenAddress;
    valueTemp               = {};
    valueTemp.tokenSymbol   = 'WAN';
    valueTemp.tokenStand    = 'WAN';
    valueTemp.tokenType     = 'WAN';
    valueTemp.buddy         = 'WAN';
    valueTemp.storemenGroup = [];
    valueTemp.token2WanRatio = 0;
    valueTemp.tokenDecimals   = 18;

    chainsNameMap.set(keyTemp,valueTemp);

    chainsNameMapWan.set(keyTemp,valueTemp);
    chainsNameMap.set('WAN',chainsNameMapWan);

    return chainsNameMap;
  };

  /**
   * Build promise array which is used to get the tokens' symbol and tokens' decimal
   * @returns {Array} - promise array about getting symbol
   */
  initChainsSymbol() {
    global.logger.debug("Entering initChainsSymbol...");
    let promiseArray = [];
    for (let dicValue of this.chainsNameMap.values()) {
      for(let [keyTemp, valueTemp] of dicValue){
        if (valueTemp.tokenStand === 'E20'){
          promiseArray.push(ccUtil.getErc20Info(keyTemp).then(ret => {
              global.logger.debug("tokenSymbol: tokenDecimals:", ret.symbol,ret.decimals);
              valueTemp.tokenSymbol = ret.symbol;
              valueTemp.tokenDecimals = ret.decimals;
            },
            err=>{
              global.logger.debug("initChainsSymbol err:", err);
              global.logger.debug("Symbol key deleted:", keyTemp);

              let subMap = this.chainsNameMap.get('ETH');
              subMap.delete(keyTemp);
            }));
        }
      }
    }
    return promiseArray;
  };
  /**
   * Build promise array which is used to get the storemen group information related to the token or coin.
   * @returns {Array}
   */
  initChainsStoremenGroup(){
    global.logger.debug("Entering initChainsStoremenGroup...");
    let promiseArray = [];
    for (let dicValue of this.chainsNameMap.values()) {
      for(let [keyTemp, valueTemp] of dicValue){
        switch(valueTemp.tokenStand){
          case 'ETH':
          {
            promiseArray.push(ccUtil.getEthSmgList().then(ret => valueTemp.storemenGroup = ret));
            break;
          }
          case 'E20':
          {
            promiseArray.push(ccUtil.syncErc20StoremanGroups(keyTemp).then(ret => valueTemp.storemenGroup = ret));
            break;
          }
          // case 'BTC':
          // {
          //   valueTemp.storemenGroup = await ccUtil.getEthSmgList();
          //   break;
          // }
          case 'WAN':
          {
            promiseArray.push(ccUtil.getEthSmgList().then(ret => valueTemp.storemenGroup = ret));
            break;
          }
          default:
            break;
        }
      }
    }
    return promiseArray;
  };

  /**
   * Init source chains map, this map is also a two layer map data structure.</br>
   * first layer: key is chains name(such as 'ETH', 'WAN','BTC'), value is all the tokens and coins info. on this chain.</br>
   * second layer: key is the token unique address(currently, system use contract address of the tokens.</br>
   * second layer: value is the info. which used to finish cross token or coin from source chain to destination chain.</br>
   * In this map, there is no WAN info., If the source chain in this map, it surely cross source chain to 'WAN'</br>
   * If the destination chain in destination map (dstChainsMap), it surely the source chain is 'WAN'</br>
   * Using this machine , system can decide the inbound (to 'WAN' chain)or outbound (from 'WAN')direction easily, </br>
   * and in the next cross chain step</br>
   * system gets rid of inbound or outbound decision, this sharply make service logic more easier.</br>
   * Attention : in srcChainsMap and in dstChainsMap ,there is no info. of 'WAN', because after system know</br>
   * one side non 'WAN' chain, the other side chain is surely 'WAN'.
   * Below is an example.</br>
   {@link CrossInvoker#srcChainsMap [example for source chains map]}
   * @returns {Map<any, any>}
   */
  initSrcChainsMap(){

    let srcChainsMap    = new Map();
    let srcChainsMapEth = new Map();
    let srcChainsMapBtc = new Map();

    for (let item of this.chainsNameMap) {
      let dicValue  = item[1];
      for(let chainName of dicValue){
        let tockenAddr      = chainName[0];
        let chainNameValue  = chainName[1];
        if(chainNameValue.tokenStand === 'WAN'){
          continue;
        }
        let srcChainsKey    = tockenAddr;
        let srcChainsValue  = {};
        srcChainsValue.srcChain = chainNameValue.tokenSymbol;
        srcChainsValue.dstChain = 'WAN';

        srcChainsValue.tokenSymbol  = chainNameValue.tokenSymbol;
        srcChainsValue.tokenStand   = chainNameValue.tokenStand;
        srcChainsValue.useLocalNode = this.config.useLocalNode;
        srcChainsValue.tokenDecimals = chainNameValue.tokenDecimals;

        switch(chainNameValue.tokenStand){
          case 'ETH':
          {
            srcChainsValue.srcSCAddr      = this.config.ethHtlcAddr;
            srcChainsValue.srcSCAddrKey   = tockenAddr;
            srcChainsValue.midSCAddr      = this.config.ethHtlcAddr;
            srcChainsValue.dstSCAddr      = this.config.wanHtlcAddr;
            srcChainsValue.dstSCAddrKey   = this.config.wanTokenAddress;
            srcChainsValue.srcAbi         = this.config.HtlcETHAbi;
            srcChainsValue.midSCAbi       = this.config.HtlcETHAbi;
            srcChainsValue.dstAbi         = this.config.HtlcWANAbi;
            srcChainsValue.srcKeystorePath= this.config.ethKeyStorePath ;
            srcChainsValue.dstKeyStorePath= this.config.wanKeyStorePath;
            srcChainsValue.lockClass      = 'CrossChainEthLock';
            srcChainsValue.redeemClass    = 'CrossChainEthRedeem';
            srcChainsValue.revokeClass    = 'CrossChainEthRevoke';
            srcChainsValue.normalTransClass    = 'NormalChainEth';
            srcChainsValue.approveScFunc  = 'approve';
            srcChainsValue.lockScFunc     = 'eth2wethLock';
            srcChainsValue.redeemScFunc   = 'eth2wethRefund';
            srcChainsValue.revokeScFunc   = 'eth2wethRevoke';
            srcChainsValue.srcChainType   = 'ETH';
            srcChainsValue.dstChainType   = 'WAN';
            srcChainsValue.crossCollection    = this.config.crossCollection;
            srcChainsValue.normalCollection    = this.config.normalCollection;
          }
            break;
          case 'E20':
          {
            srcChainsValue.srcSCAddr      = tockenAddr;
            srcChainsValue.srcSCAddrKey   = tockenAddr;
            srcChainsValue.midSCAddr      = this.config.ethHtlcAddrE20;
            srcChainsValue.dstSCAddr      = this.config.wanHtlcAddrE20;
            srcChainsValue.dstSCAddrKey   = this.config.wanTokenAddress;
            srcChainsValue.srcAbi         = this.config.orgEthAbiE20;
            srcChainsValue.midSCAbi       = this.config.ethAbiE20;
            srcChainsValue.dstAbi         = this.config.wanAbiE20;
            srcChainsValue.srcKeystorePath= this.config.ethKeyStorePath ;
            srcChainsValue.dstKeyStorePath= this.config.wanKeyStorePath;
            srcChainsValue.approveClass   = 'CrossChainE20Approve';
            srcChainsValue.lockClass      = 'CrossChainE20Lock';
            srcChainsValue.redeemClass    = 'CrossChainE20Redeem';
            srcChainsValue.revokeClass    = 'CrossChainE20Revoke';
            srcChainsValue.normalTransClass    = 'NormalChainE20';
            srcChainsValue.approveScFunc  = 'approve';
            srcChainsValue.transferScFunc = 'transfer';
            srcChainsValue.lockScFunc     = 'inboundLock';
            srcChainsValue.redeemScFunc   = 'inboundRedeem';
            srcChainsValue.revokeScFunc   = 'inboundRevoke';
            srcChainsValue.srcChainType   = 'ETH';
            srcChainsValue.dstChainType   = 'WAN';
            srcChainsValue.crossCollection    = this.config.crossCollection;
            srcChainsValue.normalCollection    = this.config.normalCollection;
            srcChainsValue.token2WanRatio     = chainNameValue.token2WanRatio;
          }
            break;
          case 'BTC':
          {
            srcChainsValue.srcSCAddr      = tockenAddr;
            srcChainsValue.srcSCAddrKey   = tockenAddr;
            srcChainsValue.midSCAddr      = this.config.ethHtlcAddrBtc;
            srcChainsValue.dstSCAddr      = this.config.wanHtlcAddrBtc;
            srcChainsValue.dstSCAddrKey   = this.config.wanTokenAddress;
            srcChainsValue.srcAbi         = this.config.orgEthAbiBtc;
            srcChainsValue.midSCAbi       = this.config.ethAbiBtc;
            srcChainsValue.dstAbi         = this.config.wanAbiBtc;
            srcChainsValue.srcKeystorePath= this.config.btcKeyStorePath ;
            srcChainsValue.dstKeyStorePath= this.config.wanKeyStorePath;
            srcChainsValue.approveClass   = 'CrossChainE20Approve';
            srcChainsValue.lockClass      = 'CrossChainBtcLock';
            srcChainsValue.redeemClass    = 'CrossChainBtcRedeem';
            srcChainsValue.revokeClass    = 'CrossChainBtcRevoke';
            srcChainsValue.normalTransClass    = 'NormalChainBtc';
            srcChainsValue.approveScFunc  = 'approve';
            srcChainsValue.lockScFunc     = 'inboundLock';
            srcChainsValue.redeemScFunc   = 'inboundRedeem';
            srcChainsValue.revokeScFunc   = 'inboundRevoke';
            srcChainsValue.srcChainType   = 'BTC';
            srcChainsValue.dstChainType   = 'WAN';
            srcChainsValue.crossCollection    = this.config.crossCollectionBtc;
            srcChainsValue.normalCollection    = this.config.normalCollection;
          }
            break;
          default:
            break;
        }
        switch(chainNameValue.tokenType){
          case 'ETH':
          {

            srcChainsMapEth.set(srcChainsKey,srcChainsValue);
            break;
          }
          case 'BTC':
          {
            srcChainsMapBtc.set(srcChainsKey,srcChainsValue);
            break;
          }
          default:
          {
            break;
          }
        }

      }
    }
    srcChainsMap.set('ETH',srcChainsMapEth);
    srcChainsMap.set('BTC',srcChainsMapBtc);

    return srcChainsMap;
  };

  /**
   * Build destination chains info. It is similar to source chains info.
   * {@link CrossInvoker#srcChainsMap [example for destination chains map]}
   * @returns {Map<any, any>}
   */
  initDstChainsMap(){

    let config            = this.config;
    let dstChainsMap      = new Map();

    let dstChainsMapEth   = new Map();
    let dstChainsMapBtc   = new Map();

    for (let item of this.chainsNameMap) {
      let dicValue = item[1];
      for(let chainName of dicValue){
        let tockenAddr      = chainName[0];
        let chainNameValue  = chainName[1];
        if(chainNameValue.tokenStand === 'WAN'){
          continue;
        }
        let srcChainsKey            = tockenAddr;
        let srcChainsValue          = {};
        srcChainsValue.srcChain     = 'WAN';
        srcChainsValue.dstChain     = chainNameValue.tokenSymbol;

        srcChainsValue.tokenSymbol  = chainNameValue.tokenSymbol;
        srcChainsValue.tokenStand   = chainNameValue.tokenStand;
        srcChainsValue.useLocalNode = config.useLocalNode;
        srcChainsValue.tokenDecimals = chainNameValue.tokenDecimals;

        switch(chainNameValue.tokenStand){
          case 'ETH':
          {
            srcChainsValue.srcSCAddr      = config.wanHtlcAddr;
            srcChainsValue.srcSCAddrKey   = config.wanTokenAddress;
            srcChainsValue.midSCAddr      = config.wanHtlcAddr;
            srcChainsValue.dstSCAddr      = config.ethHtlcAddr;
            srcChainsValue.dstSCAddrKey   = tockenAddr;
            srcChainsValue.srcAbi         = config.HtlcWANAbi;
            srcChainsValue.midSCAbi       = config.HtlcWANAbi;
            srcChainsValue.dstAbi         = config.HtlcETHAbi;
            srcChainsValue.srcKeystorePath= config.wanKeyStorePath ;
            srcChainsValue.dstKeyStorePath= config.ethKeyStorePath;
            srcChainsValue.lockClass      = 'CrossChainEthLock';
            srcChainsValue.redeemClass    = 'CrossChainEthRedeem';
            srcChainsValue.revokeClass    = 'CrossChainEthRevoke';
            srcChainsValue.normalTransClass = 'NormalChainEth';
            srcChainsValue.approveScFunc  = 'approve';
            srcChainsValue.lockScFunc     = 'weth2ethLock';
            srcChainsValue.redeemScFunc   = 'weth2ethRefund';
            srcChainsValue.revokeScFunc   = 'weth2ethRevoke';
            srcChainsValue.srcChainType   = 'WAN';
            srcChainsValue.dstChainType   = 'ETH';
            srcChainsValue.crossCollection    = this.config.crossCollection;
            srcChainsValue.normalCollection    = this.config.normalCollection;
          }
            break;
          case 'E20':
          {
            srcChainsValue.buddySCAddr    = chainNameValue.buddy;  // use for WAN approve
            srcChainsValue.srcSCAddr      = tockenAddr;            // use for contract parameter
            srcChainsValue.srcSCAddrKey   = config.wanTokenAddress;
            srcChainsValue.midSCAddr      = config.wanHtlcAddrE20;
            srcChainsValue.dstSCAddr      = config.ethHtlcAddrE20;
            srcChainsValue.dstSCAddrKey   = tockenAddr;
            srcChainsValue.srcAbi         = config.orgWanAbiE20;    // for approve
            srcChainsValue.midSCAbi       = config.wanAbiE20;       // for lock
            srcChainsValue.dstAbi         = config.ethAbiE20;
            srcChainsValue.srcKeystorePath= config.wanKeyStorePath ;
            srcChainsValue.dstKeyStorePath= config.ethKeyStorePath;
            srcChainsValue.approveClass   = 'CrossChainE20Approve';
            srcChainsValue.lockClass      = 'CrossChainE20Lock';
            srcChainsValue.redeemClass    = 'CrossChainE20Redeem';
            srcChainsValue.revokeClass    = 'CrossChainE20Revoke';
            srcChainsValue.normalTransClass    = 'NormalChainE20';
            srcChainsValue.approveScFunc  = 'approve';
            srcChainsValue.lockScFunc     = 'outboundLock';
            srcChainsValue.redeemScFunc   = 'outboundRedeem';
            srcChainsValue.revokeScFunc   = 'outboundRevoke';
            srcChainsValue.srcChainType   = 'WAN';
            srcChainsValue.dstChainType   = 'ETH';
            srcChainsValue.crossCollection    = this.config.crossCollection;
            srcChainsValue.token2WanRatio     = chainNameValue.token2WanRatio;
            srcChainsValue.normalCollection    = this.config.normalCollection;
          }
            break;
          case 'BTC':
          {
            srcChainsValue.srcSCAddr      = chainNameValue.buddy;
            srcChainsValue.srcSCAddrKey   = config.wanTokenAddress;
            srcChainsValue.midSCAddr      = config.wanHtlcAddrBtc;
            srcChainsValue.dstSCAddr      = config.ethHtlcAddrBtc;
            srcChainsValue.dstSCAddrKey   = config.ethHtlcAddrBtc;
            srcChainsValue.srcAbi         = config.orgWanAbiBtc;
            srcChainsValue.midSCAbi       = config.wanAbiBtc;
            srcChainsValue.dstAbi         = config.ethAbiBtc;
            srcChainsValue.srcKeystorePath= config.wanKeyStorePath ;
            srcChainsValue.dstKeyStorePath= config.btcKeyStorePath;
            srcChainsValue.approveClass   = 'CrossChainE20Approve';
            srcChainsValue.lockClass      = 'CrossChainBtcLock';
            srcChainsValue.redeemClass    = 'CrossChainBtcRedeem';
            srcChainsValue.revokeClass    = 'CrossChainBtcRevoke';
            srcChainsValue.normalTransClass    = 'NormalChainBtc';
            srcChainsValue.approveScFunc  = 'approve';
            srcChainsValue.lockScFunc     = 'inboundLock';
            srcChainsValue.redeemScFunc   = 'inboundRedeem';
            srcChainsValue.revokeScFunc   = 'inboundRevoke';
            srcChainsValue.srcChainType   = 'WAN';
            srcChainsValue.dstChainType   = 'BTC';
            srcChainsValue.crossCollection    = this.config.crossCollectionBtc;
            srcChainsValue.normalCollection    = this.config.normalCollection;
          }
            break;
          default:
            break;
        }

        switch(chainNameValue.tokenType){
          case 'ETH':
          {
            dstChainsMapEth.set(srcChainsKey,srcChainsValue);
            break;
          }
          case 'BTC':
          {
            dstChainsMapBtc.set(srcChainsKey,srcChainsValue);
            break;
          }
          default:
          {
            break;
          }
        }
      }
    }

    dstChainsMap.set('ETH',dstChainsMapEth);
    dstChainsMap.set('BTC',dstChainsMapBtc);


    return dstChainsMap;
  };

  /**
   * Check the chainName whether in source chain Map or not , if yes, the cross chain is chainName->'WAN'</br>
   * @param {Object} chainName   - {@link CrossInvoker#chainsNameMap chainName} ,chainName[0] the contract address of
   * coin or token; chainName[1] the value of toke or coin chain's info.
   * @returns {boolean}
   * true: In source chains map, the destination chain is 'WAN'</br>
   * false: Not in source chains map.
   */
  isInSrcChainsMap(chainName){
    let keyTemp   = chainName[0];
    let valueTemp = chainName[1];
    let chainType = valueTemp.tokenType;

    if(this.srcChainsMap.has(chainType)){
      let  subMap = this.srcChainsMap.get(chainType);
      if(subMap.has(keyTemp)){
        return true;
      }
    }
    return false;
  }
  /**
   * Check the chainName whether in destination chain Map or not , if yes, the cross chain is 'WAN'>chainName</br>
   * @param {Object} chainName   - {@link CrossInvoker#chainsNameMap chainName} ,chainName[0] the contract address of
   * coin or token; chainName[1] the value of toke or coin chain's info.
   * @returns {boolean}
   * true: In destination chains map, the source chain is 'WAN'</br>
   * false: Not in destination chains map.
   */
  isInDstChainsMap(chainName){
    let keyTemp   = chainName[0];
    let valueTemp = chainName[1];
    let chainType = valueTemp.tokenType;

    if(this.dstChainsMap.has(chainType)){
      let  subMap = this.dstChainsMap.get(chainType);
      if(subMap.has(keyTemp)){
        return true;
      }
    }
    return false;
  }

  /**
   * Get the source chains info. supported by system.
   * @returns {Promise<Map|*>} similar to {@link CrossInvoker#chainsNameMap this}
   */
  async getSrcChainName(){
    try{
      await this.freshErc20Symbols();
      return this.chainsNameMap;
    }catch(err){
      global.logger.debug("getSrcChainName error:",err);
      process.exit();
    }
  };

  /**
   * Get the destination chains info. after SDK users have selected the source chain.</br>
   * @param {Object}selectedSrcChainName  - {@link CrossInvoker#chainsNameMap selectedSrcChainName} ,selectedSrcChainName[0] the contract address of
   * coin or token; selectedSrcChainName[1] the value of toke or coin chain's info.
   * @returns {Map<any, any>} similar to {@link CrossInvoker#chainsNameMap this}
   */
  getDstChainName(selectedSrcChainName){
    try{
      let ret = new Map();
      if(selectedSrcChainName[1].tokenType !== 'WAN'){
        ret.set('WAN',this.chainsNameMap.get('WAN'));
      }else{
        // get new E20 symbols
        // update delete or insert E20 symbols in the old chainsName
        // update delete or insert E20 symbols in srcChainName and  dstChainName
        //await this.freshErc20Symbols();
        for(let item of this.chainsNameMap){
          if(item[0] !== 'WAN'){
            ret.set(item[0],item[1]);
          }
        }
      }
      return ret;
    }catch(err){
      global.logger.error("getDstChainName error:",err);
      process.exit();
    }
  };

  /**
   * Because during the system running, there is no Erc20 symbols added or deleted. </br>
   * system can process this scenario automatically.
   * @returns {Promise<void>}
   */
  async freshErc20Symbols(){
    global.logger.debug("Entering freshErc20Symbols");
    try{
      let tokensE20New      = await this.getTokensE20();
      global.logger.debug("freshErc20Symbols new tokens: \n",tokensE20New);
      global.logger.debug("freshErc20Symbols old tokens: \n",this.tokensE20);

      let tokenAdded     = ccUtil.differenceABTokens(tokensE20New,this.tokensE20);
      let tokenDeleted   = ccUtil.differenceABTokens(this.tokensE20,tokensE20New);
      global.logger.info("tokenAdded size: freshErc20Symbols:", tokenAdded.size,tokenAdded);
      global.logger.info("tokenDeleted size: freshErc20Symbols:",tokenDeleted.size,tokenDeleted);
      let chainsNameMapEth = this.chainsNameMap.get('ETH');
      let promiseArray          = [];
      if(tokenAdded.size !== 0){
        for(let token of tokenAdded.values()){
          // Add token
          let keyTemp;
          let valueTemp             = {};
          keyTemp                   = token.tokenOrigAddr;
          valueTemp.tokenSymbol     = '';
          valueTemp.tokenStand      = 'E20';
          valueTemp.tokenType       = 'ETH';
          valueTemp.buddy           = token.tokenWanAddr;
          valueTemp.storemenGroup   = [];
          valueTemp.token2WanRatio  = token.ratio;
          valueTemp.tokenDecimals   = 18;

          // promiseArray.push(ccUtil.syncErc20StoremanGroups(keyTemp).then(
          //   ret => valueTemp.storemenGroup = ret));

          promiseArray.push(ccUtil.getErc20Info(keyTemp).catch(tokenInfo=>{
              valueTemp.tokenSymbol   = tokenInfo.symbol;
              valueTemp.tokenDecimals = tokenInfo.decimals;
              chainsNameMapEth.set(keyTemp, valueTemp);
            },
            err=>{
              global.logger.debug("freshErc20Symbols err:", err);
            }));
        }
        await Promise.all(promiseArray);
      }else{
        global.logger.info("freshErc20Symbols no new symbols added");
      }
      if(tokenDeleted.size !== 0){
        for(let token of tokenAdded.values()){
          // delete token
          let keyTemp;
          keyTemp                 = token.tokenOrigAddr;
          chainsNameMapEth.delete(keyTemp);
        }
      }else{
        global.logger.info("freshErc20Symbols no new symbols deleted!");
      }
      // reinitialize the srcChainsMap and dstChainsMap
      if(tokenDeleted.size !== 0 || tokenAdded.size !== 0){
        this.srcChainsMap         = this.initSrcChainsMap();
        this.dstChainsMap         = this.initDstChainsMap();
      }
    }catch(err){
      global.logger.error("freshErc20Symbols error:",err);
      global.logger.debug("freshErc20Symbols error:",err);
      process.exit();
    }
  };

  /**
   * When users provide source chain, and destination chain. System can get the right keystore path </br>
   * for future use in cross chain process.
   * @param {Object}  srcChainName  - {@link CrossInvoker#chainsNameMap srcChainName} ,srcChainName[0] the contract address of
   * coin or token; srcChainName[1] the value of toke or coin chain's info.
   * @param {Object}  dstChainName - {@link CrossInvoker#chainsNameMap dstChainName} ,dstChainName[0] the contract address of
   * coin or token; dstChainName[1] the value of toke or coin chain's info.
   * @returns {Array}
   */
  getKeyStorePaths(srcChainName,dstChainName){
    let valueTemp = srcChainName[1];
    let keyStorePaths = [];
    switch(valueTemp.tokenStand){
      case 'WAN':
      {
        keyStorePaths.push({path:config.wanKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      case 'E20':
      {
        keyStorePaths.push({path:config.ethKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      case 'ETH':
      {
        keyStorePaths.push({path:config.ethKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      case 'BTC':
      {
        keyStorePaths.push({path:config.btcKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      default:
        break;
    }
    valueTemp = dstChainName[1];
    switch(valueTemp.tokenStand){
      case 'WAN':
      {
        keyStorePaths.push({path:config.wanKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      case 'E20':
      {
        keyStorePaths.push({path:config.ethKeyStorePath,type:valueTemp.tokenStand });

      }
        break;
      case 'ETH':
      {
        keyStorePaths.push({path:config.ethKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      case 'BTC':
      {
        keyStorePaths.push({path:config.btcKeyStorePath,type:valueTemp.tokenStand });
      }
        break;
      default:
        break;
    }
    return keyStorePaths;
  };

  /**
   * Get the buddy contract address, in chain info., there is two contract address. One is contract address</br>
   * on the chain, for example ZRX is ERC20 token,ZRX token has a contract address on ETH chain; The other </br>
   * contract address for token ZRX is  the contract address on WAN chain.Here the buddy contract address is the</br>
   * second contract address. System does not support get contract address on ETH by contract address on WAN</br>
   * @param {sting} contractAddr  - The coin or token's contract address,unique representing a coin or token.
   * @param {string} chainType    - enum {'ETH','WAN','BTC'}
   * @returns {string}            - The buddy address of coin or token's contract address.
   */
  getKeyByBuddyContractAddr(contractAddr,chainType){
    let chainNameSubMap =  this.chainsNameMap.get(chainType);
    for(let chainsNameItem of chainNameSubMap){
      if(chainsNameItem[1].buddy === contractAddr){
        return chainsNameItem[0];
      }
    }
    return null;
  };

  /**
   * Build the right storeman group list by srcChainName and dstChainName.</br>
   * Since each storeman group has two address, one is the address on ETH chain, the other address is on WAN.</br>
   * System can get the right address by below two parameters.
   * @param {Object}  srcChainName  - {@link CrossInvoker#chainsNameMap srcChainName} ,srcChainName[0] the contract address of
   * coin or token; srcChainName[1] the value of toke or coin chain's info.
   * @param {Object}  dstChainName - {@link CrossInvoker#chainsNameMap dstChainName} ,dstChainName[0] the contract address of
   * coin or token; dstChainName[1] the value of toke or coin chain's info.
   * @returns {Promise<Array>}
   */
  async getStoremanGroupList(srcChainName,dstChainName){

    try{
      let valueSrcTemp      = srcChainName[1];
      let valueDstTemp      = dstChainName[1];

      let keySrcTemp        = srcChainName[0];
      let keyDstTemp        = dstChainName[0];

      let storemanGroupListResult  = [];

      if (this.isInSrcChainsMap(srcChainName)){
        // destination is WAN
        // build StoremenGroupList src address list
        // get latest storemengroup

        switch(valueSrcTemp.tokenStand){
          case 'ETH':
          {
            valueSrcTemp.storemenGroup = await ccUtil.getEthSmgList();
            break;
          }
          case 'E20':
          {
            valueSrcTemp.storemenGroup = await ccUtil.syncErc20StoremanGroups(keySrcTemp);
            break;
          }
          default:
          {
            break;
          }
        }

        for(let itemOfStoreman of valueSrcTemp.storemenGroup){
          switch(valueSrcTemp.tokenStand){
            case 'ETH':
            {
              itemOfStoreman.storemenGroupAddr = itemOfStoreman.ethAddress;
              break;
            }
            case 'E20':
            {
              //itemOfStoreman.storemenGroupAddr = itemOfStoreman.smgOriginalChainAddress;
              itemOfStoreman.storemenGroupAddr = itemOfStoreman.smgOrigAddr;
              break;
            }
            default:
            {
              itemOfStoreman.storemenGroupAddr = itemOfStoreman.ethAddress;
              break;
            }
          }
          storemanGroupListResult.push(itemOfStoreman);
        }
      }else{
        if(this.isInDstChainsMap(dstChainName)){
          // source is WAN
          // build StoremenGroupList dst address list
          // get latest storemengroup

          switch(valueDstTemp.tokenStand){
            case 'ETH':
            {
              valueDstTemp.storemenGroup = await ccUtil.getEthSmgList();
              break;
            }
            case 'E20':
            {
              valueDstTemp.storemenGroup = await ccUtil.syncErc20StoremanGroups(keyDstTemp);
              break;
            }
            default:
            {
              break;
            }
          }

          for(let itemOfStoreman of valueDstTemp.storemenGroup){
            switch(valueDstTemp.tokenStand){
              case 'ETH':
              {
                itemOfStoreman.storemenGroupAddr = itemOfStoreman.wanAddress;
                break;
              }
              case 'E20':
              {
                //itemOfStoreman.storemenGroupAddr = itemOfStoreman.storemanGroup;
                itemOfStoreman.storemenGroupAddr = itemOfStoreman.smgWanAddr;
                break;
              }
              default:
              {
                itemOfStoreman.storemenGroupAddr = itemOfStoreman.wanAddress;
                break;
              }
            }
            storemanGroupListResult.push(itemOfStoreman);
          }
        }else{
          process.exit();
        }
      }
      return storemanGroupListResult;
    }catch(err){
      global.logger.error("getStoremanGroupList error:",err);
      process.exit();
    }
  };

  /**
   * Get the chain info by contract address, and the chainType.</br>
   * First, system search  value in two layer MAP by chainType. </br>
   * Second, system search value in the second layer, and get the right info. of chain</br>
   * @param contractAddr
   * @param chainType
   * @returns {null}
   */
  getSrcChainNameByContractAddr(contractAddr,chainType){
    // global.logger.debug("contractAddr",contractAddr);
    // global.logger.debug("chainType",chainType);
    if(this.chainsNameMap.has(chainType) === false){
      return null;
    }
    let subMap = this.chainsNameMap.get(chainType);
    for(let chainsNameItem of subMap){
      if(chainsNameItem[0] === contractAddr){
        return chainsNameItem;
      }
    }
    return null;
  };

  /**
   * Get the configuration used during cross chain.</br>
   * @param {Object}  srcChainName  - {@link CrossInvoker#chainsNameMap srcChainName} ,srcChainName[0] the contract address of
   * coin or token; srcChainName[1] the value of toke or coin chain's info.
   * @param {Object}  dstChainName - {@link CrossInvoker#chainsNameMap dstChainName} ,dstChainName[0] the contract address of
   * coin or token; dstChainName[1] the value of toke or coin chain's info.
   */
  getCrossInvokerConfig(srcChainName, dstChainName) {
    let config = {};
    //global.logger.debug("this.srcChainsMap:",this.srcChainsMap);
    if (srcChainName && this.isInSrcChainsMap(srcChainName)){
      // destination is WAN
      let chainType   = srcChainName[1].tokenType;
      let subMap      = this.srcChainsMap.get(chainType);
      config          = subMap.get(srcChainName[0]);
    } else {
      if (dstChainName && this.isInDstChainsMap(dstChainName)) {
        // source is WAN
        let chainType = dstChainName[1].tokenType;
        let subMap    = this.dstChainsMap.get(chainType);
        config        = subMap.get(dstChainName[0]);
      } else {
        global.logger.debug("invoke error!");
        global.logger.debug("srcChainName: ", srcChainName);
        global.logger.debug("dstChainName: ", dstChainName);
        process.exit();
      }
    }
    return config;
  };

  /**
   * Get the class, invoke this class's function run, users can finish cross chain.
   * @param crossInvokerConfig
   * @param action
   * @returns {*}
   */
  getCrossInvokerClass(crossInvokerConfig, action){
    let ACTION = action.toString().toUpperCase();
    let invokeClass = null;
    switch(ACTION){
      case 'LOCK':
      {
        invokeClass = crossInvokerConfig.lockClass;
      }
        break;

      case 'REDEEM':
      {
        invokeClass = crossInvokerConfig.redeemClass;
      };
        break;
      case 'REVOKE':
      {
        invokeClass = crossInvokerConfig.revokeClass;
      };
        break;
      case 'APPROVE':
      {
        invokeClass = crossInvokerConfig.approveClass;
      };
        break;
      default:
      {
        global.logger.debug("Error action! ", ACTION);
      }
    }
    return invokeClass;
  };

  /**
   * Get invoker which includes class, input, config ,this invoker used to finish cross chain.
   * @param crossInvokerClass
   * @param crossInvokerInput
   * @param crossInvokerConfig
   * @returns {any}
   */
  getInvoker(crossInvokerClass, crossInvokerInput, crossInvokerConfig){
    let invoke = eval(`new ${crossInvokerClass}(crossInvokerInput,crossInvokerConfig)`);
    return invoke;
  }

  /**
   * Users provide source chain info., destination chain info., and the action,input(amount, gas,gas limit..)</br>
   * 1) SDK build the configuration</br>
   * 2) SDK get the invoke class</br>
   * 3) SDK generate invoker</br>
   * 4) SDK call run function of invoker to finish cross chain.</br>
   * @param srcChainName
   * @param dstChainName
   * @param action
   * @param input
   * @returns {Promise<*>}
   */
  async invoke(srcChainName, dstChainName, action, input){
    let config      = this.getCrossInvokerConfig(srcChainName,dstChainName);
    let ACTION      = action.toString().toUpperCase();
    let invokeClass = null;

    switch(ACTION){
      case 'LOCK':
      {
        invokeClass = config.lockClass;
      }
        break;

      case 'REDEEM':
      {
        invokeClass = config.redeemClass;
      }
        break;
      case 'REVOKE':
      {
        invokeClass = config.revokeClass;
      }
        break;
      case 'APPROVE':
      {
        invokeClass = config.approveClass;
      }
        break;
      default:
      {
        global.logger.debug("Error action! ", ACTION);
        process.exit();
      }
    }
    // global.logger.debug("Action is : ", ACTION);
    // global.logger.debug("invoke class : ", invokeClass);
    // global.logger.debug("config is :",config);
    // global.logger.debug("input is :",input);
    let invoke = eval(`new ${invokeClass}(input,config)`);
    let ret = await invoke.run();
    return ret;
  }

  /**
   * This function is used to transfer coin or token on the same chain.</br>
   * Source chain name and destination chain name is same.</br>
   * For example:</br>
   * ETH->ETH, ETH(ZRX)->ETH(ZRX),WAN->WAN</br>
   * @param srcChainName
   * @param input
   * @returns {Promise<*>}
   */
  async  invokeNormalTrans(srcChainName, input){
    let config;
    let dstChainName  = null;
    if(srcChainName[1].tokenType === 'WAN'){
      // on wan chain: coin WAN->WAN
      dstChainName    = ccUtil.getSrcChainNameByContractAddr(this.config.ethTokenAddress,'ETH');
    }
    config            = this.getCrossInvokerConfig(srcChainName,dstChainName);
    global.logger.debug("invokeNormalTrans config is :",config);
    let invokeClass;
    invokeClass       = config.normalTransClass;
    global.logger.debug("invokeNormalTrans invoke class : ", invokeClass);
    let invoke        = eval(`new ${invokeClass}(input,config)`);
    let ret           = await invoke.run();
    return ret;
  }

  /**
   * This function is used to transfer coin or token on the same chain.</br>
   * Source chain name and destination chain name is same.</br>
   * For example:</br>
   * ETH->ETH, ETH(ZRX)->ETH(ZRX),WAN->WAN;</br>
   * WAN(WETH)->WAN(WETH), WAN(WZRX)->WANWZRX),WAN(WBTC)->WAN(WBTC)</br>
   * @param srcChainName
   * @param dstChainName
   * @param input
   * @returns {Promise<*>}
   */
  async  invokeNormal(srcChainName,dstChainName,input){
    let config;
    // on wan chain: support  WZRX->WZRX, WETH->WETH
    config            = this.getCrossInvokerConfig(srcChainName,dstChainName);
    global.logger.debug("invokeNormal config is :",config);
    let invokeClass;
    invokeClass       = config.normalTransClass;
    global.logger.debug("invokeNormal invoke class : ", invokeClass);
    let invoke        = eval(`new ${invokeClass}(input,config)`);
    let ret           = await invoke.run();
    return ret;
  }
}
module.exports = CrossInvoker;