/ common / features / selectors.ts
selectors.ts
  1  import erc20 from 'libs/erc20';
  2  import BN from 'bn.js';
  3  import EthTx from 'ethereumjs-tx';
  4  import { bufferToHex } from 'ethereumjs-util';
  5  
  6  import { Address, Wei, TokenValue, Nonce, getDecimalFromEtherUnit } from 'libs/units';
  7  import {
  8    EAC_SCHEDULING_CONFIG,
  9    EAC_ADDRESSES,
 10    calcEACEndowment,
 11    getSchedulerAddress,
 12    getScheduleData,
 13    getValidateRequestParamsData
 14  } from 'libs/scheduling';
 15  import { makeTransaction, getTransactionFields, IHexStrTransaction } from 'libs/transaction';
 16  import { stripHexPrefixAndLower } from 'libs/formatters';
 17  import { SecureWalletName, WalletName, getAddressMessage, AddressMessage } from 'config';
 18  import { Token } from 'types/network';
 19  import { ICurrentTo, ICurrentValue, IGetTransaction } from './types';
 20  import { AppState } from './reducers';
 21  import { addressBookSelectors } from './addressBook';
 22  import { walletTypes, walletSelectors } from './wallet';
 23  import { ratesSelectors } from './rates';
 24  import { customTokensSelectors } from './customTokens';
 25  import { scheduleSelectors, scheduleHelpers } from './schedule';
 26  import { transactionsSelectors } from './transactions';
 27  import { SavedTransaction } from 'types/transactions';
 28  import * as configMetaSelectors from './config/meta/selectors';
 29  import * as configSelectors from './config/selectors';
 30  import * as transactionSelectors from './transaction/selectors';
 31  import * as transactionFieldsSelectors from './transaction/fields/selectors';
 32  import * as transactionMetaSelectors from './transaction/meta/selectors';
 33  import * as transactionSignTypes from './transaction/sign/types';
 34  import * as transactionSignSelectors from './transaction/sign/selectors';
 35  import { reduceToValues, isFullTx } from './helpers';
 36  
 37  export const isAnyOfflineWithWeb3 = (state: AppState): boolean => {
 38    const { isWeb3Wallet } = walletSelectors.getWalletType(state);
 39    const offline = configMetaSelectors.getOffline(state);
 40    return offline && isWeb3Wallet;
 41  };
 42  
 43  // TODO: Convert to reselect selector (Issue #884)
 44  export function getDisabledWallets(state: AppState): any {
 45    const network = configSelectors.getNetworkConfig(state);
 46    const isOffline = configMetaSelectors.getOffline(state);
 47    const disabledWallets: any = {
 48      wallets: [],
 49      reasons: {}
 50    };
 51  
 52    const addReason = (wallets: WalletName[], reason: string) => {
 53      if (!wallets.length) {
 54        return;
 55      }
 56  
 57      disabledWallets.wallets = disabledWallets.wallets.concat(wallets);
 58      wallets.forEach(wallet => {
 59        disabledWallets.reasons[wallet] = reason;
 60      });
 61    };
 62  
 63    // Some wallets don't support some networks
 64    addReason(
 65      configSelectors.unSupportedWalletFormatsOnNetwork(state),
 66      `This wallet doesn’t support the ${network.name} network`
 67    );
 68  
 69    // Some wallets are unavailable offline
 70    if (isOffline) {
 71      addReason(
 72        [SecureWalletName.WEB3, SecureWalletName.TREZOR, SecureWalletName.SAFE_T],
 73        'This wallet cannot be accessed offline'
 74      );
 75    }
 76  
 77    // Some wallets are disabled on certain platforms
 78    if (process.env.BUILD_ELECTRON) {
 79      addReason([SecureWalletName.WEB3], 'This wallet is not supported in the MyCrypto app');
 80      addReason(
 81        [SecureWalletName.SAFE_T],
 82        'Coming soon. Please use the MyCrypto.com website in the meantime'
 83      );
 84    }
 85  
 86    // Dedupe and sort for consistency
 87    disabledWallets.wallets = disabledWallets.wallets
 88      .filter((name: string, idx: number) => disabledWallets.wallets.indexOf(name) === idx)
 89      .sort();
 90  
 91    return disabledWallets;
 92  }
 93  
 94  export function getTokens(state: AppState): walletTypes.MergedToken[] {
 95    const network = configSelectors.getStaticNetworkConfig(state);
 96    const tokens: Token[] = network ? network.tokens : [];
 97    return tokens.concat(
 98      state.customTokens.map((token: Token) => {
 99        const mergedToken = { ...token, custom: true };
100        return mergedToken;
101      })
102    ) as walletTypes.MergedToken[];
103  }
104  
105  export function getWalletConfigTokens(state: AppState): walletTypes.MergedToken[] {
106    const tokens = getTokens(state);
107    const config = walletSelectors.getWalletConfig(state);
108    if (!config || !config.tokens) {
109      return [];
110    }
111    return config.tokens
112      .map(symbol => tokens.find(t => t.symbol === symbol))
113      .filter(token => token) as walletTypes.MergedToken[];
114  }
115  
116  export const getToken = (state: AppState, unit: string): walletTypes.MergedToken | undefined => {
117    const tokens = getTokens(state);
118    const token = tokens.find(t => t.symbol === unit);
119    return token;
120  };
121  
122  export function getTokenBalances(
123    state: AppState,
124    nonZeroOnly: boolean = false
125  ): walletTypes.TokenBalance[] {
126    const tokens = getTokens(state);
127    if (!tokens) {
128      return [];
129    }
130    const ret = tokens.map(t => ({
131      symbol: t.symbol,
132      balance: state.wallet.tokens[t.symbol]
133        ? state.wallet.tokens[t.symbol].balance
134        : TokenValue('0'),
135      error: state.wallet.tokens[t.symbol] ? state.wallet.tokens[t.symbol].error : null,
136      custom: t.custom,
137      decimal: t.decimal
138    }));
139  
140    return nonZeroOnly ? ret.filter(t => !t.balance.isZero()) : ret;
141  }
142  
143  export const getTokenWithBalance = (state: AppState, unit: string): walletTypes.TokenBalance => {
144    const tokens = getTokenBalances(state, false);
145    const currentToken = tokens.filter(t => t.symbol === unit);
146    //TODO: getting the first index is kinda hacky
147    return currentToken[0];
148  };
149  
150  export const getTokenBalance = (state: AppState, unit: string): TokenValue | null => {
151    const token = getTokenWithBalance(state, unit);
152    if (!token) {
153      return token;
154    }
155    return token.balance;
156  };
157  
158  export function getShownTokenBalances(
159    state: AppState,
160    nonZeroOnly: boolean = false
161  ): walletTypes.TokenBalance[] {
162    const tokenBalances = getTokenBalances(state, nonZeroOnly);
163    const walletConfig = walletSelectors.getWalletConfig(state);
164  
165    let walletTokens: string[] = [];
166    if (walletConfig) {
167      if (walletConfig.tokens) {
168        walletTokens = walletConfig.tokens;
169      }
170    }
171  
172    return tokenBalances.filter(t => walletTokens.includes(t.symbol));
173  }
174  
175  const getUSDConversionRate = (state: AppState, unit: string) => {
176    const { isTestnet } = configSelectors.getNetworkConfig(state);
177    const { rates } = ratesSelectors.getRates(state);
178    if (isTestnet) {
179      return null;
180    }
181  
182    const conversionRate = rates[unit];
183  
184    if (!conversionRate) {
185      return null;
186    }
187    return conversionRate.USD;
188  };
189  
190  export const getValueInUSD = (state: AppState, value: TokenValue | Wei) => {
191    const unit = getUnit(state);
192    const conversionRate = getUSDConversionRate(state, unit);
193    if (!conversionRate) {
194      return null;
195    }
196    const sendValueUSD = value.muln(conversionRate);
197    return sendValueUSD;
198  };
199  export const getTransactionFeeInUSD = (state: AppState, fee: Wei) => {
200    const { unit } = configSelectors.getNetworkConfig(state);
201    const conversionRate = getUSDConversionRate(state, unit);
202  
203    if (!conversionRate) {
204      return null;
205    }
206  
207    const feeValueUSD = fee.muln(conversionRate);
208    return feeValueUSD;
209  };
210  
211  export interface AllUSDValues {
212    valueUSD: BN | null;
213    feeUSD: BN | null;
214    totalUSD: BN | null;
215  }
216  
217  export const getAllUSDValuesFromSerializedTx = (state: AppState): AllUSDValues => {
218    const fields = getParamsFromSerializedTx(state);
219    if (!fields) {
220      return {
221        feeUSD: null,
222        valueUSD: null,
223        totalUSD: null
224      };
225    }
226    const { currentValue, fee } = fields;
227    const valueUSD = getValueInUSD(state, currentValue);
228    const feeUSD = getTransactionFeeInUSD(state, fee);
229    return {
230      feeUSD,
231      valueUSD,
232      totalUSD: feeUSD && valueUSD ? valueUSD.add(feeUSD) : null
233    };
234  };
235  
236  export function getRecentNetworkTransactions(state: AppState): SavedTransaction[] {
237    const txs = transactionsSelectors.getRecentTransactions(state);
238    const network = configSelectors.getNetworkConfig(state);
239    return txs.filter(tx => tx.chainId === network.chainId);
240  }
241  
242  export function getRecentWalletTransactions(state: AppState): SavedTransaction[] {
243    const networkTxs = getRecentNetworkTransactions(state);
244    const wallet = walletSelectors.getWalletInst(state);
245  
246    if (wallet) {
247      const addr = wallet.getAddressString().toLowerCase();
248      return networkTxs.filter(tx => tx.from.toLowerCase() === addr);
249    } else {
250      return [];
251    }
252  }
253  
254  export const getSchedulingTransaction = (state: AppState): IGetTransaction => {
255    const { isFullTransaction } = getTransaction(state);
256  
257    const currentTo = getCurrentTo(state);
258    const currentValue = getCurrentValue(state);
259    const nonce = transactionFieldsSelectors.getNonce(state);
260    const gasPrice = transactionFieldsSelectors.getGasPrice(state);
261    const timeBounty = scheduleSelectors.getTimeBounty(state);
262    const scheduleGasPrice = scheduleSelectors.getScheduleGasPrice(state);
263    const scheduleGasLimit = scheduleSelectors.getScheduleGasLimit(state);
264    const scheduleType = scheduleSelectors.getScheduleType(state);
265  
266    const endowment = calcEACEndowment(
267      scheduleGasLimit.value,
268      currentValue.value,
269      scheduleGasPrice.value,
270      timeBounty.value
271    );
272  
273    let transactionData = null;
274  
275    const transactionFullAndValid = isFullTransaction && isSchedulingTransactionValid(state);
276  
277    if (transactionFullAndValid) {
278      const deposit = scheduleSelectors.getScheduleDeposit(state);
279      const scheduleTimestamp = scheduleSelectors.getScheduleTimestamp(state);
280      const windowSize = scheduleSelectors.getWindowSize(state);
281      const callData = transactionFieldsSelectors.getData(state);
282      const scheduleTimezone = scheduleSelectors.getScheduleTimezone(state);
283      const windowStart = scheduleSelectors.getWindowStart(state);
284  
285      transactionData = getScheduleData(
286        currentTo.raw,
287        callData.raw,
288        scheduleGasLimit.value,
289        currentValue.value,
290        scheduleHelpers.windowSizeBlockToMin(windowSize.value, scheduleType.value),
291        scheduleHelpers.calculateWindowStart(
292          scheduleType.value,
293          scheduleTimestamp,
294          scheduleTimezone.value,
295          windowStart.value
296        ),
297        scheduleGasPrice.value,
298        timeBounty.value,
299        deposit.value
300      );
301    }
302  
303    const transactionOptions = {
304      to: getSchedulerAddress(scheduleType.value),
305      data: transactionData,
306      gasLimit: EAC_SCHEDULING_CONFIG.SCHEDULING_GAS_LIMIT,
307      gasPrice: gasPrice.value,
308      nonce: Nonce('0'),
309      value: endowment
310    };
311  
312    if (nonce) {
313      transactionOptions.nonce = Nonce(nonce.raw);
314    }
315  
316    const schedulingTransaction: EthTx = makeTransaction(transactionOptions);
317  
318    return {
319      transaction: schedulingTransaction,
320      isFullTransaction: transactionFullAndValid
321    };
322  };
323  
324  const isSchedulingTransactionValid = (state: AppState): boolean => {
325    const schedulingState = scheduleSelectors.getScheduleState(state);
326    const windowSizeValid = scheduleSelectors.isWindowSizeValid(state);
327    const windowStartValid = scheduleHelpers.isWindowStartValid(
328      schedulingState,
329      configMetaSelectors.getLatestBlock(state)
330    );
331    const scheduleTimestampValid = scheduleHelpers.isScheduleTimestampValid(schedulingState);
332    const scheduleGasPriceValid = scheduleSelectors.isValidScheduleGasPrice(state);
333    const scheduleGasLimitValid = scheduleSelectors.isValidScheduleGasLimit(state);
334    const depositValid = scheduleSelectors.isValidScheduleDeposit(state);
335    const timeBountyValid = scheduleSelectors.isValidCurrentTimeBounty(state);
336  
337    // return true if all fields are valid
338    return (
339      // either windowStart or scheduleTimestamp is used for scheduling
340      (windowStartValid || scheduleTimestampValid) &&
341      windowSizeValid &&
342      scheduleGasPriceValid &&
343      scheduleGasLimitValid &&
344      depositValid &&
345      timeBountyValid
346    );
347  };
348  
349  export interface IGetValidateScheduleParamsCallPayload {
350    to: string;
351    data: string;
352  }
353  
354  export const getValidateScheduleParamsCallPayload = (
355    state: AppState
356  ): IGetValidateScheduleParamsCallPayload | undefined => {
357    const wallet = walletSelectors.getWalletInst(state);
358    const currentTo = getCurrentTo(state);
359    const currentValue = getCurrentValue(state);
360    const timeBounty = scheduleSelectors.getTimeBounty(state);
361    const scheduleGasPrice = scheduleSelectors.getScheduleGasPrice(state);
362    const scheduleGasLimit = scheduleSelectors.getScheduleGasLimit(state);
363    const scheduleType = scheduleSelectors.getScheduleType(state);
364    const deposit = scheduleSelectors.getScheduleDeposit(state);
365    const scheduleTimestamp = scheduleSelectors.getScheduleTimestamp(state);
366    const windowSize = scheduleSelectors.getWindowSize(state);
367    const scheduleTimezone = scheduleSelectors.getScheduleTimezone(state);
368    const windowStart = scheduleSelectors.getWindowStart(state);
369  
370    /*
371       * Checks if any of these values are null or invalid
372       * due to an user input.
373       */
374    if (
375      !currentValue.value ||
376      !currentTo.value ||
377      !scheduleGasPrice.value ||
378      !wallet ||
379      !windowSize.value ||
380      // we need either windowStart or scheduleTimestamp for scheduling
381      !(windowStart.value || scheduleTimestamp.value)
382    ) {
383      return;
384    }
385  
386    const callGasLimit = scheduleGasLimit.value || EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK;
387  
388    const endowment = calcEACEndowment(
389      callGasLimit,
390      currentValue.value,
391      scheduleGasPrice.value,
392      timeBounty.value
393    );
394  
395    const fromAddress = wallet.getAddressString();
396  
397    const data = getValidateRequestParamsData(
398      bufferToHex(currentTo.value),
399      callGasLimit,
400      currentValue.value,
401      scheduleHelpers.windowSizeBlockToMin(windowSize.value, scheduleType.value),
402      scheduleHelpers.calculateWindowStart(
403        scheduleType.value,
404        scheduleTimestamp,
405        scheduleTimezone.value,
406        windowStart.value
407      ),
408      scheduleGasPrice.value,
409      timeBounty.value,
410      deposit.value || Wei('0'),
411      scheduleType.value === 'time',
412      endowment,
413      fromAddress
414    );
415  
416    return {
417      to: EAC_ADDRESSES.KOVAN.requestFactory,
418      data
419    };
420  };
421  
422  export const isValidCurrentWindowStart = (state: AppState) => {
423    const currentWindowStart = scheduleSelectors.getWindowStart(state);
424  
425    if (!currentWindowStart.value) {
426      return false;
427    }
428  
429    return currentWindowStart.value > parseInt(configMetaSelectors.getLatestBlock(state), 10);
430  };
431  
432  export const isEtherTransaction = (state: AppState) => {
433    const unit = getUnit(state);
434    const etherUnit = configSelectors.isNetworkUnit(state, unit);
435    return etherUnit;
436  };
437  
438  export const getValidGasCost = (state: AppState) => {
439    const gasCost = transactionSelectors.getGasCost(state);
440    const etherBalance = walletSelectors.getEtherBalance(state);
441    const isOffline = configMetaSelectors.getOffline(state);
442    if (isOffline || !etherBalance) {
443      return true;
444    }
445    return gasCost.lte(etherBalance);
446  };
447  
448  export const getDecimalFromUnit = (state: AppState, unit: string) => {
449    if (configSelectors.isNetworkUnit(state, unit)) {
450      return getDecimalFromEtherUnit('ether');
451    } else {
452      const token = getToken(state, unit);
453      if (!token) {
454        throw Error(`Token ${unit} not found`);
455      }
456      return token.decimal;
457    }
458  };
459  
460  export const getCurrentTo = (state: AppState): ICurrentTo =>
461    isEtherTransaction(state)
462      ? transactionFieldsSelectors.getTo(state)
463      : transactionMetaSelectors.getTokenTo(state);
464  
465  export const getCurrentValue = (state: AppState): ICurrentValue =>
466    isEtherTransaction(state)
467      ? transactionFieldsSelectors.getValue(state)
468      : transactionMetaSelectors.getTokenValue(state);
469  
470  export const isValidCurrentTo = (state: AppState) => {
471    const currentTo = getCurrentTo(state);
472    const dataExists = transactionSelectors.getDataExists(state);
473    if (isEtherTransaction(state)) {
474      // if data exists the address can be 0x
475      return !!currentTo.value || dataExists;
476    } else {
477      return !!currentTo.value;
478    }
479  };
480  
481  export const isCurrentToLabelEntry = (state: AppState): boolean => {
482    const currentTo = getCurrentTo(state);
483    return !currentTo.raw.startsWith('0x');
484  };
485  
486  export function getCurrentToAddressMessage(state: AppState): AddressMessage | undefined {
487    const to = getCurrentTo(state);
488    return getAddressMessage(to.raw);
489  }
490  
491  export const getUnit = (state: AppState) => {
492    const serializedTransaction = getSerializedTransaction(state);
493    const contractInteraction = transactionMetaSelectors.isContractInteraction(state);
494    // attempt to get the to address from the transaction
495    if (serializedTransaction && !contractInteraction) {
496      const transactionInstance = new EthTx(serializedTransaction);
497      const { to } = transactionInstance;
498      if (to) {
499        // see if any tokens match
500        let networkTokens: null | Token[] = null;
501        const customTokens = customTokensSelectors.getCustomTokens(state);
502        const networkConfig = configSelectors.getNetworkConfig(state);
503        if (!networkConfig.isCustom) {
504          networkTokens = networkConfig.tokens;
505        }
506        const mergedTokens = networkTokens ? [...networkTokens, ...customTokens] : customTokens;
507        const toChecksumAddress = configSelectors.getChecksumAddressFn(state);
508        const stringTo = toChecksumAddress(stripHexPrefixAndLower(to.toString('hex')));
509        const result = mergedTokens.find(t => t.address === stringTo);
510        if (result) {
511          return result.symbol;
512        }
513      }
514    }
515  
516    return transactionMetaSelectors.getMetaState(state).unit;
517  };
518  
519  export const signaturePending = (state: AppState) => {
520    const { isHardwareWallet } = walletSelectors.getWalletType(state);
521    const { pending } = state.transaction.sign;
522    return { isHardwareWallet, isSignaturePending: pending };
523  };
524  
525  export const getSerializedTransaction = (state: AppState) =>
526    walletSelectors.getWalletType(state).isWeb3Wallet
527      ? transactionSignSelectors.getWeb3Tx(state)
528      : transactionSignSelectors.getSignedTx(state);
529  
530  export const getParamsFromSerializedTx = (
531    state: AppState
532  ): transactionSignTypes.SerializedTxParams => {
533    const tx = getSerializedTransaction(state);
534    const isEther = isEtherTransaction(state);
535    const decimal = transactionMetaSelectors.getDecimal(state);
536  
537    if (!tx) {
538      throw Error('Serialized transaction not found');
539    }
540    const fields = getTransactionFields(makeTransaction(tx));
541    const { value, data, gasLimit, gasPrice, to } = fields;
542    const currentValue = isEther ? Wei(value) : TokenValue(erc20.transfer.decodeInput(data)._value);
543    const currentTo = isEther ? Address(to) : Address(erc20.transfer.decodeInput(data)._to);
544    const unit = getUnit(state);
545    const fee = Wei(gasLimit).mul(Wei(gasPrice));
546    const total = fee.add(Wei(value));
547    return { ...fields, currentValue, currentTo, fee, total, unit, decimal, isToken: !isEther };
548  };
549  
550  export const getTransaction = (state: AppState): IGetTransaction => {
551    const currentTo = getCurrentTo(state);
552    const currentValue = getCurrentValue(state);
553    const transactionFields = transactionFieldsSelectors.getFields(state);
554    const unit = getUnit(state);
555    const reducedValues = reduceToValues(transactionFields);
556    const transaction: EthTx = makeTransaction(reducedValues);
557    const dataExists = transactionSelectors.getDataExists(state);
558    const validGasCost = getValidGasCost(state);
559    const isFullTransaction = isFullTx(
560      state,
561      transactionFields,
562      currentTo,
563      currentValue,
564      dataExists,
565      validGasCost,
566      unit
567    );
568  
569    return { transaction, isFullTransaction };
570  };
571  
572  export const nonStandardTransaction = (state: AppState): boolean => {
573    const etherTransaction = isEtherTransaction(state);
574    const { isFullTransaction } = getTransaction(state);
575    const dataExists = transactionSelectors.getDataExists(state);
576    return isFullTransaction && dataExists && etherTransaction;
577  };
578  
579  export const serializedAndTransactionFieldsMatch = (state: AppState, isLocallySigned: boolean) => {
580    const serialzedTransaction = getSerializedTransaction(state);
581    const { transaction, isFullTransaction } = getTransaction(state);
582    if (!isFullTransaction || !serialzedTransaction) {
583      return false;
584    }
585    const t1 = getTransactionFields(transaction);
586    // inject chainId into t1 as it wont have it from the fields
587    const networkConfig = configSelectors.getNetworkConfig(state);
588    if (!networkConfig) {
589      return false;
590    }
591    const { chainId } = networkConfig;
592    t1.chainId = chainId;
593  
594    const t2 = getTransactionFields(makeTransaction(serialzedTransaction));
595    const checkValidity = (tx: IHexStrTransaction) =>
596      Object.keys(tx).reduce(
597        (match, currField: keyof IHexStrTransaction) => match && t1[currField] === t2[currField],
598        true
599      );
600    //reduce both ways to make sure both are exact same
601    const transactionsMatch = checkValidity(t1) && checkValidity(t2);
602    // if its signed then verify the signature too
603    return transactionsMatch && isLocallySigned
604      ? makeTransaction(serialzedTransaction).verifySignature()
605      : true;
606  };
607  
608  export const getFrom = (state: AppState) => {
609    const serializedTransaction = getSerializedTransaction(state);
610  
611    // attempt to get the from address from the transaction
612    if (serializedTransaction) {
613      const transactionInstance = new EthTx(serializedTransaction);
614  
615      try {
616        const from = transactionInstance.from;
617        if (from) {
618          const toChecksumAddress = configSelectors.getChecksumAddressFn(state);
619          return toChecksumAddress(from.toString('hex'));
620        }
621      } catch (e) {
622        console.warn(e);
623      }
624    }
625    return transactionMetaSelectors.getMetaState(state).from;
626  };
627  
628  export const getCurrentBalance = (state: AppState): Wei | TokenValue | null => {
629    const etherTransaction = isEtherTransaction(state);
630    if (etherTransaction) {
631      return walletSelectors.getEtherBalance(state);
632    } else {
633      const unit = getUnit(state);
634      return getTokenBalance(state, unit);
635    }
636  };
637  
638  export function getSelectedTokenContractAddress(state: AppState): string {
639    const allTokens = configSelectors.getAllTokens(state);
640    const currentUnit = getUnit(state);
641  
642    if (configSelectors.isNetworkUnit(state, currentUnit)) {
643      return '';
644    }
645  
646    return allTokens.reduce((tokenAddr, tokenInfo) => {
647      if (tokenAddr && tokenAddr.length) {
648        return tokenAddr;
649      }
650  
651      if (tokenInfo.symbol === currentUnit) {
652        return tokenInfo.address;
653      }
654  
655      return tokenAddr;
656    }, '');
657  }
658  
659  export function getCurrentToLabel(state: AppState) {
660    const addresses = addressBookSelectors.getAddressLabels(state);
661    const currentTo = getCurrentTo(state);
662  
663    return addresses[currentTo.raw.toLowerCase()] || null;
664  }