import abi from 'human-standard-token-abi' import pify from 'pify' import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url' import { getTokenAddressFromTokenObject, 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 WebcamUtils from '../../lib/webcam-utils' export const actionConstants = { GO_HOME: 'GO_HOME', // modal state MODAL_OPEN: 'UI_MODAL_OPEN', MODAL_CLOSE: 'UI_MODAL_CLOSE', // notification state CLOSE_NOTIFICATION_WINDOW: 'CLOSE_NOTIFICATION_WINDOW', // sidebar state SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN', SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE', // sidebar state ALERT_OPEN: 'UI_ALERT_OPEN', ALERT_CLOSE: 'UI_ALERT_CLOSE', QR_CODE_DETECTED: 'UI_QR_CODE_DETECTED', // network dropdown open NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN', NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE', // transition state TRANSITION_FORWARD: 'TRANSITION_FORWARD', // remote state UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE', FORGOT_PASSWORD: 'FORGOT_PASSWORD', SHOW_INFO_PAGE: 'SHOW_INFO_PAGE', SET_NEW_ACCOUNT_FORM: 'SET_NEW_ACCOUNT_FORM', CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN', // unlock screen UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS', UNLOCK_FAILED: 'UNLOCK_FAILED', UNLOCK_SUCCEEDED: 'UNLOCK_SUCCEEDED', UNLOCK_METAMASK: 'UNLOCK_METAMASK', LOCK_METAMASK: 'LOCK_METAMASK', // error handling DISPLAY_WARNING: 'DISPLAY_WARNING', HIDE_WARNING: 'HIDE_WARNING', // accounts screen SET_SELECTED_TOKEN: 'SET_SELECTED_TOKEN', SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL', SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE', SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE', SET_CURRENT_FIAT: 'SET_CURRENT_FIAT', // account detail screen SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', SHOW_SEND_TOKEN_PAGE: 'SHOW_SEND_TOKEN_PAGE', SHOW_PRIVATE_KEY: 'SHOW_PRIVATE_KEY', SET_ACCOUNT_LABEL: 'SET_ACCOUNT_LABEL', SET_NETWORK_NONCE: 'SET_NETWORK_NONCE', // tx conf screen COMPLETED_TX: 'COMPLETED_TX', TRANSACTION_ERROR: 'TRANSACTION_ERROR', UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS', SET_NEXT_NONCE: 'SET_NEXT_NONCE', // send screen UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT', UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE', UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL', UPDATE_SEND_HEX_DATA: 'UPDATE_SEND_HEX_DATA', UPDATE_SEND_TOKEN_BALANCE: 'UPDATE_SEND_TOKEN_BALANCE', UPDATE_SEND_TO: 'UPDATE_SEND_TO', UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT', UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS', UPDATE_MAX_MODE: 'UPDATE_MAX_MODE', UPDATE_SEND: 'UPDATE_SEND', CLEAR_SEND: 'CLEAR_SEND', GAS_LOADING_STARTED: 'GAS_LOADING_STARTED', GAS_LOADING_FINISHED: 'GAS_LOADING_FINISHED', UPDATE_SEND_ENS_RESOLUTION: 'UPDATE_SEND_ENS_RESOLUTION', UPDATE_SEND_ENS_RESOLUTION_ERROR: 'UPDATE_SEND_ENS_RESOLUTION_ERROR', // config screen SHOW_CONFIG_PAGE: 'SHOW_CONFIG_PAGE', SET_RPC_TARGET: 'SET_RPC_TARGET', SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE', SET_PREVIOUS_PROVIDER: 'SET_PREVIOUS_PROVIDER', SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE', UPDATE_TOKENS: 'UPDATE_TOKENS', SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH', // loading overlay SHOW_LOADING: 'SHOW_LOADING_INDICATION', HIDE_LOADING: 'HIDE_LOADING_INDICATION', // buy Eth with coinbase BUY_ETH: 'BUY_ETH', PAIR_UPDATE: 'PAIR_UPDATE', SHOW_SUB_LOADING_INDICATION: 'SHOW_SUB_LOADING_INDICATION', HIDE_SUB_LOADING_INDICATION: 'HIDE_SUB_LOADING_INDICATION', // QR STUFF: SHOW_QR: 'SHOW_QR', SHOW_QR_VIEW: 'SHOW_QR_VIEW', TOGGLE_ACCOUNT_MENU: 'TOGGLE_ACCOUNT_MENU', SET_USE_BLOCKIE: 'SET_USE_BLOCKIE', SET_USE_NONCEFIELD: 'SET_USE_NONCEFIELD', UPDATE_CUSTOM_NONCE: 'UPDATE_CUSTOM_NONCE', SET_IPFS_GATEWAY: 'SET_IPFS_GATEWAY', SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS', SET_METAMETRICS_SEND_COUNT: 'SET_METAMETRICS_SEND_COUNT', // locale SET_CURRENT_LOCALE: 'SET_CURRENT_LOCALE', // Feature Flags UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS', // Preferences UPDATE_PREFERENCES: 'UPDATE_PREFERENCES', // Onboarding COMPLETE_ONBOARDING: 'COMPLETE_ONBOARDING', SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE', // Network SET_PENDING_TOKENS: 'SET_PENDING_TOKENS', CLEAR_PENDING_TOKENS: 'CLEAR_PENDING_TOKENS', SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE', SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL', SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE', LOADING_METHOD_DATA_STARTED: 'LOADING_METHOD_DATA_STARTED', LOADING_METHOD_DATA_FINISHED: 'LOADING_METHOD_DATA_FINISHED', LOADING_TOKEN_PARAMS_STARTED: 'LOADING_TOKEN_PARAMS_STARTED', LOADING_TOKEN_PARAMS_FINISHED: 'LOADING_TOKEN_PARAMS_FINISHED', SET_REQUEST_ACCOUNT_TABS: 'SET_REQUEST_ACCOUNT_TABS', SET_CURRENT_WINDOW_TAB: 'SET_CURRENT_WINDOW_TAB', SET_OPEN_METAMASK_TAB_IDS: 'SET_OPEN_METAMASK_TAB_IDS', } let background = null export function _setBackgroundConnection (backgroundConnection) { background = backgroundConnection } 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) => { background.verifySeedPhrase(err => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } resolve() }) }) }) .then(() => { dispatch(transitionForward()) dispatch(hideLoadingIndication()) }) .catch(err => { dispatch(unlockFailed(err.message)) dispatch(hideLoadingIndication()) return Promise.reject(err) }) } } export function transitionForward () { return { type: actionConstants.TRANSITION_FORWARD, } } 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 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 dispatch => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { background.removeAccount(address, (err, account) => { dispatch(hideLoadingIndication()) if (err) { dispatch(displayWarning(err.message)) return reject(err) } log.info('Account removed: ' + account) dispatch(showAccountsPage()) resolve() }) }) } } export function addNewKeyring (type, opts) { return (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.addNewKeyring`) background.addNewKeyring(type, opts, (err) => { dispatch(hideLoadingIndication()) if (err) { return dispatch(displayWarning(err.message)) } 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 pify(background.importAccountWithStrategy).call(background, strategy, args) log.debug(`background.getState`) newState = await pify(background.getState).call(background) } 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 (dispatch, getState) => { const oldIdentities = getState().metamask.identities dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { background.addNewAccount((err, { identities: newIdentities }) => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } const newAccountAddress = Object.keys(newIdentities).find(address => !oldIdentities[address]) dispatch(hideLoadingIndication()) forceUpdateMetamaskState(dispatch) return resolve(newAccountAddress) }) }) } } export function checkHardwareStatus (deviceName, hdPath) { log.debug(`background.checkHardwareStatus`, deviceName, hdPath) return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => { if (err) { log.error(err) dispatch(displayWarning(err.message)) return reject(err) } dispatch(hideLoadingIndication()) forceUpdateMetamaskState(dispatch) return resolve(unlocked) }) }) } } export function forgetDevice (deviceName) { log.debug(`background.forgetDevice`, deviceName) return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { background.forgetDevice(deviceName, (err) => { if (err) { log.error(err) dispatch(displayWarning(err.message)) return reject(err) } dispatch(hideLoadingIndication()) forceUpdateMetamaskState(dispatch) return resolve() }) }) } } export function connectHardware (deviceName, page, hdPath) { log.debug(`background.connectHardware`, deviceName, page, hdPath) return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { background.connectHardware(deviceName, page, hdPath, (err, accounts) => { if (err) { log.error(err) dispatch(displayWarning(err.message)) return reject(err) } dispatch(hideLoadingIndication()) forceUpdateMetamaskState(dispatch) return resolve(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 showInfoPage () { return { type: actionConstants.SHOW_INFO_PAGE, } } export function showQrScanner (ROUTE) { return (dispatch) => { return WebcamUtils.checkStatus() .then(status => { if (!status.environmentReady) { // We need to switch to fullscreen mode to ask for permission global.platform.openExtensionInBrowser(`${ROUTE}`, `scan=true`) } else { dispatch(showModal({ name: 'QR_SCANNER', })) } }).catch(e => { dispatch(showModal({ name: 'QR_SCANNER', error: true, errorType: e.type, })) }) } } export function setCurrentCurrency (currencyCode) { return (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.setCurrentCurrency`) background.setCurrentCurrency(currencyCode, (err, data) => { dispatch(hideLoadingIndication()) if (err) { log.error(err.stack) return dispatch(displayWarning(err.message)) } 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 (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { log.debug(`actions calling background.signMessage`) background.signMessage(msgData, (err, newState) => { log.debug('signMessage called back') dispatch(updateMetamaskState(newState)) dispatch(hideLoadingIndication()) if (err) { log.error(err) dispatch(displayWarning(err.message)) return reject(err) } dispatch(completedTx(msgData.metamaskId)) dispatch(closeCurrentNotificationWindow()) return resolve(msgData) }) }) } } export function signPersonalMsg (msgData) { log.debug('action - signPersonalMsg') return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { log.debug(`actions calling background.signPersonalMessage`) background.signPersonalMessage(msgData, (err, newState) => { log.debug('signPersonalMessage called back') dispatch(updateMetamaskState(newState)) dispatch(hideLoadingIndication()) if (err) { log.error(err) dispatch(displayWarning(err.message)) return reject(err) } dispatch(completedTx(msgData.metamaskId)) dispatch(closeCurrentNotificationWindow()) return resolve(msgData) }) }) } } export function signTypedMsg (msgData) { log.debug('action - signTypedMsg') return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { log.debug(`actions calling background.signTypedMessage`) background.signTypedMessage(msgData, (err, newState) => { log.debug('signTypedMessage called back') dispatch(updateMetamaskState(newState)) dispatch(hideLoadingIndication()) if (err) { log.error(err) dispatch(displayWarning(err.message)) return reject(err) } dispatch(completedTx(msgData.metamaskId)) dispatch(closeCurrentNotificationWindow()) return resolve(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, selectedToken, to, value, data, }) { return (dispatch) => { dispatch(gasLoadingStarted()) return estimateGas({ estimateGasMethod: background.estimateGas, blockGasLimit, selectedAddress, selectedToken, 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 ({ selectedToken, tokenContract, address, }) { return (dispatch) => { const tokenBalancePromise = tokenContract ? tokenContract.balanceOf(address) : Promise.resolve() return tokenBalancePromise .then(usersToken => { if (usersToken) { const newTokenBalance = calcTokenBalance({ selectedToken, 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 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) { log.info('actions: updateTx: ' + JSON.stringify(txData)) return dispatch => { log.debug(`actions calling background.updateTx`) 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) { log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) return (dispatch) => { log.debug(`actions calling background.updateAndApproveTx`) 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 { type: actionConstants.COMPLETED_TX, value: id, } } 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 (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { log.debug(`background.cancelMessage`) background.cancelMessage(msgData.id, (err, newState) => { dispatch(updateMetamaskState(newState)) dispatch(hideLoadingIndication()) if (err) { return reject(err) } dispatch(completedTx(msgData.id)) dispatch(closeCurrentNotificationWindow()) return resolve(msgData) }) }) } } export function cancelPersonalMsg (msgData) { return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { const id = msgData.id background.cancelPersonalMessage(id, (err, newState) => { dispatch(updateMetamaskState(newState)) dispatch(hideLoadingIndication()) if (err) { return reject(err) } dispatch(completedTx(id)) dispatch(closeCurrentNotificationWindow()) return resolve(msgData) }) }) } } export function cancelTypedMsg (msgData) { return (dispatch) => { dispatch(showLoadingIndication()) return new Promise((resolve, reject) => { const id = msgData.id background.cancelTypedMessage(id, (err, newState) => { dispatch(updateMetamaskState(newState)) dispatch(hideLoadingIndication()) if (err) { return reject(err) } dispatch(completedTx(id)) dispatch(closeCurrentNotificationWindow()) return resolve(msgData) }) }) } } export function cancelTx (txData) { return (dispatch) => { log.debug(`background.cancelTransaction`) 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} txDataList a list of tx data objects * @return {function(*): Promise} */ 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 (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) { return global.platform.closeCurrentWindow() } } } export function markPasswordForgotten () { return (dispatch) => { return background.markPasswordForgotten(() => { dispatch(hideLoadingIndication()) dispatch(forgotPassword()) 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 setNewAccountForm (formToSelect) { return { type: actionConstants.SET_NEW_ACCOUNT_FORM, formToSelect, } } 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 unlockMetamask (account) { return { type: actionConstants.UNLOCK_METAMASK, value: account, } } export function updateMetamaskState (newState) { return { 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 }) }) } } export function setCurrentAccountTab (newTabName) { log.debug(`background.setCurrentAccountTab: ${newTabName}`) return callBackgroundThenUpdateNoSpinner(background.setCurrentAccountTab, newTabName) } export function setSelectedToken (tokenAddress) { return { type: actionConstants.SET_SELECTED_TOKEN, value: tokenAddress || null, } } export function setSelectedAddress (address) { return (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.setSelectedAddress`) background.setSelectedAddress(address, (err) => { dispatch(hideLoadingIndication()) if (err) { return dispatch(displayWarning(err.message)) } }) } } export function showAccountDetail (address) { return (dispatch) => { dispatch(showLoadingIndication()) log.debug(`background.setSelectedAddress`) background.setSelectedAddress(address, (err, tokens) => { dispatch(hideLoadingIndication()) if (err) { return dispatch(displayWarning(err.message)) } dispatch(updateTokens(tokens)) dispatch({ type: actionConstants.SHOW_ACCOUNT_DETAIL, value: address, }) dispatch(setSelectedToken()) }) } } export function showAccountsPage () { return { type: actionConstants.SHOW_ACCOUNTS_PAGE, } } export function showConfTxPage ({ transForward = true, id }) { return { type: actionConstants.SHOW_CONF_TX_PAGE, transForward, id, } } export function showConfigPage (transitionForward = true) { return { type: actionConstants.SHOW_CONFIG_PAGE, value: transitionForward, } } export function showAddTokenPage (transitionForward = true) { return { type: actionConstants.SHOW_ADD_TOKEN_PAGE, value: transitionForward, } } 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)) { dispatch(setSelectedToken(getTokenAddressFromTokenObject(tokens[0]))) return Promise.all(tokens.map(({ address, symbol, decimals }) => ( dispatch(addToken(address, symbol, decimals)) ))) } else { dispatch(setSelectedToken(getTokenAddressFromTokenObject(tokens))) 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 (global.METAMASK_UI_TYPE === 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 retryTransaction (txId, gasPrice) { log.debug(`background.retryTransaction`) let newTxId return dispatch => { return new Promise((resolve, reject) => { background.retryTransaction(txId, gasPrice, (err, newState) => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } const { selectedAddressTxList } = newState const { id } = selectedAddressTxList[selectedAddressTxList.length - 1] newTxId = id resolve(newState) }) }) .then(newState => dispatch(updateMetamaskState(newState))) .then(() => newTxId) } } 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 { selectedAddressTxList } = newState const { id } = selectedAddressTxList[selectedAddressTxList.length - 1] newTxId = id resolve(newState) }) }) .then(newState => dispatch(updateMetamaskState(newState))) .then(() => newTxId) } } export function createSpeedUpTransaction (txId, customGasPrice) { log.debug('background.createSpeedUpTransaction') let newTx return dispatch => { return new Promise((resolve, reject) => { background.createSpeedUpTransaction(txId, customGasPrice, (err, newState) => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } const { selectedAddressTxList } = newState newTx = selectedAddressTxList[selectedAddressTxList.length - 1] resolve(newState) }) }) .then(newState => dispatch(updateMetamaskState(newState))) .then(() => newTx) } } export function createRetryTransaction (txId, customGasPrice) { log.debug('background.createRetryTransaction') let newTx return dispatch => { return new Promise((resolve, reject) => { background.createSpeedUpTransaction(txId, customGasPrice, (err, newState) => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } const { selectedAddressTxList } = newState newTx = selectedAddressTxList[selectedAddressTxList.length - 1] resolve(newState) }) }) .then(newState => dispatch(updateMetamaskState(newState))) .then(() => newTx) } } // // config // export function setProviderType (type) { return (dispatch, getState) => { const { type: currentProviderType } = getState().metamask.provider log.debug(`background.setProviderType`, type) background.setProviderType(type, (err) => { if (err) { log.error(err) return dispatch(displayWarning('Had a problem changing networks!')) } dispatch(setPreviousProvider(currentProviderType)) dispatch(updateProviderType(type)) dispatch(setSelectedToken()) }) } } 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 (dispatch) => { log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`) background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => { if (err) { log.error(err) return dispatch(displayWarning('Had a problem changing networks!')) } dispatch({ type: actionConstants.SET_RPC_TARGET, value: newRpc, }) }) } } export function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) { return (dispatch) => { log.debug(`background.delRpcTarget: ${oldRpc}`) background.delCustomRpc(oldRpc, (err) => { if (err) { log.error(err) return dispatch(displayWarning('Had a problem removing network!')) } dispatch(setSelectedToken()) background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => { if (err) { log.error(err) return dispatch(displayWarning('Had a problem changing networks!')) } dispatch({ type: actionConstants.SET_RPC_TARGET, value: newRpc, }) }) }) } } export function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) { return (dispatch) => { log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`) background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => { if (err) { log.error(err) return dispatch(displayWarning('Had a problem changing networks!')) } dispatch(setSelectedToken()) }) } } 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) } dispatch(setSelectedToken()) resolve() }) }) } } // Calls the addressBookController to add a new address. export function addToAddressBook (recipient, nickname = '', memo = '') { log.debug(`background.addToAddressBook`) return (dispatch, getState) => { const chainId = getState().metamask.network background.setAddressBook(checksumAddress(recipient), nickname, chainId, memo, (err, set) => { if (err) { log.error(err) dispatch(displayWarning('Address book failed to update')) throw err } 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 () => { background.removeFromAddressBook(chainId, checksumAddress(addressToRemove)) } } export function useEtherscanProvider () { log.debug(`background.useEtherscanProvider`) background.useEtherscanProvider() return { type: actionConstants.USE_ETHERSCAN_PROVIDER, } } 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 (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION && !hasUnconfirmedTransactions(getState())) { global.platform.closeCurrentWindow() dispatch(closeNotifacationWindow()) } } } export function closeNotifacationWindow () { 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 showSubLoadingIndication () { return { type: actionConstants.SHOW_SUB_LOADING_INDICATION, } } export function hideSubLoadingIndication () { return { type: actionConstants.HIDE_SUB_LOADING_INDICATION, } } 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 submiting 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 showSendPage () { return { type: actionConstants.SHOW_SEND_PAGE, } } export function showSendTokenPage () { return { type: actionConstants.SHOW_SEND_TOKEN_PAGE, } } export function buyEth (opts) { return (dispatch) => { const url = getBuyEthUrl(opts) global.platform.openWindow({ url }) dispatch({ type: actionConstants.BUY_ETH, }) } } export function pairUpdate (coin) { return (dispatch) => { dispatch(showSubLoadingIndication()) dispatch(hideWarning()) shapeShiftRequest('marketinfo', { pair: `${coin.toLowerCase()}_eth` }, (mktResponse) => { dispatch(hideSubLoadingIndication()) if (mktResponse.error) { return dispatch(displayWarning(mktResponse.error)) } dispatch({ type: actionConstants.PAIR_UPDATE, value: { marketinfo: mktResponse, }, }) }) } } export function showQrView (data, message) { return { type: actionConstants.SHOW_QR_VIEW, value: { message: message, data: data, }, } } export function reshowQrCode (data, coin) { return (dispatch) => { dispatch(showLoadingIndication()) shapeShiftRequest('marketinfo', { pair: `${coin.toLowerCase()}_eth` }, (mktResponse) => { if (mktResponse.error) { return dispatch(displayWarning(mktResponse.error)) } const message = [ `Deposit your ${coin} to the address below:`, `Deposit Limit: ${mktResponse.limit}`, `Deposit Minimum:${mktResponse.minimum}`, ] dispatch(hideLoadingIndication()) return dispatch(showQrView(data, message)) }) } } export function shapeShiftRequest (query, options = {}, cb) { let queryResponse, method options.method ? method = options.method : method = 'GET' const requestListner = function () { try { queryResponse = JSON.parse(this.responseText) if (cb) { cb(queryResponse) } return queryResponse } catch (e) { if (cb) { cb({ error: e }) } return e } } const shapShiftReq = new XMLHttpRequest() shapShiftReq.addEventListener('load', requestListner) shapShiftReq.open(method, `https://shapeshift.io/${query}/${options.pair ? options.pair : ''}`, true) if (options.method === 'POST') { const jsonObj = JSON.stringify(options.data) shapShiftReq.setRequestHeader('Content-Type', 'application/json') return shapShiftReq.send(jsonObj) } else { return shapShiftReq.send() } } 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 setUseNativeCurrencyAsPrimaryCurrencyPreference (value) { return setPreference('useNativeCurrencyAsPrimaryCurrency', value) } export function setShowFiatConversionOnTestnetsPreference (value) { return setPreference('showFiatInTestnets', value) } export function setAutoLogoutTimeLimit (value) { return setPreference('autoLogoutTimeLimit', value) } export function setCompletedOnboarding () { return async dispatch => { dispatch(showLoadingIndication()) try { await pify(background.completeOnboarding).call(background) } catch (err) { dispatch(displayWarning(err.message)) throw err } dispatch(completeOnboarding()) dispatch(hideLoadingIndication()) } } export function completeOnboarding () { return { type: actionConstants.COMPLETE_ONBOARDING, } } export function setNetworkNonce (networkNonce) { return { type: actionConstants.SET_NETWORK_NONCE, value: networkNonce, } } export function updateNetworkNonce (address) { return (dispatch) => { return new Promise((resolve, reject) => { global.ethQuery.getTransactionCount(address, (err, data) => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } dispatch(setNetworkNonce(data)) resolve(data) }) }) } } export function setMouseUserState (isMouseUser) { return { type: actionConstants.SET_MOUSE_USER_STATE, value: isMouseUser, } } // Call Background Then Update // // A function generator for a common pattern wherein: // We show loading indication. // We call a background method. // We hide loading indication. // If it errored, we show a warning. // If it didn't, we update the state. export function callBackgroundThenUpdateNoSpinner (method, ...args) { return (dispatch) => { method.call(background, ...args, (err) => { if (err) { return dispatch(displayWarning(err.message)) } forceUpdateMetamaskState(dispatch) }) } } export function forceUpdateMetamaskState (dispatch) { log.debug(`background.getState`) return new Promise((resolve, reject) => { background.getState((err, newState) => { if (err) { dispatch(displayWarning(err.message)) return reject(err) } dispatch(updateMetamaskState(newState)) resolve(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 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 /** * 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 () => { background.rejectPermissionsRequest(requestId) } } /** * Exposes the given account(s) to the given origin. * Call ONLY as a result of direct user action. */ export function legacyExposeAccounts (origin, accounts) { return () => { return background.legacyExposeAccounts(origin, accounts) } } /** * 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 setMkrMigrationReminderTimestamp (timestamp) { return (dispatch) => { background.setMkrMigrationReminderTimestamp(timestamp, (err) => { if (err) { return dispatch(displayWarning(err.message)) } }) } } export function loadingMethoDataStarted () { return { type: actionConstants.LOADING_METHOD_DATA_STARTED, } } export function loadingMethoDataFinished () { 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]) { return Promise.resolve(knownMethodData[fourBytePrefix]) } dispatch(loadingMethoDataStarted()) log.debug(`loadingMethodData`) return getMethodDataAsync(fourBytePrefix) .then(({ name, params }) => { dispatch(loadingMethoDataFinished()) 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, 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 hideSeedPhraseBackupAfterOnboarding () { return { type: actionConstants.HIDE_SEED_PHRASE_BACKUP_AFTER_ONBOARDING, } } 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 pify(background.getRequestAccountTabIds).call(background) 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 pify(background.getOpenMetamaskTabsIds).call(background) 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)) } }