Merge pull request #9589 from MetaMask/Version-v8.1.1

Version v8.1.1 RC
feature/default_network_editable
Mark Stacey 4 years ago committed by GitHub
commit ffd3de0ec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      CHANGELOG.md
  2. 36
      app/_locales/en/messages.json
  3. 2
      app/manifest/_base.json
  4. 8
      app/scripts/controllers/transactions/index.js
  5. 8
      app/scripts/metamask-controller.js
  6. 6
      package.json
  7. 2
      ui/app/components/app/account-menu/account-menu.component.js
  8. 2
      ui/app/components/app/account-menu/tests/account-menu.test.js
  9. 4
      ui/app/components/app/dropdowns/network-dropdown.js
  10. 4
      ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
  11. 14
      ui/app/components/app/wallet-overview/token-overview.js
  12. 1
      ui/app/components/ui/icon-button/icon-button.js
  13. 2
      ui/app/components/ui/list-item/index.scss
  14. 31
      ui/app/contexts/metametrics.new.js
  15. 5
      ui/app/ducks/swaps/swaps.js
  16. 1
      ui/app/pages/create-account/import-account/private-key.js
  17. 1
      ui/app/pages/create-account/new-account.component.js
  18. 50
      ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
  19. 4
      ui/app/pages/send/send.utils.js
  20. 34
      ui/app/pages/send/tests/send-utils.test.js
  21. 18
      ui/app/pages/swaps/build-quote/build-quote.js
  22. 3
      ui/app/pages/swaps/dropdown-input-pair/dropdown-input-pair.js
  23. 58
      ui/app/pages/swaps/fee-card/fee-card.js
  24. 2
      ui/app/pages/swaps/index.js
  25. 2
      ui/app/pages/swaps/select-quote-popover/select-quote-popover-constants.js
  26. 3
      ui/app/pages/swaps/swaps.util.test.js
  27. 27
      ui/app/pages/swaps/view-quote/view-quote.js
  28. 35
      ui/app/store/actions.js
  29. 159
      yarn.lock

