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:
parent
2ff184d77e
commit
748801f417
6
package-lock.json
generated
6
package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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')()
|
||||||
|
@ -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')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
28
ui/app/helpers/utils/fetch-with-cache.js
Normal file
28
ui/app/helpers/utils/fetch-with-cache.js
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
25
ui/app/helpers/utils/fetch.js
Normal file
25
ui/app/helpers/utils/fetch.js
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
54
ui/app/helpers/utils/fetch.test.js
Normal file
54
ui/app/helpers/utils/fetch.test.js
Normal 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')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 }} />
|
||||||
|
@ -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()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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]
|
||||||
|
}
|
||||||
|
@ -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 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user