mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-27 12:56:01 +01:00
b75f812953
Update accounts permission history on accountsChanged Create PermissionsLogController Fix permissions activity log pruning Add selectors, background hooks for better UX Make selected account the first account returned Use enums for store keys in log controller Add last selected address history to PreferencesController
564 lines
17 KiB
JavaScript
564 lines
17 KiB
JavaScript
import { NETWORK_TYPES } from '../helpers/constants/common'
|
|
import { mapObjectValues } from '../../../app/scripts/lib/util'
|
|
import { stripHexPrefix, addHexPrefix } from 'ethereumjs-util'
|
|
import { createSelector } from 'reselect'
|
|
|
|
import abi from 'human-standard-token-abi'
|
|
import { multiplyCurrencies } from '../helpers/utils/conversion-util'
|
|
import {
|
|
addressSlicer,
|
|
checksumAddress,
|
|
formatDate,
|
|
getOriginFromUrl,
|
|
} from '../helpers/utils/util'
|
|
|
|
import { getPermittedAccounts } from './permissions'
|
|
|
|
export { getPermittedAccounts } from './permissions'
|
|
|
|
export function getNetworkIdentifier (state) {
|
|
const { metamask: { provider: { type, nickname, rpcTarget } } } = state
|
|
|
|
return nickname || rpcTarget || type
|
|
}
|
|
|
|
export function getCurrentKeyring (state) {
|
|
const identity = getSelectedIdentity(state)
|
|
|
|
if (!identity) {
|
|
return null
|
|
}
|
|
|
|
const simpleAddress = stripHexPrefix(identity.address).toLowerCase()
|
|
|
|
const keyring = state.metamask.keyrings.find((kr) => {
|
|
return kr.accounts.includes(simpleAddress) ||
|
|
kr.accounts.includes(identity.address)
|
|
})
|
|
|
|
return keyring
|
|
}
|
|
|
|
export function getAccountType (state) {
|
|
const currentKeyring = getCurrentKeyring(state)
|
|
const type = currentKeyring && currentKeyring.type
|
|
|
|
switch (type) {
|
|
case 'Trezor Hardware':
|
|
case 'Ledger Hardware':
|
|
return 'hardware'
|
|
case 'Simple Key Pair':
|
|
return 'imported'
|
|
default:
|
|
return 'default'
|
|
}
|
|
}
|
|
|
|
export function getSelectedAsset (state) {
|
|
const selectedToken = getSelectedToken(state)
|
|
return selectedToken && selectedToken.symbol || 'ETH'
|
|
}
|
|
|
|
export function getCurrentNetworkId (state) {
|
|
return state.metamask.network
|
|
}
|
|
|
|
export const getMetaMaskAccounts = createSelector(
|
|
getMetaMaskAccountsRaw,
|
|
getMetaMaskCachedBalances,
|
|
(currentAccounts, cachedBalances) => Object.entries(currentAccounts).reduce((selectedAccounts, [accountID, account]) => {
|
|
if (account.balance === null || account.balance === undefined) {
|
|
return {
|
|
...selectedAccounts,
|
|
[accountID]: {
|
|
...account,
|
|
balance: cachedBalances && cachedBalances[accountID],
|
|
},
|
|
|
|
}
|
|
} else {
|
|
return {
|
|
...selectedAccounts,
|
|
[accountID]: account,
|
|
}
|
|
}
|
|
}, {})
|
|
)
|
|
|
|
export function getSelectedAddress (state) {
|
|
const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0]
|
|
|
|
return selectedAddress
|
|
}
|
|
|
|
function lastSelectedAddressSelector (state, origin) {
|
|
return state.metamask.lastSelectedAddressByOrigin[origin] || null
|
|
}
|
|
|
|
// not using reselect here since the returns are contingent;
|
|
// we have no reasons to recompute the permitted accounts if there
|
|
// exists a lastSelectedAddress
|
|
export function getLastSelectedAddress (state, origin) {
|
|
return (
|
|
lastSelectedAddressSelector(state, origin) ||
|
|
getPermittedAccounts(state, origin)[0] || // always returns array
|
|
getSelectedAddress(state)
|
|
)
|
|
}
|
|
|
|
export function getSelectedIdentity (state) {
|
|
const selectedAddress = getSelectedAddress(state)
|
|
const identities = state.metamask.identities
|
|
|
|
return identities[selectedAddress]
|
|
}
|
|
|
|
export function getNumberOfAccounts (state) {
|
|
return Object.keys(state.metamask.accounts).length
|
|
}
|
|
|
|
export function getNumberOfTokens (state) {
|
|
const tokens = state.metamask.tokens
|
|
return tokens ? tokens.length : 0
|
|
}
|
|
|
|
export function getMetaMaskKeyrings (state) {
|
|
return state.metamask.keyrings
|
|
}
|
|
|
|
export function getMetaMaskIdentities (state) {
|
|
return state.metamask.identities
|
|
}
|
|
|
|
export function getMetaMaskAccountsRaw (state) {
|
|
return state.metamask.accounts
|
|
}
|
|
|
|
export function getMetaMaskCachedBalances (state) {
|
|
const network = getCurrentNetworkId(state)
|
|
|
|
return state.metamask.cachedBalances[network]
|
|
}
|
|
|
|
/**
|
|
* Get ordered (by keyrings) accounts with identity and balance
|
|
*/
|
|
export const getMetaMaskAccountsOrdered = createSelector(
|
|
getMetaMaskKeyrings,
|
|
getMetaMaskIdentities,
|
|
getMetaMaskAccounts,
|
|
(keyrings, identities, accounts) => keyrings
|
|
.reduce((list, keyring) => list.concat(keyring.accounts), [])
|
|
.filter(address => !!identities[address])
|
|
.map(address => ({ ...identities[address], ...accounts[address] }))
|
|
)
|
|
|
|
export function isBalanceCached (state) {
|
|
const selectedAccountBalance = state.metamask.accounts[getSelectedAddress(state)].balance
|
|
const cachedBalance = getSelectedAccountCachedBalance(state)
|
|
|
|
return Boolean(!selectedAccountBalance && cachedBalance)
|
|
}
|
|
|
|
export function getSelectedAccountCachedBalance (state) {
|
|
const cachedBalances = state.metamask.cachedBalances[state.metamask.network]
|
|
const selectedAddress = getSelectedAddress(state)
|
|
|
|
return cachedBalances && cachedBalances[selectedAddress]
|
|
}
|
|
|
|
export function getSelectedAccount (state) {
|
|
const accounts = getMetaMaskAccounts(state)
|
|
const selectedAddress = getSelectedAddress(state)
|
|
|
|
return accounts[selectedAddress]
|
|
}
|
|
|
|
export function getSelectedToken (state) {
|
|
const tokens = state.metamask.tokens || []
|
|
const selectedTokenAddress = state.metamask.selectedTokenAddress
|
|
const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
|
|
const sendToken = state.metamask.send && state.metamask.send.token
|
|
|
|
return selectedToken || sendToken || null
|
|
}
|
|
|
|
export function getSelectedTokenExchangeRate (state) {
|
|
const contractExchangeRates = state.metamask.contractExchangeRates
|
|
const selectedToken = getSelectedToken(state) || {}
|
|
const { address } = selectedToken
|
|
return contractExchangeRates[address] || 0
|
|
}
|
|
|
|
export function getSelectedTokenAssetImage (state) {
|
|
const assetImages = state.metamask.assetImages || {}
|
|
const selectedToken = getSelectedToken(state) || {}
|
|
const { address } = selectedToken
|
|
return assetImages[address]
|
|
}
|
|
|
|
export function getAssetImages (state) {
|
|
const assetImages = state.metamask.assetImages || {}
|
|
return assetImages
|
|
}
|
|
|
|
export function getTokenExchangeRate (state, address) {
|
|
const contractExchangeRates = state.metamask.contractExchangeRates
|
|
return contractExchangeRates[address] || 0
|
|
}
|
|
|
|
export function conversionRateSelector (state) {
|
|
return state.metamask.conversionRate
|
|
}
|
|
|
|
export function getAddressBook (state) {
|
|
const network = state.metamask.network
|
|
if (!state.metamask.addressBook[network]) {
|
|
return []
|
|
}
|
|
return Object.values(state.metamask.addressBook[network])
|
|
}
|
|
|
|
export function getAddressBookEntry (state, address) {
|
|
const addressBook = getAddressBook(state)
|
|
const entry = addressBook.find(contact => contact.address === checksumAddress(address))
|
|
return entry
|
|
}
|
|
|
|
export function getAddressBookEntryName (state, address) {
|
|
const entry = getAddressBookEntry(state, address) || state.metamask.identities[address]
|
|
return entry && entry.name !== '' ? entry.name : addressSlicer(address)
|
|
}
|
|
|
|
export function getDaiV1Token (state) {
|
|
const OLD_DAI_CONTRACT_ADDRESS = '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'
|
|
const tokens = state.metamask.tokens || []
|
|
return tokens.find(({ address }) => checksumAddress(address) === OLD_DAI_CONTRACT_ADDRESS)
|
|
}
|
|
|
|
export function accountsWithSendEtherInfoSelector (state) {
|
|
const accounts = getMetaMaskAccounts(state)
|
|
const { identities } = state.metamask
|
|
|
|
const accountsWithSendEtherInfo = Object.entries(accounts).map(([key, account]) => {
|
|
return Object.assign({}, account, identities[key])
|
|
})
|
|
|
|
return accountsWithSendEtherInfo
|
|
}
|
|
|
|
export function getAccountsWithLabels (state) {
|
|
const accountsWithoutLabel = accountsWithSendEtherInfoSelector(state)
|
|
const accountsWithLabels = accountsWithoutLabel.map(account => {
|
|
const { address, name, balance } = account
|
|
return {
|
|
address,
|
|
truncatedAddress: `${address.slice(0, 6)}...${address.slice(-4)}`,
|
|
addressLabel: `${name} (...${address.slice(address.length - 4)})`,
|
|
label: name,
|
|
balance,
|
|
}
|
|
})
|
|
return accountsWithLabels
|
|
}
|
|
|
|
export function getCurrentAccountWithSendEtherInfo (state) {
|
|
const currentAddress = getSelectedAddress(state)
|
|
const accounts = accountsWithSendEtherInfoSelector(state)
|
|
|
|
return accounts.find(({ address }) => address === currentAddress)
|
|
}
|
|
|
|
export function getCurrentEthBalance (state) {
|
|
return getCurrentAccountWithSendEtherInfo(state).balance
|
|
}
|
|
|
|
export function getGasIsLoading (state) {
|
|
return state.appState.gasIsLoading
|
|
}
|
|
|
|
export function getForceGasMin (state) {
|
|
return state.metamask.send.forceGasMin
|
|
}
|
|
|
|
export function getSendFrom (state) {
|
|
return state.metamask.send.from
|
|
}
|
|
|
|
export function getSendAmount (state) {
|
|
return state.metamask.send.amount
|
|
}
|
|
|
|
export function getSendMaxModeState (state) {
|
|
return state.metamask.send.maxModeOn
|
|
}
|
|
|
|
export function getCurrentCurrency (state) {
|
|
return state.metamask.currentCurrency
|
|
}
|
|
|
|
export function getNativeCurrency (state) {
|
|
return state.metamask.nativeCurrency
|
|
}
|
|
|
|
export function getSelectedTokenToFiatRate (state) {
|
|
const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
|
|
const conversionRate = conversionRateSelector(state)
|
|
|
|
const tokenToFiatRate = multiplyCurrencies(
|
|
conversionRate,
|
|
selectedTokenExchangeRate,
|
|
{ toNumericBase: 'dec' }
|
|
)
|
|
|
|
return tokenToFiatRate
|
|
}
|
|
|
|
export function getSelectedTokenContract (state) {
|
|
const selectedToken = getSelectedToken(state)
|
|
return selectedToken
|
|
? global.eth.contract(abi).at(selectedToken.address)
|
|
: null
|
|
}
|
|
|
|
export function getTotalUnapprovedCount ({ metamask }) {
|
|
const {
|
|
unapprovedTxs = {},
|
|
unapprovedMsgCount,
|
|
unapprovedPersonalMsgCount,
|
|
unapprovedTypedMessagesCount,
|
|
} = metamask
|
|
|
|
return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount +
|
|
unapprovedTypedMessagesCount
|
|
}
|
|
|
|
export function getIsMainnet (state) {
|
|
const networkType = getNetworkIdentifier(state)
|
|
return networkType === NETWORK_TYPES.MAINNET
|
|
}
|
|
|
|
export function isEthereumNetwork (state) {
|
|
const networkType = getNetworkIdentifier(state)
|
|
const {
|
|
KOVAN,
|
|
MAINNET,
|
|
RINKEBY,
|
|
ROPSTEN,
|
|
GOERLI,
|
|
} = NETWORK_TYPES
|
|
|
|
return [ KOVAN, MAINNET, RINKEBY, ROPSTEN, GOERLI].includes(networkType)
|
|
}
|
|
|
|
export function preferencesSelector ({ metamask }) {
|
|
return metamask.preferences
|
|
}
|
|
|
|
export function getAdvancedInlineGasShown (state) {
|
|
return Boolean(state.metamask.featureFlags.advancedInlineGas)
|
|
}
|
|
|
|
export function getUseNonceField (state) {
|
|
return Boolean(state.metamask.useNonceField)
|
|
}
|
|
|
|
export function getCustomNonceValue (state) {
|
|
return String(state.metamask.customNonceValue)
|
|
}
|
|
|
|
export function getPermissionsDescriptions (state) {
|
|
return state.metamask.permissionsDescriptions
|
|
}
|
|
|
|
export function getPermissionsRequests (state) {
|
|
return state.metamask.permissionsRequests
|
|
}
|
|
|
|
export function getDomainMetadata (state) {
|
|
return state.metamask.domainMetadata
|
|
}
|
|
|
|
export function getActiveTab (state) {
|
|
return state.activeTab
|
|
}
|
|
|
|
export function getMetaMetricState (state) {
|
|
return {
|
|
network: getCurrentNetworkId(state),
|
|
activeCurrency: getSelectedAsset(state),
|
|
accountType: getAccountType(state),
|
|
metaMetricsId: state.metamask.metaMetricsId,
|
|
numberOfTokens: getNumberOfTokens(state),
|
|
numberOfAccounts: getNumberOfAccounts(state),
|
|
participateInMetaMetrics: state.metamask.participateInMetaMetrics,
|
|
}
|
|
}
|
|
|
|
export function getRpcPrefsForCurrentProvider (state) {
|
|
const { frequentRpcListDetail, provider } = state.metamask
|
|
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
|
|
const { rpcPrefs = {} } = selectRpcInfo || {}
|
|
return rpcPrefs
|
|
}
|
|
|
|
export function getKnownMethodData (state, data) {
|
|
if (!data) {
|
|
return null
|
|
}
|
|
const prefixedData = addHexPrefix(data)
|
|
const fourBytePrefix = prefixedData.slice(0, 10)
|
|
const { knownMethodData } = state.metamask
|
|
|
|
return knownMethodData && knownMethodData[fourBytePrefix]
|
|
}
|
|
|
|
export function getFeatureFlags (state) {
|
|
return state.metamask.featureFlags
|
|
}
|
|
|
|
export function getFirstPermissionRequest (state) {
|
|
const requests = getPermissionsRequests(state)
|
|
return requests && requests[0] ? requests[0] : null
|
|
}
|
|
|
|
export function hasPermissionRequests (state) {
|
|
return Boolean(getFirstPermissionRequest(state))
|
|
}
|
|
|
|
export function getPermissionsDomains (state) {
|
|
return state.metamask.domains
|
|
}
|
|
|
|
export function getAddressConnectedDomainMap (state) {
|
|
const {
|
|
domains,
|
|
domainMetadata,
|
|
} = state.metamask
|
|
|
|
const addressConnectedIconMap = {}
|
|
|
|
if (domains) {
|
|
Object.keys(domains).forEach(domainKey => {
|
|
const { permissions } = domains[domainKey]
|
|
const { icon, name } = domainMetadata[domainKey] || {}
|
|
permissions.forEach(perm => {
|
|
const caveats = perm.caveats || []
|
|
const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts')
|
|
if (exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length) {
|
|
exposedAccountCaveat.value.forEach(address => {
|
|
const nameToRender = name || domainKey
|
|
addressConnectedIconMap[address] = addressConnectedIconMap[address]
|
|
? { ...addressConnectedIconMap[address], [domainKey]: { icon, name: nameToRender } }
|
|
: { [domainKey]: { icon, name: nameToRender } }
|
|
})
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
return addressConnectedIconMap
|
|
}
|
|
|
|
export function getDomainToConnectedAddressMap (state) {
|
|
const { domains = {} } = state.metamask
|
|
|
|
const domainToConnectedAddressMap = mapObjectValues(domains, (_, { permissions }) => {
|
|
const ethAccountsPermissions = permissions.filter(permission => permission.parentCapability === 'eth_accounts')
|
|
const ethAccountsPermissionsExposedAccountAddresses = ethAccountsPermissions.map(permission => {
|
|
const caveats = permission.caveats
|
|
const exposedAccountsCaveats = caveats.filter(caveat => caveat.name === 'exposedAccounts')
|
|
const exposedAccountsAddresses = exposedAccountsCaveats.map(caveat => caveat.value[0])
|
|
return exposedAccountsAddresses
|
|
})
|
|
const allAddressesConnectedToDomain = ethAccountsPermissionsExposedAccountAddresses.reduce((acc, arrayOfAddresses) => {
|
|
return [ ...acc, ...arrayOfAddresses ]
|
|
}, [])
|
|
return allAddressesConnectedToDomain
|
|
})
|
|
|
|
return domainToConnectedAddressMap
|
|
}
|
|
|
|
export function getAddressConnectedToCurrentTab (state) {
|
|
const domainToConnectedAddressMap = getDomainToConnectedAddressMap(state)
|
|
const originOfCurrentTab = getOriginOfCurrentTab(state)
|
|
const addressesConnectedToCurrentTab = domainToConnectedAddressMap[originOfCurrentTab]
|
|
const addressConnectedToCurrentTab = addressesConnectedToCurrentTab && addressesConnectedToCurrentTab[0]
|
|
return addressConnectedToCurrentTab
|
|
}
|
|
|
|
export function getRenderablePermissionsDomains (state) {
|
|
const {
|
|
domains = {},
|
|
domainMetadata,
|
|
permissionsHistory,
|
|
permissionsDescriptions,
|
|
selectedAddress,
|
|
} = state.metamask
|
|
|
|
const renderableDomains = Object.keys(domains).reduce((acc, domainKey) => {
|
|
const { permissions } = domains[domainKey]
|
|
const permissionsWithCaveatsForSelectedAddress = permissions.filter(perm => {
|
|
const caveats = perm.caveats || []
|
|
const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts')
|
|
const exposedAccountCaveatValue = exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length
|
|
? exposedAccountCaveat.value[0]
|
|
: {}
|
|
return exposedAccountCaveatValue === selectedAddress
|
|
})
|
|
|
|
if (permissionsWithCaveatsForSelectedAddress.length) {
|
|
const permissionKeys = permissions.map(permission => permission.parentCapability)
|
|
const {
|
|
name,
|
|
icon,
|
|
extensionId,
|
|
} = domainMetadata[domainKey] || {}
|
|
const permissionsHistoryForDomain = permissionsHistory[domainKey] || {}
|
|
const ethAccountsPermissionsForDomain = permissionsHistoryForDomain['eth_accounts'] || {}
|
|
const accountsLastConnectedTime = ethAccountsPermissionsForDomain.accounts || {}
|
|
const selectedAddressLastConnectedTime = accountsLastConnectedTime[selectedAddress]
|
|
|
|
const lastConnectedTime = selectedAddressLastConnectedTime
|
|
? formatDate(selectedAddressLastConnectedTime, 'yyyy-M-d')
|
|
: ''
|
|
|
|
return [ ...acc, {
|
|
name: name || domainKey,
|
|
secondaryName: name ? domainKey : '',
|
|
icon,
|
|
key: domainKey,
|
|
lastConnectedTime,
|
|
permissionDescriptions: permissionKeys.map(permissionKey => permissionsDescriptions[permissionKey]),
|
|
extensionId,
|
|
}]
|
|
} else {
|
|
return acc
|
|
}
|
|
}, [])
|
|
|
|
return renderableDomains
|
|
}
|
|
|
|
export function getOriginOfCurrentTab (state) {
|
|
const { activeTab } = state
|
|
return activeTab && activeTab.url && getOriginFromUrl(activeTab.url)
|
|
}
|
|
|
|
export function getLastConnectedInfo (state) {
|
|
const { permissionsHistory = {} } = state.metamask
|
|
const lastConnectedInfoData = Object.keys(permissionsHistory).reduce((acc, origin) => {
|
|
const ethAccountsHistory = JSON.parse(JSON.stringify(permissionsHistory[origin]['eth_accounts']))
|
|
return {
|
|
...acc,
|
|
[origin]: ethAccountsHistory.accounts,
|
|
}
|
|
}, {})
|
|
return lastConnectedInfoData
|
|
}
|
|
|
|
export function getIpfsGateway (state) {
|
|
return state.metamask.ipfsGateway
|
|
}
|