1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

4byte fallback (#6551)

* Adds 4byte registry fallback to getMethodData() (#6435)

* Adds fetchWithCache to guard against unnecessary API calls

* Add custom fetch wrapper with abort on timeout

* Use opts and cacheRefreshTime in fetch-with-cache util

* Use custom fetch wrapper with timeout for fetch-with-cache

* Improve contract method data fetching (#6623)

* Remove async call from getTransactionActionKey()

* Stop blocking confirm screen rendering on method data loading, and base screen route on transactionCategory

* Remove use of withMethodData, fix use of knownMethodData, in relation to transaction-list-item.component

* Load data contract method data progressively, making it non-blocking; requires simplifying conf-tx-base lifecycle logic.

* Allow editing of gas price while loading on the confirm screen.

* Fix transactionAction component and its unit tests.

* Fix confirm transaction components for cases of route transitions within metamask.

* Only call toString on id if truthy in getNavigateTxData()

* Fix knownMethodData retrieval and data fetching from fourbyte
This commit is contained in:
Whymarrh Whitby 2019-06-18 09:47:14 -02:30 committed by Dan J Miller
parent 2ff184d77e
commit 748801f417
23 changed files with 374 additions and 260 deletions

6
package-lock.json generated
View File

@ -6233,6 +6233,12 @@
} }
} }
}, },
"abortcontroller-polyfill": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.3.0.tgz",
"integrity": "sha512-lbWQgf+eRvku3va8poBlDBO12FigTQr9Zb7NIjXrePrhxWVKdCP2wbDl1tLDaYa18PWTom3UEWwdH13S46I+yA==",
"dev": true
},
"abstract-leveldown": { "abstract-leveldown": {
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz",

View File

@ -200,6 +200,7 @@
"@storybook/react": "^5.1.1", "@storybook/react": "^5.1.1",
"addons-linter": "^1.10.0", "addons-linter": "^1.10.0",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"abortcontroller-polyfill": "^1.3.0",
"babel-eslint": "^8.0.0", "babel-eslint": "^8.0.0",
"babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",

View File

@ -27,6 +27,7 @@ global.log = log
// fetch // fetch
global.fetch = require('isomorphic-fetch') global.fetch = require('isomorphic-fetch')
require('abortcontroller-polyfill/dist/polyfill-patch-fetch')
// dom // dom
require('jsdom-global')() require('jsdom-global')()

View File

@ -18,33 +18,6 @@ describe('TransactionAction Component', () => {
} }
}) })
it('should render -- when methodData is still fetching', () => {
const methodData = { data: {}, done: false, error: null }
const transaction = {
id: 1,
status: 'confirmed',
submittedTime: 1534045442919,
time: 1534045440641,
txParams: {
from: '0xc5ae6383e126f901dcb06131d97a88745bfa88d6',
gas: '0x5208',
gasPrice: '0x3b9aca00',
nonce: '0x96',
to: '0x50a9d56c2b8ba9a5c7f2c08c3d26e0499f23a706',
value: '0x2386f26fc10000',
},
}
const wrapper = shallow(<TransactionAction
methodData={methodData}
transaction={transaction}
className="transaction-action"
/>, { context: { t }})
assert.equal(wrapper.find('.transaction-action').length, 1)
assert.equal(wrapper.text(), '--')
})
it('should render Sent Ether', () => { it('should render Sent Ether', () => {
const methodData = { data: {}, done: true, error: null } const methodData = { data: {}, done: true, error: null }
const transaction = { const transaction = {
@ -75,15 +48,7 @@ describe('TransactionAction Component', () => {
it('should render Approved', async () => { it('should render Approved', async () => {
const methodData = { const methodData = {
data: { name: 'Approve',
name: 'Approve',
params: [
{ type: 'address' },
{ type: 'uint256' },
],
},
done: true,
error: null,
} }
const transaction = { const transaction = {
id: 1, id: 1,
@ -99,6 +64,7 @@ describe('TransactionAction Component', () => {
value: '0x2386f26fc10000', value: '0x2386f26fc10000',
data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003', data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003',
}, },
transactionCategory: 'contractInteraction',
} }
const wrapper = shallow( const wrapper = shallow(
@ -111,23 +77,12 @@ describe('TransactionAction Component', () => {
) )
assert.ok(wrapper) assert.ok(wrapper)
assert.equal(wrapper.find('.test-class').length, 1) assert.equal(wrapper.find('.transaction-action').length, 1)
await wrapper.instance().getTransactionAction() assert.equal(wrapper.find('.transaction-action').text().trim(), 'Approve')
assert.equal(wrapper.state('transactionAction'), 'approve')
}) })
it('should render Accept Fulfillment', async () => { it('should render contractInteraction', async () => {
const methodData = { const methodData = {}
data: {
name: 'AcceptFulfillment',
params: [
{ type: 'address' },
{ type: 'uint256' },
],
},
done: true,
error: null,
}
const transaction = { const transaction = {
id: 1, id: 1,
status: 'confirmed', status: 'confirmed',
@ -142,6 +97,7 @@ describe('TransactionAction Component', () => {
value: '0x2386f26fc10000', value: '0x2386f26fc10000',
data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003', data: '0x095ea7b300000000000000000000000050a9d56c2b8ba9a5c7f2c08c3d26e0499f23a7060000000000000000000000000000000000000000000000000000000000000003',
}, },
transactionCategory: 'contractInteraction',
} }
const wrapper = shallow( const wrapper = shallow(
@ -154,9 +110,8 @@ describe('TransactionAction Component', () => {
) )
assert.ok(wrapper) assert.ok(wrapper)
assert.equal(wrapper.find('.test-class').length, 1) assert.equal(wrapper.find('.transaction-action').length, 1)
await wrapper.instance().getTransactionAction() assert.equal(wrapper.find('.transaction-action').text().trim(), 'contractInteraction')
assert.equal(wrapper.state('transactionAction'), ' Accept Fulfillment')
}) })
}) })
}) })

View File

@ -15,43 +15,23 @@ export default class TransactionAction extends PureComponent {
methodData: PropTypes.object, methodData: PropTypes.object,
} }
state = { getTransactionAction () {
transactionAction: '',
}
componentDidMount () {
this.getTransactionAction()
}
componentDidUpdate () {
this.getTransactionAction()
}
async getTransactionAction () {
const { transactionAction } = this.state
const { transaction, methodData } = this.props const { transaction, methodData } = this.props
const { data, done } = methodData const { name } = methodData
const { name = '' } = data
if (!done || transactionAction) { const actionKey = getTransactionActionKey(transaction)
return const action = actionKey && this.context.t(actionKey)
} const methodName = name && camelCaseToCapitalize(name)
const actionKey = await getTransactionActionKey(transaction, data) return methodName || action || ''
const action = actionKey
? this.context.t(actionKey)
: camelCaseToCapitalize(name)
this.setState({ transactionAction: action })
} }
render () { render () {
const { className, methodData: { done } } = this.props const { className } = this.props
const { transactionAction } = this.state
return ( return (
<div className={classnames('transaction-action', className)}> <div className={classnames('transaction-action', className)}>
{ (done && transactionAction) || '--' } { this.getTransactionAction() }
</div> </div>
) )
} }

View File

@ -34,6 +34,8 @@ export default class TransactionListItem extends PureComponent {
fetchBasicGasAndTimeEstimates: PropTypes.func, fetchBasicGasAndTimeEstimates: PropTypes.func,
fetchGasEstimates: PropTypes.func, fetchGasEstimates: PropTypes.func,
rpcPrefs: PropTypes.object, rpcPrefs: PropTypes.object,
data: PropTypes.string,
getContractMethodData: PropTypes.func,
} }
static defaultProps = { static defaultProps = {
@ -150,6 +152,12 @@ export default class TransactionListItem extends PureComponent {
) )
} }
componentDidMount () {
if (this.props.data) {
this.props.getContractMethodData(this.props.data)
}
}
render () { render () {
const { const {
assetImages, assetImages,
@ -214,7 +222,7 @@ export default class TransactionListItem extends PureComponent {
<TransactionListItemDetails <TransactionListItemDetails
transactionGroup={transactionGroup} transactionGroup={transactionGroup}
onRetry={this.handleRetry} onRetry={this.handleRetry}
showRetry={showRetry && methodData.done} showRetry={showRetry}
onCancel={this.handleCancel} onCancel={this.handleCancel}
showCancel={showCancel} showCancel={showCancel}
cancelDisabled={!hasEnoughCancelGas} cancelDisabled={!hasEnoughCancelGas}

View File

@ -1,9 +1,8 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import { compose } from 'recompose' import { compose } from 'recompose'
import withMethodData from '../../../helpers/higher-order-components/with-method-data'
import TransactionListItem from './transaction-list-item.component' import TransactionListItem from './transaction-list-item.component'
import { setSelectedToken, showModal, showSidebar, addKnownMethodData } from '../../../store/actions' import { setSelectedToken, showModal, showSidebar, getContractMethodData } from '../../../store/actions'
import { hexToDecimal } from '../../../helpers/utils/conversions.util' import { hexToDecimal } from '../../../helpers/utils/conversions.util'
import { getTokenData } from '../../../helpers/utils/transactions.util' import { getTokenData } from '../../../helpers/utils/transactions.util'
import { getHexGasTotal, increaseLastGasPrice } from '../../../helpers/utils/confirm-tx.util' import { getHexGasTotal, increaseLastGasPrice } from '../../../helpers/utils/confirm-tx.util'
@ -14,15 +13,15 @@ import {
setCustomGasPriceForRetry, setCustomGasPriceForRetry,
setCustomGasLimit, setCustomGasLimit,
} from '../../../ducks/gas/gas.duck' } from '../../../ducks/gas/gas.duck'
import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors' import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector, getKnownMethodData } from '../../../selectors/selectors'
import { isBalanceSufficient } from '../../../pages/send/send.utils' import { isBalanceSufficient } from '../../../pages/send/send.utils'
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { metamask: { knownMethodData, accounts, provider, frequentRpcListDetail } } = state const { metamask: { accounts, provider, frequentRpcListDetail } } = state
const { showFiatInTestnets } = preferencesSelector(state) const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state) const isMainnet = getIsMainnet(state)
const { transactionGroup: { primaryTransaction } = {} } = ownProps const { transactionGroup: { primaryTransaction } = {} } = ownProps
const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction const { txParams: { gas: gasLimit, gasPrice, data } = {} } = primaryTransaction
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget) const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
const { rpcPrefs } = selectRpcInfo || {} const { rpcPrefs } = selectRpcInfo || {}
@ -38,7 +37,7 @@ const mapStateToProps = (state, ownProps) => {
}) })
return { return {
knownMethodData, methodData: getKnownMethodData(state, data) || {},
showFiat: (isMainnet || !!showFiatInTestnets), showFiat: (isMainnet || !!showFiatInTestnets),
selectedAccountBalance, selectedAccountBalance,
hasEnoughCancelGas, hasEnoughCancelGas,
@ -51,7 +50,7 @@ const mapDispatchToProps = dispatch => {
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)), fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)), setSelectedToken: tokenAddress => dispatch(setSelectedToken(tokenAddress)),
addKnownMethodData: (fourBytePrefix, methodData) => dispatch(addKnownMethodData(fourBytePrefix, methodData)), getContractMethodData: methodData => dispatch(getContractMethodData(methodData)),
retryTransaction: (transaction, gasPrice) => { retryTransaction: (transaction, gasPrice) => {
dispatch(setCustomGasPriceForRetry(gasPrice || transaction.txParams.gasPrice)) dispatch(setCustomGasPriceForRetry(gasPrice || transaction.txParams.gasPrice))
dispatch(setCustomGasLimit(transaction.txParams.gas)) dispatch(setCustomGasLimit(transaction.txParams.gas))
@ -97,5 +96,4 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
export default compose( export default compose(
withRouter, withRouter,
connect(mapStateToProps, mapDispatchToProps, mergeProps), connect(mapStateToProps, mapDispatchToProps, mergeProps),
withMethodData,
)(TransactionListItem) )(TransactionListItem)

View File

@ -79,6 +79,7 @@ function reduceApp (state, action) {
lastSelectedProvider: null, lastSelectedProvider: null,
networksTabSelectedRpcUrl: '', networksTabSelectedRpcUrl: '',
networksTabIsInAddMode: false, networksTabIsInAddMode: false,
loadingMethodData: false,
}, state.appState) }, state.appState)
switch (action.type) { switch (action.type) {
@ -763,6 +764,17 @@ function reduceApp (state, action) {
networksTabIsInAddMode: action.value, networksTabIsInAddMode: action.value,
}) })
case actions.LOADING_METHOD_DATA_STARTED:
return extend(appState, {
loadingMethodData: true,
})
case actions.LOADING_METHOD_DATA_FINISHED:
return extend(appState, {
loadingMethodData: false,
})
default: default:
return appState return appState
} }

