1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-30 16:18:07 +01:00
metamask-extension/ui/app/store/actions.js
Mark Stacey fe28e0d134
Cleanup beforeunload handler after transaction is resolved (#7333)
* Cleanup beforeunload handler after transaction is resolved

The notification window was updated to reject transactions upon close
in #6340. A handler that rejects the transaction was added to
`window.onbeforeunload`, and it was cleared in `actions.js` if it was
confirmed or rejected.

However, the `onbeforeunload` handler remained uncleared if the
transaction was resolved in another window. This results in the
transaction being rejected when the notification window closes, even
long after the transaction is submitted and confirmed. This has been
the cause of many problems with the Firefox e2e tests.

Instead the `onbeforeunload` handler is cleared in the
`componentWillUnmount` lifecycle function, alongside where it's set in
the first place. This ensures that it's correctly unset regardless
of how the transaction was resolved, and it better matches user
expectations.

* Fix indentation and remove redundant export

The `run-all.sh` Bash script now uses consistent indentation, and is
consistent about only re-exporting the Ganache arguments when they
change.

* Ensure transactions are completed before checking balance

Various intermittent e2e test failures appear to be caused by React
re-rendering the transaction list during the test, as the transaction
goes from pending to confirmed. To avoid this race condition, the
transaction is now explicitly looked for in the confirmed transaction
list in each of the tests using this pattern.

* Enable all e2e tests on Firefox

The remaining tests that were disabled on Firefox now work correctly.
Only a few timing adjustments were needed.

* Update Firefox used in CI

Firefox v70 is now used on CI instead of v68. This necessitated
rewriting the function where the extension ID was obtained because the
Firefox extensions page was redesigned.
2019-10-31 13:27:22 -03:00

2957 lines
74 KiB
JavaScript

const abi = require('human-standard-token-abi')
const pify = require('pify')
const getBuyEthUrl = require('../../../app/scripts/lib/buy-eth-url')
const { getTokenAddressFromTokenObject, checksumAddress } = require('../helpers/utils/util')
const {
calcTokenBalance,
estimateGas,
} = require('../pages/send/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../helpers/utils/i18n-helper')
const { getMethodDataAsync } = require('../helpers/utils/transactions.util')
const { fetchSymbolAndDecimals } = require('../helpers/utils/token-util')
import switchDirection from '../helpers/utils/switch-direction'
const log = require('loglevel')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums')
const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util')
const gasDuck = require('../ducks/gas/gas.duck')
const WebcamUtils = require('../../lib/webcam-utils')
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
GO_HOME: 'GO_HOME',
goHome: goHome,
// modal state
MODAL_OPEN: 'UI_MODAL_OPEN',
MODAL_CLOSE: 'UI_MODAL_CLOSE',
showModal: showModal,
hideModal: hideModal,
// notification state
CLOSE_NOTIFICATION_WINDOW: 'CLOSE_NOTIFICATION_WINDOW',
closeNotifacationWindow: closeNotifacationWindow,
// sidebar state
SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN',
SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE',
showSidebar: showSidebar,
hideSidebar: hideSidebar,
// sidebar state
ALERT_OPEN: 'UI_ALERT_OPEN',
ALERT_CLOSE: 'UI_ALERT_CLOSE',
showAlert: showAlert,
hideAlert: hideAlert,
QR_CODE_DETECTED: 'UI_QR_CODE_DETECTED',
qrCodeDetected,
// network dropdown open
NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN',
NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
showNetworkDropdown: showNetworkDropdown,
hideNetworkDropdown: hideNetworkDropdown,
// menu state/
getNetworkStatus: 'getNetworkStatus',
// transition state
TRANSITION_FORWARD: 'TRANSITION_FORWARD',
TRANSITION_BACKWARD: 'TRANSITION_BACKWARD',
transitionForward,
transitionBackward,
// remote state
UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE',
updateMetamaskState: updateMetamaskState,
markAccountsFound,
// intialize screen
CREATE_NEW_VAULT_IN_PROGRESS: 'CREATE_NEW_VAULT_IN_PROGRESS',
SHOW_CREATE_VAULT: 'SHOW_CREATE_VAULT',
SHOW_RESTORE_VAULT: 'SHOW_RESTORE_VAULT',
fetchInfoToSync,
FORGOT_PASSWORD: 'FORGOT_PASSWORD',
forgotPassword: forgotPassword,
markPasswordForgotten,
unMarkPasswordForgotten,
SHOW_INIT_MENU: 'SHOW_INIT_MENU',
SHOW_INFO_PAGE: 'SHOW_INFO_PAGE',
SHOW_IMPORT_PAGE: 'SHOW_IMPORT_PAGE',
SHOW_NEW_ACCOUNT_PAGE: 'SHOW_NEW_ACCOUNT_PAGE',
SET_NEW_ACCOUNT_FORM: 'SET_NEW_ACCOUNT_FORM',
unlockMetamask: unlockMetamask,
unlockFailed: unlockFailed,
unlockSucceeded,
showCreateVault: showCreateVault,
showRestoreVault: showRestoreVault,
showInitializeMenu: showInitializeMenu,
showImportPage,
showNewAccountPage,
setNewAccountForm,
createNewVaultAndRestore: createNewVaultAndRestore,
createNewVaultInProgress: createNewVaultInProgress,
createNewVaultAndGetSeedPhrase,
unlockAndGetSeedPhrase,
addNewKeyring,
importNewAccount,
addNewAccount,
connectHardware,
checkHardwareStatus,
forgetDevice,
unlockHardwareWalletAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
resetAccount,
removeAccount,
showInfoPage: showInfoPage,
CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN',
closeWelcomeScreen,
// seed recovery actions
REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION',
revealSeedConfirmation: revealSeedConfirmation,
requestRevealSeedWords,
// unlock screen
UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS',
UNLOCK_FAILED: 'UNLOCK_FAILED',
UNLOCK_SUCCEEDED: 'UNLOCK_SUCCEEDED',
UNLOCK_METAMASK: 'UNLOCK_METAMASK',
LOCK_METAMASK: 'LOCK_METAMASK',
tryUnlockMetamask: tryUnlockMetamask,
lockMetamask: lockMetamask,
unlockInProgress: unlockInProgress,
// error handling
displayWarning: displayWarning,
DISPLAY_WARNING: 'DISPLAY_WARNING',
HIDE_WARNING: 'HIDE_WARNING',
hideWarning: hideWarning,
// accounts screen
SET_SELECTED_ACCOUNT: 'SET_SELECTED_ACCOUNT',
SET_SELECTED_TOKEN: 'SET_SELECTED_TOKEN',
setSelectedToken,
SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL',
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
SET_CURRENT_FIAT: 'SET_CURRENT_FIAT',
showQrScanner,
setCurrentCurrency,
setCurrentAccountTab,
// account detail screen
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
showSendPage: showSendPage,
SHOW_SEND_TOKEN_PAGE: 'SHOW_SEND_TOKEN_PAGE',
showSendTokenPage,
ADD_TO_ADDRESS_BOOK: 'ADD_TO_ADDRESS_BOOK',
addToAddressBook: addToAddressBook,
REMOVE_FROM_ADDRESS_BOOK: 'REMOVE_FROM_ADDRESS_BOOK',
removeFromAddressBook: removeFromAddressBook,
REQUEST_ACCOUNT_EXPORT: 'REQUEST_ACCOUNT_EXPORT',
requestExportAccount: requestExportAccount,
EXPORT_ACCOUNT: 'EXPORT_ACCOUNT',
exportAccount: exportAccount,
SHOW_PRIVATE_KEY: 'SHOW_PRIVATE_KEY',
showPrivateKey: showPrivateKey,
exportAccountComplete,
SET_ACCOUNT_LABEL: 'SET_ACCOUNT_LABEL',
setAccountLabel,
updateNetworkNonce,
SET_NETWORK_NONCE: 'SET_NETWORK_NONCE',
// tx conf screen
COMPLETED_TX: 'COMPLETED_TX',
TRANSACTION_ERROR: 'TRANSACTION_ERROR',
NEXT_TX: 'NEXT_TX',
PREVIOUS_TX: 'PREV_TX',
EDIT_TX: 'EDIT_TX',
signMsg: signMsg,
cancelMsg: cancelMsg,
signPersonalMsg,
cancelPersonalMsg,
signTypedMsg,
cancelTypedMsg,
sendTx: sendTx,
signTx: signTx,
signTokenTx: signTokenTx,
updateTransaction,
updateAndApproveTx,
cancelTx: cancelTx,
cancelTxs,
completedTx: completedTx,
txError: txError,
nextTx: nextTx,
editTx,
previousTx: previousTx,
cancelAllTx: cancelAllTx,
viewPendingTx: viewPendingTx,
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
updateTransactionParams,
UPDATE_TRANSACTION_PARAMS: 'UPDATE_TRANSACTION_PARAMS',
setNextNonce,
SET_NEXT_NONCE: 'SET_NEXT_NONCE',
getNextNonce,
// send screen
UPDATE_GAS_LIMIT: 'UPDATE_GAS_LIMIT',
UPDATE_GAS_PRICE: 'UPDATE_GAS_PRICE',
UPDATE_GAS_TOTAL: 'UPDATE_GAS_TOTAL',
UPDATE_SEND_FROM: 'UPDATE_SEND_FROM',
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_MEMO: 'UPDATE_SEND_MEMO',
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
UPDATE_MAX_MODE: 'UPDATE_MAX_MODE',
UPDATE_SEND: 'UPDATE_SEND',
CLEAR_SEND: 'CLEAR_SEND',
OPEN_FROM_DROPDOWN: 'OPEN_FROM_DROPDOWN',
CLOSE_FROM_DROPDOWN: 'CLOSE_FROM_DROPDOWN',
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',
updateSendEnsResolution,
updateSendEnsResolutionError,
setGasLimit,
setGasPrice,
updateGasData,
setGasTotal,
setSendTokenBalance,
updateSendTokenBalance,
updateSendHexData,
updateSendTo,
updateSendAmount,
updateSendMemo,
setMaxModeTo,
updateSend,
updateSendErrors,
clearSend,
setSelectedAddress,
gasLoadingStarted,
gasLoadingFinished,
// app messages
showAccountDetail: showAccountDetail,
BACK_TO_ACCOUNT_DETAIL: 'BACK_TO_ACCOUNT_DETAIL',
backToAccountDetail: backToAccountDetail,
showAccountsPage: showAccountsPage,
showConfTxPage: showConfTxPage,
// config screen
SHOW_CONFIG_PAGE: 'SHOW_CONFIG_PAGE',
SET_RPC_TARGET: 'SET_RPC_TARGET',
SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET',
SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE',
SET_PREVIOUS_PROVIDER: 'SET_PREVIOUS_PROVIDER',
showConfigPage,
SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE',
SHOW_ADD_SUGGESTED_TOKEN_PAGE: 'SHOW_ADD_SUGGESTED_TOKEN_PAGE',
showAddTokenPage,
showAddSuggestedTokenPage,
addToken,
addTokens,
removeToken,
updateTokens,
removeSuggestedTokens,
addKnownMethodData,
UPDATE_TOKENS: 'UPDATE_TOKENS',
updateAndSetCustomRpc: updateAndSetCustomRpc,
setRpcTarget: setRpcTarget,
delRpcTarget: delRpcTarget,
editRpc: editRpc,
setProviderType: setProviderType,
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
setHardwareWalletDefaultHdPath,
updateProviderType,
// loading overlay
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
HIDE_LOADING: 'HIDE_LOADING_INDICATION',
showLoadingIndication: showLoadingIndication,
hideLoadingIndication: hideLoadingIndication,
// buy Eth with coinbase
onboardingBuyEthView,
ONBOARDING_BUY_ETH_VIEW: 'ONBOARDING_BUY_ETH_VIEW',
BUY_ETH: 'BUY_ETH',
buyEth: buyEth,
buyEthView: buyEthView,
buyWithShapeShift,
BUY_ETH_VIEW: 'BUY_ETH_VIEW',
COINBASE_SUBVIEW: 'COINBASE_SUBVIEW',
coinBaseSubview: coinBaseSubview,
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
shapeShiftSubview: shapeShiftSubview,
PAIR_UPDATE: 'PAIR_UPDATE',
pairUpdate: pairUpdate,
coinShiftRquest: coinShiftRquest,
SHOW_SUB_LOADING_INDICATION: 'SHOW_SUB_LOADING_INDICATION',
showSubLoadingIndication: showSubLoadingIndication,
HIDE_SUB_LOADING_INDICATION: 'HIDE_SUB_LOADING_INDICATION',
hideSubLoadingIndication: hideSubLoadingIndication,
// QR STUFF:
SHOW_QR: 'SHOW_QR',
showQrView: showQrView,
reshowQrCode: reshowQrCode,
SHOW_QR_VIEW: 'SHOW_QR_VIEW',
// FORGOT PASSWORD:
BACK_TO_INIT_MENU: 'BACK_TO_INIT_MENU',
goBackToInitView: goBackToInitView,
RECOVERY_IN_PROGRESS: 'RECOVERY_IN_PROGRESS',
BACK_TO_UNLOCK_VIEW: 'BACK_TO_UNLOCK_VIEW',
backToUnlockView: backToUnlockView,
// SHOWING KEYCHAIN
SHOW_NEW_KEYCHAIN: 'SHOW_NEW_KEYCHAIN',
showNewKeychain: showNewKeychain,
callBackgroundThenUpdate,
forceUpdateMetamaskState,
TOGGLE_ACCOUNT_MENU: 'TOGGLE_ACCOUNT_MENU',
toggleAccountMenu,
useEtherscanProvider,
SET_USE_BLOCKIE: 'SET_USE_BLOCKIE',
setUseBlockie,
SET_USE_NONCEFIELD: 'SET_USE_NONCEFIELD',
setUseNonceField,
UPDATE_CUSTOM_NONCE: 'UPDATE_CUSTOM_NONCE',
updateCustomNonce,
SET_PARTICIPATE_IN_METAMETRICS: 'SET_PARTICIPATE_IN_METAMETRICS',
SET_METAMETRICS_SEND_COUNT: 'SET_METAMETRICS_SEND_COUNT',
setParticipateInMetaMetrics,
setMetaMetricsSendCount,
// locale
SET_CURRENT_LOCALE: 'SET_CURRENT_LOCALE',
setCurrentLocale,
updateCurrentLocale,
//
// Feature Flags
setFeatureFlag,
updateFeatureFlags,
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
// Preferences
setPreference,
updatePreferences,
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
setAutoLogoutTimeLimit,
unsetMigratedPrivacyMode,
// Onboarding
setCompletedOnboarding,
completeOnboarding,
COMPLETE_ONBOARDING: 'COMPLETE_ONBOARDING',
setMouseUserState,
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
// Network
updateNetworkEndpointType,
UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
retryTransaction,
SET_PENDING_TOKENS: 'SET_PENDING_TOKENS',
CLEAR_PENDING_TOKENS: 'CLEAR_PENDING_TOKENS',
setPendingTokens,
clearPendingTokens,
createCancelTransaction,
createSpeedUpTransaction,
createRetryTransaction,
approveProviderRequestByOrigin,
rejectProviderRequestByOrigin,
clearApprovedOrigins,
setFirstTimeFlowType,
SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE',
SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL',
setSelectedSettingsRpcUrl,
SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE',
setNetworksTabAddMode,
// AppStateController-related actions
SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME',
setLastActiveTime,
getContractMethodData,
loadingMethoDataStarted,
loadingMethoDataFinished,
LOADING_METHOD_DATA_STARTED: 'LOADING_METHOD_DATA_STARTED',
LOADING_METHOD_DATA_FINISHED: 'LOADING_METHOD_DATA_FINISHED',
getTokenParams,
loadingTokenParamsStarted,
LOADING_TOKEN_PARAMS_STARTED: 'LOADING_TOKEN_PARAMS_STARTED',
loadingTokenParamsFinished,
LOADING_TOKEN_PARAMS_FINISHED: 'LOADING_TOKEN_PARAMS_FINISHED',
setSeedPhraseBackedUp,
verifySeedPhrase,
hideSeedPhraseBackupAfterOnboarding,
SET_SEED_PHRASE_BACKED_UP_TO_TRUE: 'SET_SEED_PHRASE_BACKED_UP_TO_TRUE',
initializeThreeBox,
restoreFromThreeBox,
getThreeBoxLastUpdated,
setThreeBoxSyncingPermission,
setShowRestorePromptToFalse,
turnThreeBoxSyncingOn,
turnThreeBoxSyncingOnAndInitialize,
}
module.exports = actions
var background = null
function _setBackgroundConnection (backgroundConnection) {
background = backgroundConnection
}
function goHome () {
return {
type: actions.GO_HOME,
}
}
// async actions
function tryUnlockMetamask (password) {
return dispatch => {
dispatch(actions.showLoadingIndication())
dispatch(actions.unlockInProgress())
log.debug(`background.submitPassword`)
return new Promise((resolve, reject) => {
background.submitPassword(password, error => {
if (error) {
return reject(error)
}
resolve()
})
})
.then(() => {
dispatch(actions.unlockSucceeded())
return forceUpdateMetamaskState(dispatch)
})
.then(() => {
return new Promise((resolve, reject) => {
background.verifySeedPhrase(err => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve()
})
})
})
.then(() => {
dispatch(actions.transitionForward())
dispatch(actions.hideLoadingIndication())
})
.catch(err => {
dispatch(actions.unlockFailed(err.message))
dispatch(actions.hideLoadingIndication())
return Promise.reject(err)
})
}
}
function transitionForward () {
return {
type: this.TRANSITION_FORWARD,
}
}
function transitionBackward () {
return {
type: this.TRANSITION_BACKWARD,
}
}
function createNewVaultAndRestore (password, seed) {
return (dispatch) => {
dispatch(actions.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(actions.unMarkPasswordForgotten()))
.then(() => {
dispatch(actions.showAccountsPage())
dispatch(actions.hideLoadingIndication())
return vault
})
.catch(err => {
dispatch(actions.displayWarning(err.message))
dispatch(actions.hideLoadingIndication())
return Promise.reject(err)
})
}
}
function createNewVaultAndGetSeedPhrase (password) {
return async dispatch => {
dispatch(actions.showLoadingIndication())
try {
await createNewVault(password)
const seedWords = await verifySeedPhrase()
dispatch(actions.hideLoadingIndication())
return seedWords
} catch (error) {
dispatch(actions.hideLoadingIndication())
dispatch(actions.displayWarning(error.message))
throw new Error(error.message)
}
}
}
function unlockAndGetSeedPhrase (password) {
return async dispatch => {
dispatch(actions.showLoadingIndication())
try {
await submitPassword(password)
const seedWords = await verifySeedPhrase()
await forceUpdateMetamaskState(dispatch)
dispatch(actions.hideLoadingIndication())
return seedWords
} catch (error) {
dispatch(actions.hideLoadingIndication())
dispatch(actions.displayWarning(error.message))
throw new Error(error.message)
}
}
}
function revealSeedConfirmation () {
return {
type: this.REVEAL_SEED_CONFIRMATION,
}
}
function submitPassword (password) {
return new Promise((resolve, reject) => {
background.submitPassword(password, error => {
if (error) {
return reject(error)
}
resolve()
})
})
}
function createNewVault (password) {
return new Promise((resolve, reject) => {
background.createNewVaultAndKeychain(password, error => {
if (error) {
return reject(error)
}
resolve(true)
})
})
}
function verifyPassword (password) {
return new Promise((resolve, reject) => {
background.submitPassword(password, error => {
if (error) {
return reject(error)
}
resolve(true)
})
})
}
function verifySeedPhrase () {
return new Promise((resolve, reject) => {
background.verifySeedPhrase((error, seedWords) => {
if (error) {
return reject(error)
}
resolve(seedWords)
})
})
}
function requestRevealSeedWords (password) {
return async dispatch => {
dispatch(actions.showLoadingIndication())
log.debug(`background.submitPassword`)
try {
await verifyPassword(password)
const seedWords = await verifySeedPhrase()
dispatch(actions.hideLoadingIndication())
return seedWords
} catch (error) {
dispatch(actions.hideLoadingIndication())
dispatch(actions.displayWarning(error.message))
throw new Error(error.message)
}
}
}
function fetchInfoToSync () {
return dispatch => {
log.debug(`background.fetchInfoToSync`)
return new Promise((resolve, reject) => {
background.fetchInfoToSync((err, result) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve(result)
})
})
}
}
function resetAccount () {
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.resetAccount((err, account) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
log.info('Transaction history reset for ' + account)
dispatch(actions.showAccountsPage())
resolve(account)
})
})
}
}
function removeAccount (address) {
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.removeAccount(address, (err, account) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
log.info('Account removed: ' + account)
dispatch(actions.showAccountsPage())
resolve()
})
})
}
}
function addNewKeyring (type, opts) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.addNewKeyring`)
background.addNewKeyring(type, opts, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.showAccountsPage())
})
}
}
function importNewAccount (strategy, args) {
return async (dispatch) => {
let newState
dispatch(actions.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(actions.hideLoadingIndication())
dispatch(actions.displayWarning(err.message))
throw err
}
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateMetamaskState(newState))
if (newState.selectedAddress) {
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: newState.selectedAddress,
})
}
return newState
}
}
function navigateToNewAccountScreen () {
return {
type: this.NEW_ACCOUNT_SCREEN,
}
}
function addNewAccount () {
log.debug(`background.addNewAccount`)
return (dispatch, getState) => {
const oldIdentities = getState().metamask.identities
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.addNewAccount((err, { identities: newIdentities}) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
const newAccountAddress = Object.keys(newIdentities).find(address => !oldIdentities[address])
dispatch(actions.hideLoadingIndication())
forceUpdateMetamaskState(dispatch)
return resolve(newAccountAddress)
})
})
}
}
function checkHardwareStatus (deviceName, hdPath) {
log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideLoadingIndication())
forceUpdateMetamaskState(dispatch)
return resolve(unlocked)
})
})
}
}
function forgetDevice (deviceName) {
log.debug(`background.forgetDevice`, deviceName)
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.forgetDevice(deviceName, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideLoadingIndication())
forceUpdateMetamaskState(dispatch)
return resolve()
})
})
}
}
function connectHardware (deviceName, page, hdPath) {
log.debug(`background.connectHardware`, deviceName, page, hdPath)
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.connectHardware(deviceName, page, hdPath, (err, accounts) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideLoadingIndication())
forceUpdateMetamaskState(dispatch)
return resolve(accounts)
})
})
}
}
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.hideLoadingIndication())
return resolve()
})
})
}
}
function showInfoPage () {
return {
type: actions.SHOW_INFO_PAGE,
}
}
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(actions.showModal({
name: 'QR_SCANNER',
}))
}
}).catch(e => {
dispatch(actions.showModal({
name: 'QR_SCANNER',
error: true,
errorType: e.type,
}))
})
}
}
function setCurrentCurrency (currencyCode) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setCurrentCurrency`)
background.setCurrentCurrency(currencyCode, (err, data) => {
dispatch(actions.hideLoadingIndication())
if (err) {
log.error(err.stack)
return dispatch(actions.displayWarning(err.message))
}
dispatch({
type: actions.SET_CURRENT_FIAT,
value: {
currentCurrency: data.currentCurrency,
conversionRate: data.conversionRate,
conversionDate: data.conversionDate,
},
})
})
}
}
function signMsg (msgData) {
log.debug('action - signMsg')
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signMessage`)
background.signMessage(msgData, (err, newState) => {
log.debug('signMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
function signPersonalMsg (msgData) {
log.debug('action - signPersonalMsg')
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signPersonalMessage`)
background.signPersonalMessage(msgData, (err, newState) => {
log.debug('signPersonalMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
function signTypedMsg (msgData) {
log.debug('action - signTypedMsg')
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signTypedMessage`)
background.signTypedMessage(msgData, (err, newState) => {
log.debug('signTypedMessage called back')
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.completedTx(msgData.metamaskId))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
function signTx (txData) {
return (dispatch) => {
global.ethQuery.sendTransaction(txData, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
})
dispatch(actions.showConfTxPage({}))
}
}
function setGasLimit (gasLimit) {
return {
type: actions.UPDATE_GAS_LIMIT,
value: gasLimit,
}
}
function setGasPrice (gasPrice) {
return {
type: actions.UPDATE_GAS_PRICE,
value: gasPrice,
}
}
function setGasTotal (gasTotal) {
return {
type: actions.UPDATE_GAS_TOTAL,
value: gasTotal,
}
}
function updateGasData ({
gasPrice,
blockGasLimit,
selectedAddress,
selectedToken,
to,
value,
data,
}) {
return (dispatch) => {
dispatch(actions.gasLoadingStarted())
return estimateGas({
estimateGasMethod: background.estimateGas,
blockGasLimit,
selectedAddress,
selectedToken,
to,
value,
estimateGasPrice: gasPrice,
data,
})
.then(gas => {
dispatch(actions.setGasLimit(gas))
dispatch(gasDuck.setCustomGasLimit(gas))
dispatch(updateSendErrors({ gasLoadingError: null }))
dispatch(actions.gasLoadingFinished())
})
.catch(err => {
log.error(err)
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' }))
dispatch(actions.gasLoadingFinished())
})
}
}
function gasLoadingStarted () {
return {
type: actions.GAS_LOADING_STARTED,
}
}
function gasLoadingFinished () {
return {
type: actions.GAS_LOADING_FINISHED,
}
}
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' })
})
}
}
function updateSendErrors (errorObject) {
return {
type: actions.UPDATE_SEND_ERRORS,
value: errorObject,
}
}
function setSendTokenBalance (tokenBalance) {
return {
type: actions.UPDATE_SEND_TOKEN_BALANCE,
value: tokenBalance,
}
}
function updateSendHexData (value) {
return {
type: actions.UPDATE_SEND_HEX_DATA,
value,
}
}
function updateSendTo (to, nickname = '') {
return {
type: actions.UPDATE_SEND_TO,
value: { to, nickname },
}
}
function updateSendAmount (amount) {
return {
type: actions.UPDATE_SEND_AMOUNT,
value: amount,
}
}
function updateCustomNonce (value) {
return {
type: actions.UPDATE_CUSTOM_NONCE,
value: value,
}
}
function updateSendMemo (memo) {
return {
type: actions.UPDATE_SEND_MEMO,
value: memo,
}
}
function setMaxModeTo (bool) {
return {
type: actions.UPDATE_MAX_MODE,
value: bool,
}
}
function updateSend (newSend) {
return {
type: actions.UPDATE_SEND,
value: newSend,
}
}
function clearSend () {
return {
type: actions.CLEAR_SEND,
}
}
function updateSendEnsResolution (ensResolution) {
return {
type: actions.UPDATE_SEND_ENS_RESOLUTION,
payload: ensResolution,
}
}
function updateSendEnsResolutionError (errorMessage) {
return {
type: actions.UPDATE_SEND_ENS_RESOLUTION_ERROR,
payload: errorMessage,
}
}
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
return (dispatch, getState) => {
log.debug(`actions calling background.approveTransaction`)
background.approveTransaction(txData.id, (err) => {
if (err) {
dispatch(actions.txError(err))
return log.error(err.message)
}
dispatch(actions.completedTx(txData.id))
if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION &&
!hasUnconfirmedTransactions(getState())) {
return global.platform.closeCurrentWindow()
}
})
}
}
function signTokenTx (tokenAddress, toAddress, amount, txData) {
return dispatch => {
dispatch(actions.showLoadingIndication())
const token = global.eth.contract(abi).at(tokenAddress)
token.transfer(toAddress, ethUtil.addHexPrefix(amount), txData)
.catch(err => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.displayWarning(err.message))
})
dispatch(actions.showConfTxPage({}))
}
}
const updateMetamaskStateFromBackground = () => {
log.debug(`background.getState`)
return new Promise((resolve, reject) => {
background.getState((error, newState) => {
if (error) {
return reject(error)
}
resolve(newState)
})
})
}
function updateTransaction (txData) {
log.info('actions: updateTx: ' + JSON.stringify(txData))
return dispatch => {
log.debug(`actions calling background.updateTx`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.updateTransaction(txData, (err) => {
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
log.error(err.message)
return reject(err)
}
resolve(txData)
})
})
.then(() => updateMetamaskStateFromBackground())
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => {
dispatch(actions.showConfTxPage({ id: txData.id }))
dispatch(actions.hideLoadingIndication())
return txData
})
}
}
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.updateAndApproveTransaction(txData, err => {
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
dispatch(actions.clearSend())
if (err) {
dispatch(actions.txError(err))
dispatch(actions.goHome())
log.error(err.message)
return reject(err)
}
resolve(txData)
})
})
.then(() => updateMetamaskStateFromBackground())
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateCustomNonce(''))
dispatch(closeCurrentNotificationWindow())
return txData
})
.catch((err) => {
dispatch(actions.hideLoadingIndication())
return Promise.reject(err)
})
}
}
function completedTx (id) {
return {
type: actions.COMPLETED_TX,
value: id,
}
}
function updateTransactionParams (id, txParams) {
return {
type: actions.UPDATE_TRANSACTION_PARAMS,
id,
value: txParams,
}
}
function txError (err) {
return {
type: actions.TRANSACTION_ERROR,
message: err.message,
}
}
function cancelMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`background.cancelMessage`)
background.cancelMessage(msgData.id, (err, newState) => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(actions.completedTx(msgData.id))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
function cancelPersonalMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
const id = msgData.id
background.cancelPersonalMessage(id, (err, newState) => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(actions.completedTx(id))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
function cancelTypedMsg (msgData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
const id = msgData.id
background.cancelTypedMessage(id, (err, newState) => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(actions.completedTx(id))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
function cancelTx (txData) {
return (dispatch) => {
log.debug(`background.cancelTransaction`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, err => {
if (err) {
return reject(err)
}
resolve()
})
})
.then(() => updateMetamaskStateFromBackground())
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
dispatch(closeCurrentNotificationWindow())
return txData
})
}
}
/**
* Cancels all of the given transactions
* @param {Array<object>} txDataList a list of tx data objects
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
return async (dispatch) => {
dispatch(actions.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(actions.updateMetamaskState(newState))
dispatch(actions.clearSend())
txIds.forEach((id) => {
dispatch(actions.completedTx(id))
})
dispatch(actions.hideLoadingIndication())
if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
return global.platform.closeCurrentWindow()
}
}
}
/**
* @deprecated
* @param {Array<object>} txsData
* @return {Function}
*/
function cancelAllTx (txsData) {
return (dispatch) => {
txsData.forEach((txData, i) => {
background.cancelTransaction(txData.id, () => {
dispatch(actions.completedTx(txData.id))
if (i === txsData.length - 1) {
dispatch(actions.goHome())
}
})
})
}
}
//
// initialize screen
//
function showCreateVault () {
return {
type: actions.SHOW_CREATE_VAULT,
}
}
function showRestoreVault () {
return {
type: actions.SHOW_RESTORE_VAULT,
}
}
function markPasswordForgotten () {
return (dispatch) => {
return background.markPasswordForgotten(() => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.forgotPassword())
forceUpdateMetamaskState(dispatch)
})
}
}
function unMarkPasswordForgotten () {
return dispatch => {
return new Promise(resolve => {
background.unMarkPasswordForgotten(() => {
dispatch(actions.forgotPassword(false))
resolve()
})
})
.then(() => forceUpdateMetamaskState(dispatch))
}
}
function forgotPassword (forgotPasswordState = true) {
return {
type: actions.FORGOT_PASSWORD,
value: forgotPasswordState,
}
}
function showInitializeMenu () {
return {
type: actions.SHOW_INIT_MENU,
}
}
function showImportPage () {
return {
type: actions.SHOW_IMPORT_PAGE,
}
}
function showNewAccountPage (formToSelect) {
return {
type: actions.SHOW_NEW_ACCOUNT_PAGE,
formToSelect,
}
}
function setNewAccountForm (formToSelect) {
return {
type: actions.SET_NEW_ACCOUNT_FORM,
formToSelect,
}
}
function createNewVaultInProgress () {
return {
type: actions.CREATE_NEW_VAULT_IN_PROGRESS,
}
}
function closeWelcomeScreen () {
return {
type: actions.CLOSE_WELCOME_SCREEN,
}
}
function backToUnlockView () {
return {
type: actions.BACK_TO_UNLOCK_VIEW,
}
}
function showNewKeychain () {
return {
type: actions.SHOW_NEW_KEYCHAIN,
}
}
//
// unlock screen
//
function unlockInProgress () {
return {
type: actions.UNLOCK_IN_PROGRESS,
}
}
function unlockFailed (message) {
return {
type: actions.UNLOCK_FAILED,
value: message,
}
}
function unlockSucceeded (message) {
return {
type: actions.UNLOCK_SUCCEEDED,
value: message,
}
}
function unlockMetamask (account) {
return {
type: actions.UNLOCK_METAMASK,
value: account,
}
}
function updateMetamaskState (newState) {
return {
type: actions.UPDATE_METAMASK_STATE,
value: newState,
}
}
const backgroundSetLocked = () => {
return new Promise((resolve, reject) => {
background.setLocked(error => {
if (error) {
return reject(error)
}
resolve()
})
})
}
function lockMetamask () {
log.debug(`background.setLocked`)
return dispatch => {
dispatch(actions.showLoadingIndication())
return backgroundSetLocked()
.then(() => updateMetamaskStateFromBackground())
.catch(error => {
dispatch(actions.displayWarning(error.message))
return Promise.reject(error)
})
.then(newState => {
dispatch(actions.updateMetamaskState(newState))
dispatch(actions.hideLoadingIndication())
dispatch({ type: actions.LOCK_METAMASK })
})
.catch(() => {
dispatch(actions.hideLoadingIndication())
dispatch({ type: actions.LOCK_METAMASK })
})
}
}
function setCurrentAccountTab (newTabName) {
log.debug(`background.setCurrentAccountTab: ${newTabName}`)
return callBackgroundThenUpdateNoSpinner(background.setCurrentAccountTab, newTabName)
}
function setSelectedToken (tokenAddress) {
return {
type: actions.SET_SELECTED_TOKEN,
value: tokenAddress || null,
}
}
function setSelectedAddress (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setSelectedAddress`)
background.setSelectedAddress(address, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
})
}
}
function showAccountDetail (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setSelectedAddress`)
background.setSelectedAddress(address, (err, tokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
dispatch(updateTokens(tokens))
dispatch({
type: actions.SHOW_ACCOUNT_DETAIL,
value: address,
})
dispatch(actions.setSelectedToken())
})
}
}
function backToAccountDetail (address) {
return {
type: actions.BACK_TO_ACCOUNT_DETAIL,
value: address,
}
}
function showAccountsPage () {
return {
type: actions.SHOW_ACCOUNTS_PAGE,
}
}
function showConfTxPage ({transForward = true, id}) {
return {
type: actions.SHOW_CONF_TX_PAGE,
transForward,
id,
}
}
function nextTx () {
return {
type: actions.NEXT_TX,
}
}
function viewPendingTx (txId) {
return {
type: actions.VIEW_PENDING_TX,
value: txId,
}
}
function previousTx () {
return {
type: actions.PREVIOUS_TX,
}
}
function editTx (txId) {
return {
type: actions.EDIT_TX,
value: txId,
}
}
function showConfigPage (transitionForward = true) {
return {
type: actions.SHOW_CONFIG_PAGE,
value: transitionForward,
}
}
function showAddTokenPage (transitionForward = true) {
return {
type: actions.SHOW_ADD_TOKEN_PAGE,
value: transitionForward,
}
}
function showAddSuggestedTokenPage (transitionForward = true) {
return {
type: actions.SHOW_ADD_SUGGESTED_TOKEN_PAGE,
value: transitionForward,
}
}
function addToken (address, symbol, decimals, image) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.addToken(address, symbol, decimals, image, (err, tokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateTokens(tokens))
resolve(tokens)
})
})
}
}
function removeToken (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.removeToken(address, (err, tokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateTokens(tokens))
resolve(tokens)
})
})
}
}
function addTokens (tokens) {
return dispatch => {
if (Array.isArray(tokens)) {
dispatch(actions.setSelectedToken(getTokenAddressFromTokenObject(tokens[0])))
return Promise.all(tokens.map(({ address, symbol, decimals }) => (
dispatch(addToken(address, symbol, decimals))
)))
} else {
dispatch(actions.setSelectedToken(getTokenAddressFromTokenObject(tokens)))
return Promise.all(
Object
.entries(tokens)
.map(([_, { address, symbol, decimals }]) => (
dispatch(addToken(address, symbol, decimals))
))
)
}
}
}
function removeSuggestedTokens () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve) => {
background.removeSuggestedTokens((err, suggestedTokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
}
dispatch(actions.clearPendingTokens())
if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
return global.platform.closeCurrentWindow()
}
resolve(suggestedTokens)
})
})
.then(() => updateMetamaskStateFromBackground())
.then(suggestedTokens => dispatch(actions.updateMetamaskState({...suggestedTokens})))
}
}
function addKnownMethodData (fourBytePrefix, methodData) {
return () => {
background.addKnownMethodData(fourBytePrefix, methodData)
}
}
function updateTokens (newTokens) {
return {
type: actions.UPDATE_TOKENS,
newTokens,
}
}
function clearPendingTokens () {
return {
type: actions.CLEAR_PENDING_TOKENS,
}
}
function goBackToInitView () {
return {
type: actions.BACK_TO_INIT_MENU,
}
}
function markAccountsFound () {
log.debug(`background.markAccountsFound`)
return callBackgroundThenUpdate(background.markAccountsFound)
}
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(actions.displayWarning(err.message))
return reject(err)
}
const { selectedAddressTxList } = newState
const { id } = selectedAddressTxList[selectedAddressTxList.length - 1]
newTxId = id
resolve(newState)
})
})
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => newTxId)
}
}
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(actions.displayWarning(err.message))
return reject(err)
}
const { selectedAddressTxList } = newState
const { id } = selectedAddressTxList[selectedAddressTxList.length - 1]
newTxId = id
resolve(newState)
})
})
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => newTxId)
}
}
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(actions.displayWarning(err.message))
return reject(err)
}
const { selectedAddressTxList } = newState
newTx = selectedAddressTxList[selectedAddressTxList.length - 1]
resolve(newState)
})
})
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => newTx)
}
}
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(actions.displayWarning(err.message))
return reject(err)
}
const { selectedAddressTxList } = newState
newTx = selectedAddressTxList[selectedAddressTxList.length - 1]
resolve(newState)
})
})
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then(() => newTx)
}
}
//
// config
//
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(actions.displayWarning('Had a problem changing networks!'))
}
dispatch(setPreviousProvider(currentProviderType))
dispatch(actions.updateProviderType(type))
dispatch(actions.setSelectedToken())
})
}
}
function updateProviderType (type) {
return {
type: actions.SET_PROVIDER_TYPE,
value: type,
}
}
function setPreviousProvider (type) {
return {
type: actions.SET_PREVIOUS_PROVIDER,
value: type,
}
}
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(actions.displayWarning('Had a problem changing networks!'))
}
dispatch({
type: actions.SET_RPC_TARGET,
value: newRpc,
})
})
}
}
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(self.displayWarning('Had a problem removing network!'))
}
dispatch(actions.setSelectedToken())
background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
}
dispatch({
type: actions.SET_RPC_TARGET,
value: newRpc,
})
})
})
}
}
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(actions.displayWarning('Had a problem changing networks!'))
}
dispatch(actions.setSelectedToken())
})
}
}
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(self.displayWarning('Had a problem removing network!'))
return reject(err)
}
dispatch(actions.setSelectedToken())
resolve()
})
})
}
}
// Calls the addressBookController to add a new address.
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
*/
function removeFromAddressBook (chainId, addressToRemove) {
log.debug(`background.removeFromAddressBook`)
return () => {
background.removeFromAddressBook(chainId, checksumAddress(addressToRemove))
}
}
function useEtherscanProvider () {
log.debug(`background.useEtherscanProvider`)
background.useEtherscanProvider()
return {
type: actions.USE_ETHERSCAN_PROVIDER,
}
}
function showNetworkDropdown () {
return {
type: actions.NETWORK_DROPDOWN_OPEN,
}
}
function hideNetworkDropdown () {
return {
type: actions.NETWORK_DROPDOWN_CLOSE,
}
}
function showModal (payload) {
return {
type: actions.MODAL_OPEN,
payload,
}
}
function hideModal (payload) {
return {
type: actions.MODAL_CLOSE,
payload,
}
}
function closeCurrentNotificationWindow () {
return (dispatch, getState) => {
if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION &&
!hasUnconfirmedTransactions(getState())) {
global.platform.closeCurrentWindow()
dispatch(closeNotifacationWindow())
}
}
}
function closeNotifacationWindow () {
return {
type: actions.CLOSE_NOTIFICATION_WINDOW,
}
}
function showSidebar ({ transitionName, type, props }) {
return {
type: actions.SIDEBAR_OPEN,
value: {
transitionName,
type,
props,
},
}
}
function hideSidebar () {
return {
type: actions.SIDEBAR_CLOSE,
}
}
function showAlert (msg) {
return {
type: actions.ALERT_OPEN,
value: msg,
}
}
function hideAlert () {
return {
type: actions.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)
*/
function qrCodeDetected (qrCodeData) {
return {
type: actions.QR_CODE_DETECTED,
value: qrCodeData,
}
}
function showLoadingIndication (message) {
return {
type: actions.SHOW_LOADING,
value: message,
}
}
function setHardwareWalletDefaultHdPath ({ device, path }) {
return {
type: actions.SET_HARDWARE_WALLET_DEFAULT_HD_PATH,
value: {device, path},
}
}
function hideLoadingIndication () {
return {
type: actions.HIDE_LOADING,
}
}
function showSubLoadingIndication () {
return {
type: actions.SHOW_SUB_LOADING_INDICATION,
}
}
function hideSubLoadingIndication () {
return {
type: actions.HIDE_SUB_LOADING_INDICATION,
}
}
function displayWarning (text) {
return {
type: actions.DISPLAY_WARNING,
value: text,
}
}
function hideWarning () {
return {
type: actions.HIDE_WARNING,
}
}
function requestExportAccount () {
return {
type: actions.REQUEST_ACCOUNT_EXPORT,
}
}
function exportAccount (password, address) {
var self = this
return function (dispatch) {
dispatch(self.showLoadingIndication())
log.debug(`background.submitPassword`)
return new Promise((resolve, reject) => {
background.submitPassword(password, function (err) {
if (err) {
log.error('Error in submiting password.')
dispatch(self.hideLoadingIndication())
dispatch(self.displayWarning('Incorrect Password.'))
return reject(err)
}
log.debug(`background.exportAccount`)
return background.exportAccount(address, function (err, result) {
dispatch(self.hideLoadingIndication())
if (err) {
log.error(err)
dispatch(self.displayWarning('Had a problem exporting the account.'))
return reject(err)
}
// dispatch(self.exportAccountComplete())
dispatch(self.showPrivateKey(result))
return resolve(result)
})
})
})
}
}
function exportAccountComplete () {
return {
type: actions.EXPORT_ACCOUNT,
}
}
function showPrivateKey (key) {
return {
type: actions.SHOW_PRIVATE_KEY,
value: key,
}
}
function setAccountLabel (account, label) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setAccountLabel`)
return new Promise((resolve, reject) => {
background.setAccountLabel(account, label, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch({
type: actions.SET_ACCOUNT_LABEL,
value: { account, label },
})
resolve(account)
})
})
}
}
function showSendPage () {
return {
type: actions.SHOW_SEND_PAGE,
}
}
function showSendTokenPage () {
return {
type: actions.SHOW_SEND_TOKEN_PAGE,
}
}
function buyEth (opts) {
return (dispatch) => {
const url = getBuyEthUrl(opts)
global.platform.openWindow({ url })
dispatch({
type: actions.BUY_ETH,
})
}
}
function onboardingBuyEthView (address) {
return {
type: actions.ONBOARDING_BUY_ETH_VIEW,
value: address,
}
}
function buyEthView (address) {
return {
type: actions.BUY_ETH_VIEW,
value: address,
}
}
function coinBaseSubview () {
return {
type: actions.COINBASE_SUBVIEW,
}
}
function pairUpdate (coin) {
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
dispatch(actions.hideWarning())
shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => {
dispatch(actions.hideSubLoadingIndication())
if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error))
dispatch({
type: actions.PAIR_UPDATE,
value: {
marketinfo: mktResponse,
},
})
})
}
}
function shapeShiftSubview () {
var pair = 'btc_eth'
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
shapeShiftRequest('marketinfo', {pair}, (mktResponse) => {
shapeShiftRequest('getcoins', {}, (response) => {
dispatch(actions.hideSubLoadingIndication())
if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error))
dispatch({
type: actions.SHAPESHIFT_SUBVIEW,
value: {
marketinfo: mktResponse,
coinOptions: response,
},
})
})
})
}
}
function coinShiftRquest (data, marketData) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
dispatch(actions.hideLoadingIndication())
if (response.error) return dispatch(actions.displayWarning(response.error))
var message = `
Deposit your ${response.depositType} to the address below:`
log.debug(`background.createShapeShiftTx`)
background.createShapeShiftTx(response.deposit, response.depositType)
dispatch(actions.showQrView(response.deposit, [message].concat(marketData)))
})
}
}
function buyWithShapeShift (data) {
return () => new Promise((resolve, reject) => {
shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
if (response.error) {
return reject(response.error)
}
background.createShapeShiftTx(response.deposit, response.depositType)
return resolve(response)
})
})
}
function showQrView (data, message) {
return {
type: actions.SHOW_QR_VIEW,
value: {
message: message,
data: data,
},
}
}
function reshowQrCode (data, coin) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => {
if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error))
var message = [
`Deposit your ${coin} to the address below:`,
`Deposit Limit: ${mktResponse.limit}`,
`Deposit Minimum:${mktResponse.minimum}`,
]
dispatch(actions.hideLoadingIndication())
return dispatch(actions.showQrView(data, message))
})
}
}
function shapeShiftRequest (query, options = {}, cb) {
var queryResponse, method
options.method ? method = options.method : method = 'GET'
var requestListner = function () {
try {
queryResponse = JSON.parse(this.responseText)
if (cb) {
cb(queryResponse)
}
return queryResponse
} catch (e) {
if (cb) {
cb({error: e})
}
return e
}
}
var shapShiftReq = new XMLHttpRequest()
shapShiftReq.addEventListener('load', requestListner)
shapShiftReq.open(method, `https://shapeshift.io/${query}/${options.pair ? options.pair : ''}`, true)
if (options.method === 'POST') {
var jsonObj = JSON.stringify(options.data)
shapShiftReq.setRequestHeader('Content-Type', 'application/json')
return shapShiftReq.send(jsonObj)
} else {
return shapShiftReq.send()
}
}
function setFeatureFlag (feature, activated, notificationType) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.setFeatureFlag(feature, activated, (err, updatedFeatureFlags) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateFeatureFlags(updatedFeatureFlags))
notificationType && dispatch(actions.showModal({ name: notificationType }))
resolve(updatedFeatureFlags)
})
})
}
}
function updateFeatureFlags (updatedFeatureFlags) {
return {
type: actions.UPDATE_FEATURE_FLAGS,
value: updatedFeatureFlags,
}
}
function setPreference (preference, value) {
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.setPreference(preference, value, (err, updatedPreferences) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updatePreferences(updatedPreferences))
resolve(updatedPreferences)
})
})
}
}
function updatePreferences (value) {
return {
type: actions.UPDATE_PREFERENCES,
value,
}
}
function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
}
function setShowFiatConversionOnTestnetsPreference (value) {
return setPreference('showFiatInTestnets', value)
}
function setAutoLogoutTimeLimit (value) {
return setPreference('autoLogoutTimeLimit', value)
}
function setCompletedOnboarding () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
try {
await pify(background.completeOnboarding).call(background)
} catch (err) {
dispatch(actions.displayWarning(err.message))
throw err
}
dispatch(actions.completeOnboarding())
dispatch(actions.hideLoadingIndication())
}
}
function completeOnboarding () {
return {
type: actions.COMPLETE_ONBOARDING,
}
}
function setNetworkNonce (networkNonce) {
return {
type: actions.SET_NETWORK_NONCE,
value: networkNonce,
}
}
function updateNetworkNonce (address) {
return (dispatch) => {
return new Promise((resolve, reject) => {
global.ethQuery.getTransactionCount(address, (err, data) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(setNetworkNonce(data))
resolve(data)
})
})
}
}
function setMouseUserState (isMouseUser) {
return {
type: actions.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.
function callBackgroundThenUpdateNoSpinner (method, ...args) {
return (dispatch) => {
method.call(background, ...args, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
forceUpdateMetamaskState(dispatch)
})
}
}
function callBackgroundThenUpdate (method, ...args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
method.call(background, ...args, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
forceUpdateMetamaskState(dispatch)
})
}
}
function forceUpdateMetamaskState (dispatch) {
log.debug(`background.getState`)
return new Promise((resolve, reject) => {
background.getState((err, newState) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateMetamaskState(newState))
resolve(newState)
})
})
}
function toggleAccountMenu () {
return {
type: actions.TOGGLE_ACCOUNT_MENU,
}
}
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(actions.displayWarning(err.message))
return reject(err)
}
dispatch({
type: actions.SET_PARTICIPATE_IN_METAMETRICS,
value: val,
})
resolve([val, metaMetricsId])
})
})
}
}
function setMetaMetricsSendCount (val) {
return (dispatch) => {
log.debug(`background.setMetaMetricsSendCount`)
return new Promise((resolve, reject) => {
background.setMetaMetricsSendCount(val, (err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch({
type: actions.SET_METAMETRICS_SEND_COUNT,
value: val,
})
resolve(val)
})
})
}
}
function setUseBlockie (val) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setUseBlockie`)
background.setUseBlockie(val, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
})
dispatch({
type: actions.SET_USE_BLOCKIE,
value: val,
})
}
}
function setUseNonceField (val) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.setUseNonceField`)
background.setUseNonceField(val, (err) => {
dispatch(actions.hideLoadingIndication())
if (err) {
return dispatch(actions.displayWarning(err.message))
}
})
dispatch({
type: actions.SET_USE_NONCEFIELD,
value: val,
})
}
}
function updateCurrentLocale (key) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return fetchLocale(key)
.then((localeMessages) => {
log.debug(`background.setCurrentLocale`)
background.setCurrentLocale(key, (err, textDirection) => {
if (err) {
dispatch(actions.hideLoadingIndication())
return dispatch(actions.displayWarning(err.message))
}
switchDirection(textDirection)
dispatch(actions.setCurrentLocale(key, localeMessages))
dispatch(actions.hideLoadingIndication())
})
})
}
}
function setCurrentLocale (locale, messages) {
return {
type: actions.SET_CURRENT_LOCALE,
value: {
locale,
messages,
},
}
}
function updateNetworkEndpointType (networkEndpointType) {
return {
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,
value: networkEndpointType,
}
}
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: actions.SET_PENDING_TOKENS,
payload: tokens,
}
}
function approveProviderRequestByOrigin (origin) {
return () => {
background.approveProviderRequestByOrigin(origin)
}
}
function rejectProviderRequestByOrigin (origin) {
return () => {
background.rejectProviderRequestByOrigin(origin)
}
}
function clearApprovedOrigins () {
return () => {
background.clearApprovedOrigins()
}
}
function setFirstTimeFlowType (type) {
return (dispatch) => {
log.debug(`background.setFirstTimeFlowType`)
background.setFirstTimeFlowType(type, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
})
dispatch({
type: actions.SET_FIRST_TIME_FLOW_TYPE,
value: type,
})
}
}
function setSelectedSettingsRpcUrl (newRpcUrl) {
return {
type: actions.SET_SELECTED_SETTINGS_RPC_URL,
value: newRpcUrl,
}
}
function setNetworksTabAddMode (isInAddMode) {
return {
type: actions.SET_NETWORKS_TAB_ADD_MODE,
value: isInAddMode,
}
}
function setLastActiveTime () {
return (dispatch) => {
background.setLastActiveTime((err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
})
}
}
function loadingMethoDataStarted () {
return {
type: actions.LOADING_METHOD_DATA_STARTED,
}
}
function loadingMethoDataFinished () {
return {
type: actions.LOADING_METHOD_DATA_FINISHED,
}
}
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(actions.loadingMethoDataStarted())
log.debug(`loadingMethodData`)
return getMethodDataAsync(fourBytePrefix)
.then(({ name, params }) => {
dispatch(actions.loadingMethoDataFinished())
background.addKnownMethodData(fourBytePrefix, { name, params })
return { name, params }
})
}
}
function loadingTokenParamsStarted () {
return {
type: actions.LOADING_TOKEN_PARAMS_STARTED,
}
}
function loadingTokenParamsFinished () {
return {
type: actions.LOADING_TOKEN_PARAMS_FINISHED,
}
}
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(actions.loadingTokenParamsStarted())
log.debug(`loadingTokenParams`)
return fetchSymbolAndDecimals(tokenAddress, existingTokens)
.then(({ symbol, decimals }) => {
dispatch(actions.addToken(tokenAddress, symbol, decimals))
dispatch(actions.loadingTokenParamsFinished())
})
}
}
function unsetMigratedPrivacyMode () {
return () => {
background.unsetMigratedPrivacyMode()
}
}
function setSeedPhraseBackedUp (seedPhraseBackupState) {
return (dispatch) => {
log.debug(`background.setSeedPhraseBackedUp`)
return new Promise((resolve, reject) => {
background.setSeedPhraseBackedUp(seedPhraseBackupState, (err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
return forceUpdateMetamaskState(dispatch)
.then(resolve)
.catch(reject)
})
})
}
}
function hideSeedPhraseBackupAfterOnboarding () {
return {
type: actions.HIDE_SEED_PHRASE_BACKUP_AFTER_ONBOARDING,
}
}
function initializeThreeBox () {
return (dispatch) => {
return new Promise((resolve, reject) => {
background.initializeThreeBox((err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve()
})
})
}
}
function setShowRestorePromptToFalse () {
return (dispatch) => {
return new Promise((resolve, reject) => {
background.setShowRestorePromptToFalse((err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve()
})
})
}
}
function turnThreeBoxSyncingOn () {
return (dispatch) => {
return new Promise((resolve, reject) => {
background.turnThreeBoxSyncingOn((err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve()
})
})
}
}
function restoreFromThreeBox (accountAddress) {
return (dispatch) => {
return new Promise((resolve, reject) => {
background.restoreFromThreeBox(accountAddress, (err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve()
})
})
}
}
function getThreeBoxLastUpdated () {
return (dispatch) => {
return new Promise((resolve, reject) => {
background.getThreeBoxLastUpdated((err, lastUpdated) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve(lastUpdated)
})
})
}
}
function setThreeBoxSyncingPermission (threeBoxSyncingAllowed) {
return (dispatch) => {
return new Promise((resolve, reject) => {
background.setThreeBoxSyncingPermission(threeBoxSyncingAllowed, (err) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve()
})
})
}
}
function turnThreeBoxSyncingOnAndInitialize () {
return async (dispatch) => {
await dispatch(setThreeBoxSyncingPermission(true))
await dispatch(turnThreeBoxSyncingOn())
await dispatch(initializeThreeBox(true))
}
}
function setNextNonce (nextNonce) {
return {
type: actions.SET_NEXT_NONCE,
value: nextNonce,
}
}
function getNextNonce () {
return (dispatch, getState) => {
const address = getState().metamask.selectedAddress
return new Promise((resolve, reject) => {
background.getNextNonce(address, (err, nextNonce) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(setNextNonce(nextNonce))
resolve(nextNonce)
})
})
}
}