text: import abi from 'human-standard-token-abi'
  import pify from 'pify'
  import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url'
  import { checksumAddress } from '../helpers/utils/util'
  import { calcTokenBalance, estimateGas } from '../pages/send/send.utils'
  import ethUtil from 'ethereumjs-util'
  import { fetchLocale } from '../helpers/utils/i18n-helper'
  import { getMethodDataAsync } from '../helpers/utils/transactions.util'
  import { fetchSymbolAndDecimals } from '../helpers/utils/token-util'
  import switchDirection from '../helpers/utils/switch-direction'
  import log from 'loglevel'
  import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../app/scripts/lib/enums'
  import { hasUnconfirmedTransactions } from '../helpers/utils/confirm-tx.util'
  import { setCustomGasLimit } from '../ducks/gas/gas.duck'
  import txHelper from '../../lib/tx-helper'
  import { getEnvironmentType } from '../../../app/scripts/lib/util'
  import * as actionConstants from './actionConstants'
  import {
    getPermittedAccountsForCurrentTab,
    getSelectedAddress,
  } from '../selectors'
  import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account'
  import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask'
  
  let background = null
  let promisifiedBackground = null
  export function _setBackgroundConnection (backgroundConnection) {
    background = backgroundConnection
    promisifiedBackground = pify(background)
  }
  
  export function goHome () {
    return {
      type: actionConstants.GO_HOME,
    }
  }
  
  // async actions
  
  export function tryUnlockMetamask (password) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      dispatch(unlockInProgress())
      log.debug(`background.submitPassword`)
      return new Promise((resolve, reject) => {
        background.submitPassword(password, (error) => {
          if (error) {
            return reject(error)
          }
  
          resolve()
        })
      })
        .then(() => {
          dispatch(unlockSucceeded())
          return forceUpdateMetamaskState(dispatch)
        })
        // .then(() => {
        //   return new Promise((resolve, reject) => {
        //     debugger;
        //     background.verifySeedPhrase((err) => {
        //       if (err) {
        //         dispatch(displayWarning(err.message))
        //         return reject(err)
        //       }
  
        //       resolve()
        //     })
        //   })
        // })
        .then(() => {
          dispatch(hideLoadingIndication())
        })
        .catch((err) => {
          dispatch(unlockFailed(err.message))
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  
  export function tryUnlockMetamask2 (newKeyAssign) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      dispatch(unlockInProgress())
      log.debug(`background.googleLogin`)
      return new Promise((resolve, reject) => {
        background.torusGoogleLogin(newKeyAssign, (error) => {
          if (error) {
            return reject(error)
          }
          resolve()
        })
      })
        .then(() => {
          dispatch(unlockSucceeded())
          dispatch(setCompletedOnboarding())
          return forceUpdateMetamaskState(dispatch)
        })
        .then(() => {
          dispatch(hideLoadingIndication())
        })
        .catch((err) => {
          // dispatch(unlockFailed(err.message))
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  export function tryUnlockMetamask3 (password) {
  
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.reconstructTorusKeyWithPassword(password, (err) => {
          if (err) {
            reject(err)
          }
          resolve()
        })
      }).then(() => {
  
        dispatch(unMarkPasswordForgotten())
        return promisifiedBackground.getState()
      })
        .then((newState) => {
  
          dispatch(updateMetamaskState(newState))
          dispatch(showAccountsPage())
          dispatch(hideLoadingIndication())
  
          if (newState.selectedAddress) {
            dispatch({
              type: actionConstants.SHOW_ACCOUNT_DETAIL,
              value: newState.selectedAddress,
            })
          }
          return newState
        })
        .catch((err) => {
          dispatch(displayWarning(err.message))
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  export function createNewVaultAndRestore (password, seed) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.createNewVaultAndRestore`)
      let vault
      return new Promise((resolve, reject) => {
        background.createNewVaultAndRestore(password, seed, (err, _vault) => {
          if (err) {
            return reject(err)
          }
          vault = _vault
          resolve()
        })
      })
        .then(() => dispatch(unMarkPasswordForgotten()))
        .then(() => {
          dispatch(showAccountsPage())
          dispatch(hideLoadingIndication())
          return vault
        })
        .catch((err) => {
          dispatch(displayWarning(err.message))
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  export function createNewTorusVaultAndRestore (password, privateKey, userDetails) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.createNewTorusVaultAndRestore`)
      let vault
      return new Promise((resolve, reject) => {
        log.info('actions.js createnewtoruvaultcalled')
        background.createNewTorusVaultAndRestore(password, privateKey, userDetails, (err, _vault) => {
          log.info('callback from vault')
          if (err) {
            log.debug(err)
            return reject(err)
          }
          vault = _vault
          resolve()
        })
      })
        .then(() => dispatch(unMarkPasswordForgotten()))
        .then(() => {
          dispatch(showAccountsPage())
          dispatch(hideLoadingIndication())
          return vault
        })
        .catch((err) => {
          dispatch(displayWarning(err.message))
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  export function createNewVaultAndGetSeedPhrase (password) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      try {
        await createNewVault(password)
        const seedWords = await verifySeedPhrase()
        dispatch(hideLoadingIndication())
        return seedWords
      } catch (error) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(error.message))
        throw new Error(error.message)
      }
    }
  }
  
  export function unlockAndGetSeedPhrase (password) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      try {
        await submitPassword(password)
        const seedWords = await verifySeedPhrase()
        await forceUpdateMetamaskState(dispatch)
        dispatch(hideLoadingIndication())
        return seedWords
      } catch (error) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(error.message))
        throw new Error(error.message)
      }
    }
  }
  
  export function submitPassword (password) {
    return new Promise((resolve, reject) => {
      background.submitPassword(password, (error) => {
        if (error) {
          return reject(error)
        }
  
        resolve()
      })
    })
  }
  
  export function createNewVault (password) {
    return new Promise((resolve, reject) => {
      background.createNewVaultAndKeychain(password, (error) => {
        if (error) {
          return reject(error)
        }
  
        resolve(true)
      })
    })
  }
  
  export function verifyPassword (password) {
    return new Promise((resolve, reject) => {
      background.submitPassword(password, (error) => {
        if (error) {
          return reject(error)
        }
  
        resolve(true)
      })
    })
  }
  
  export function verifySeedPhrase () {
    return new Promise((resolve, reject) => {
      background.verifySeedPhrase((error, seedWords) => {
        if (error) {
          return reject(error)
        }
  
        resolve(seedWords)
      })
    })
  }
  
  export function requestRevealSeedWords (password) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.submitPassword`)
  
      try {
        await verifyPassword(password)
        const seedWords = await verifySeedPhrase()
        dispatch(hideLoadingIndication())
        return seedWords
      } catch (error) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(error.message))
        throw new Error(error.message)
      }
    }
  }
  
  export function tryReverseResolveAddress (address) {
    return () => {
      return new Promise((resolve) => {
        background.tryReverseResolveAddress(address, (err) => {
          if (err) {
            log.error(err)
          }
          resolve()
        })
      })
    }
  }
  
  export function fetchInfoToSync () {
    return (dispatch) => {
      log.debug(`background.fetchInfoToSync`)
      return new Promise((resolve, reject) => {
        background.fetchInfoToSync((err, result) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve(result)
        })
      })
    }
  }
  
  export function resetAccount () {
    return (dispatch) => {
      dispatch(showLoadingIndication())
  
      return new Promise((resolve, reject) => {
        background.resetAccount((err, account) => {
          dispatch(hideLoadingIndication())
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          log.info('Transaction history reset for ' + account)
          dispatch(showAccountsPage())
          resolve(account)
        })
      })
    }
  }
  
  export function removeAccount (address) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      try {
        await new Promise((resolve, reject) => {
          background.removeAccount(address, (error, account) => {
            if (error) {
              return reject(error)
            }
            return resolve(account)
          })
        })
        await forceUpdateMetamaskState(dispatch)
      } catch (error) {
        dispatch(displayWarning(error.message))
        throw error
      } finally {
        dispatch(hideLoadingIndication())
      }
  
      log.info('Account removed: ' + address)
      dispatch(showAccountsPage())
    }
  }
  
  export function importNewAccount (strategy, args, userDetails) {
    return async (dispatch) => {
      let newState
      dispatch(showLoadingIndication('This may take a while, please be patient.'))
      try {
        log.debug(`background.importAccountWithStrategy`)
        await promisifiedBackground.importAccountWithStrategy(strategy, args, userDetails)
        log.debug(`background.getState`)
        newState = await promisifiedBackground.getState()
      } catch (err) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(err.message))
        throw err
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      if (newState.selectedAddress) {
        dispatch({
          type: actionConstants.SHOW_ACCOUNT_DETAIL,
          value: newState.selectedAddress,
        })
      }
      return newState
    }
  }
  
  export function addNewAccount () {
    log.debug(`background.addNewAccount`)
    return async (dispatch, getState) => {
      const oldIdentities = getState().metamask.identities
      dispatch(showLoadingIndication())
  
      let newIdentities
      try {
        const { identities } = await promisifiedBackground.addNewAccount()
        newIdentities = identities
      } catch (error) {
        dispatch(displayWarning(error.message))
        throw error
      }
      const newAccountAddress = Object.keys(newIdentities).find((address) => !oldIdentities[address])
      dispatch(hideLoadingIndication())
      await forceUpdateMetamaskState(dispatch)
      return newAccountAddress
    }
  }
  
  export function checkHardwareStatus (deviceName, hdPath) {
    log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let unlocked
      try {
        unlocked = await promisifiedBackground.checkHardwareStatus(deviceName, hdPath)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
  
      dispatch(hideLoadingIndication())
      await forceUpdateMetamaskState(dispatch)
      return unlocked
    }
  }
  
  export function forgetDevice (deviceName) {
    log.debug(`background.forgetDevice`, deviceName)
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      try {
        await promisifiedBackground.forgetDevice(deviceName)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
  
      dispatch(hideLoadingIndication())
      await forceUpdateMetamaskState(dispatch)
    }
  }
  
  export function connectHardware (deviceName, page, hdPath) {
    log.debug(`background.connectHardware`, deviceName, page, hdPath)
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let accounts
      try {
        accounts = await promisifiedBackground.connectHardware(deviceName, page, hdPath)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
      dispatch(hideLoadingIndication())
      await forceUpdateMetamaskState(dispatch)
  
      return accounts
    }
  }
  
  export function unlockHardwareWalletAccount (index, deviceName, hdPath) {
    log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err) => {
          if (err) {
            log.error(err)
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          dispatch(hideLoadingIndication())
          return resolve()
        })
      })
    }
  }
  
  export function showQrScanner () {
    return (dispatch) => {
      dispatch(showModal({
        name: 'QR_SCANNER',
      }))
    }
  }
  
  export function setCurrentCurrency (currencyCode) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setCurrentCurrency`)
      let data
      try {
        data = await promisifiedBackground.setCurrentCurrency(currencyCode)
      } catch (error) {
        dispatch(hideLoadingIndication())
        log.error(error.stack)
        dispatch(displayWarning(error.message))
        return
      }
      dispatch(hideLoadingIndication())
      dispatch({
        type: actionConstants.SET_CURRENT_FIAT,
        value: {
          currentCurrency: data.currentCurrency,
          conversionRate: data.conversionRate,
          conversionDate: data.conversionDate,
        },
      })
    }
  }
  
  export function signMsg (msgData) {
    log.debug('action - signMsg')
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`actions calling background.signMessage`)
      let newState
      try {
        newState = await promisifiedBackground.signMessage(msgData)
      } catch (error) {
        dispatch(hideLoadingIndication())
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.metamaskId))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function signPersonalMsg (msgData) {
    log.debug('action - signPersonalMsg')
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`actions calling background.signPersonalMessage`)
  
      let newState
      try {
        newState = await promisifiedBackground.signPersonalMessage(msgData)
      } catch (error) {
        dispatch(hideLoadingIndication())
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.metamaskId))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function decryptMsgInline (decryptedMsgData) {
    log.debug('action - decryptMsgInline')
    return async (dispatch) => {
      log.debug(`actions calling background.decryptMessageInline`)
  
      let newState
      try {
        newState = await promisifiedBackground.decryptMessageInline(decryptedMsgData)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
  
      dispatch(updateMetamaskState(newState))
      decryptedMsgData = newState.unapprovedDecryptMsgs[decryptedMsgData.metamaskId]
      return decryptedMsgData
    }
  }
  
  export function decryptMsg (decryptedMsgData) {
    log.debug('action - decryptMsg')
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`actions calling background.decryptMessage`)
  
      let newState
      try {
        newState = await promisifiedBackground.decryptMessage(decryptedMsgData)
      } catch (error) {
        dispatch(hideLoadingIndication())
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(decryptedMsgData.metamaskId))
      dispatch(closeCurrentNotificationWindow())
      return decryptedMsgData
    }
  }
  
  export function encryptionPublicKeyMsg (msgData) {
    log.debug('action - encryptionPublicKeyMsg')
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`actions calling background.encryptionPublicKey`)
  
      let newState
      try {
        newState = await promisifiedBackground.encryptionPublicKey(msgData)
      } catch (error) {
        dispatch(hideLoadingIndication())
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.metamaskId))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function signTypedMsg (msgData) {
    log.debug('action - signTypedMsg')
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`actions calling background.signTypedMessage`)
  
      let newState
      try {
        newState = await promisifiedBackground.signTypedMessage(msgData)
      } catch (error) {
        dispatch(hideLoadingIndication())
        log.error(error)
        dispatch(displayWarning(error.message))
        throw error
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.metamaskId))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function signTx (txData) {
    return (dispatch) => {
      global.ethQuery.sendTransaction(txData, (err) => {
        if (err) {
          return dispatch(displayWarning(err.message))
        }
      })
      dispatch(showConfTxPage())
    }
  }
  
  export function setGasLimit (gasLimit) {
    return {
      type: actionConstants.UPDATE_GAS_LIMIT,
      value: gasLimit,
    }
  }
  
  export function setGasPrice (gasPrice) {
    return {
      type: actionConstants.UPDATE_GAS_PRICE,
      value: gasPrice,
    }
  }
  
  export function setGasTotal (gasTotal) {
    return {
      type: actionConstants.UPDATE_GAS_TOTAL,
      value: gasTotal,
    }
  }
  
  export function updateGasData ({
    gasPrice,
    blockGasLimit,
    selectedAddress,
    sendToken,
    to,
    value,
    data,
  }) {
    return (dispatch) => {
      dispatch(gasLoadingStarted())
      return estimateGas({
        estimateGasMethod: promisifiedBackground.estimateGas,
        blockGasLimit,
        selectedAddress,
        sendToken,
        to,
        value,
        estimateGasPrice: gasPrice,
        data,
      })
        .then((gas) => {
          dispatch(setGasLimit(gas))
          dispatch(setCustomGasLimit(gas))
          dispatch(updateSendErrors({ gasLoadingError: null }))
          dispatch(gasLoadingFinished())
        })
        .catch((err) => {
          log.error(err)
          dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
          dispatch(gasLoadingFinished())
        })
    }
  }
  
  export function gasLoadingStarted () {
    return {
      type: actionConstants.GAS_LOADING_STARTED,
    }
  }
  
  export function gasLoadingFinished () {
    return {
      type: actionConstants.GAS_LOADING_FINISHED,
    }
  }
  
  export function updateSendTokenBalance ({
    sendToken,
    tokenContract,
    address,
  }) {
    return (dispatch) => {
      const tokenBalancePromise = tokenContract
        ? tokenContract.balanceOf(address)
        : Promise.resolve()
      return tokenBalancePromise
        .then((usersToken) => {
          if (usersToken) {
            const newTokenBalance = calcTokenBalance({ sendToken, usersToken })
            dispatch(setSendTokenBalance(newTokenBalance))
          }
        })
        .catch((err) => {
          log.error(err)
          updateSendErrors({ tokenBalance: 'tokenBalanceError' })
        })
    }
  }
  
  export function updateSendErrors (errorObject) {
    return {
      type: actionConstants.UPDATE_SEND_ERRORS,
      value: errorObject,
    }
  }
  
  export function setSendTokenBalance (tokenBalance) {
    return {
      type: actionConstants.UPDATE_SEND_TOKEN_BALANCE,
      value: tokenBalance,
    }
  }
  
  export function updateSendHexData (value) {
    return {
      type: actionConstants.UPDATE_SEND_HEX_DATA,
      value,
    }
  }
  
  export function updateSendTo (to, nickname = '') {
    return {
      type: actionConstants.UPDATE_SEND_TO,
      value: { to, nickname },
    }
  }
  
  export function updateSendAmount (amount) {
    return {
      type: actionConstants.UPDATE_SEND_AMOUNT,
      value: amount,
    }
  }
  
  export function updateCustomNonce (value) {
    return {
      type: actionConstants.UPDATE_CUSTOM_NONCE,
      value: value,
    }
  }
  
  export function setMaxModeTo (bool) {
    return {
      type: actionConstants.UPDATE_MAX_MODE,
      value: bool,
    }
  }
  
  export function updateSend (newSend) {
    return {
      type: actionConstants.UPDATE_SEND,
      value: newSend,
    }
  }
  
  export function updateSendToken (token) {
    return {
      type: actionConstants.UPDATE_SEND_TOKEN,
      value: token,
    }
  }
  
  export function clearSend () {
    return {
      type: actionConstants.CLEAR_SEND,
    }
  }
  
  export function updateSendEnsResolution (ensResolution) {
    return {
      type: actionConstants.UPDATE_SEND_ENS_RESOLUTION,
      payload: ensResolution,
    }
  }
  
  export function updateSendEnsResolutionError (errorMessage) {
    return {
      type: actionConstants.UPDATE_SEND_ENS_RESOLUTION_ERROR,
      payload: errorMessage,
    }
  }
  
  export function signTokenTx (tokenAddress, toAddress, amount, txData) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      const token = global.eth.contract(abi).at(tokenAddress)
      token.transfer(toAddress, ethUtil.addHexPrefix(amount), txData)
        .catch((err) => {
          dispatch(hideLoadingIndication())
          dispatch(displayWarning(err.message))
        })
      dispatch(showConfTxPage())
    }
  }
  
  const updateMetamaskStateFromBackground = () => {
    log.debug(`background.getState`)
  
    return new Promise((resolve, reject) => {
      background.getState((error, newState) => {
        if (error) {
          return reject(error)
        }
  
        resolve(newState)
      })
    })
  }
  
  export function updateTransaction (txData) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
  
      return new Promise((resolve, reject) => {
        background.updateTransaction(txData, (err) => {
          dispatch(updateTransactionParams(txData.id, txData.txParams))
          if (err) {
            dispatch(txError(err))
            dispatch(goHome())
            log.error(err.message)
            return reject(err)
          }
  
          resolve(txData)
        })
      })
        .then(() => updateMetamaskStateFromBackground())
        .then((newState) => dispatch(updateMetamaskState(newState)))
        .then(() => {
          dispatch(showConfTxPage({ id: txData.id }))
          dispatch(hideLoadingIndication())
          return txData
        })
    }
  }
  
  export function updateAndApproveTx (txData) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.updateAndApproveTransaction(txData, (err) => {
          dispatch(updateTransactionParams(txData.id, txData.txParams))
          dispatch(clearSend())
  
          if (err) {
            dispatch(txError(err))
            dispatch(goHome())
            log.error(err.message)
            return reject(err)
          }
  
          resolve(txData)
        })
      })
        .then(() => updateMetamaskStateFromBackground())
        .then((newState) => dispatch(updateMetamaskState(newState)))
        .then(() => {
          dispatch(clearSend())
          dispatch(completedTx(txData.id))
          dispatch(hideLoadingIndication())
          dispatch(updateCustomNonce(''))
          dispatch(closeCurrentNotificationWindow())
  
          return txData
        })
        .catch((err) => {
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  export function completedTx (id) {
    return (dispatch, getState) => {
      const state = getState()
      const {
        unapprovedTxs,
        unapprovedMsgs,
        unapprovedPersonalMsgs,
        unapprovedTypedMessages,
        network,
      } = state.metamask
      const unconfirmedActions = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
      const otherUnconfirmedActions = unconfirmedActions.filter((tx) => tx.id !== id)
      dispatch({
        type: actionConstants.COMPLETED_TX,
        value: {
          id,
          unconfirmedActionsCount: otherUnconfirmedActions.length,
        },
      })
    }
  }
  
  export function updateTransactionParams (id, txParams) {
    return {
      type: actionConstants.UPDATE_TRANSACTION_PARAMS,
      id,
      value: txParams,
    }
  }
  
  export function txError (err) {
    return {
      type: actionConstants.TRANSACTION_ERROR,
      message: err.message,
    }
  }
  
  export function cancelMsg (msgData) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let newState
      try {
        newState = await promisifiedBackground.cancelMessage(msgData.id)
      } finally {
        dispatch(hideLoadingIndication())
      }
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.id))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function cancelPersonalMsg (msgData) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let newState
      try {
        newState = await promisifiedBackground.cancelPersonalMessage(msgData.id)
      } finally {
        dispatch(hideLoadingIndication())
      }
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.id))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function cancelDecryptMsg (msgData) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let newState
      try {
        newState = await promisifiedBackground.cancelDecryptMessage(msgData.id)
      } finally {
        dispatch(hideLoadingIndication())
      }
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.id))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function cancelEncryptionPublicKeyMsg (msgData) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let newState
      try {
        newState = await promisifiedBackground.cancelEncryptionPublicKey(msgData.id)
      } finally {
        dispatch(hideLoadingIndication())
      }
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.id))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function cancelTypedMsg (msgData) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      let newState
      try {
        newState = await promisifiedBackground.cancelTypedMessage(msgData.id)
      } finally {
        dispatch(hideLoadingIndication())
      }
      dispatch(updateMetamaskState(newState))
      dispatch(completedTx(msgData.id))
      dispatch(closeCurrentNotificationWindow())
      return msgData
    }
  }
  
  export function cancelTx (txData) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.cancelTransaction(txData.id, (err) => {
          if (err) {
            return reject(err)
          }
  
          resolve()
        })
      })
        .then(() => updateMetamaskStateFromBackground())
        .then((newState) => dispatch(updateMetamaskState(newState)))
        .then(() => {
          dispatch(clearSend())
          dispatch(completedTx(txData.id))
          dispatch(hideLoadingIndication())
          dispatch(closeCurrentNotificationWindow())
  
          return txData
        })
    }
  }
  
  /**
   * Cancels all of the given transactions
   * @param {Array<object>} txDataList - a list of tx data objects
   * @returns {function(*): Promise<void>}
   */
  export function cancelTxs (txDataList) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      const txIds = txDataList.map(({ id }) => id)
      const cancellations = txIds.map((id) => new Promise((resolve, reject) => {
        background.cancelTransaction(id, (err) => {
          if (err) {
            return reject(err)
          }
  
          resolve()
        })
      }))
  
      await Promise.all(cancellations)
      const newState = await updateMetamaskStateFromBackground()
      dispatch(updateMetamaskState(newState))
      dispatch(clearSend())
  
      txIds.forEach((id) => {
        dispatch(completedTx(id))
      })
  
      dispatch(hideLoadingIndication())
  
      if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
        return global.platform.closeCurrentWindow()
      }
    }
  }
  
  export function markPasswordForgotten () {
    return async (dispatch) => {
      try {
        await new Promise((resolve, reject) => {
          return background.markPasswordForgotten((error) => {
            if (error) {
              return reject(error)
            }
            return resolve()
          })
        })
      } finally {
        // TODO: handle errors
        dispatch(hideLoadingIndication())
        dispatch(forgotPassword())
        await forceUpdateMetamaskState(dispatch)
      }
    }
  }
  
  export function unMarkPasswordForgotten () {
    return (dispatch) => {
      return new Promise((resolve) => {
        background.unMarkPasswordForgotten(() => {
          dispatch(forgotPassword(false))
          resolve()
        })
      })
        .then(() => forceUpdateMetamaskState(dispatch))
    }
  }
  
  export function forgotPassword (forgotPasswordState = true) {
    return {
      type: actionConstants.FORGOT_PASSWORD,
      value: forgotPasswordState,
    }
  }
  
  export function closeWelcomeScreen () {
    return {
      type: actionConstants.CLOSE_WELCOME_SCREEN,
    }
  }
  
  //
  // unlock screen
  //
  
  export function unlockInProgress () {
    return {
      type: actionConstants.UNLOCK_IN_PROGRESS,
    }
  }
  
  export function unlockFailed (message) {
    return {
      type: actionConstants.UNLOCK_FAILED,
      value: message,
    }
  }
  
  export function unlockSucceeded (message) {
    return {
      type: actionConstants.UNLOCK_SUCCEEDED,
      value: message,
    }
  }
  
  export function updateMetamaskState (newState) {
    return (dispatch, getState) => {
      const { metamask: currentState } = getState()
  
      const {
        currentLocale,
        selectedAddress,
      } = currentState
      const {
        currentLocale: newLocale,
        selectedAddress: newSelectedAddress,
      } = newState
  
      if (currentLocale && newLocale && currentLocale !== newLocale) {
        dispatch(updateCurrentLocale(newLocale))
      }
      if (selectedAddress !== newSelectedAddress) {
        dispatch({ type: actionConstants.SELECTED_ADDRESS_CHANGED })
      }
  
      dispatch({
        type: actionConstants.UPDATE_METAMASK_STATE,
        value: newState,
      })
    }
  }
  
  const backgroundSetLocked = () => {
    return new Promise((resolve, reject) => {
      background.setLocked((error) => {
        if (error) {
          return reject(error)
        }
        resolve()
      })
    })
  }
  
  export function lockMetamask () {
    log.debug(`background.setLocked`)
  
    return (dispatch) => {
      dispatch(showLoadingIndication())
  
      return backgroundSetLocked()
        .then(() => updateMetamaskStateFromBackground())
        .catch((error) => {
          dispatch(displayWarning(error.message))
          return Promise.reject(error)
        })
        .then((newState) => {
          dispatch(updateMetamaskState(newState))
          dispatch(hideLoadingIndication())
          dispatch({ type: actionConstants.LOCK_METAMASK })
        })
        .catch(() => {
          dispatch(hideLoadingIndication())
          dispatch({ type: actionConstants.LOCK_METAMASK })
        })
    }
  }
  
  async function _setSelectedAddress (dispatch, address) {
    log.debug(`background.setSelectedAddress`)
    const tokens = await promisifiedBackground.setSelectedAddress(address)
    dispatch(updateTokens(tokens))
  }
  
  export function getUserDetails () {
    return () => {
      return background.getUserDetails()
    }
  }
  
  /** Torus functions */
  
  export function setUserDetails (el) {
    return () => {
      return background.setUserDetails(el)
    }
  }
  
  export function changePasswordShare (password) {
    return async (dispatch) => {
      dispatch(showLoadingIndication('This may take a while, please be patient.'))
      // dispatch(unlockInProgress())
      log.debug(`background.torusGoogleLogin`)
      return new Promise((resolve, reject) => {
        background.torusChangePasswordShare(password, (err) => {
          if (err) {
            return reject(err)
          }
          resolve()
        })
      }).then(() => {
        dispatch(hideLoadingIndication())
      }).catch((err) => {
        dispatch(hideLoadingIndication())
        return Promise.reject(err)
      })
    }
  }
  
  export function addPasswordShare (password) {
    return async (dispatch) => {
      dispatch(showLoadingIndication('This may take a while, please be patient.'))
      // dispatch(unlockInProgress())
      log.debug(`background.torusGoogleLogin`)
      return new Promise((resolve, reject) => {
        background.torusAddPasswordShare(password, (err) => {
          if (err) {
            return reject(err)
          }
          resolve()
        })
      }).then(() => {
        dispatch(hideLoadingIndication())
      }).catch((err) => {
        dispatch(hideLoadingIndication())
        return Promise.reject(err)
      })
    }
  }
  
  
  export function inputPasswordShare (password) {
    return async (dispatch) => {
      log.debug(`background.torusGoogleLogin`)
  
      try {
        dispatch(showLoadingIndication('This may take a while, please be patient.'))
        await promisifiedBackground.torusInputPasswordShare(password)
        dispatch(hideLoadingIndication())
      } catch (err) {
        dispatch(hideLoadingIndication())
        return Promise.reject(err)
      }
      // // dispatch(unlockInProgress())
      // return new Promise((resolve, reject) => {
      //   background.torusInputPasswordShare(password, (err) => {
      //     if (err) return reject(err)
      //     resolve()
      //   })
      // }).then(() => {
      //   dispatch(hideLoadingIndication())
      // }).catch(err => {
      //   debugger
      //   return Promise.reject(err)
      // })
    }
  }
  
  export function getTkeyDataForSettingsPage (dispatch) {
    return async (dispatch) => {
      const tb = await promisifiedBackground.getTkeyDataForSettingsPage()
      // log.info(tb)
      return tb
    }
  }
  
  export function getTotalDeviceShares (dispatch) {
    return async (dispatch) => {
      const data = await promisifiedBackground.getTotalDeviceShares()
      log.info(data)
      return data
    }
  }
  
  export function copyShareUsingIndexAndStoreLocally (index, dispatch) {
    return async (dispatch) => {
      try {
        dispatch(showLoadingIndication('This may take a while, please be patient.'))
        const data = await promisifiedBackground.copyShareUsingIndexAndStoreLocally(index)
        dispatch(hideLoadingIndication())
        return data
      } catch (err) {
        dispatch(hideLoadingIndication())
        return Promise.reject(err)
      }
    }
  }
  
  export function requestShareFromOtherDevice () {
    return async (dispatch) => {
      try {
        const key = await promisifiedBackground.requestShareFromOtherDevice()
        console.log('actions.js', key)
        return key
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
  
  export function startRequestStatusCheck (encryptionPublicKeyX) {
    return async (dispatch) => {
      try {
        dispatch(showLoadingIndication('Please approve share request from another device'))
        await promisifiedBackground.startRequestStatusCheck(encryptionPublicKeyX)
        await forceUpdateMetamaskState(dispatch)
        dispatch(hideLoadingIndication())
      } catch (err) {
        dispatch(hideLoadingIndication())
        return Promise.reject(err)
      }
    }
  }
  
  export function lookForNewRequests () {
    return (async) => {
      try {
        return new Promise((resolve, reject) => {
          background.lookForRequests((error, data) => {
            console.log('data in lookForNewRequests is', data)
            if (error) {
              return reject(error)
            }
            resolve(data)
          })
        })
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
  
  export function approveShareRequest (pubkey) {
    return async (dispatch) => {
      try {
        const key = await promisifiedBackground.approveShareRequest(pubkey)
        console.log('approveShareRequest', key)
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
  
  export function cancelShareRequest () {
    return async (dispatch) => {
      try {
        const key = await promisifiedBackground.cancelShareRequest()
        console.log('cancelShareRequest')
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
  
  export function addSeedPhrase (seedPhrase) {
    return async (dispatch) => {
      let newState
      dispatch(showLoadingIndication('This may take a while, please be patient.'))
      try {
        log.debug(`background.addSeedPhrase`)
        const data = await promisifiedBackground.addSeedPhrase(seedPhrase)
        log.debug(`background.getState`)
        newState = await promisifiedBackground.getState()
      } catch (err) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(err.message))
        throw err
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      if (newState.selectedAddress) {
        dispatch({
          type: actionConstants.SHOW_ACCOUNT_DETAIL,
          value: newState.selectedAddress,
        })
      }
      return newState
    }
  }
  
  
  export function addPrivateKeys (keys, dispatch) {
    return async (dispatch) => {
      let newState
      dispatch(showLoadingIndication('This may take a while, please be patient.'))
      try {
        log.debug(`background.addPrivateKeys`)
        const data = await promisifiedBackground.addPrivateKeys(keys)
        log.debug(`background.getState`)
        newState = await promisifiedBackground.getState()
      } catch (err) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(err.message))
        throw err
      }
      dispatch(hideLoadingIndication())
      dispatch(updateMetamaskState(newState))
      if (newState.selectedAddress) {
        dispatch({
          type: actionConstants.SHOW_ACCOUNT_DETAIL,
          value: newState.selectedAddress,
        })
      }
      return newState
    }
  }
  
  export function getPostboxKey () {
    return async (dispatch) => {
      try {
        const state = await promisifiedBackground.getState()
        const postbox = state.postbox
        log.info(postbox)
        return postbox
      } catch (err) {
        log.info(err)
      }
    }
  }
  
  export function deleteShareDescription (shareIndex, desc) {
    return async (dispatch) => {
      try {
        dispatch(showLoadingIndication('This may take a while, please be patient.'))
        await promisifiedBackground.deleteShareDescription(shareIndex, desc)
        dispatch(hideLoadingIndication())
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
  
  export function generateAndStoreNewDeviceShare (dispatch) {
    return async (dispatch) => {
      try {
        dispatch(showLoadingIndication('This may take a while, please be patient.'))
        const data = await promisifiedBackground.generateAndStoreNewDeviceShare()
        // log.info(data)
        dispatch(hideLoadingIndication())
        return data
      } catch (err) {
        dispatch(hideLoadingIndication())
        return Promise.reject(err)
      }
    }
  }
  
  export function googleLogin (newKeyAssign, dispatch) {
    return async (dispatch) => {
      dispatch(showLoadingIndication('This may take a while, please be patient.'))
      // dispatch(unlockInProgress())
      log.debug(`background.torusGoogleLogin`)
      return new Promise((resolve, reject) => {
        background.torusGoogleLogin(newKeyAssign, (error) => {
          if (error) {
            return reject(error)
          }
          resolve()
        })
      })
        .then(() => {
          log.debug('actions.login completed')
          dispatch(unMarkPasswordForgotten())
          return promisifiedBackground.getState()
        })
        .then((newState) => {
  
          dispatch(updateMetamaskState(newState))
          dispatch(showAccountsPage())
          dispatch(hideLoadingIndication())
  
          if (newState.selectedAddress) {
            dispatch({
              type: actionConstants.SHOW_ACCOUNT_DETAIL,
              value: newState.selectedAddress,
            })
          }
          return newState
        })
        .catch((err) => {
          dispatch(displayWarning(err.message))
          dispatch(hideLoadingIndication())
          return Promise.reject(err)
        })
    }
  }
  
  export function setSelectedAddress (address) {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setSelectedAddress`)
      try {
        await _setSelectedAddress(dispatch, address)
      } catch (error) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(error.message))
        return
      }
      dispatch(hideLoadingIndication())
    }
  }
  
  export function showAccountDetail (address) {
    return async (dispatch, getState) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setSelectedAddress`)
  
      const state = getState()
      const unconnectedAccountAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(state)
      const activeTabOrigin = state.activeTab.origin
      const selectedAddress = getSelectedAddress(state)
      const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(state)
      const currentTabIsConnectedToPreviousAddress = permittedAccountsForCurrentTab.includes(selectedAddress)
      const currentTabIsConnectedToNextAddress = permittedAccountsForCurrentTab.includes(address)
      const switchingToUnconnectedAddress = currentTabIsConnectedToPreviousAddress && !currentTabIsConnectedToNextAddress
  
      try {
        await _setSelectedAddress(dispatch, address)
      } catch (error) {
        dispatch(hideLoadingIndication())
        dispatch(displayWarning(error.message))
        return
      }
      dispatch(hideLoadingIndication())
      dispatch({
        type: actionConstants.SHOW_ACCOUNT_DETAIL,
        value: address,
      })
      if (unconnectedAccountAccountAlertIsEnabled && switchingToUnconnectedAddress) {
        dispatch(switchedToUnconnectedAccount())
        await setUnconnectedAccountAlertShown(activeTabOrigin)
      }
    }
  }
  
  export function addPermittedAccount (origin, address) {
    return async (dispatch) => {
      await new Promise((resolve, reject) => {
        background.addPermittedAccount(origin, address, (error) => {
          if (error) {
            return reject(error)
          }
          resolve()
        })
      })
      await forceUpdateMetamaskState(dispatch)
    }
  }
  
  export function removePermittedAccount (origin, address) {
    return async (dispatch) => {
      await new Promise((resolve, reject) => {
        background.removePermittedAccount(origin, address, (error) => {
          if (error) {
            return reject(error)
          }
          resolve()
        })
      })
      await forceUpdateMetamaskState(dispatch)
    }
  }
  
  export function showAccountsPage () {
    return {
      type: actionConstants.SHOW_ACCOUNTS_PAGE,
    }
  }
  
  export function showConfTxPage ({ id } = {}) {
    return {
      type: actionConstants.SHOW_CONF_TX_PAGE,
      id,
    }
  }
  
  export function addToken (address, symbol, decimals, image) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.addToken(address, symbol, decimals, image, (err, tokens) => {
          dispatch(hideLoadingIndication())
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          dispatch(updateTokens(tokens))
          resolve(tokens)
        })
      })
    }
  }
  
  export function removeToken (address) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.removeToken(address, (err, tokens) => {
          dispatch(hideLoadingIndication())
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          dispatch(updateTokens(tokens))
          resolve(tokens)
        })
      })
    }
  }
  
  export function addTokens (tokens) {
    return (dispatch) => {
      if (Array.isArray(tokens)) {
        return Promise.all(tokens.map(({ address, symbol, decimals }) => (
          dispatch(addToken(address, symbol, decimals))
        )))
      } else {
        return Promise.all(
          Object
            .entries(tokens)
            .map(([_, { address, symbol, decimals }]) => (
              dispatch(addToken(address, symbol, decimals))
            ))
        )
      }
    }
  }
  
  export function removeSuggestedTokens () {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve) => {
        background.removeSuggestedTokens((err, suggestedTokens) => {
          dispatch(hideLoadingIndication())
          if (err) {
            dispatch(displayWarning(err.message))
          }
          dispatch(clearPendingTokens())
          if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
            return global.platform.closeCurrentWindow()
          }
          resolve(suggestedTokens)
        })
      })
        .then(() => updateMetamaskStateFromBackground())
        .then((suggestedTokens) => dispatch(updateMetamaskState({ ...suggestedTokens })))
    }
  }
  
  export function addKnownMethodData (fourBytePrefix, methodData) {
    return () => {
      background.addKnownMethodData(fourBytePrefix, methodData)
    }
  }
  
  export function updateTokens (newTokens) {
    return {
      type: actionConstants.UPDATE_TOKENS,
      newTokens,
    }
  }
  
  export function clearPendingTokens () {
    return {
      type: actionConstants.CLEAR_PENDING_TOKENS,
    }
  }
  
  export function createCancelTransaction (txId, customGasPrice) {
    log.debug('background.cancelTransaction')
    let newTxId
  
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.createCancelTransaction(txId, customGasPrice, (err, newState) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          const { currentNetworkTxList } = newState
          const { id } = currentNetworkTxList[currentNetworkTxList.length - 1]
          newTxId = id
          resolve(newState)
        })
      })
        .then((newState) => dispatch(updateMetamaskState(newState)))
        .then(() => newTxId)
    }
  }
  
  export function createSpeedUpTransaction (txId, customGasPrice, customGasLimit) {
    log.debug('background.createSpeedUpTransaction')
    let newTx
  
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.createSpeedUpTransaction(txId, customGasPrice, customGasLimit, (err, newState) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          const { currentNetworkTxList } = newState
          newTx = currentNetworkTxList[currentNetworkTxList.length - 1]
          resolve(newState)
        })
      })
        .then((newState) => dispatch(updateMetamaskState(newState)))
        .then(() => newTx)
    }
  }
  
  export function createRetryTransaction (txId, customGasPrice, customGasLimit) {
    log.debug('background.createRetryTransaction')
    let newTx
  
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.createSpeedUpTransaction(txId, customGasPrice, customGasLimit, (err, newState) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          const { currentNetworkTxList } = newState
          newTx = currentNetworkTxList[currentNetworkTxList.length - 1]
          resolve(newState)
        })
      })
        .then((newState) => dispatch(updateMetamaskState(newState)))
        .then(() => newTx)
    }
  }
  
  //
  // config
  //
  
  export function setProviderType (type) {
    return async (dispatch, getState) => {
      const { type: currentProviderType } = getState().metamask.provider
      log.debug(`background.setProviderType`, type)
  
      try {
        await promisifiedBackground.setProviderType(type)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning('Had a problem changing networks!'))
        return
      }
      dispatch(setPreviousProvider(currentProviderType))
      dispatch(updateProviderType(type))
    }
  }
  
  export function updateProviderType (type) {
    return {
      type: actionConstants.SET_PROVIDER_TYPE,
      value: type,
    }
  }
  
  export function setPreviousProvider (type) {
    return {
      type: actionConstants.SET_PREVIOUS_PROVIDER,
      value: type,
    }
  }
  
  export function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
    return async (dispatch) => {
      log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`)
  
      try {
        await promisifiedBackground.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning('Had a problem changing networks!'))
        return
      }
  
      dispatch({
        type: actionConstants.SET_RPC_TARGET,
        value: newRpc,
      })
    }
  }
  
  export function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
    return async (dispatch) => {
      log.debug(`background.delRpcTarget: ${oldRpc}`)
      try {
        promisifiedBackground.delCustomRpc(oldRpc)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning('Had a problem removing network!'))
        return
      }
  
      try {
        await promisifiedBackground.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning('Had a problem changing networks!'))
        return
      }
  
      dispatch({
        type: actionConstants.SET_RPC_TARGET,
        value: newRpc,
      })
    }
  }
  
  export function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
    return async (dispatch) => {
      log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
  
      try {
        await promisifiedBackground.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning('Had a problem changing networks!'))
        return
      }
    }
  }
  
  export function delRpcTarget (oldRpc) {
    return (dispatch) => {
      log.debug(`background.delRpcTarget: ${oldRpc}`)
      return new Promise((resolve, reject) => {
        background.delCustomRpc(oldRpc, (err) => {
          if (err) {
            log.error(err)
            dispatch(displayWarning('Had a problem removing network!'))
            return reject(err)
          }
          resolve()
        })
      })
    }
  }
  
  // Calls the addressBookController to add a new address.
  export function addToAddressBook (recipient, nickname = '', memo = '') {
    log.debug(`background.addToAddressBook`)
  
    return async (dispatch, getState) => {
      const chainId = getState().metamask.network
  
      let set
      try {
        set = await promisifiedBackground.setAddressBook(checksumAddress(recipient), nickname, chainId, memo)
      } catch (error) {
        log.error(error)
        dispatch(displayWarning('Address book failed to update'))
        throw error
      }
      if (!set) {
        return dispatch(displayWarning('Address book failed to update'))
      }
    }
  }
  
  /**
   * @description Calls the addressBookController to remove an existing address.
   * @param {string} addressToRemove - Address of the entry to remove from the address book
   */
  export function removeFromAddressBook (chainId, addressToRemove) {
    log.debug(`background.removeFromAddressBook`)
  
    return async () => {
      await promisifiedBackground.removeFromAddressBook(chainId, checksumAddress(addressToRemove))
    }
  }
  
  export function showNetworkDropdown () {
    return {
      type: actionConstants.NETWORK_DROPDOWN_OPEN,
    }
  }
  
  export function hideNetworkDropdown () {
    return {
      type: actionConstants.NETWORK_DROPDOWN_CLOSE,
    }
  }
  
  
  export function showModal (payload) {
    return {
      type: actionConstants.MODAL_OPEN,
      payload,
    }
  }
  
  export function hideModal (payload) {
    return {
      type: actionConstants.MODAL_CLOSE,
      payload,
    }
  }
  
  export function closeCurrentNotificationWindow () {
    return (dispatch, getState) => {
      if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION &&
        !hasUnconfirmedTransactions(getState())) {
        global.platform.closeCurrentWindow()
  
        dispatch(closeNotificationWindow())
      }
    }
  }
  
  export function closeNotificationWindow () {
    return {
      type: actionConstants.CLOSE_NOTIFICATION_WINDOW,
    }
  }
  
  export function showSidebar ({ transitionName, type, props }) {
    return {
      type: actionConstants.SIDEBAR_OPEN,
      value: {
        transitionName,
        type,
        props,
      },
    }
  }
  
  export function hideSidebar () {
    return {
      type: actionConstants.SIDEBAR_CLOSE,
    }
  }
  
  export function showAlert (msg) {
    return {
      type: actionConstants.ALERT_OPEN,
      value: msg,
    }
  }
  
  export function hideAlert () {
    return {
      type: actionConstants.ALERT_CLOSE,
    }
  }
  
  /**
   * This action will receive two types of values via qrCodeData
   * an object with the following structure {type, values}
   * or null (used to clear the previous value)
   */
  export function qrCodeDetected (qrCodeData) {
    return {
      type: actionConstants.QR_CODE_DETECTED,
      value: qrCodeData,
    }
  }
  
  export function showLoadingIndication (message) {
    return {
      type: actionConstants.SHOW_LOADING,
      value: message,
    }
  }
  
  export function setHardwareWalletDefaultHdPath ({ device, path }) {
    return {
      type: actionConstants.SET_HARDWARE_WALLET_DEFAULT_HD_PATH,
      value: { device, path },
    }
  }
  
  export function hideLoadingIndication () {
    return {
      type: actionConstants.HIDE_LOADING,
    }
  }
  
  export function displayWarning (text) {
    return {
      type: actionConstants.DISPLAY_WARNING,
      value: text,
    }
  }
  
  export function hideWarning () {
    return {
      type: actionConstants.HIDE_WARNING,
    }
  }
  
  export function exportAccount (password, address) {
    return function (dispatch) {
      dispatch(showLoadingIndication())
  
      log.debug(`background.submitPassword`)
      return new Promise((resolve, reject) => {
        background.submitPassword(password, function (err) {
          if (err) {
            log.error('Error in submitting password.')
            dispatch(hideLoadingIndication())
            dispatch(displayWarning('Incorrect Password.'))
            return reject(err)
          }
          log.debug(`background.exportAccount`)
          return background.exportAccount(address, function (err, result) {
            dispatch(hideLoadingIndication())
  
            if (err) {
              log.error(err)
              dispatch(displayWarning('Had a problem exporting the account.'))
              return reject(err)
            }
  
            dispatch(showPrivateKey(result))
  
            return resolve(result)
          })
        })
      })
    }
  }
  
  export function showPrivateKey (key) {
    return {
      type: actionConstants.SHOW_PRIVATE_KEY,
      value: key,
    }
  }
  
  export function setAccountLabel (account, label) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setAccountLabel`)
  
      return new Promise((resolve, reject) => {
        background.setAccountLabel(account, label, (err) => {
          dispatch(hideLoadingIndication())
  
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          dispatch({
            type: actionConstants.SET_ACCOUNT_LABEL,
            value: { account, label },
          })
  
          resolve(account)
        })
      })
    }
  }
  
  export function showSendTokenPage () {
    return {
      type: actionConstants.SHOW_SEND_TOKEN_PAGE,
    }
  }
  
  export function buyEth (opts) {
    return (dispatch) => {
      const url = getBuyEthUrl(opts)
      global.platform.openTab({ url })
      dispatch({
        type: actionConstants.BUY_ETH,
      })
    }
  }
  
  export function setFeatureFlag (feature, activated, notificationType) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.setFeatureFlag(feature, activated, (err, updatedFeatureFlags) => {
          dispatch(hideLoadingIndication())
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          dispatch(updateFeatureFlags(updatedFeatureFlags))
          notificationType && dispatch(showModal({ name: notificationType }))
          resolve(updatedFeatureFlags)
        })
      })
    }
  }
  
  export function updateFeatureFlags (updatedFeatureFlags) {
    return {
      type: actionConstants.UPDATE_FEATURE_FLAGS,
      value: updatedFeatureFlags,
    }
  }
  
  export function setPreference (preference, value) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return new Promise((resolve, reject) => {
        background.setPreference(preference, value, (err, updatedPreferences) => {
          dispatch(hideLoadingIndication())
  
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          dispatch(updatePreferences(updatedPreferences))
          resolve(updatedPreferences)
        })
      })
    }
  }
  
  export function updatePreferences (value) {
    return {
      type: actionConstants.UPDATE_PREFERENCES,
      value,
    }
  }
  
  export function setDefaultHomeActiveTabName (value) {
    return async () => {
      await promisifiedBackground.setDefaultHomeActiveTabName(value)
    }
  }
  
  export function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
    return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
  }
  
  export function setShowFiatConversionOnTestnetsPreference (value) {
    return setPreference('showFiatInTestnets', value)
  }
  
  export function setAutoLockTimeLimit (value) {
    return setPreference('autoLockTimeLimit', value)
  }
  
  export function setCompletedOnboarding () {
    return async (dispatch) => {
      dispatch(showLoadingIndication())
  
      try {
        await promisifiedBackground.completeOnboarding()
      } catch (err) {
        dispatch(displayWarning(err.message))
        throw err
      }
  
      dispatch(completeOnboarding())
      dispatch(hideLoadingIndication())
    }
  }
  
  export function completeOnboarding () {
    return {
      type: actionConstants.COMPLETE_ONBOARDING,
    }
  }
  
  export function setMouseUserState (isMouseUser) {
    return {
      type: actionConstants.SET_MOUSE_USER_STATE,
      value: isMouseUser,
    }
  }
  
  export async function forceUpdateMetamaskState (dispatch) {
    log.debug(`background.getState`)
  
    let newState
    try {
      newState = await promisifiedBackground.getState()
    } catch (error) {
      dispatch(displayWarning(error.message))
      throw error
    }
  
    dispatch(updateMetamaskState(newState))
    return newState
  }
  
  export function toggleAccountMenu () {
    return {
      type: actionConstants.TOGGLE_ACCOUNT_MENU,
    }
  }
  
  export function setParticipateInMetaMetrics (val) {
    return (dispatch) => {
      log.debug(`background.setParticipateInMetaMetrics`)
      return new Promise((resolve, reject) => {
        background.setParticipateInMetaMetrics(val, (err, metaMetricsId) => {
          log.debug(err)
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          dispatch({
            type: actionConstants.SET_PARTICIPATE_IN_METAMETRICS,
            value: val,
          })
  
          resolve([val, metaMetricsId])
        })
      })
    }
  }
  
  export function setMetaMetricsSendCount (val) {
    return (dispatch) => {
      log.debug(`background.setMetaMetricsSendCount`)
      return new Promise((resolve, reject) => {
        background.setMetaMetricsSendCount(val, (err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
  
          dispatch({
            type: actionConstants.SET_METAMETRICS_SEND_COUNT,
            value: val,
          })
  
          resolve(val)
        })
      })
    }
  }
  
  export function setUseBlockie (val) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setUseBlockie`)
      background.setUseBlockie(val, (err) => {
        dispatch(hideLoadingIndication())
        if (err) {
          return dispatch(displayWarning(err.message))
        }
      })
      dispatch({
        type: actionConstants.SET_USE_BLOCKIE,
        value: val,
      })
    }
  }
  
  export function setUseNonceField (val) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setUseNonceField`)
      background.setUseNonceField(val, (err) => {
        dispatch(hideLoadingIndication())
        if (err) {
          return dispatch(displayWarning(err.message))
        }
      })
      dispatch({
        type: actionConstants.SET_USE_NONCEFIELD,
        value: val,
      })
    }
  }
  
  export function setUsePhishDetect (val) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setUsePhishDetect`)
      background.setUsePhishDetect(val, (err) => {
        dispatch(hideLoadingIndication())
        if (err) {
          return dispatch(displayWarning(err.message))
        }
      })
    }
  }
  
  export function setIpfsGateway (val) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      log.debug(`background.setIpfsGateway`)
      background.setIpfsGateway(val, (err) => {
        dispatch(hideLoadingIndication())
        if (err) {
          return dispatch(displayWarning(err.message))
        } else {
          dispatch({
            type: actionConstants.SET_IPFS_GATEWAY,
            value: val,
          })
        }
      })
    }
  }
  
  export function updateCurrentLocale (key) {
    return (dispatch) => {
      dispatch(showLoadingIndication())
      return fetchLocale(key)
        .then((localeMessages) => {
          log.debug(`background.setCurrentLocale`)
          background.setCurrentLocale(key, (err, textDirection) => {
            if (err) {
              dispatch(hideLoadingIndication())
              return dispatch(displayWarning(err.message))
            }
            switchDirection(textDirection)
            dispatch(setCurrentLocale(key, localeMessages))
            dispatch(hideLoadingIndication())
          })
        })
    }
  }
  
  export function setCurrentLocale (locale, messages) {
    return {
      type: actionConstants.SET_CURRENT_LOCALE,
      value: {
        locale,
        messages,
      },
    }
  }
  
  export function setPendingTokens (pendingTokens) {
    const { customToken = {}, selectedTokens = {} } = pendingTokens
    const { address, symbol, decimals } = customToken
    const tokens = address && symbol && decimals
      ? { ...selectedTokens, [address]: { ...customToken, isCustom: true } }
      : selectedTokens
  
    return {
      type: actionConstants.SET_PENDING_TOKENS,
      payload: tokens,
    }
  }
  
  // Permissions
  
  export function requestAccountsPermission (origin) {
    return async (dispatch) => {
      const id = await promisifiedBackground.requestAccountsPermission(origin)
      await forceUpdateMetamaskState(dispatch)
      return id
    }
  }
  
  /**
   * Approves the permissions request.
   * @param {Object} request - The permissions request to approve
   * @param {string[]} accounts - The accounts to expose, if any.
   */
  export function approvePermissionsRequest (request, accounts) {
    return () => {
      background.approvePermissionsRequest(request, accounts)
    }
  }
  
  /**
   * Rejects the permissions request with the given ID.
   * @param {string} requestId - The id of the request to be rejected
   */
  export function rejectPermissionsRequest (requestId) {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.rejectPermissionsRequest(requestId, (err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          return forceUpdateMetamaskState(dispatch)
            .then(resolve)
            .catch(reject)
        })
      })
    }
  }
  
  /**
   * Clears the given permissions for the given origin.
   */
  export function removePermissionsFor (domains) {
    return () => {
      background.removePermissionsFor(domains)
    }
  }
  
  /**
   * Clears all permissions for all domains.
   */
  export function clearPermissions () {
    return () => {
      background.clearPermissions()
    }
  }
  
  export function setFirstTimeFlowType (type) {
    return (dispatch) => {
      log.debug(`background.setFirstTimeFlowType`)
      background.setFirstTimeFlowType(type, (err) => {
        if (err) {
          return dispatch(displayWarning(err.message))
        }
      })
      dispatch({
        type: actionConstants.SET_FIRST_TIME_FLOW_TYPE,
        value: type,
      })
    }
  }
  
  export function setSelectedSettingsRpcUrl (newRpcUrl) {
    return {
      type: actionConstants.SET_SELECTED_SETTINGS_RPC_URL,
      value: newRpcUrl,
    }
  }
  
  export function setNetworksTabAddMode (isInAddMode) {
    return {
      type: actionConstants.SET_NETWORKS_TAB_ADD_MODE,
      value: isInAddMode,
    }
  }
  
  export function setLastActiveTime () {
    return (dispatch) => {
      background.setLastActiveTime((err) => {
        if (err) {
          return dispatch(displayWarning(err.message))
        }
      })
    }
  }
  
  export function setConnectedStatusPopoverHasBeenShown () {
    return () => {
      background.setConnectedStatusPopoverHasBeenShown((err) => {
        if (err) {
          throw new Error(err.message)
        }
      })
    }
  }
  
  export function setAlertEnabledness (alertId, enabledness) {
    return async () => {
      await promisifiedBackground.setAlertEnabledness(alertId, enabledness)
    }
  }
  
  export async function setUnconnectedAccountAlertShown (origin) {
    await promisifiedBackground.setUnconnectedAccountAlertShown(origin)
  }
  
  export function loadingMethodDataStarted () {
    return {
      type: actionConstants.LOADING_METHOD_DATA_STARTED,
    }
  }
  
  export function loadingMethodDataFinished () {
    return {
      type: actionConstants.LOADING_METHOD_DATA_FINISHED,
    }
  }
  
  export function getContractMethodData (data = '') {
    return (dispatch, getState) => {
      const prefixedData = ethUtil.addHexPrefix(data)
      const fourBytePrefix = prefixedData.slice(0, 10)
      const { knownMethodData } = getState().metamask
      if (knownMethodData && knownMethodData[fourBytePrefix] && Object.keys(knownMethodData[fourBytePrefix]).length !== 0) {
        return Promise.resolve(knownMethodData[fourBytePrefix])
      }
  
      dispatch(loadingMethodDataStarted())
      log.debug(`loadingMethodData`)
  
      return getMethodDataAsync(fourBytePrefix)
        .then(({ name, params }) => {
          dispatch(loadingMethodDataFinished())
  
          background.addKnownMethodData(fourBytePrefix, { name, params })
  
          return { name, params }
        })
    }
  }
  
  export function loadingTokenParamsStarted () {
    return {
      type: actionConstants.LOADING_TOKEN_PARAMS_STARTED,
    }
  }
  
  export function loadingTokenParamsFinished () {
    return {
      type: actionConstants.LOADING_TOKEN_PARAMS_FINISHED,
    }
  }
  
  export function getTokenParams (tokenAddress) {
    return (dispatch, getState) => {
      const existingTokens = getState().metamask.tokens
      const existingToken = existingTokens.find(({ address }) => tokenAddress === address)
  
      if (existingToken) {
        return Promise.resolve({
          symbol: existingToken.symbol,
          decimals: existingToken.decimals,
        })
      }
  
      dispatch(loadingTokenParamsStarted())
      log.debug(`loadingTokenParams`)
  
  
      return fetchSymbolAndDecimals(tokenAddress, existingTokens)
        .then(({ symbol, decimals }) => {
          dispatch(addToken(tokenAddress, symbol, Number(decimals)))
          dispatch(loadingTokenParamsFinished())
        })
    }
  }
  
  export function setSeedPhraseBackedUp (seedPhraseBackupState) {
    return (dispatch) => {
      log.debug(`background.setSeedPhraseBackedUp`)
      return new Promise((resolve, reject) => {
        background.setSeedPhraseBackedUp(seedPhraseBackupState, (err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          return forceUpdateMetamaskState(dispatch)
            .then(resolve)
            .catch(reject)
        })
      })
    }
  }
  
  export function initializeThreeBox () {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.initializeThreeBox((err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve()
        })
      })
    }
  }
  
  export function setShowRestorePromptToFalse () {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.setShowRestorePromptToFalse((err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve()
        })
      })
    }
  }
  
  export function turnThreeBoxSyncingOn () {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.turnThreeBoxSyncingOn((err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve()
        })
      })
    }
  }
  
  export function restoreFromThreeBox (accountAddress) {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.restoreFromThreeBox(accountAddress, (err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve()
        })
      })
    }
  }
  
  export function getThreeBoxLastUpdated () {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.getThreeBoxLastUpdated((err, lastUpdated) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve(lastUpdated)
        })
      })
    }
  }
  
  export function setThreeBoxSyncingPermission (threeBoxSyncingAllowed) {
    return (dispatch) => {
      return new Promise((resolve, reject) => {
        background.setThreeBoxSyncingPermission(threeBoxSyncingAllowed, (err) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          resolve()
        })
      })
    }
  }
  
  export function turnThreeBoxSyncingOnAndInitialize () {
    return async (dispatch) => {
      await dispatch(setThreeBoxSyncingPermission(true))
      await dispatch(turnThreeBoxSyncingOn())
      await dispatch(initializeThreeBox(true))
    }
  }
  
  export function setNextNonce (nextNonce) {
    return {
      type: actionConstants.SET_NEXT_NONCE,
      value: nextNonce,
    }
  }
  
  export function getNextNonce () {
    return (dispatch, getState) => {
      const address = getState().metamask.selectedAddress
      return new Promise((resolve, reject) => {
        background.getNextNonce(address, (err, nextNonce) => {
          if (err) {
            dispatch(displayWarning(err.message))
            return reject(err)
          }
          dispatch(setNextNonce(nextNonce))
          resolve(nextNonce)
        })
      })
    }
  }
  
  export function setRequestAccountTabIds (requestAccountTabIds) {
    return {
      type: actionConstants.SET_REQUEST_ACCOUNT_TABS,
      value: requestAccountTabIds,
    }
  }
  
  export function getRequestAccountTabIds () {
    return async (dispatch) => {
      const requestAccountTabIds = await promisifiedBackground.getRequestAccountTabIds()
      dispatch(setRequestAccountTabIds(requestAccountTabIds))
    }
  }
  
  export function setOpenMetamaskTabsIDs (openMetaMaskTabIDs) {
    return {
      type: actionConstants.SET_OPEN_METAMASK_TAB_IDS,
      value: openMetaMaskTabIDs,
    }
  }
  
  export function getOpenMetamaskTabsIds () {
    return async (dispatch) => {
      const openMetaMaskTabIDs = await promisifiedBackground.getOpenMetamaskTabsIds()
      dispatch(setOpenMetamaskTabsIDs(openMetaMaskTabIDs))
    }
  }
  
  export function setCurrentWindowTab (currentWindowTab) {
    return {
      type: actionConstants.SET_CURRENT_WINDOW_TAB,
      value: currentWindowTab,
    }
  }
  
  
  export function getCurrentWindowTab () {
    return async (dispatch) => {
      const currentWindowTab = await global.platform.currentTab()
      dispatch(setCurrentWindowTab(currentWindowTab))
    }
  }
  
  export function getPostBox () {
    return async (dispatch) => {
      try {
        const postbox = await promisifiedBackground.getPostBox()
        return postbox
      } catch (err) {
        log.info(err)
      }
    }
  }
  