View File

@ -1,4 +1,3 @@
import log from 'loglevel'
import { import {
conversionRateSelector, conversionRateSelector,
currentCurrencySelector, currentCurrencySelector,
@ -18,12 +17,9 @@ import {
import { import {
getTokenData, getTokenData,
getMethodData,
isSmartContractAddress,
sumHexes, sumHexes,
} from '../../helpers/utils/transactions.util' } from '../../helpers/utils/transactions.util'
import { getSymbolAndDecimals } from '../../helpers/utils/token-util'
import { conversionUtil } from '../../helpers/utils/conversion-util' import { conversionUtil } from '../../helpers/utils/conversion-util'
import { addHexPrefix } from 'ethereumjs-util' import { addHexPrefix } from 'ethereumjs-util'
@ -348,7 +344,7 @@ export function updateTxDataAndCalculate (txData) {
} }
export function setTransactionToConfirm (transactionId) { export function setTransactionToConfirm (transactionId) {
return async (dispatch, getState) => { return (dispatch, getState) => {
const state = getState() const state = getState()
const unconfirmedTransactionsHash = unconfirmedTransactionsHashSelector(state) const unconfirmedTransactionsHash = unconfirmedTransactionsHashSelector(state)
const transaction = unconfirmedTransactionsHash[transactionId] const transaction = unconfirmedTransactionsHash[transactionId]
@ -364,34 +360,14 @@ export function setTransactionToConfirm (transactionId) {
dispatch(updateTxDataAndCalculate(txData)) dispatch(updateTxDataAndCalculate(txData))
const { txParams } = transaction const { txParams } = transaction
const { to } = txParams
if (txParams.data) { if (txParams.data) {
const { tokens: existingTokens } = state const { data } = txParams
const { data, to: tokenAddress } = txParams
dispatch(setFetchingData(true))
const methodData = await getMethodData(data)
dispatch(updateMethodData(methodData))
try {
const toSmartContract = await isSmartContractAddress(to || '')
dispatch(updateToSmartContract(toSmartContract))
} catch (error) {
log.error(error)
}
dispatch(setFetchingData(false))
const tokenData = getTokenData(data) const tokenData = getTokenData(data)
dispatch(updateTokenData(tokenData)) dispatch(updateTokenData(tokenData))
try {
const tokenSymbolData = await getSymbolAndDecimals(tokenAddress, existingTokens) || {}
const { symbol: tokenSymbol = '', decimals: tokenDecimals = '' } = tokenSymbolData
dispatch(updateTokenProps({ tokenSymbol, tokenDecimals }))
} catch (error) {
dispatch(updateTokenProps({ tokenSymbol: '', tokenDecimals: '' }))
}
} }
if (txParams.nonce) { if (txParams.nonce) {

View File

@ -630,7 +630,7 @@ describe('Confirm Transaction Duck', () => {
storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index])) storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index]))
}) })
it('updates confirmTransaction transaction', done => { it('updates confirmTransaction transaction', () => {
const mockState = { const mockState = {
metamask: { metamask: {
conversionRate: 468.58, conversionRate: 468.58,
@ -673,13 +673,10 @@ describe('Confirm Transaction Duck', () => {
] ]
store.dispatch(actions.setTransactionToConfirm(2603411941761054)) store.dispatch(actions.setTransactionToConfirm(2603411941761054))
.then(() => { const storeActions = store.getActions()
const storeActions = store.getActions() assert.equal(storeActions.length, expectedActions.length)
assert.equal(storeActions.length, expectedActions.length)
storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index])) storeActions.forEach((action, index) => assert.equal(action.type, expectedActions[index]))
done()
})
}) })
}) })
}) })