@ -2,6 +2,18 @@
## Current Develop Branch ## Current Develop Branch
## 8.1.1 Tue Oct 13 2020
- [#9586](https://github.com/MetaMask/metamask-extension/pull/9586): Prevent build quote crash when swapping from non-tracked token with balance (#9586)
- [#9592](https://github.com/MetaMask/metamask-extension/pull/9592): Remove commitment to maintain a public metrics dashboard (#9592)
- [#9596](https://github.com/MetaMask/metamask-extension/pull/9596): Fix TypeError when `signTypedData` throws (#9596)
- [#9591](https://github.com/MetaMask/metamask-extension/pull/9591): Fix Firefox overflow on transaction items with long amounts (#9591)
- [#9601](https://github.com/MetaMask/metamask-extension/pull/9601): Update text content of invalid custom network alert (#9601)
- [#9575](https://github.com/MetaMask/metamask-extension/pull/9575): Ensure proper hover display for accounts in main menu (#9575)
- [#9576](https://github.com/MetaMask/metamask-extension/pull/9576): Autofocus the appropriate text fields in the Create/Import/Hardware screen (#9576)
- [#9581](https://github.com/MetaMask/metamask-extension/pull/9581): AutoFocus the from input on swaps screen (#9581)
- [#9602](https://github.com/MetaMask/metamask-extension/pull/9602): Prevent swap button from being focused when disabled (#9602)
- [#9609](https://github.com/MetaMask/metamask-extension/pull/9609): Ensure swaps customize gas modal values are set correctly (#9609)
## 8.1.0 Tue Oct 13 2020 ## 8.1.0 Tue Oct 13 2020
- [#9565](https://github.com/MetaMask/metamask-extension/pull/9565): Ensure address book entries are shared between networks with the same chain ID - [#9565](https://github.com/MetaMask/metamask-extension/pull/9565): Ensure address book entries are shared between networks with the same chain ID
- [#9552](https://github.com/MetaMask/metamask-extension/pull/9552): Fix `eth_signTypedData_v4` chain ID validation for non-default networks - [#9552](https://github.com/MetaMask/metamask-extension/pull/9552): Fix `eth_signTypedData_v4` chain ID validation for non-default networks

@ -842,7 +842,7 @@
"message": "Invalid Block Explorer URL" "message": "Invalid Block Explorer URL"
}, },
"invalidCustomNetworkAlertContent1": { "invalidCustomNetworkAlertContent1": {
"message": "The custom network '$1' is missing its chain ID.", "message": "The chain ID for custom network '$1' has to be re-entered.",
"description": "$1 is the name/identifier of the network." "description": "$1 is the name/identifier of the network."
}, },
"invalidCustomNetworkAlertContent2": { "invalidCustomNetworkAlertContent2": {
@ -967,6 +967,37 @@
"metamaskVersion": { "metamaskVersion": {
"message": "MetaMask Version" "message": "MetaMask Version"
}, },
"metametricsCommitmentsAllowOptOut": {
"message": "Always allow you to opt-out via Settings"
},
"metametricsCommitmentsBoldNever": {
"message": "Never",
"description": "This string is localized separately from some of the commitments so that we can bold it"
},
"metametricsCommitmentsIntro": {
"message": "MetaMask will.."
},
"metametricsCommitmentsNeverCollectIP": {
"message": "$1 collect your full IP address",
"description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'"
},
"metametricsCommitmentsNeverCollectKeysEtc": {
"message": "$1 collect keys, addresses, transactions, balances, hashes, or any personal information",
"description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'"
},
"metametricsCommitmentsNeverSellDataForProfit": {
"message": "$1 sell data for profit. Ever!",
"description": "The $1 is the bolded word 'Never', from 'metametricsCommitmentsBoldNever'"
},
"metametricsCommitmentsSendAnonymizedEvents": {
"message": "Send anonymized click & pageview events"
},
"metametricsHelpImproveMetaMask": {
"message": "Help Us Improve MetaMask"
},
"metametricsOptInDescription": {
"message": "MetaMask would like to gather usage data to better understand how our users interact with the extension. This data will be used to continually improve the usability and user experience of our product and the Ethereum ecosystem."
},
"mobileSyncText": { "mobileSyncText": {
"message": "Please enter your password to confirm it's you!" "message": "Please enter your password to confirm it's you!"
}, },
@ -1753,6 +1784,9 @@
"swapSelectAQuote": { "swapSelectAQuote": {
"message": "Select a quote" "message": "Select a quote"
}, },
"swapSelectAToken": {
"message": "Select a token"
},
"swapSelectQuotePopoverDescription": { "swapSelectQuotePopoverDescription": {
"message": "Below are all the quotes gathered from multiple liquidity sources." "message": "Below are all the quotes gathered from multiple liquidity sources."
}, },

@ -68,6 +68,6 @@
"notifications" "notifications"
], ],
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "8.1.0", "version": "8.1.1",
"web_accessible_resources": ["inpage.js", "phishing.html"] "web_accessible_resources": ["inpage.js", "phishing.html"]
} }

@ -858,6 +858,13 @@ export default class TransactionController extends EventEmitter {
.round(2) .round(2)
}%` }%`
const estimatedVsUsedGasRatio = `${
(new BigNumber(txMeta.txReceipt.gasUsed, 16))
.div(txMeta.swapMetaData.estimated_gas, 16)
.times(100)
.round(2)
}%`
this._trackSegmentEvent({ this._trackSegmentEvent({
event: 'Swap Completed', event: 'Swap Completed',
category: 'swaps', category: 'swaps',
@ -871,6 +878,7 @@ export default class TransactionController extends EventEmitter {
...txMeta.swapMetaData, ...txMeta.swapMetaData,
token_to_amount_received: tokensReceived, token_to_amount_received: tokensReceived,
quote_vs_executionRatio: quoteVsExecutionRatio, quote_vs_executionRatio: quoteVsExecutionRatio,
estimated_vs_used_gasRatio: estimatedVsUsedGasRatio,
}, },
excludeMetaMetricsId: true, excludeMetaMetricsId: true,
}) })

@ -1414,7 +1414,7 @@ export default class MetamaskController extends EventEmitter {
} catch (error) { } catch (error) {
log.info('MetaMaskController - eth_signTypedData failed.', error) log.info('MetaMaskController - eth_signTypedData failed.', error)
this.typedMessageManager.errorMessage(msgId, error) this.typedMessageManager.errorMessage(msgId, error)
return undefined throw error
} }
} }
@ -1864,12 +1864,8 @@ export default class MetamaskController extends EventEmitter {
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
async getNextNonce (address) { async getNextNonce (address) {
let nonceLock const nonceLock = await this.txController.nonceTracker.getNonceLock(address)
try {
nonceLock = await this.txController.nonceTracker.getNonceLock(address)
} finally {
nonceLock.releaseLock() nonceLock.releaseLock()
}
return nonceLock.nextNonce return nonceLock.nextNonce
} }

@ -81,8 +81,8 @@
"@metamask/logo": "^2.5.0", "@metamask/logo": "^2.5.0",
"@popperjs/core": "^2.4.0", "@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.3.2", "@reduxjs/toolkit": "^1.3.2",
"@sentry/browser": "^5.11.1", "@sentry/browser": "^5.26.0",
"@sentry/integrations": "^5.11.1", "@sentry/integrations": "^5.26.0",
"@zxing/library": "^0.8.0", "@zxing/library": "^0.8.0",
"abortcontroller-polyfill": "^1.4.0", "abortcontroller-polyfill": "^1.4.0",
"analytics-node": "^3.4.0-beta.2", "analytics-node": "^3.4.0-beta.2",
@ -187,7 +187,7 @@
"@metamask/eslint-config": "^3.2.0", "@metamask/eslint-config": "^3.2.0",
"@metamask/forwarder": "^1.1.0", "@metamask/forwarder": "^1.1.0",
"@metamask/test-dapp": "^3.1.0", "@metamask/test-dapp": "^3.1.0",
"@sentry/cli": "^1.49.0", "@sentry/cli": "^1.58.0",
"@storybook/addon-actions": "^5.3.14", "@storybook/addon-actions": "^5.3.14",
"@storybook/addon-backgrounds": "^5.3.14", "@storybook/addon-backgrounds": "^5.3.14",
"@storybook/addon-knobs": "^5.3.14", "@storybook/addon-knobs": "^5.3.14",

@ -179,7 +179,7 @@ export default class AccountMenu extends Component {
return ( return (
<div <div
className="account-menu__account menu__item--clickable" className="account-menu__account account-menu__item--clickable"
onClick={() => { onClick={() => {
this.context.metricsEvent({ this.context.metricsEvent({
eventOpts: { eventOpts: {

@ -88,7 +88,7 @@ describe('Account Menu', function () {
}) })
it('simulate click', function () { it('simulate click', function () {
const click = wrapper.find('.account-menu__account.menu__item--clickable') const click = wrapper.find('.account-menu__account.account-menu__item--clickable')
click.first().simulate('click') click.first().simulate('click')
assert(props.showAccountDetail.calledOnce) assert(props.showAccountDetail.calledOnce)

@ -34,8 +34,8 @@ function mapDispatchToProps (dispatch) {
setProviderType: (type) => { setProviderType: (type) => {
dispatch(actions.setProviderType(type)) dispatch(actions.setProviderType(type))
}, },
setRpcTarget: (target, network, ticker, nickname) => { setRpcTarget: (target, chainId, ticker, nickname) => {
dispatch(actions.setRpcTarget(target, network, ticker, nickname)) dispatch(actions.setRpcTarget(target, chainId, ticker, nickname))
}, },
delRpcTarget: (target) => { delRpcTarget: (target) => {
dispatch(actions.delRpcTarget(target)) dispatch(actions.delRpcTarget(target))

@ -51,9 +51,9 @@ export default class GasModalPageContainer extends Component {
isEthereumNetwork: PropTypes.bool, isEthereumNetwork: PropTypes.bool,
customGasLimitMessage: PropTypes.string, customGasLimitMessage: PropTypes.string,
customTotalSupplement: PropTypes.string, customTotalSupplement: PropTypes.string,
isSwap: PropTypes.boolean, isSwap: PropTypes.bool,
value: PropTypes.string, value: PropTypes.string,
conversionRate: PropTypes.string, conversionRate: PropTypes.number,
} }
state = { state = {

@ -38,8 +38,9 @@ const TokenOverview = ({ className, token }) => {
const keyring = useSelector(getCurrentKeyring) const keyring = useSelector(getCurrentKeyring)
const usingHardwareWallet = keyring.type.search('Hardware') !== -1 const usingHardwareWallet = keyring.type.search('Hardware') !== -1
const { tokensWithBalances } = useTokenTracker([token]) const { tokensWithBalances } = useTokenTracker([token])
const balance = tokensWithBalances[0]?.string const balanceToRender = tokensWithBalances[0]?.string
const formattedFiatBalance = useTokenFiatAmount(token.address, balance, token.symbol) const balance = tokensWithBalances[0]?.balance
const formattedFiatBalance = useTokenFiatAmount(token.address, balanceToRender, token.symbol)
const networkId = useSelector(getCurrentNetworkId) const networkId = useSelector(getCurrentNetworkId)
const enteredSwapsEvent = useNewMetricEvent({ event: 'Swaps Opened', properties: { source: 'Token View', active_currency: token.symbol }, category: 'swaps' }) const enteredSwapsEvent = useNewMetricEvent({ event: 'Swaps Opened', properties: { source: 'Token View', active_currency: token.symbol }, category: 'swaps' })
const swapsEnabled = useSelector(getSwapsFeatureLiveness) const swapsEnabled = useSelector(getSwapsFeatureLiveness)
@ -50,7 +51,7 @@ const TokenOverview = ({ className, token }) => {
<div className="token-overview__balance"> <div className="token-overview__balance">
<CurrencyDisplay <CurrencyDisplay
className="token-overview__primary-balance" className="token-overview__primary-balance"
displayValue={balance} displayValue={balanceToRender}
suffix={token.symbol} suffix={token.symbol}
/> />
{ {
@ -87,7 +88,12 @@ const TokenOverview = ({ className, token }) => {
onClick={() => { onClick={() => {
if (networkId === MAINNET_NETWORK_ID) { if (networkId === MAINNET_NETWORK_ID) {
enteredSwapsEvent() enteredSwapsEvent()
dispatch(setSwapsFromToken({ ...token, iconUrl: assetImages[token.address] })) dispatch(setSwapsFromToken({
...token,
iconUrl: assetImages[token.address],
balance,
string: balanceToRender,
}))
if (usingHardwareWallet) { if (usingHardwareWallet) {
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE) global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE)
} else { } else {

@ -11,6 +11,7 @@ export default function IconButton ({ onClick, Icon, disabled, label, tooltipRen
className={classNames('icon-button', className, { 'icon-button--disabled': disabled })} className={classNames('icon-button', className, { 'icon-button--disabled': disabled })}
data-testid={props['data-testid'] ?? undefined} data-testid={props['data-testid'] ?? undefined}
onClick={onClick} onClick={onClick}
disabled={disabled}
> >
{renderWrapper( {renderWrapper(
<> <>

@ -85,6 +85,8 @@
grid-area: right; grid-area: right;
text-align: right; text-align: right;
align-items: flex-end; align-items: flex-end;
overflow: hidden;
white-space: nowrap;
} }
@media (max-width: 575px) { @media (max-width: 575px) {

@ -92,16 +92,26 @@ export function MetaMetricsProvider ({ children }) {
const idTrait = metaMetricsId ? 'userId' : 'anonymousId' const idTrait = metaMetricsId ? 'userId' : 'anonymousId'
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID
const match = matchPath(location.pathname, { path: PATHS_TO_CHECK, exact: true, strict: true }) const match = matchPath(location.pathname, { path: PATHS_TO_CHECK, exact: true, strict: true })
if ( // Start by checking for a missing match route. If this falls through to the else if, then we know we
match && // have a matched route for tracking.
if (!match) {
// We have more specific pages for each type of transaction confirmation
// The user lands on /confirm-transaction first, then is redirected based on
// the contents of state.
if (location.pathname !== '/confirm-transaction') {
// Otherwise we are legitimately missing a matching route
captureMessage(`Segment page tracking found unmatched route`, {
previousMatch,
currentPath: location.pathname,
})
}
} else if (
previousMatch.current !== match.path && previousMatch.current !== match.path &&
// If we're in a popup or notification we don't want the initial home route to track !(environmentType === 'notification' && match.path === '/' && previousMatch.current === undefined)
!(
(environmentType === 'popup' || environmentType === 'notification') &&
match.path === '/' &&
previousMatch.current === undefined
)
) { ) {
// When a notification window is open by a Dapp we do not want to track the initial home route load that can
// sometimes happen. To handle this we keep track of the previousMatch, and we skip the event track in the event
// that we are dealing with the initial load of the homepage
const { path, params } = match const { path, params } = match
const name = PATH_NAME_MAP[path] const name = PATH_NAME_MAP[path]
segment.page({ segment.page({
@ -116,11 +126,6 @@ export function MetaMetricsProvider ({ children }) {
}, },
context, context,
}) })
} else if (location.pathname !== '/confirm-transaction') {
// We have more specific pages for each type of transaction confirmation
// The user lands on /confirm-transaction first, then is redirected based on
// the contents of state.
captureMessage(`${location.pathname} would have issued a page track event to segment, but no route match was found`)
} }
previousMatch.current = match?.path previousMatch.current = match?.path
} }

@ -38,7 +38,7 @@ import {
SWAP_FAILED_ERROR, SWAP_FAILED_ERROR,
} from '../../helpers/constants/swaps' } from '../../helpers/constants/swaps'
import { SWAP, SWAP_APPROVAL } from '../../helpers/constants/transactions' import { SWAP, SWAP_APPROVAL } from '../../helpers/constants/transactions'
import { fetchBasicGasAndTimeEstimates, fetchGasEstimates, resetCustomData } from '../gas/gas.duck' import { fetchBasicGasAndTimeEstimates, fetchGasEstimates, resetCustomGasState } from '../gas/gas.duck'
import { formatCurrency } from '../../helpers/utils/confirm-tx.util' import { formatCurrency } from '../../helpers/utils/confirm-tx.util'
const initialState = { const initialState = {
@ -235,7 +235,7 @@ export const prepareForRetryGetQuotes = () => {
export const prepareToLeaveSwaps = () => { export const prepareToLeaveSwaps = () => {
return async (dispatch) => { return async (dispatch) => {
dispatch(resetCustomData()) dispatch(resetCustomGasState())
dispatch(clearSwapsState()) dispatch(clearSwapsState())
await dispatch(resetBackgroundSwapsState()) await dispatch(resetBackgroundSwapsState())
@ -474,6 +474,7 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
other_quote_selected: usedQuote.aggregator !== getTopQuote(state)?.aggregator, other_quote_selected: usedQuote.aggregator !== getTopQuote(state)?.aggregator,
other_quote_selected_source: usedQuote.aggregator === getTopQuote(state)?.aggregator ? '' : usedQuote.aggregator, other_quote_selected_source: usedQuote.aggregator === getTopQuote(state)?.aggregator ? '' : usedQuote.aggregator,
gas_fees: formatCurrency(gasEstimateTotalInEth, 'usd')?.slice(1), gas_fees: formatCurrency(gasEstimateTotalInEth, 'usd')?.slice(1),
estimated_gas: estimatedGasLimit.toString(16),
} }
const metaMetricsConfig = { const metaMetricsConfig = {

@ -91,6 +91,7 @@ class PrivateKeyImportView extends Component {
onKeyPress={(e) => this.createKeyringOnEnter(e)} onKeyPress={(e) => this.createKeyringOnEnter(e)}
onChange={() => this.checkInputEmpty()} onChange={() => this.checkInputEmpty()}
ref={this.inputRef} ref={this.inputRef}
autoFocus
/> />
</div> </div>
<div className="new-account-import-form__buttons"> <div className="new-account-import-form__buttons">

@ -54,6 +54,7 @@ export default class NewAccountCreateForm extends Component {
value={newAccountName} value={newAccountName}
placeholder={defaultAccountName} placeholder={defaultAccountName}
onChange={(event) => this.setState({ newAccountName: event.target.value })} onChange={(event) => this.setState({ newAccountName: event.target.value })}
autoFocus
/> />
<div className="new-account-create-form__buttons"> <div className="new-account-create-form__buttons">
<Button <Button

@ -34,51 +34,71 @@ export default class MetaMetricsOptIn extends Component {
<div className="metametrics-opt-in__body-graphic"> <div className="metametrics-opt-in__body-graphic">
<img src="images/metrics-chart.svg" /> <img src="images/metrics-chart.svg" />
</div> </div>
<div className="metametrics-opt-in__title">Help Us Improve MetaMask</div> <div className="metametrics-opt-in__title">{t('metametricsHelpImproveMetaMask')}</div>
<div className="metametrics-opt-in__body"> <div className="metametrics-opt-in__body">
<div className="metametrics-opt-in__description"> <div className="metametrics-opt-in__description">
MetaMask would like to gather usage data to better understand how our users interact with the extension. This data {t('metametricsOptInDescription')}
will be used to continually improve the usability and user experience of our product and the Ethereum ecosystem.
</div> </div>
<div className="metametrics-opt-in__description"> <div className="metametrics-opt-in__description">
MetaMask will.. {t('metametricsCommitmentsIntro')}
</div> </div>
<div className="metametrics-opt-in__committments"> <div className="metametrics-opt-in__committments">
<div className="metametrics-opt-in__row"> <div className="metametrics-opt-in__row">
<i className="fa fa-check" /> <i className="fa fa-check" />
<div className="metametrics-opt-in__row-description"> <div className="metametrics-opt-in__row-description">
Always allow you to opt-out via Settings {t('metametricsCommitmentsAllowOptOut')}
</div> </div>
</div> </div>
<div className="metametrics-opt-in__row"> <div className="metametrics-opt-in__row">
<i className="fa fa-check" /> <i className="fa fa-check" />
<div className="metametrics-opt-in__row-description"> <div className="metametrics-opt-in__row-description">
Send anonymized click & pageview events {t('metametricsCommitmentsSendAnonymizedEvents')}
</div>
</div>
<div className="metametrics-opt-in__row">
<i className="fa fa-check" />
<div className="metametrics-opt-in__row-description">
Maintain a public aggregate dashboard to educate the community
</div> </div>
</div> </div>
<div className="metametrics-opt-in__row metametrics-opt-in__break-row"> <div className="metametrics-opt-in__row metametrics-opt-in__break-row">
<i className="fa fa-times" /> <i className="fa fa-times" />
<div className="metametrics-opt-in__row-description"> <div className="metametrics-opt-in__row-description">
<span className="metametrics-opt-in__bold">Never</span> collect keys, addresses, transactions, balances, hashes, or any personal information {
t(
'metametricsCommitmentsNeverCollectKeysEtc',
[(
<span className="metametrics-opt-in__bold" key="neverCollectKeys">
{t('metametricsCommitmentsBoldNever')}
</span>
)],
)
}
</div> </div>
</div> </div>
<div className="metametrics-opt-in__row"> <div className="metametrics-opt-in__row">
<i className="fa fa-times" /> <i className="fa fa-times" />
<div className="metametrics-opt-in__row-description"> <div className="metametrics-opt-in__row-description">
<span className="metametrics-opt-in__bold">Never</span> collect your full IP address {
t(
'metametricsCommitmentsNeverCollectIP',
[(
<span className="metametrics-opt-in__bold" key="neverCollectKeys">
{t('metametricsCommitmentsBoldNever')}
</span>
)],
)
}
</div> </div>
</div> </div>
<div className="metametrics-opt-in__row"> <div className="metametrics-opt-in__row">
<i className="fa fa-times" /> <i className="fa fa-times" />
<div className="metametrics-opt-in__row-description"> <div className="metametrics-opt-in__row-description">
<span className="metametrics-opt-in__bold">Never</span> sell data for profit. Ever! {
t(
'metametricsCommitmentsNeverSellDataForProfit',
[(
<span className="metametrics-opt-in__bold" key="neverCollectKeys">
{t('metametricsCommitmentsBoldNever')}
</span>
)],
)
}
</div> </div>
</div> </div>
</div> </div>

@ -26,7 +26,7 @@ export {
calcGasTotal, calcGasTotal,
calcTokenBalance, calcTokenBalance,
doesAmountErrorRequireUpdate, doesAmountErrorRequireUpdate,
estimateGas, estimateGasForSend,
generateTokenTransferData, generateTokenTransferData,
getAmountErrorObject, getAmountErrorObject,
getGasFeeErrorObject, getGasFeeErrorObject,
@ -193,7 +193,7 @@ function doesAmountErrorRequireUpdate ({
return amountErrorRequiresUpdate return amountErrorRequiresUpdate
} }
async function estimateGas ({ async function estimateGasForSend ({
selectedAddress, selectedAddress,
sendToken, sendToken,
blockGasLimit = MIN_GAS_LIMIT_HEX, blockGasLimit = MIN_GAS_LIMIT_HEX,

@ -45,7 +45,7 @@ const sendUtils = proxyquire('../send.utils.js', {
const { const {
calcGasTotal, calcGasTotal,
estimateGas, estimateGasForSend,
doesAmountErrorRequireUpdate, doesAmountErrorRequireUpdate,
generateTokenTransferData, generateTokenTransferData,
getAmountErrorObject, getAmountErrorObject,
@ -288,7 +288,7 @@ describe('send utils', function () {
}) })
}) })
describe('estimateGas', function () { describe('estimateGasForSend', function () {
const baseMockParams = { const baseMockParams = {
blockGasLimit: '0x64', blockGasLimit: '0x64',
selectedAddress: 'mockAddress', selectedAddress: 'mockAddress',
@ -322,8 +322,8 @@ describe('send utils', function () {
global.eth.getCode.resetHistory() global.eth.getCode.resetHistory()
}) })
it('should call ethQuery.estimateGas with the expected params', async function () { it('should call ethQuery.estimateGasForSend with the expected params', async function () {
const result = await sendUtils.estimateGas(baseMockParams) const result = await estimateGasForSend(baseMockParams)
assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual( assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0], baseMockParams.estimateGasMethod.getCall(0).args[0],
@ -332,8 +332,8 @@ describe('send utils', function () {
assert.equal(result, '0xabc16') assert.equal(result, '0xabc16')
}) })
it('should call ethQuery.estimateGas with the expected params when initialGasLimitHex is lower than the upperGasLimit', async function () { it('should call ethQuery.estimateGasForSend with the expected params when initialGasLimitHex is lower than the upperGasLimit', async function () {
const result = await estimateGas({ ...baseMockParams, blockGasLimit: '0xbcd' }) const result = await estimateGasForSend({ ...baseMockParams, blockGasLimit: '0xbcd' })
assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual( assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0], baseMockParams.estimateGasMethod.getCall(0).args[0],
@ -342,8 +342,8 @@ describe('send utils', function () {
assert.equal(result, '0xabc16x1.5') assert.equal(result, '0xabc16x1.5')
}) })
it('should call ethQuery.estimateGas with a value of 0x0 and the expected data and to if passed a sendToken', async function () { it('should call ethQuery.estimateGasForSend with a value of 0x0 and the expected data and to if passed a sendToken', async function () {
const result = await estimateGas({ data: 'mockData', sendToken: { address: 'mockAddress' }, ...baseMockParams }) const result = await estimateGasForSend({ data: 'mockData', sendToken: { address: 'mockAddress' }, ...baseMockParams })
assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual( assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0], baseMockParams.estimateGasMethod.getCall(0).args[0],
@ -357,10 +357,10 @@ describe('send utils', function () {
assert.equal(result, '0xabc16') assert.equal(result, '0xabc16')
}) })
it('should call ethQuery.estimateGas without a recipient if the recipient is empty and data passed', async function () { it('should call ethQuery.estimateGasForSend without a recipient if the recipient is empty and data passed', async function () {
const data = 'mockData' const data = 'mockData'
const to = '' const to = ''
const result = await estimateGas({ ...baseMockParams, data, to }) const result = await estimateGasForSend({ ...baseMockParams, data, to })
assert.equal(baseMockParams.estimateGasMethod.callCount, 1) assert.equal(baseMockParams.estimateGasMethod.callCount, 1)
assert.deepEqual( assert.deepEqual(
baseMockParams.estimateGasMethod.getCall(0).args[0], baseMockParams.estimateGasMethod.getCall(0).args[0],
@ -371,40 +371,40 @@ describe('send utils', function () {
it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async function () { it(`should return ${SIMPLE_GAS_COST} if ethQuery.getCode does not return '0x'`, async function () {
assert.equal(baseMockParams.estimateGasMethod.callCount, 0) assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
const result = await estimateGas({ ...baseMockParams, to: '0x123' }) const result = await estimateGasForSend({ ...baseMockParams, to: '0x123' })
assert.equal(result, SIMPLE_GAS_COST) assert.equal(result, SIMPLE_GAS_COST)
}) })
it(`should return ${SIMPLE_GAS_COST} if not passed a sendToken or truthy to address`, async function () { it(`should return ${SIMPLE_GAS_COST} if not passed a sendToken or truthy to address`, async function () {
assert.equal(baseMockParams.estimateGasMethod.callCount, 0) assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
const result = await estimateGas({ ...baseMockParams, to: null }) const result = await estimateGasForSend({ ...baseMockParams, to: null })
assert.equal(result, SIMPLE_GAS_COST) assert.equal(result, SIMPLE_GAS_COST)
}) })
it(`should not return ${SIMPLE_GAS_COST} if passed a sendToken`, async function () { it(`should not return ${SIMPLE_GAS_COST} if passed a sendToken`, async function () {
assert.equal(baseMockParams.estimateGasMethod.callCount, 0) assert.equal(baseMockParams.estimateGasMethod.callCount, 0)
const result = await estimateGas({ ...baseMockParams, to: '0x123', sendToken: { address: '0x0' } }) const result = await estimateGasForSend({ ...baseMockParams, to: '0x123', sendToken: { address: '0x0' } })
assert.notEqual(result, SIMPLE_GAS_COST) assert.notEqual(result, SIMPLE_GAS_COST)
}) })
it(`should return ${BASE_TOKEN_GAS_COST} if passed a sendToken but no to address`, async function () { it(`should return ${BASE_TOKEN_GAS_COST} if passed a sendToken but no to address`, async function () {
const result = await estimateGas({ ...baseMockParams, to: null, sendToken: { address: '0x0' } }) const result = await estimateGasForSend({ ...baseMockParams, to: null, sendToken: { address: '0x0' } })
assert.equal(result, BASE_TOKEN_GAS_COST) assert.equal(result, BASE_TOKEN_GAS_COST)
}) })
it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async function () { it(`should return the adjusted blockGasLimit if it fails with a 'Transaction execution error.'`, async function () {
const result = await estimateGas({ ...baseMockParams, to: 'isContract willFailBecauseOf:Transaction execution error.' }) const result = await estimateGasForSend({ ...baseMockParams, to: 'isContract willFailBecauseOf:Transaction execution error.' })
assert.equal(result, '0x64x0.95') assert.equal(result, '0x64x0.95')
}) })
it(`should return the adjusted blockGasLimit if it fails with a 'gas required exceeds allowance or always failing transaction.'`, async function () { it(`should return the adjusted blockGasLimit if it fails with a 'gas required exceeds allowance or always failing transaction.'`, async function () {
const result = await estimateGas({ ...baseMockParams, to: 'isContract willFailBecauseOf:gas required exceeds allowance or always failing transaction.' }) const result = await estimateGasForSend({ ...baseMockParams, to: 'isContract willFailBecauseOf:gas required exceeds allowance or always failing transaction.' })
assert.equal(result, '0x64x0.95') assert.equal(result, '0x64x0.95')
}) })
it(`should reject other errors`, async function () { it(`should reject other errors`, async function () {
try { try {
await estimateGas({ ...baseMockParams, to: 'isContract willFailBecauseOf:some other error' }) await estimateGasForSend({ ...baseMockParams, to: 'isContract willFailBecauseOf:some other error' })
} catch (err) { } catch (err) {
assert.equal(err.message, 'some other error') assert.equal(err.message, 'some other error')
} }

@ -24,7 +24,7 @@ import {
getTopAssets, getTopAssets,
getFetchParams, getFetchParams,
} from '../../../ducks/swaps/swaps' } from '../../../ducks/swaps/swaps'
import { getValueFromWeiHex } from '../../../helpers/utils/conversions.util' import { getValueFromWeiHex, hexToDecimal } from '../../../helpers/utils/conversions.util'
import { calcTokenAmount } from '../../../helpers/utils/token-util' import { calcTokenAmount } from '../../../helpers/utils/token-util'
import { usePrevious } from '../../../hooks/usePrevious' import { usePrevious } from '../../../hooks/usePrevious'
import { useTokenTracker } from '../../../hooks/useTokenTracker' import { useTokenTracker } from '../../../hooks/useTokenTracker'
@ -132,9 +132,8 @@ export default function BuildQuote ({
.then((fetchedBalance) => { .then((fetchedBalance) => {
if (fetchedBalance?.balance) { if (fetchedBalance?.balance) {
const balanceAsDecString = fetchedBalance.balance.toString(10) const balanceAsDecString = fetchedBalance.balance.toString(10)
const balanceAsHexString = fetchedBalance.balance.toString(16)
const userTokenBalance = calcTokenAmount(balanceAsDecString, token.decimals) const userTokenBalance = calcTokenAmount(balanceAsDecString, token.decimals)
dispatch(setSwapsFromToken({ ...token, string: userTokenBalance.toString(10), balance: balanceAsHexString })) dispatch(setSwapsFromToken({ ...token, string: userTokenBalance.toString(10), balance: balanceAsDecString }))
} }
}) })
} }
@ -167,8 +166,12 @@ export default function BuildQuote ({
// If the eth balance changes while on build quote, we update the selected from token // If the eth balance changes while on build quote, we update the selected from token
useEffect(() => { useEffect(() => {
if (fromToken?.address === ETH_SWAPS_TOKEN_OBJECT.address && (fromToken?.balance !== ethBalance)) { if (fromToken?.address === ETH_SWAPS_TOKEN_OBJECT.address && (fromToken?.balance !== hexToDecimal(ethBalance))) {
dispatch(setSwapsFromToken({ ...fromToken, balance: ethBalance, string: getValueFromWeiHex({ value: ethBalance, numberOfDecimals: 4, toDenomination: 'ETH' }) })) dispatch(setSwapsFromToken({
...fromToken,
balance: hexToDecimal(ethBalance),
string: getValueFromWeiHex({ value: ethBalance, numberOfDecimals: 4, toDenomination: 'ETH' }),
}))
} }
}, [dispatch, fromToken, ethBalance]) }, [dispatch, fromToken, ethBalance])
@ -206,9 +209,10 @@ export default function BuildQuote ({
selectedItem={selectedFromToken} selectedItem={selectedFromToken}
maxListItems={30} maxListItems={30}
loading={loading && (!tokensToSearch?.length || !topAssets || !Object.keys(topAssets).length)} loading={loading && (!tokensToSearch?.length || !topAssets || !Object.keys(topAssets).length)}
selectPlaceHolderText="Select" selectPlaceHolderText={t('swapSelect')}
hideItemIf={(item) => item.address === selectedToToken?.address} hideItemIf={(item) => item.address === selectedToToken?.address}
listContainerClassName="build-quote__open-dropdown" listContainerClassName="build-quote__open-dropdown"
autoFocus
/> />
<div <div
className={classnames('build-quote__balance-message', { className={classnames('build-quote__balance-message', {
@ -242,7 +246,7 @@ export default function BuildQuote ({
itemsToSearch={tokensToSearch} itemsToSearch={tokensToSearch}
searchPlaceholderText={t('swapSearchForAToken')} searchPlaceholderText={t('swapSearchForAToken')}
fuseSearchKeys={fuseSearchKeys} fuseSearchKeys={fuseSearchKeys}
selectPlaceHolderText="Select a token" selectPlaceHolderText={t('swapSelectAToken')}
maxListItems={30} maxListItems={30}
onSelect={onToSelect} onSelect={onToSelect}
loading={loading && (!tokensToSearch?.length || !topAssets || !Object.keys(topAssets).length)} loading={loading && (!tokensToSearch?.length || !topAssets || !Object.keys(topAssets).length)}

@ -36,6 +36,7 @@ export default function DropdownInputPair ({
loading, loading,
hideItemIf, hideItemIf,
listContainerClassName, listContainerClassName,
autoFocus,
}) { }) {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const open = () => setIsOpen(true) const open = () => setIsOpen(true)
@ -92,6 +93,7 @@ export default function DropdownInputPair ({
fullWidth fullWidth
margin="dense" margin="dense"
value={inputValue} value={inputValue}
autoFocus={autoFocus}
/> />
)} )}
{ {
@ -123,4 +125,5 @@ DropdownInputPair.propTypes = {
loading: PropTypes.bool, loading: PropTypes.bool,
hideItemIf: PropTypes.func, hideItemIf: PropTypes.func,
listContainerClassName: PropTypes.string, listContainerClassName: PropTypes.string,
autoFocus: PropTypes.bool,
} }

@ -1,21 +1,26 @@
import React from 'react' import React, { useContext } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { I18nContext } from '../../../contexts/i18n'
import InfoTooltip from '../../../components/ui/info-tooltip' import InfoTooltip from '../../../components/ui/info-tooltip'
export default function FeeCard ({ export default function FeeCard ({
feeRowText,
primaryFee, primaryFee,
secondaryFee, secondaryFee,
thirdRow, hideTokenApprovalRow,
maxFeeRow, onFeeCardMaxRowClick,
tokenApprovalTextComponent,
tokenApprovalSourceTokenSymbol,
onTokenApprovalClick,
}) { }) {
const t = useContext(I18nContext)
return ( return (
<div className="fee-card"> <div className="fee-card">
<div className="fee-card__main"> <div className="fee-card__main">
<div className="fee-card__row-header"> <div className="fee-card__row-header">
<div> <div>
<div className="fee-card__row-header-text--bold"> <div className="fee-card__row-header-text--bold">
{feeRowText} {t('swapEstimatedNetworkFee')}
</div> </div>
</div> </div>
<div> <div>
@ -29,18 +34,18 @@ export default function FeeCard ({
)} )}
</div> </div>
</div> </div>
<div className="fee-card__row-header" onClick={() => maxFeeRow.onClick()}> <div className="fee-card__row-header" onClick={() => onFeeCardMaxRowClick()}>
<div> <div>
<div className="fee-card__row-header-text"> <div className="fee-card__row-header-text">
{maxFeeRow.text} {t('swapMaxNetworkFees')}
</div> </div>
<div className="fee-card__link"> <div className="fee-card__link">
{maxFeeRow.linkText} {t('edit')}
</div> </div>
<div className="fee-card__row-label"> <div className="fee-card__row-label">
<InfoTooltip <InfoTooltip
position="top" position="top"
contentText={maxFeeRow.tooltipText} contentText={t('swapMaxNetworkFeeInfo')}
/> />
</div> </div>
</div> </div>
@ -55,18 +60,18 @@ export default function FeeCard ({
)} )}
</div> </div>
</div> </div>
{thirdRow && !thirdRow.hide && ( {!hideTokenApprovalRow && (
<div className="fee-card__top-bordered-row"> <div className="fee-card__top-bordered-row">
<div className="fee-card__row-label"> <div className="fee-card__row-label">
<div className="fee-card__row-header-text"> <div className="fee-card__row-header-text">
{thirdRow.text} {t('swapThisWillAllowApprove', [tokenApprovalTextComponent])}
</div> </div>
<div className="fee-card__link" onClick={() => thirdRow.onClick()}> <div className="fee-card__link" onClick={() => onTokenApprovalClick()}>
{thirdRow.linkText} {t('swapEditLimit')}
</div> </div>
<InfoTooltip <InfoTooltip
position="top" position="top"
contentText={thirdRow.tooltipText} contentText={t('swapEnableDescription', [tokenApprovalSourceTokenSymbol])}
/> />
</div> </div>
</div> </div>
@ -77,7 +82,6 @@ export default function FeeCard ({
} }
FeeCard.propTypes = { FeeCard.propTypes = {
feeRowText: PropTypes.string.isRequired,
primaryFee: PropTypes.shape({ primaryFee: PropTypes.shape({
fee: PropTypes.string.isRequired, fee: PropTypes.string.isRequired,
maxFee: PropTypes.string.isRequired, maxFee: PropTypes.string.isRequired,
@ -86,23 +90,9 @@ FeeCard.propTypes = {
fee: PropTypes.string.isRequired, fee: PropTypes.string.isRequired,
maxFee: PropTypes.string.isRequired, maxFee: PropTypes.string.isRequired,
}), }),
maxFeeRow: PropTypes.shape({ onFeeCardMaxRowClick: PropTypes.func.isRequired,
text: PropTypes.string.isRequired, hideTokenApprovalRow: PropTypes.bool.isRequired,
linkText: PropTypes.string.isRequired, tokenApprovalTextComponent: PropTypes.node,
tooltipText: PropTypes.string.isRequired, tokenApprovalSourceTokenSymbol: PropTypes.string,
onClick: PropTypes.func.isRequired, onTokenApprovalClick: PropTypes.func,
}).isRequired,
thirdRow: PropTypes.shape({
text: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node,
]).isRequired,
linkText: PropTypes.string.isRequired,
tooltipText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node,
]).isRequired,
onClick: PropTypes.func.isRequired,
hide: PropTypes.bool.isRequired,
}),
} }

@ -25,6 +25,7 @@ import {
prepareToLeaveSwaps, prepareToLeaveSwaps,
fetchAndSetSwapsGasPriceInfo, fetchAndSetSwapsGasPriceInfo,
} from '../../ducks/swaps/swaps' } from '../../ducks/swaps/swaps'
import { resetCustomGasState } from '../../ducks/gas/gas.duck'
import { import {
AWAITING_SWAP_ROUTE, AWAITING_SWAP_ROUTE,
BUILD_QUOTE_ROUTE, BUILD_QUOTE_ROUTE,
@ -159,6 +160,7 @@ export default function Swap () {
dispatch(setMetamaskFeeAmount(metaMaskFeeAmount)) dispatch(setMetamaskFeeAmount(metaMaskFeeAmount))
}) })
dispatch(resetCustomGasState())
dispatch(fetchAndSetSwapsGasPriceInfo()) dispatch(fetchAndSetSwapsGasPriceInfo())
return () => { return () => {

@ -6,7 +6,7 @@ export const QUOTE_DATA_ROWS_PROPTYPES_SHAPE = PropTypes.shape({
destinationTokenDecimals: PropTypes.number.isRequired, destinationTokenDecimals: PropTypes.number.isRequired,
destinationTokenSymbol: PropTypes.string.isRequired, destinationTokenSymbol: PropTypes.string.isRequired,
destinationTokenValue: PropTypes.string.isRequired, destinationTokenValue: PropTypes.string.isRequired,
isBestQuote: PropTypes.bool.isRequired, isBestQuote: PropTypes.bool,
liquiditySource: PropTypes.string.isRequired, liquiditySource: PropTypes.string.isRequired,
metaMaskFee: PropTypes.string.isRequired, metaMaskFee: PropTypes.string.isRequired,
networkFees: PropTypes.string.isRequired, networkFees: PropTypes.string.isRequired,

@ -12,9 +12,6 @@ import {
} from './swaps-util-test-constants' } from './swaps-util-test-constants'
const swapsUtils = proxyquire('./swaps.util.js', { const swapsUtils = proxyquire('./swaps.util.js', {
'../../store/actions': {
estimateGasFromTxParams: () => Promise.resolve('0x8888'),
},
'../../helpers/utils/fetch-with-cache': { '../../helpers/utils/fetch-with-cache': {
default: (url, fetchObject) => { default: (url, fetchObject) => {
assert.equal(fetchObject.method, 'GET') assert.equal(fetchObject.method, 'GET')

@ -340,7 +340,7 @@ export default function ViewQuote () {
} }
}, [sourceTokenSymbol, sourceTokenValue, destinationTokenSymbol, destinationTokenValue, fetchParams, topQuote, numberOfQuotes, feeInFiat, bestQuoteReviewedEvent, anonymousBestQuoteReviewedEvent]) }, [sourceTokenSymbol, sourceTokenValue, destinationTokenSymbol, destinationTokenValue, fetchParams, topQuote, numberOfQuotes, feeInFiat, bestQuoteReviewedEvent, anonymousBestQuoteReviewedEvent])
const onFeeCardThirdRowClickHandler = () => { const onFeeCardTokenApprovalClick = () => {
anonymousEditSpendLimitOpened() anonymousEditSpendLimitOpened()
editSpendLimitOpened() editSpendLimitOpened()
dispatch(showModal({ dispatch(showModal({
@ -375,7 +375,7 @@ export default function ViewQuote () {
})) }))
} }
const onFeeCardMaxRowClickHandler = () => dispatch(showModal({ const onFeeCardMaxRowClick = () => dispatch(showModal({
name: 'CUSTOMIZE_GAS', name: 'CUSTOMIZE_GAS',
txData: { txParams: { ...tradeTxParams, gas: maxGasLimit } }, txData: { txParams: { ...tradeTxParams, gas: maxGasLimit } },
isSwap: true, isSwap: true,
@ -396,7 +396,7 @@ export default function ViewQuote () {
useFastestButtons: true, useFastestButtons: true,
})) }))
const thirdRowTextComponent = ( const tokenApprovalTextComponent = (
<span <span
key="swaps-view-quote-approve-symbol-1" key="swaps-view-quote-approve-symbol-1"
className="view-quote__bold" className="view-quote__bold"
@ -501,7 +501,6 @@ export default function ViewQuote () {
})} })}
> >
<FeeCard <FeeCard
feeRowText={t('swapEstimatedNetworkFee')}
primaryFee={({ primaryFee={({
fee: feeInEth, fee: feeInEth,
maxFee: maxFeeInEth, maxFee: maxFeeInEth,
@ -510,19 +509,13 @@ export default function ViewQuote () {
fee: feeInFiat, fee: feeInFiat,
maxFee: maxFeeInFiat, maxFee: maxFeeInFiat,
})} })}
maxFeeRow={({ onFeeCardMaxRowClick={onFeeCardMaxRowClick}
text: t('swapMaxNetworkFees'), hideTokenApprovalRow={
linkText: t('edit'), !approveTxParams || (balanceError && !warningHidden)
tooltipText: t('swapMaxNetworkFeeInfo'), }
onClick: onFeeCardMaxRowClickHandler, tokenApprovalTextComponent={tokenApprovalTextComponent}
})} tokenApprovalSourceTokenSymbol={sourceTokenSymbol}
thirdRow={({ onTokenApprovalClick={onFeeCardTokenApprovalClick}
text: t('swapThisWillAllowApprove', [thirdRowTextComponent]),
linkText: t('swapEditLimit'),
tooltipText: t('swapEnableDescription', [sourceTokenSymbol]),
onClick: onFeeCardThirdRowClickHandler,
hide: !approveTxParams || (balanceError && !warningHidden),
})}
/> />
</div> </div>
</div> </div>

@ -5,7 +5,7 @@ import log from 'loglevel'
import { capitalize } from 'lodash' import { capitalize } from 'lodash'
import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url' import getBuyEthUrl from '../../../app/scripts/lib/buy-eth-url'
import { checksumAddress } from '../helpers/utils/util' import { checksumAddress } from '../helpers/utils/util'
import { calcTokenBalance, estimateGas } from '../pages/send/send.utils' import { calcTokenBalance, estimateGasForSend } from '../pages/send/send.utils'
import { fetchLocale, loadRelativeTimeFormatLocaleData } from '../helpers/utils/i18n-helper' import { fetchLocale, loadRelativeTimeFormatLocaleData } from '../helpers/utils/i18n-helper'
import { getMethodDataAsync } from '../helpers/utils/transactions.util' import { getMethodDataAsync } from '../helpers/utils/transactions.util'
import { fetchSymbolAndDecimals } from '../helpers/utils/token-util' import { fetchSymbolAndDecimals } from '../helpers/utils/token-util'
@ -626,7 +626,7 @@ export function updateGasData ({
}) { }) {
return (dispatch) => { return (dispatch) => {
dispatch(gasLoadingStarted()) dispatch(gasLoadingStarted())
return estimateGas({ return estimateGasForSend({
estimateGasMethod: promisifiedBackground.estimateGas, estimateGasMethod: promisifiedBackground.estimateGas,
blockGasLimit, blockGasLimit,
selectedAddress, selectedAddress,
@ -796,37 +796,6 @@ const updateMetamaskStateFromBackground = () => {
}) })
} }
export function estimateGasMethod ({
gasPrice,
blockGasLimit,
selectedAddress,
sendToken,
to,
value,
data,
}) {
return estimateGas({
estimateGasMethod: promisifiedBackground.estimateGas,
blockGasLimit,
selectedAddress,
sendToken,
to,
value,
gasPrice,
data,
})
}
export async function estimateGasFromTxParams (txParams) {
const backgroundState = await updateMetamaskStateFromBackground()
const blockGasLimit = backgroundState.currentBlockGasLimit
return estimateGasMethod({
...txParams,
selectedAddress: txParams.from,
blockGasLimit,
})
}
export function updateTransaction (txData, dontShowLoadingIndicator) { export function updateTransaction (txData, dontShowLoadingIndicator) {
return (dispatch) => { return (dispatch) => {
!dontShowLoadingIndicator && dispatch(showLoadingIndication()) !dontShowLoadingIndicator && dispatch(showLoadingIndication())

@ -2239,77 +2239,77 @@
component-type "^1.2.1" component-type "^1.2.1"
join-component "^1.1.0" join-component "^1.1.0"
"@sentry/browser@^5.11.1": "@sentry/browser@^5.26.0":
version "5.11.1" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.1.tgz#337ffcb52711b23064c847a07629e966f54a5ebb" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.26.0.tgz#e90a197fb94c5f26c8e05d6a539c118f33c7d598"
integrity sha512-oqOX/otmuP92DEGRyZeBuQokXdeT9HQRxH73oqIURXXNLMP3PWJALSb4HtT4AftEt/2ROGobZLuA4TaID6My/Q== integrity sha512-52kNVpy10Zd3gJRGFkhnOQvr80WJg7+XBqjMOE0//Akh4PfvEK3IqmAjVqysz6aHdruwTTivKF4ZoAxL/pA7Rg==
dependencies: dependencies:
"@sentry/core" "5.11.1" "@sentry/core" "5.26.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.26.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.26.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/cli@^1.49.0": "@sentry/cli@^1.58.0":
version "1.49.0" version "1.58.0"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.49.0.tgz#174152978acbe6023986a8fb0b247cf58b4653d8" resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.58.0.tgz#b1609f10e71539951499866502b13bf3a270fe79"
integrity sha512-Augz7c42Cxz/xWQ/NOVjUGePKVA370quvskWbCICMUwxcTvKnCLI+7KDdzEoCexj4MSuxFfBzLnrrn4w2+c9TQ== integrity sha512-bUBKBYyKVzjNhQpAfPJ3XAvAyNNvrD2Rtpo6B0MR3Okw3prdLFgv9Ta8TN19IXT7u9w13B2EdMnNA6dQDtrD4g==
dependencies: dependencies:
fs-copy-file-sync "^1.1.1" https-proxy-agent "^5.0.0"
https-proxy-agent "^3.0.0" mkdirp "^0.5.5"
mkdirp "^0.5.1" node-fetch "^2.6.0"
node-fetch "^2.1.2" progress "^2.0.3"
progress "2.0.0" proxy-from-env "^1.1.0"
proxy-from-env "^1.0.0"
"@sentry/core@5.11.1": "@sentry/core@5.26.0":
version "5.11.1" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.1.tgz#9e2da485e196ae32971545c1c49ee6fe719930e2" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.26.0.tgz#9b5fe4de8a869d733ebcc77f5ec9c619f8717a51"
integrity sha512-BpvPosVNT20Xso4gAV54Lu3KqDmD20vO63HYwbNdST5LUi8oYV4JhvOkoBraPEM2cbBwQvwVcFdeEYKk4tin9A== integrity sha512-Ubrw7K52orTVsaxpz8Su40FPXugKipoQC+zPrXcH+JIMB+o18kutF81Ae4WzuUqLfP7YB91eAlRrP608zw0EXA==
dependencies: dependencies:
"@sentry/hub" "5.11.1" "@sentry/hub" "5.26.0"
"@sentry/minimal" "5.11.1" "@sentry/minimal" "5.26.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.26.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.26.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/hub@5.11.1": "@sentry/hub@5.26.0":
version "5.11.1" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.1.tgz#ddcb865563fae53852d405885c46b4c6de68a91b" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.26.0.tgz#b2bbd8128cd5915f2ee59cbc29fff30272d74ec5"
integrity sha512-ucKprYCbGGLLjVz4hWUqHN9KH0WKUkGf5ZYfD8LUhksuobRkYVyig0ZGbshECZxW5jcDTzip4Q9Qimq/PkkXBg== integrity sha512-lAYeWvvhGYS6eQ5d0VEojw0juxGc3v4aAu8VLvMKWcZ1jXD13Bhc46u9Nvf4qAY6BAQsJDQcpEZLpzJu1bk1Qw==
dependencies: dependencies:
"@sentry/types" "5.11.0" "@sentry/types" "5.26.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.26.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/integrations@^5.11.1": "@sentry/integrations@^5.26.0":
version "5.11.1" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-5.11.1.tgz#6612efc620c187ba85a57c6e70e69d9474af7fb6" resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-5.26.0.tgz#cf90005359862c5b1df4df0f1ce8be5e56c9e1ad"
integrity sha512-zubOE9zQ4qSutS0ZTnAteDnzbVcHSI2bXD/0nTD3t3ljY+OWgcluBXYCAeAp8vOv2qCoef5ySdQa1DBCW7NQ3Q== integrity sha512-XBMPm3wWW+3EJvWFHdVcl0PSWjjNEzmQxjjWeMv9vLWAC1zhS8gcpk/LyDIFWojJBzhASD8f1mLv2ZdKZtA1ZQ==
dependencies: dependencies:
"@sentry/types" "5.11.0" "@sentry/types" "5.26.0"
"@sentry/utils" "5.11.1" "@sentry/utils" "5.26.0"
localforage "1.8.1"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/minimal@5.11.1": "@sentry/minimal@5.26.0":
version "5.11.1" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.1.tgz#0e705d01a567282d8fbbda2aed848b4974cc3cec" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.26.0.tgz#851dea3644153ed3ac4837fa8ed5661d94e7a313"
integrity sha512-HK8zs7Pgdq7DsbZQTThrhQPrJsVWzz7MaluAbQA0rTIAJ3TvHKQpsVRu17xDpjZXypqWcKCRsthDrC4LxDM1Bg== integrity sha512-mdFo3FYaI1W3KEd8EHATYx8mDOZIxeoUhcBLlH7Iej6rKvdM7p8GoECrmHPU1l6sCCPtBuz66QT5YeXc7WILsA==
dependencies: dependencies:
"@sentry/hub" "5.11.1" "@sentry/hub" "5.26.0"
"@sentry/types" "5.11.0" "@sentry/types" "5.26.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/types@5.11.0": "@sentry/types@5.26.0":
version "5.11.0" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.26.0.tgz#b0cbacb0b24cd86620fb296b46cf7277bb004a3e"
integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg== integrity sha512-ugpa1ePOhK55pjsyutAsa2tiJVQEyGYCaOXzaheg/3+EvhMdoW+owiZ8wupfvPhtZFIU3+FPOVz0d5k9K5d1rw==
"@sentry/utils@5.11.1": "@sentry/utils@5.26.0":
version "5.11.1" version "5.26.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.26.0.tgz#09a3d01d91747f38f796cafeb24f8fd86e4fa05f"
integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA== integrity sha512-F2gnHIAWbjiowcAgxz3VpKxY/NQ39NTujEd/NPnRTWlRynLFg3bAV+UvZFXljhYJeN3b/zRlScNDcpCWTrtZGw==
dependencies: dependencies:
"@sentry/types" "5.11.0" "@sentry/types" "5.26.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sindresorhus/is@^0.14.0": "@sindresorhus/is@^0.14.0":
@ -3633,6 +3633,13 @@ agent-base@4, agent-base@^4.2.0:
dependencies: dependencies:
es6-promisify "^5.0.0" es6-promisify "^5.0.0"
agent-base@6:
version "6.0.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4"
integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==
dependencies:
debug "4"
agent-base@^4.3.0: agent-base@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
@ -12195,11 +12202,6 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-copy-file-sync@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz#11bf32c096c10d126e5f6b36d06eece776062918"
integrity sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ==
fs-extra@6.0.1: fs-extra@6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
@ -13894,6 +13896,14 @@ https-proxy-agent@3.0.0, https-proxy-agent@^3.0.0:
agent-base "^4.3.0" agent-base "^4.3.0"
debug "^3.1.0" debug "^3.1.0"
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
human-standard-collectible-abi@^1.0.2: human-standard-collectible-abi@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/human-standard-collectible-abi/-/human-standard-collectible-abi-1.0.2.tgz#077bae9ed1b0b0b82bc46932104b4b499c941aa0" resolved "https://registry.yarnpkg.com/human-standard-collectible-abi/-/human-standard-collectible-abi-1.0.2.tgz#077bae9ed1b0b0b82bc46932104b4b499c941aa0"
@ -17269,6 +17279,13 @@ libp2p@~0.25.3:
peer-info "^0.15.1" peer-info "^0.15.1"
superstruct "^0.6.0" superstruct "^0.6.0"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"
lie@~3.3.0: lie@~3.3.0:
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
@ -17382,6 +17399,13 @@ locale-currency@0.0.1:
resolved "https://registry.yarnpkg.com/locale-currency/-/locale-currency-0.0.1.tgz#c9e15a22ff575b4b4bb947a4bf92ac236bd1fe9b" resolved "https://registry.yarnpkg.com/locale-currency/-/locale-currency-0.0.1.tgz#c9e15a22ff575b4b4bb947a4bf92ac236bd1fe9b"
integrity sha1-yeFaIv9XW0tLuUekv5KsI2vR/ps= integrity sha1-yeFaIv9XW0tLuUekv5KsI2vR/ps=
localforage@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.8.1.tgz#f6c0a24b41ab33b10e4dc84342dd696f6f3e3433"
integrity sha512-azSSJJfc7h4bVpi0PGi+SmLQKJl2/8NErI+LhJsrORNikMZnhaQ7rv9fHj+ofwgSHrKRlsDCL/639a6nECIKuQ==
dependencies:
lie "3.1.1"
localstorage-down@^0.6.7: localstorage-down@^0.6.7:
version "0.6.7" version "0.6.7"
resolved "https://registry.yarnpkg.com/localstorage-down/-/localstorage-down-0.6.7.tgz#d0799a93b31e6c5fa5188ec06242eb1cce9d6d15" resolved "https://registry.yarnpkg.com/localstorage-down/-/localstorage-down-0.6.7.tgz#d0799a93b31e6c5fa5188ec06242eb1cce9d6d15"
@ -18557,7 +18581,7 @@ mkdirp-promise@^5.0.1:
dependencies: dependencies:
mkdirp "*" mkdirp "*"
mkdirp@*, mkdirp@0.5.5, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1: mkdirp@*, mkdirp@0.5.5, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1:
version "0.5.5" version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@ -19091,7 +19115,7 @@ node-fetch@^1.0.1, node-fetch@~1.7.1:
encoding "^0.1.11" encoding "^0.1.11"
is-stream "^1.0.1" is-stream "^1.0.1"
node-fetch@^2.1.2, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
@ -21384,16 +21408,16 @@ process@~0.5.1:
resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=
progress@2.0.0, progress@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=
progress@^1.1.8: progress@^1.1.8:
version "1.1.8" version "1.1.8"
resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=
progress@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=
progress@^2.0.1, progress@^2.0.3: progress@^2.0.1, progress@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@ -21577,6 +21601,11 @@ proxy-from-env@^1.0.0:
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
proxyquire@^2.1.3: proxyquire@^2.1.3:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39"

Loading…
Cancel
Save