import abi from 'human-standard-token-abi' import pify from 'pify' import log from 'loglevel' import { capitalize } from 'lodash' import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url' import { checksumAddress } from '../helpers/utils/util' import { calcTokenBalance, estimateGasForSend } from '../pages/send/send.utils' import { fetchLocale, loadRelativeTimeFormatLocaleData, } 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 { 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, addHexPrefix } from '../../../app/scripts/lib/util' import { getPermittedAccountsForCurrentTab, getSelectedAddress, } from '../selectors' import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account' import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask' import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens' import * as actionConstants from './actionConstants' 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) { reject(error) return } resolve() }) }) .then(() => { dispatch(unlockSucceeded()) return forceUpdateMetamaskState(dispatch) }) .then(() => { return new Promise((resolve, reject) => { background.verifySeedPhrase((err) => { if (err) { dispatch(displayWarning(err.message)) reject(err) return } resolve() }) }) }) .then(() => { dispatch(hideLoadingIndication()) }) .catch((err) => { dispatch(unlockFailed(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) { reject(err) return } 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() return seedWords } catch (error) { dispatch(displayWarning(error.message)) throw new Error(error.message) } finally { dispatch(hideLoadingIndication()) } } } export function unlockAndGetSeedPhrase(password) { return async (dispatch) => { dispatch(showLoadingIndication()) try { await submitPassword(password) const seedWords = await verifySeedPhrase() await forceUpdateMetamaskState(dispatch) return seedWords } catch (error) { dispatch(displayWarning(error.message)) throw new Error(error.message) } finally { dispatch(hideLoadingIndication()) } } } export function submitPassword(password) { return new Promise((resolve, reject) => { background.submitPassword(password, (error) => { if (error) { reject(error) return } resolve() }) }) } export function createNewVault(password) { return new Promise((resolve, reject) => { background.createNewVaultAndKeychain(password, (error) => { if (error) { reject(error) return } resolve(true) }) }) } export function verifyPassword(password) { return new Promise((resolve, reject) => { background.verifyPassword(password, (error) => { if (error) { reject(error) return } resolve(true) }) }) } export function verifySeedPhrase() { return new Promise((resolve, reject) => { background.verifySeedPhrase((error, seedWords) => { if (error) { reject(error) return } resolve(seedWords) }) }) } export function requestRevealSeedWords(password) { return async (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.verifyPassword`) try { await verifyPassword(password) const seedWords = await verifySeedPhrase() return seedWords } catch (error) { dispatch(displayWarning(error.message)) throw new Error(error.message) } finally { dispatch(hideLoadingIndication()) } } } 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)) reject(err) return } 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)) reject(err) return } 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) { 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) { 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) log.debug(`background.getState`) newState = await promisifiedBackground.getState() } catch (err) { dispatch(displayWarning(err.message)) throw err } finally { 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 } finally { dispatch(hideLoadingIndication()) } const newAccountAddress = Object.keys(newIdentities).find( (address) => !oldIdentities[address], ) 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 } finally { 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 } finally { dispatch(hideLoadingIndication()) } await forceUpdateMetamaskState(dispatch) } } export function connectHardware(deviceName, page, hdPath) { log.debug(`background.connectHardware`, deviceName, page, hdPath) return async (dispatch) => { dispatch( showLoadingIndication(`Looking for your ${capitalize(deviceName)}...`), ) let accounts try { accounts = await promisifiedBackground.connectHardware( deviceName, page, hdPath, ) } catch (error) { log.error(error) dispatch(displayWarning(error.message)) throw error } finally { 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) => { dispatch(hideLoadingIndication()) if (err) { log.error(err) dispatch(displayWarning(err.message)) reject(err) 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) { log.error(error.stack) dispatch(displayWarning(error.message)) return } finally { 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) { log.error(error) dispatch(displayWarning(error.message)) throw error } finally { 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) { log.error(error) dispatch(displayWarning(error.message)) throw error } finally { dispatch(hideLoadingIndication()) } 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)) return newState.unapprovedDecryptMsgs[decryptedMsgData.metamaskId] } } 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) { log.error(error) dispatch(displayWarning(error.message)) throw error } finally { 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) { log.error(error) dispatch(displayWarning(error.message)) throw error } finally { 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) { log.error(error) dispatch(displayWarning(error.message)) throw error } finally { 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) { 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 estimateGasForSend({ 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, } } 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 async (dispatch) => { dispatch(showLoadingIndication()) try { const token = global.eth.contract(abi).at(tokenAddress) const txPromise = token.transfer(toAddress, addHexPrefix(amount), txData) dispatch(showConfTxPage()) dispatch(hideLoadingIndication()) await txPromise } catch (error) { dispatch(hideLoadingIndication()) dispatch(displayWarning(error.message)) } } } const updateMetamaskStateFromBackground = () => { log.debug(`background.getState`) return new Promise((resolve, reject) => { background.getState((error, newState) => { if (error) { reject(error) return } resolve(newState) }) }) } export function updateTransaction(txData, dontShowLoadingIndicator) { return async (dispatch) => { !dontShowLoadingIndicator && dispatch(showLoadingIndication()) try { await promisifiedBackground.updateTransaction(txData) } catch (error) { dispatch(updateTransactionParams(txData.id, txData.txParams)) dispatch(hideLoadingIndication()) dispatch(txError(error)) dispatch(goHome()) log.error(error.message) throw error } try { dispatch(updateTransactionParams(txData.id, txData.txParams)) const newState = await updateMetamaskStateFromBackground() dispatch(updateMetamaskState(newState)) dispatch(showConfTxPage({ id: txData.id })) return txData } finally { dispatch(hideLoadingIndication()) } } } export function addUnapprovedTransaction(txParams, origin) { log.debug('background.addUnapprovedTransaction') return () => { return new Promise((resolve, reject) => { background.addUnapprovedTransaction(txParams, origin, (err, txMeta) => { if (err) { reject(err) return } resolve(txMeta) }) }) } } export function updateAndApproveTx(txData, dontShowLoadingIndicator) { return (dispatch) => { !dontShowLoadingIndicator && 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) reject(err) return } 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, (error) => { if (error) { reject(error) return } resolve() }) }) .then(() => updateMetamaskStateFromBackground()) .then((newState) => dispatch(updateMetamaskState(newState))) .then(() => { dispatch(clearSend()) dispatch(completedTx(txData.id)) dispatch(hideLoadingIndication()) dispatch(closeCurrentNotificationWindow()) return txData }) .catch((error) => { dispatch(hideLoadingIndication()) throw error }) } } /** * Cancels all of the given transactions * @param {Array} txDataList - a list of tx data objects * @returns {function(*): Promise} */ export function cancelTxs(txDataList) { return async (dispatch) => { dispatch(showLoadingIndication()) try { const txIds = txDataList.map(({ id }) => id) const cancellations = txIds.map( (id) => new Promise((resolve, reject) => { background.cancelTransaction(id, (err) => { if (err) { reject(err) return } resolve() }) }), ) await Promise.all(cancellations) const newState = await updateMetamaskStateFromBackground() dispatch(updateMetamaskState(newState)) dispatch(clearSend()) txIds.forEach((id) => { dispatch(completedTx(id)) }) } finally { if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) { global.platform.closeCurrentWindow() } else { dispatch(hideLoadingIndication()) } } } } export function markPasswordForgotten() { return async (dispatch) => { try { await new Promise((resolve, reject) => { return background.markPasswordForgotten((error) => { if (error) { 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) { reject(error) return } 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 setSelectedAddress(address) { return async (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.setSelectedAddress`) try { await _setSelectedAddress(dispatch, address) } catch (error) { dispatch(displayWarning(error.message)) return } finally { 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 = Boolean(activeTabOrigin) && permittedAccountsForCurrentTab.includes(selectedAddress) const currentTabIsConnectedToNextAddress = Boolean(activeTabOrigin) && permittedAccountsForCurrentTab.includes(address) const switchingToUnconnectedAddress = currentTabIsConnectedToPreviousAddress && !currentTabIsConnectedToNextAddress try { await _setSelectedAddress(dispatch, address) } catch (error) { dispatch(displayWarning(error.message)) return } finally { 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) { reject(error) return } resolve() }) }) await forceUpdateMetamaskState(dispatch) } } export function removePermittedAccount(origin, address) { return async (dispatch) => { await new Promise((resolve, reject) => { background.removePermittedAccount(origin, address, (error) => { if (error) { reject(error) return } 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, dontShowLoadingIndicator, ) { return (dispatch) => { !dontShowLoadingIndicator && dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { background.addToken(address, symbol, decimals, image, (err, tokens) => { dispatch(hideLoadingIndication()) if (err) { dispatch(displayWarning(err.message)) reject(err) return } 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)) reject(err) return } 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)), ), ) } 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) { global.platform.closeCurrentWindow() return } 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)) reject(err) return } 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)) reject(err) return } 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)) reject(err) return } 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) => { log.debug(`background.setProviderType`, type) try { await promisifiedBackground.setProviderType(type) } catch (error) { log.error(error) dispatch(displayWarning('Had a problem changing networks!')) return } dispatch(updateProviderType(type)) } } export function updateProviderType(type) { return { type: actionConstants.SET_PROVIDER_TYPE, 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!')) } } } export function rollbackToPreviousProvider() { return async (dispatch) => { try { await promisifiedBackground.rollbackToPreviousProvider() } catch (error) { log.error(error) dispatch(displayWarning('Had a problem changing networks!')) } } } 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!')) reject(err) return } 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.provider 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) { 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.verifyPassword`) return new Promise((resolve, reject) => { background.verifyPassword(password, function (err) { if (err) { log.error('Error in verifying password.') dispatch(hideLoadingIndication()) dispatch(displayWarning('Incorrect Password.')) reject(err) return } log.debug(`background.exportAccount`) background.exportAccount(address, function (err2, result) { dispatch(hideLoadingIndication()) if (err2) { log.error(err2) dispatch(displayWarning('Had a problem exporting the account.')) reject(err2) return } dispatch(showPrivateKey(result)) resolve(result) }) }) }) } } export function exportAccounts(password, addresses) { return function (dispatch) { log.debug(`background.submitPassword`) return new Promise((resolve, reject) => { background.submitPassword(password, function (err) { if (err) { log.error('Error in submitting password.') reject(err) return } log.debug(`background.exportAccounts`) const accountPromises = addresses.map( (address) => new Promise((resolve2, reject2) => background.exportAccount(address, function (err2, result) { if (err2) { log.error(err2) dispatch( displayWarning('Had a problem exporting the account.'), ) reject2(err2) return } resolve2(result) }), ), ) resolve(Promise.all(accountPromises)) }) }) } } 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)) reject(err) return } dispatch({ type: actionConstants.SET_ACCOUNT_LABEL, value: { account, label }, }) resolve(account) }) }) } } export function clearAccountDetails() { return { type: actionConstants.CLEAR_ACCOUNT_DETAILS, } } 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)) reject(err) return } 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)) reject(err) return } 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() dispatch(completeOnboarding()) } catch (err) { dispatch(displayWarning(err.message)) throw err } finally { 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)) reject(err) return } 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)) reject(err) return } 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) { 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) { 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) { dispatch(displayWarning(err.message)) } }) } } export function setIpfsGateway(val) { return (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.setIpfsGateway`) background.setIpfsGateway(val, (err) => { dispatch(hideLoadingIndication()) if (err) { dispatch(displayWarning(err.message)) } else { dispatch({ type: actionConstants.SET_IPFS_GATEWAY, value: val, }) } }) } } export function updateCurrentLocale(key) { return async (dispatch) => { dispatch(showLoadingIndication()) try { await loadRelativeTimeFormatLocaleData(key) const localeMessages = await fetchLocale(key) const textDirection = await promisifiedBackground.setCurrentLocale(key) await switchDirection(textDirection) dispatch(setCurrentLocale(key, localeMessages)) } catch (error) { dispatch(displayWarning(error.message)) return } finally { 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 Object.keys(tokens).forEach((tokenAddress) => { tokens[tokenAddress].unlisted = !LISTED_CONTRACT_ADDRESSES.includes( tokenAddress.toLowerCase(), ) }) return { type: actionConstants.SET_PENDING_TOKENS, payload: tokens, } } // Swaps export function setSwapsLiveness(swapsFeatureIsLive) { return async (dispatch) => { await promisifiedBackground.setSwapsLiveness(swapsFeatureIsLive) await forceUpdateMetamaskState(dispatch) } } export function fetchAndSetQuotes(fetchParams, fetchParamsMetaData) { return async (dispatch) => { const [ quotes, selectedAggId, ] = await promisifiedBackground.fetchAndSetQuotes( fetchParams, fetchParamsMetaData, ) await forceUpdateMetamaskState(dispatch) return [quotes, selectedAggId] } } export function setSelectedQuoteAggId(aggId) { return async (dispatch) => { await promisifiedBackground.setSelectedQuoteAggId(aggId) await forceUpdateMetamaskState(dispatch) } } export function setSwapsTokens(tokens) { return async (dispatch) => { await promisifiedBackground.setSwapsTokens(tokens) await forceUpdateMetamaskState(dispatch) } } export function resetBackgroundSwapsState() { return async (dispatch) => { const id = await promisifiedBackground.resetSwapsState() await forceUpdateMetamaskState(dispatch) return id } } export function setCustomApproveTxData(data) { return async (dispatch) => { await promisifiedBackground.setCustomApproveTxData(data) await forceUpdateMetamaskState(dispatch) } } export function setSwapsTxGasPrice(gasPrice) { return async (dispatch) => { await promisifiedBackground.setSwapsTxGasPrice(gasPrice) await forceUpdateMetamaskState(dispatch) } } export function setSwapsTxGasLimit(gasLimit) { return async (dispatch) => { await promisifiedBackground.setSwapsTxGasLimit(gasLimit, true) await forceUpdateMetamaskState(dispatch) } } export function customSwapsGasParamsUpdated(gasLimit, gasPrice) { return async (dispatch) => { await promisifiedBackground.setSwapsTxGasPrice(gasPrice) await promisifiedBackground.setSwapsTxGasLimit(gasLimit, true) await forceUpdateMetamaskState(dispatch) } } export function setTradeTxId(tradeTxId) { return async (dispatch) => { await promisifiedBackground.setTradeTxId(tradeTxId) await forceUpdateMetamaskState(dispatch) } } export function setApproveTxId(approveTxId) { return async (dispatch) => { await promisifiedBackground.setApproveTxId(approveTxId) await forceUpdateMetamaskState(dispatch) } } export function safeRefetchQuotes() { return async (dispatch) => { await promisifiedBackground.safeRefetchQuotes() await forceUpdateMetamaskState(dispatch) } } export function stopPollingForQuotes() { return async (dispatch) => { await promisifiedBackground.stopPollingForQuotes() await forceUpdateMetamaskState(dispatch) } } export function setBackgroundSwapRouteState(routeState) { return async (dispatch) => { await promisifiedBackground.setBackgroundSwapRouteState(routeState) await forceUpdateMetamaskState(dispatch) } } export function resetSwapsPostFetchState() { return async (dispatch) => { await promisifiedBackground.resetPostFetchState() await forceUpdateMetamaskState(dispatch) } } export function setSwapsErrorKey(errorKey) { return async (dispatch) => { await promisifiedBackground.setSwapsErrorKey(errorKey) await forceUpdateMetamaskState(dispatch) } } export function setInitialGasEstimate(initialAggId) { return async (dispatch) => { await promisifiedBackground.setInitialGasEstimate(initialAggId) await forceUpdateMetamaskState(dispatch) } } // Permissions export function requestAccountsPermissionWithId(origin) { return async (dispatch) => { const id = await promisifiedBackground.requestAccountsPermissionWithId( 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)) 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) { 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) { dispatch(displayWarning(err.message)) } }) } } export function setConnectedStatusPopoverHasBeenShown() { return () => { background.setConnectedStatusPopoverHasBeenShown((err) => { if (err) { throw new Error(err.message) } }) } } export function setSwapsWelcomeMessageHasBeenShown() { return () => { background.setSwapsWelcomeMessageHasBeenShown((err) => { if (err) { throw new Error(err.message) } }) } } export async function setAlertEnabledness(alertId, enabledness) { await promisifiedBackground.setAlertEnabledness(alertId, enabledness) } export async function setUnconnectedAccountAlertShown(origin) { await promisifiedBackground.setUnconnectedAccountAlertShown(origin) } export async function setWeb3ShimUsageAlertDismissed(origin) { await promisifiedBackground.setWeb3ShimUsageAlertDismissed(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 = addHexPrefix(data) const fourBytePrefix = prefixedData.slice(0, 10) const { knownMethodData } = getState().metamask if ( (knownMethodData && knownMethodData[fourBytePrefix] && Object.keys(knownMethodData[fourBytePrefix]).length !== 0) || fourBytePrefix === '0x' ) { 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)) 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)) reject(err) return } resolve() }) }) } } export function setShowRestorePromptToFalse() { return (dispatch) => { return new Promise((resolve, reject) => { background.setShowRestorePromptToFalse((err) => { if (err) { dispatch(displayWarning(err.message)) reject(err) return } resolve() }) }) } } export function turnThreeBoxSyncingOn() { return (dispatch) => { return new Promise((resolve, reject) => { background.turnThreeBoxSyncingOn((err) => { if (err) { dispatch(displayWarning(err.message)) reject(err) return } resolve() }) }) } } export function restoreFromThreeBox(accountAddress) { return (dispatch) => { return new Promise((resolve, reject) => { background.restoreFromThreeBox(accountAddress, (err) => { if (err) { dispatch(displayWarning(err.message)) reject(err) return } resolve() }) }) } } export function getThreeBoxLastUpdated() { return (dispatch) => { return new Promise((resolve, reject) => { background.getThreeBoxLastUpdated((err, lastUpdated) => { if (err) { dispatch(displayWarning(err.message)) reject(err) return } resolve(lastUpdated) }) }) } } export function setThreeBoxSyncingPermission(threeBoxSyncingAllowed) { return (dispatch) => { return new Promise((resolve, reject) => { background.setThreeBoxSyncingPermission(threeBoxSyncingAllowed, (err) => { if (err) { dispatch(displayWarning(err.message)) reject(err) return } 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)) reject(err) return } 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)) } } // MetaMetrics /** * @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload * @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventOptions} MetaMetricsEventOptions * @typedef {import('../../../shared/constants/metametrics').MetaMetricsPagePayload} MetaMetricsPagePayload * @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageOptions} MetaMetricsPageOptions */ /** * @param {MetaMetricsEventPayload} payload - details of the event to track * @param {MetaMetricsEventOptions} options - options for routing/handling of event * @returns {Promise} */ export function trackMetaMetricsEvent(payload, options) { return promisifiedBackground.trackMetaMetricsEvent(payload, options) } /** * @param {MetaMetricsPagePayload} payload - details of the page viewed * @param {MetaMetricsPageOptions} options - options for handling the page view * @returns {void} */ export function trackMetaMetricsPage(payload, options) { return promisifiedBackground.trackMetaMetricsPage(payload, options) }