View File

@ -0,0 +1,28 @@
import {
loadLocalStorageData,
saveLocalStorageData,
} from '../../../lib/local-storage-helpers'
import http from './fetch'
const fetch = http({
timeout: 30000,
})
export default function fetchWithCache (url, opts, cacheRefreshTime = 360000) {
const currentTime = Date.now()
const cachedFetch = loadLocalStorageData('cachedFetch') || {}
const { cachedUrl, cachedTime } = cachedFetch[url] || {}
if (cachedUrl && currentTime - cachedTime < cacheRefreshTime) {
return cachedFetch[url]
} else {
cachedFetch[url] = { cachedUrl: url, cachedTime: currentTime }
saveLocalStorageData(cachedFetch, 'cachedFetch')
return fetch(url, {
referrerPolicy: 'no-referrer-when-downgrade',
body: null,
method: 'GET',
mode: 'cors',
...opts,
})
}
}

View File

@ -0,0 +1,25 @@
/* global AbortController */
export default function ({ timeout = 120000 } = {}) {
return function _fetch (url, opts) {
return new Promise(async (resolve, reject) => {
const abortController = new AbortController()
const abortSignal = abortController.signal
const f = fetch(url, {
...opts,
signal: abortSignal,
})
const timer = setTimeout(() => abortController.abort(), timeout)
try {
const res = await f
clearTimeout(timer)
return resolve(res)
} catch (e) {
clearTimeout(timer)
return reject(e)
}
})
}
}

View File

@ -0,0 +1,54 @@
import assert from 'assert'
import nock from 'nock'
import http from './fetch'
describe('custom fetch fn', () => {
it('fetches a url', async () => {
nock('https://api.infura.io')
.get('/money')
.reply(200, '{"hodl": false}')
const fetch = http()
const response = await (await fetch('https://api.infura.io/money')).json()
assert.deepEqual(response, {
hodl: false,
})
})
it('throws when the request hits a custom timeout', async () => {
nock('https://api.infura.io')
.get('/moon')
.delay(2000)
.reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
const fetch = http({
timeout: 123,
})
try {
await fetch('https://api.infura.io/moon').then(r => r.json())
assert.fail('Request should throw')
} catch (e) {
assert.ok(e)
}
})
it('should abort the request when the custom timeout is hit', async () => {
nock('https://api.infura.io')
.get('/moon')
.delay(2000)
.reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
const fetch = http({
timeout: 123,
})
try {
await fetch('https://api.infura.io/moon').then(r => r.json())
assert.fail('Request should be aborted')
} catch (e) {
assert.deepEqual(e.message, 'Aborted')
}
})
})

View File

@ -7,7 +7,7 @@ import {
TRANSACTION_STATUS_CONFIRMED, TRANSACTION_STATUS_CONFIRMED,
} from '../../../../app/scripts/controllers/transactions/enums' } from '../../../../app/scripts/controllers/transactions/enums'
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network' import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
import fetchWithCache from './fetch-with-cache'
import { import {
TOKEN_METHOD_TRANSFER, TOKEN_METHOD_TRANSFER,
@ -32,39 +32,57 @@ export function getTokenData (data = '') {
return abiDecoder.decodeMethod(data) return abiDecoder.decodeMethod(data)
} }
async function getMethodFrom4Byte (fourBytePrefix) {
const fourByteResponse = (await fetchWithCache(`https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`, {
referrerPolicy: 'no-referrer-when-downgrade',
body: null,
method: 'GET',
mode: 'cors',
}))
const fourByteJSON = await fourByteResponse.json()
if (fourByteJSON.count === 1) {
return fourByteJSON.results[0].text_signature
} else {
return null
}
}
const registry = new MethodRegistry({ provider: global.ethereumProvider }) const registry = new MethodRegistry({ provider: global.ethereumProvider })
/** /**
* Attempts to return the method data from the MethodRegistry library, if the method exists in the * Attempts to return the method data from the MethodRegistry library, the message registry library and the token abi, in that order of preference
* registry. Otherwise, returns an empty object. * @param {string} fourBytePrefix - The prefix from the method code associated with the data
* @param {string} data - The hex data (@code txParams.data) of a transaction
* @returns {Object} * @returns {Object}
*/ */
export async function getMethodData (data = '') { export async function getMethodDataAsync (fourBytePrefix) {
const prefixedData = ethUtil.addHexPrefix(data) try {
const fourBytePrefix = prefixedData.slice(0, 10) const fourByteSig = getMethodFrom4Byte(fourBytePrefix).catch((e) => {
log.error(e)
return null
})
try { let sig = await registry.lookup(fourBytePrefix)
const sig = await registry.lookup(fourBytePrefix)
if (!sig) { if (!sig) {
return {} sig = await fourByteSig
}
const parsedResult = registry.parse(sig)
return {
name: parsedResult.name,
params: parsedResult.args,
}
} catch (error) {
log.error(error)
const contractData = getTokenData(data)
const { name } = contractData || {}
return { name }
} }
if (!sig) {
return {}
}
const parsedResult = registry.parse(sig)
return {
name: parsedResult.name,
params: parsedResult.args,
}
} catch (error) {
log.error(error)
return {}
}
} }
export function isConfirmDeployContract (txData = {}) { export function isConfirmDeployContract (txData = {}) {
@ -87,11 +105,10 @@ export function getFourBytePrefix (data = '') {
/** /**
* Returns the action of a transaction as a key to be passed into the translator. * Returns the action of a transaction as a key to be passed into the translator.
* @param {Object} transaction - txData object * @param {Object} transaction - txData object
* @param {Object} methodData - Data returned from eth-method-registry
* @returns {string|undefined} * @returns {string|undefined}
*/ */
export async function getTransactionActionKey (transaction, methodData) { export function getTransactionActionKey (transaction) {
const { txParams: { data, to } = {}, msgParams, type } = transaction const { msgParams, type, transactionCategory } = transaction
if (type === 'cancel') { if (type === 'cancel') {
return CANCEL_ATTEMPT_ACTION_KEY return CANCEL_ATTEMPT_ACTION_KEY
@ -105,27 +122,23 @@ export async function getTransactionActionKey (transaction, methodData) {
return DEPLOY_CONTRACT_ACTION_KEY return DEPLOY_CONTRACT_ACTION_KEY
} }
if (data) { const isTokenAction = [
const toSmartContract = await isSmartContractAddress(to) TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER_FROM,
].find(actionName => actionName === transactionCategory)
const isNonTokenSmartContract = transactionCategory === CONTRACT_INTERACTION_KEY
if (!toSmartContract) { if (isTokenAction || isNonTokenSmartContract) {
return SEND_ETHER_ACTION_KEY switch (transactionCategory) {
}
const { name } = methodData
const methodName = name && name.toLowerCase()
if (!methodName) {
return CONTRACT_INTERACTION_KEY
}
switch (methodName) {
case TOKEN_METHOD_TRANSFER: case TOKEN_METHOD_TRANSFER:
return SEND_TOKEN_ACTION_KEY return SEND_TOKEN_ACTION_KEY
case TOKEN_METHOD_APPROVE: case TOKEN_METHOD_APPROVE:
return APPROVE_ACTION_KEY return APPROVE_ACTION_KEY
case TOKEN_METHOD_TRANSFER_FROM: case TOKEN_METHOD_TRANSFER_FROM:
return TRANSFER_FROM_ACTION_KEY return TRANSFER_FROM_ACTION_KEY
case CONTRACT_INTERACTION_KEY:
return CONTRACT_INTERACTION_KEY
default: default:
return undefined return undefined
} }

View File

@ -94,6 +94,7 @@ export default class ConfirmTransactionBase extends Component {
advancedInlineGasShown: PropTypes.bool, advancedInlineGasShown: PropTypes.bool,
insufficientBalance: PropTypes.bool, insufficientBalance: PropTypes.bool,
hideFiatConversion: PropTypes.bool, hideFiatConversion: PropTypes.bool,
transactionCategory: PropTypes.string,
} }
state = { state = {
@ -268,6 +269,7 @@ export default class ConfirmTransactionBase extends Component {
} = {}, } = {},
hideData, hideData,
dataComponent, dataComponent,
transactionCategory,
} = this.props } = this.props
if (hideData) { if (hideData) {
@ -279,7 +281,7 @@ export default class ConfirmTransactionBase extends Component {
<div className="confirm-page-container-content__data-box-label"> <div className="confirm-page-container-content__data-box-label">
{`${t('functionType')}:`} {`${t('functionType')}:`}
<span className="confirm-page-container-content__function-type"> <span className="confirm-page-container-content__function-type">
{ name || t('notFound') } { getMethodName(name) || this.context.tOrKey(transactionCategory) || this.context.t('contractInteraction') }
</span> </span>
</div> </div>
{ {
@ -464,6 +466,7 @@ export default class ConfirmTransactionBase extends Component {
handleNextTx (txId) { handleNextTx (txId) {
const { history, clearConfirmTransaction } = this.props const { history, clearConfirmTransaction } = this.props
if (txId) { if (txId) {
clearConfirmTransaction() clearConfirmTransaction()
history.push(`${CONFIRM_TRANSACTION_ROUTE}/${txId}`) history.push(`${CONFIRM_TRANSACTION_ROUTE}/${txId}`)
@ -473,7 +476,7 @@ export default class ConfirmTransactionBase extends Component {
getNavigateTxData () { getNavigateTxData () {
const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props
const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs).reverse() const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs).reverse()
const currentPosition = enumUnapprovedTxs.indexOf(id.toString()) const currentPosition = enumUnapprovedTxs.indexOf(id ? id.toString() : '')
return { return {
totalTx: enumUnapprovedTxs.length, totalTx: enumUnapprovedTxs.length,
@ -530,7 +533,6 @@ export default class ConfirmTransactionBase extends Component {
valid: propsValid = true, valid: propsValid = true,
errorMessage, errorMessage,
errorKey: propsErrorKey, errorKey: propsErrorKey,
actionKey,
title, title,
subtitle, subtitle,
hideSubtitle, hideSubtitle,
@ -542,6 +544,7 @@ export default class ConfirmTransactionBase extends Component {
assetImage, assetImage,
warning, warning,
unapprovedTxCount, unapprovedTxCount,
transactionCategory,
} = this.props } = this.props
const { submitting, submitError } = this.state const { submitting, submitError } = this.state
@ -557,7 +560,7 @@ export default class ConfirmTransactionBase extends Component {
toAddress={toAddress} toAddress={toAddress}
showEdit={onEdit && !isTxReprice} showEdit={onEdit && !isTxReprice}
// In the event that the key is falsy (and inherently invalid), use a fallback string // In the event that the key is falsy (and inherently invalid), use a fallback string
action={this.context.tOrKey(actionKey) || getMethodName(name) || this.context.t('contractInteraction')} action={getMethodName(name) || this.context.tOrKey(transactionCategory) || this.context.t('contractInteraction')}
title={title} title={title}
titleComponent={this.renderTitleComponent()} titleComponent={this.renderTitleComponent()}
subtitle={subtitle} subtitle={subtitle}

View File

@ -18,7 +18,7 @@ import { isBalanceSufficient, calcGasTotal } from '../send/send.utils'
import { conversionGreaterThan } from '../../helpers/utils/conversion-util' import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
import { MIN_GAS_LIMIT_DEC } from '../send/send.constants' import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util' import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors' import { getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet, getKnownMethodData } from '../../selectors/selectors'
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => { const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
return { return {
@ -27,8 +27,9 @@ const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
} }
}, {}) }, {})
const mapStateToProps = (state, props) => { const mapStateToProps = (state, ownProps) => {
const { toAddress: propsToAddress } = props const { toAddress: propsToAddress, match: { params = {} } } = ownProps
const { id: paramsTransactionId } = params
const { showFiatInTestnets } = preferencesSelector(state) const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state) const isMainnet = getIsMainnet(state)
const { confirmTransaction, metamask, gas } = state const { confirmTransaction, metamask, gas } = state
@ -43,18 +44,18 @@ const mapStateToProps = (state, props) => {
hexTransactionFee, hexTransactionFee,
hexTransactionTotal, hexTransactionTotal,
tokenData, tokenData,
methodData,
txData, txData,
tokenProps, tokenProps,
nonce, nonce,
} = confirmTransaction } = confirmTransaction
const { txParams = {}, lastGasPrice, id: transactionId } = txData const { txParams = {}, lastGasPrice, id: transactionId, transactionCategory } = txData
const { const {
from: fromAddress, from: fromAddress,
to: txParamsToAddress, to: txParamsToAddress,
gasPrice, gasPrice,
gas: gasLimit, gas: gasLimit,
value: amount, value: amount,
data,
} = txParams } = txParams
const accounts = getMetaMaskAccounts(state) const accounts = getMetaMaskAccounts(state)
const { const {
@ -87,8 +88,7 @@ const mapStateToProps = (state, props) => {
) )
const isTxReprice = Boolean(lastGasPrice) const isTxReprice = Boolean(lastGasPrice)
const transaction = R.find(({ id }) => id === (transactionId || Number(paramsTransactionId)))(selectedAddressTxList)
const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList)
const transactionStatus = transaction ? transaction.status : '' const transactionStatus = transaction ? transaction.status : ''
const currentNetworkUnapprovedTxs = R.filter( const currentNetworkUnapprovedTxs = R.filter(
@ -104,6 +104,8 @@ const mapStateToProps = (state, props) => {
conversionRate, conversionRate,
}) })
const methodData = getKnownMethodData(state, data) || {}
return { return {
balance, balance,
fromAddress, fromAddress,
@ -119,7 +121,7 @@ const mapStateToProps = (state, props) => {
hexTransactionAmount, hexTransactionAmount,
hexTransactionFee, hexTransactionFee,
hexTransactionTotal, hexTransactionTotal,
txData, txData: Object.keys(txData).length ? txData : transaction || {},
tokenData, tokenData,
methodData, methodData,
tokenProps, tokenProps,
@ -141,6 +143,7 @@ const mapStateToProps = (state, props) => {
hideSubtitle: (!isMainnet && !showFiatInTestnets), hideSubtitle: (!isMainnet && !showFiatInTestnets),
hideFiatConversion: (!isMainnet && !showFiatInTestnets), hideFiatConversion: (!isMainnet && !showFiatInTestnets),
metaMetricsSendCount, metaMetricsSendCount,
transactionCategory,
} }
} }

View File

@ -12,18 +12,17 @@ import {
CONFIRM_TOKEN_METHOD_PATH, CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH, SIGNATURE_REQUEST_PATH,
} from '../../helpers/constants/routes' } from '../../helpers/constants/routes'
import { isConfirmDeployContract } from '../../helpers/utils/transactions.util'
import { import {
TOKEN_METHOD_TRANSFER, TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_APPROVE, TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER_FROM, TOKEN_METHOD_TRANSFER_FROM,
DEPLOY_CONTRACT_ACTION_KEY,
SEND_ETHER_ACTION_KEY,
} from '../../helpers/constants/transactions' } from '../../helpers/constants/transactions'
export default class ConfirmTransactionSwitch extends Component { export default class ConfirmTransactionSwitch extends Component {
static propTypes = { static propTypes = {
txData: PropTypes.object, txData: PropTypes.object,
methodData: PropTypes.object,
fetchingData: PropTypes.bool,
isEtherTransaction: PropTypes.bool, isEtherTransaction: PropTypes.bool,
isTokenMethod: PropTypes.bool, isTokenMethod: PropTypes.bool,
} }
@ -31,31 +30,21 @@ export default class ConfirmTransactionSwitch extends Component {
redirectToTransaction () { redirectToTransaction () {
const { const {
txData, txData,
methodData: { name },
fetchingData,
isEtherTransaction,
isTokenMethod,
} = this.props } = this.props
const { id, txParams: { data } = {} } = txData const { id, txParams: { data } = {}, transactionCategory } = txData
if (fetchingData) { if (transactionCategory === DEPLOY_CONTRACT_ACTION_KEY) {
return <Loading />
}
if (isConfirmDeployContract(txData)) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}` const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
return <Redirect to={{ pathname }} /> return <Redirect to={{ pathname }} />
} }
if (isEtherTransaction && !isTokenMethod) { if (transactionCategory === SEND_ETHER_ACTION_KEY) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}` const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
return <Redirect to={{ pathname }} /> return <Redirect to={{ pathname }} />
} }
if (data) { if (data) {
const methodName = name && name.toLowerCase() switch (transactionCategory) {
switch (methodName) {
case TOKEN_METHOD_TRANSFER: { case TOKEN_METHOD_TRANSFER: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}` const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`
return <Redirect to={{ pathname }} /> return <Redirect to={{ pathname }} />

View File

@ -4,24 +4,27 @@ import {
TOKEN_METHOD_TRANSFER, TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_APPROVE, TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER_FROM, TOKEN_METHOD_TRANSFER_FROM,
SEND_ETHER_ACTION_KEY,
} from '../../helpers/constants/transactions' } from '../../helpers/constants/transactions'
import { unconfirmedTransactionsListSelector } from '../../selectors/confirm-transaction'
const mapStateToProps = state => { const mapStateToProps = (state, ownProps) => {
const { const { metamask: { unapprovedTxs } } = state
confirmTransaction: { const { match: { params = {}, url } } = ownProps
txData, const urlId = url && url.match(/\d+/) && url.match(/\d+/)[0]
methodData, const { id: paramsId } = params
fetchingData, const transactionId = paramsId || urlId
toSmartContract,
}, const unconfirmedTransactions = unconfirmedTransactionsListSelector(state)
} = state const totalUnconfirmed = unconfirmedTransactions.length
const transaction = totalUnconfirmed
? unapprovedTxs[transactionId] || unconfirmedTransactions[totalUnconfirmed - 1]
: {}
return { return {
txData, txData: transaction,
methodData, isEtherTransaction: transaction && transaction.transactionCategory === SEND_ETHER_ACTION_KEY,
fetchingData, isTokenMethod: [TOKEN_METHOD_APPROVE, TOKEN_METHOD_TRANSFER, TOKEN_METHOD_TRANSFER_FROM].includes(transaction && transaction.transactionCategory && transaction.transactionCategory.toLowerCase()),
isEtherTransaction: !toSmartContract,
isTokenMethod: [TOKEN_METHOD_APPROVE, TOKEN_METHOD_TRANSFER, TOKEN_METHOD_TRANSFER_FROM].includes(methodData.name && methodData.name.toLowerCase()),
} }
} }

View File

@ -33,6 +33,10 @@ export default class ConfirmTransaction extends Component {
confirmTransaction: PropTypes.object, confirmTransaction: PropTypes.object,
clearConfirmTransaction: PropTypes.func, clearConfirmTransaction: PropTypes.func,
fetchBasicGasAndTimeEstimates: PropTypes.func, fetchBasicGasAndTimeEstimates: PropTypes.func,
transaction: PropTypes.object,
getContractMethodData: PropTypes.func,
transactionId: PropTypes.string,
paramsTransactionId: PropTypes.string,
} }
getParamsTransactionId () { getParamsTransactionId () {
@ -45,8 +49,11 @@ export default class ConfirmTransaction extends Component {
totalUnapprovedCount = 0, totalUnapprovedCount = 0,
send = {}, send = {},
history, history,
confirmTransaction: { txData: { id: transactionId } = {} }, transaction: { txParams: { data } = {} } = {},
fetchBasicGasAndTimeEstimates, fetchBasicGasAndTimeEstimates,
getContractMethodData,
transactionId,
paramsTransactionId,
} = this.props } = this.props
if (!totalUnapprovedCount && !send.to) { if (!totalUnapprovedCount && !send.to) {
@ -54,67 +61,44 @@ export default class ConfirmTransaction extends Component {
return return
} }
if (!transactionId) { fetchBasicGasAndTimeEstimates()
fetchBasicGasAndTimeEstimates() getContractMethodData(data)
this.setTransactionToConfirm() this.props.setTransactionToConfirm(transactionId || paramsTransactionId)
}
} }
componentDidUpdate () { componentDidUpdate (prevProps) {
const { const {
setTransactionToConfirm, setTransactionToConfirm,
confirmTransaction: { txData: { id: transactionId } = {} }, transaction: { txData: { txParams: { data } = {} } = {} },
clearConfirmTransaction, clearConfirmTransaction,
getContractMethodData,
paramsTransactionId,
transactionId,
history,
totalUnapprovedCount,
} = this.props } = this.props
const paramsTransactionId = this.getParamsTransactionId()
if (paramsTransactionId && transactionId && paramsTransactionId !== transactionId + '') { if (paramsTransactionId && transactionId && prevProps.paramsTransactionId !== paramsTransactionId) {
clearConfirmTransaction() clearConfirmTransaction()
getContractMethodData(data)
setTransactionToConfirm(paramsTransactionId) setTransactionToConfirm(paramsTransactionId)
return return
} } else if (prevProps.transactionId && !transactionId && !totalUnapprovedCount) {
history.replace(DEFAULT_ROUTE)
if (!transactionId) { return
this.setTransactionToConfirm() } else if (prevProps.transactionId && transactionId && prevProps.transactionId !== transactionId) {
} history.replace(DEFAULT_ROUTE)
} return
setTransactionToConfirm () {
const {
history,
unconfirmedTransactions,
setTransactionToConfirm,
} = this.props
const paramsTransactionId = this.getParamsTransactionId()
if (paramsTransactionId) {
// Check to make sure params ID is valid
const tx = unconfirmedTransactions.find(({ id }) => id + '' === paramsTransactionId)
if (!tx) {
history.replace(DEFAULT_ROUTE)
} else {
setTransactionToConfirm(paramsTransactionId)
}
} else if (unconfirmedTransactions.length) {
const totalUnconfirmed = unconfirmedTransactions.length
const transaction = unconfirmedTransactions[totalUnconfirmed - 1]
const { id: transactionId, loadingDefaults } = transaction
if (!loadingDefaults) {
setTransactionToConfirm(transactionId)
}
} }
} }
render () { render () {
const { confirmTransaction: { txData: { id } } = {} } = this.props const { transactionId, paramsTransactionId } = this.props
const paramsTransactionId = this.getParamsTransactionId()
// Show routes when state.confirmTransaction has been set and when either the ID in the params // Show routes when state.confirmTransaction has been set and when either the ID in the params
// isn't specified or is specified and matches the ID in state.confirmTransaction in order to // isn't specified or is specified and matches the ID in state.confirmTransaction in order to
// support URLs of /confirm-transaction or /confirm-transaction/<transactionId> // support URLs of /confirm-transaction or /confirm-transaction/<transactionId>
return id && (!paramsTransactionId || paramsTransactionId === id + '')
return transactionId && (!paramsTransactionId || paramsTransactionId === transactionId)
? ( ? (
<Switch> <Switch>
<Route <Route

View File

@ -8,26 +8,45 @@ import {
import { import {
fetchBasicGasAndTimeEstimates, fetchBasicGasAndTimeEstimates,
} from '../../ducks/gas/gas.duck' } from '../../ducks/gas/gas.duck'
import {
getContractMethodData,
} from '../../store/actions'
import ConfirmTransaction from './confirm-transaction.component' import ConfirmTransaction from './confirm-transaction.component'
import { getTotalUnapprovedCount } from '../../selectors/selectors'
import { unconfirmedTransactionsListSelector } from '../../selectors/confirm-transaction' import { unconfirmedTransactionsListSelector } from '../../selectors/confirm-transaction'
const mapStateToProps = state => { const mapStateToProps = (state, ownProps) => {
const { metamask: { send }, confirmTransaction } = state const { metamask: { send, unapprovedTxs }, confirmTransaction } = state
const { match: { params = {} } } = ownProps
const { id } = params
const unconfirmedTransactions = unconfirmedTransactionsListSelector(state)
const totalUnconfirmed = unconfirmedTransactions.length
const transaction = totalUnconfirmed
? unapprovedTxs[id] || unconfirmedTransactions[totalUnconfirmed - 1]
: {}
return { return {
totalUnapprovedCount: getTotalUnapprovedCount(state), totalUnapprovedCount: totalUnconfirmed,
send, send,
confirmTransaction, confirmTransaction,
unconfirmedTransactions: unconfirmedTransactionsListSelector(state), unapprovedTxs,
id,
paramsTransactionId: id && String(id),
transactionId: transaction.id && String(transaction.id),
unconfirmedTransactions,
transaction,
} }
} }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)), setTransactionToConfirm: transactionId => {
dispatch(setTransactionToConfirm(transactionId))
},
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
getContractMethodData: (data) => dispatch(getContractMethodData(data)),
} }
} }

View File

@ -119,6 +119,10 @@ function isCustomPriceSafe (state) {
return true return true
} }
if (safeLow === null) {
return null
}
const customPriceSafe = conversionGreaterThan( const customPriceSafe = conversionGreaterThan(
{ {
value: customGasPrice, value: customGasPrice,

View File

@ -1,5 +1,6 @@
import { NETWORK_TYPES } from '../helpers/constants/common' import { NETWORK_TYPES } from '../helpers/constants/common'
import { stripHexPrefix } from 'ethereumjs-util' import { stripHexPrefix, addHexPrefix } from 'ethereumjs-util'
const abi = require('human-standard-token-abi') const abi = require('human-standard-token-abi')
import { import {
@ -50,6 +51,7 @@ const selectors = {
isEthereumNetwork, isEthereumNetwork,
getMetaMetricState, getMetaMetricState,
getRpcPrefsForCurrentProvider, getRpcPrefsForCurrentProvider,
getKnownMethodData,
} }
module.exports = selectors module.exports = selectors
@ -335,3 +337,14 @@ function getRpcPrefsForCurrentProvider (state) {
const { rpcPrefs = {} } = selectRpcInfo || {} const { rpcPrefs = {} } = selectRpcInfo || {}
return rpcPrefs return rpcPrefs
} }
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]
}

View File

@ -8,6 +8,7 @@ const {
} = require('../pages/send/send.utils') } = require('../pages/send/send.utils')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../helpers/utils/i18n-helper') const { fetchLocale } = require('../helpers/utils/i18n-helper')
const { getMethodDataAsync } = require('../helpers/utils/transactions.util')
const log = require('loglevel') const log = require('loglevel')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums') const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums')
const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util') const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util')
@ -360,6 +361,12 @@ var actions = {
// AppStateController-related actions // AppStateController-related actions
SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME', SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME',
setLastActiveTime, setLastActiveTime,
getContractMethodData,
loadingMethoDataStarted,
loadingMethoDataFinished,
LOADING_METHOD_DATA_STARTED: 'LOADING_METHOD_DATA_STARTED',
LOADING_METHOD_DATA_FINISHED: 'LOADING_METHOD_DATA_FINISHED',
} }
module.exports = actions module.exports = actions
@ -2774,3 +2781,38 @@ function setLastActiveTime () {
}) })
} }
} }
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 }
})
}
}