aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/_locales/en/messages.json12
-rw-r--r--ui/app/components/confirm-page-container/confirm-detail-row/index.scss4
-rw-r--r--ui/app/components/confirm-page-container/confirm-page-container-content/index.scss4
-rw-r--r--ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js146
-rw-r--r--ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js12
-rw-r--r--ui/app/components/gas-customization/advanced-gas-inputs/index.js1
-rw-r--r--ui/app/components/gas-customization/advanced-gas-inputs/index.scss133
-rw-r--r--ui/app/components/gas-customization/index.scss2
-rw-r--r--ui/app/components/modals/modal.js34
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js23
-rw-r--r--ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js39
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.component.js29
-rw-r--r--ui/app/components/pages/settings/settings-tab/settings-tab.container.js3
-rw-r--r--ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js85
-rw-r--r--ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js51
-rw-r--r--ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js47
-rw-r--r--ui/app/css/itcss/components/send.scss4
-rw-r--r--ui/app/ducks/confirm-transaction.duck.js3
-rw-r--r--ui/app/helpers/conversions.util.js8
-rw-r--r--ui/app/selectors.js5
20 files changed, 609 insertions, 36 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index c1692ce5e..2ace2c2a8 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -529,6 +529,9 @@
"gasLimitCalculation": {
"message": "We calculate the suggested gas limit based on network success rates."
},
+ "gasLimitInfoModalContent": {
+ "message": "Gas limit is the maximum amount of units of gas you are willing to spend."
+ },
"gasLimitRequired": {
"message": "Gas Limit Required"
},
@@ -547,6 +550,9 @@
"gasPriceExtremelyLow": {
"message": "Gas Price Extremely Low"
},
+ "gasPriceInfoModalContent": {
+ "message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
+ },
"gasPriceNoDenom": {
"message": "Gas Price"
},
@@ -1210,6 +1216,12 @@
"shapeshiftBuy": {
"message": "Buy with Shapeshift"
},
+ "showAdvancedGasInline": {
+ "message": "Advanced gas controls"
+ },
+ "showAdvancedGasInlineDescription": {
+ "message": "Select this to show gas price and limit controls directly on the send and confirm screens."
+ },
"showPrivateKeys": {
"message": "Show Private Keys"
},
diff --git a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss b/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
index 580a41fde..1672ef8c6 100644
--- a/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
+++ b/ui/app/components/confirm-page-container/confirm-detail-row/index.scss
@@ -43,4 +43,8 @@
font-size: .625rem;
}
}
+
+ .advanced-gas-inputs__gas-edit-rows {
+ margin-bottom: 16px;
+ }
}
diff --git a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
index 698e624f4..78639a435 100644
--- a/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
+++ b/ui/app/components/confirm-page-container/confirm-page-container-content/index.scss
@@ -52,6 +52,10 @@
&__gas-fee {
border-bottom: 1px solid $geyser;
+
+ .advanced-gas-inputs__gas-edit-rows {
+ margin-bottom: 16px;
+ }
}
&__function-type {
diff --git a/ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
new file mode 100644
index 000000000..f0abff478
--- /dev/null
+++ b/ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
@@ -0,0 +1,146 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import debounce from 'lodash.debounce'
+
+export default class AdvancedTabContent extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ updateCustomGasPrice: PropTypes.func,
+ updateCustomGasLimit: PropTypes.func,
+ customGasPrice: PropTypes.number,
+ customGasLimit: PropTypes.number,
+ insufficientBalance: PropTypes.bool,
+ customPriceIsSafe: PropTypes.bool,
+ isSpeedUp: PropTypes.bool,
+ showGasPriceInfoModal: PropTypes.func,
+ showGasLimitInfoModal: PropTypes.func,
+ }
+
+ debouncedGasLimitReset = debounce((dVal) => {
+ if (dVal < 21000) {
+ this.props.updateCustomGasLimit(21000)
+ }
+ }, 1000, { trailing: true })
+
+ onChangeGasLimit = (val) => {
+ this.props.updateCustomGasLimit(val)
+ this.debouncedGasLimitReset(val)
+ }
+
+ gasInputError ({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value }) {
+ const { t } = this.context
+ let errorText
+ let errorType
+ let isInError = true
+
+
+ if (insufficientBalance) {
+ errorText = t('insufficientBalance')
+ errorType = 'error'
+ } else if (labelKey === 'gasPrice' && isSpeedUp && value === 0) {
+ errorText = t('zeroGasPriceOnSpeedUpError')
+ errorType = 'error'
+ } else if (labelKey === 'gasPrice' && !customPriceIsSafe) {
+ errorText = t('gasPriceExtremelyLow')
+ errorType = 'warning'
+ } else {
+ isInError = false
+ }
+
+ return {
+ isInError,
+ errorText,
+ errorType,
+ }
+ }
+
+ gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ const {
+ isInError,
+ errorText,
+ errorType,
+ } = this.gasInputError({ labelKey, insufficientBalance, customPriceIsSafe, isSpeedUp, value })
+
+ return (
+ <div className="advanced-gas-inputs__gas-edit-row__input-wrapper">
+ <input
+ className={classnames('advanced-gas-inputs__gas-edit-row__input', {
+ 'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
+ 'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
+ })}
+ type="number"
+ value={value}
+ onChange={event => onChange(Number(event.target.value))}
+ />
+ <div className={classnames('advanced-gas-inputs__gas-edit-row__input-arrows', {
+ 'advanced-gas-inputs__gas-edit-row__input--error': isInError && errorType === 'error',
+ 'advanced-gas-inputs__gas-edit-row__input--warning': isInError && errorType === 'warning',
+ })}>
+ <div className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap" onClick={() => onChange(value + 1)}><i className="fa fa-sm fa-angle-up" /></div>
+ <div className="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap" onClick={() => onChange(value - 1)}><i className="fa fa-sm fa-angle-down" /></div>
+ </div>
+ { isInError
+ ? <div className={`advanced-gas-inputs__gas-edit-row__${errorType}-text`}>
+ { errorText }
+ </div>
+ : null }
+ </div>
+ )
+ }
+
+ infoButton (onClick) {
+ return <i className="fa fa-info-circle" onClick={onClick} />
+ }
+
+ renderGasEditRow (gasInputArgs) {
+ return (
+ <div className="advanced-gas-inputs__gas-edit-row">
+ <div className="advanced-gas-inputs__gas-edit-row__label">
+ { this.context.t(gasInputArgs.labelKey) }
+ { this.infoButton(() => gasInputArgs.infoOnClick()) }
+ </div>
+ { this.gasInput(gasInputArgs) }
+ </div>
+ )
+ }
+
+ render () {
+ const {
+ customGasPrice,
+ updateCustomGasPrice,
+ customGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ isSpeedUp,
+ showGasPriceInfoModal,
+ showGasLimitInfoModal,
+ } = this.props
+
+ return (
+ <div className="advanced-gas-inputs__gas-edit-rows">
+ { this.renderGasEditRow({
+ labelKey: 'gasPrice',
+ value: customGasPrice,
+ onChange: updateCustomGasPrice,
+ insufficientBalance,
+ customPriceIsSafe,
+ showGWEI: true,
+ isSpeedUp,
+ infoOnClick: showGasPriceInfoModal,
+ }) }
+ { this.renderGasEditRow({
+ labelKey: 'gasLimit',
+ value: customGasLimit,
+ onChange: this.onChangeGasLimit,
+ insufficientBalance,
+ customPriceIsSafe,
+ infoOnClick: showGasLimitInfoModal,
+ }) }
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
new file mode 100644
index 000000000..883d11c6d
--- /dev/null
+++ b/ui/app/components/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import { showModal } from '../../../actions'
+import AdvancedGasInputs from './advanced-gas-inputs.component'
+
+const mapDispatchToProps = dispatch => {
+ return {
+ showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
+ showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
+ }
+}
+
+export default connect(null, mapDispatchToProps)(AdvancedGasInputs)
diff --git a/ui/app/components/gas-customization/advanced-gas-inputs/index.js b/ui/app/components/gas-customization/advanced-gas-inputs/index.js
new file mode 100644
index 000000000..bd8abaa3e
--- /dev/null
+++ b/ui/app/components/gas-customization/advanced-gas-inputs/index.js
@@ -0,0 +1 @@
+export { default } from './advanced-gas-inputs.container'
diff --git a/ui/app/components/gas-customization/advanced-gas-inputs/index.scss b/ui/app/components/gas-customization/advanced-gas-inputs/index.scss
new file mode 100644
index 000000000..50953cbe5
--- /dev/null
+++ b/ui/app/components/gas-customization/advanced-gas-inputs/index.scss
@@ -0,0 +1,133 @@
+.advanced-gas-inputs {
+ &__gas-edit-rows {
+ display: flex;
+ flex-flow: row;
+ justify-content: space-between;
+ }
+
+ &__gas-edit-row {
+ display: flex;
+ flex-flow: column;
+ width: 47.5%;
+
+ &__label {
+ color: #313B5E;
+ font-size: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media screen and (max-width: 576px) {
+ font-size: 10px;
+ }
+
+ .fa-info-circle {
+ color: $silver;
+ margin-left: 10px;
+ cursor: pointer;
+ }
+
+ .fa-info-circle:hover {
+ color: $mid-gray;
+ }
+ }
+
+ &__error-text {
+ font-size: 12px;
+ color: red;
+ }
+
+ &__warning-text {
+ font-size: 12px;
+ color: orange;
+ }
+
+ &__input-wrapper {
+ position: relative;
+ }
+
+ &__input {
+ border: 1px solid $dusty-gray;
+ border-radius: 4px;
+ color: $mid-gray;
+ font-size: 16px;
+ height: 24px;
+ width: 100%;
+ padding-left: 8px;
+ padding-top: 2px;
+ margin-top: 7px;
+ }
+
+ &__input--error {
+ border: 1px solid $red;
+ }
+
+ &__input--warning {
+ border: 1px solid $orange;
+ }
+
+ &__input-arrows {
+ position: absolute;
+ top: 7px;
+ right: 0px;
+ width: 17px;
+ height: 24px;
+ border: 1px solid #dadada;
+ border-top-right-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ color: #9b9b9b;
+ font-size: .8em;
+ border-bottom-right-radius: 4px;
+ cursor: pointer;
+
+ &__i-wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ cursor: pointer;
+ }
+
+ &__i-wrap:hover {
+ background: #4EADE7;
+ color: $white;
+ }
+
+ i:hover {
+ background: #4EADE7;
+ }
+
+ i {
+ font-size: 10px;
+ }
+ }
+
+ &__input-arrows--error {
+ border: 1px solid $red;
+ }
+
+ &__input-arrows--warning {
+ border: 1px solid $orange;
+ }
+
+ input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ input[type="number"]:hover::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ display: none;
+ }
+
+ &__gwei-symbol {
+ position: absolute;
+ top: 8px;
+ right: 10px;
+ color: $dusty-gray;
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/app/components/gas-customization/index.scss b/ui/app/components/gas-customization/index.scss
index e99d4e57f..b06c1d044 100644
--- a/ui/app/components/gas-customization/index.scss
+++ b/ui/app/components/gas-customization/index.scss
@@ -3,3 +3,5 @@
@import './gas-modal-page-container/index';
@import './gas-price-chart/index';
+
+@import './advanced-gas-inputs/index';
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 32c860a7b..08bf205ef 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -230,6 +230,40 @@ const MODALS = {
},
},
+ GAS_PRICE_INFO_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'gasPriceNoDenom',
+ message: 'gasPriceInfoModalContent',
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ GAS_LIMIT_INFO_MODAL: {
+ contents: [
+ h(NotifcationModal, {
+ header: 'gasLimit',
+ message: 'gasLimitInfoModalContent',
+ }),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
CONFIRM_RESET_ACCOUNT: {
contents: h(ConfirmResetAccount),
mobileModalStyle: {
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 6bc415781..8d404aaca 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -11,6 +11,7 @@ import {
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
+import AdvancedGasInputs from '../../gas-customization/advanced-gas-inputs'
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@@ -81,6 +82,11 @@ export default class ConfirmTransactionBase extends Component {
titleComponent: PropTypes.node,
valid: PropTypes.bool,
warning: PropTypes.string,
+ advancedInlineGasShown: PropTypes.bool,
+ gasPrice: PropTypes.number,
+ gasLimit: PropTypes.number,
+ insufficientBalance: PropTypes.bool,
+ convertThenUpdateGasAndCalculate: PropTypes.func,
}
state = {
@@ -165,6 +171,11 @@ export default class ConfirmTransactionBase extends Component {
hexTransactionFee,
hexTransactionTotal,
hideDetails,
+ advancedInlineGasShown,
+ gasPrice,
+ gasLimit,
+ insufficientBalance,
+ convertThenUpdateGasAndCalculate,
} = this.props
if (hideDetails) {
@@ -182,6 +193,18 @@ export default class ConfirmTransactionBase extends Component {
headerTextClassName="confirm-detail-row__header-text--edit"
onHeaderClick={() => this.handleEditGas()}
/>
+ {advancedInlineGasShown
+ ? <AdvancedGasInputs
+ updateCustomGasPrice={newGasPrice => convertThenUpdateGasAndCalculate({ gasPrice: newGasPrice, gasLimit })}
+ updateCustomGasLimit={newGasLimit => convertThenUpdateGasAndCalculate({ gasLimit: newGasLimit, gasPrice })}
+ customGasPrice={gasPrice}
+ customGasLimit={gasLimit}
+ insufficientBalance={insufficientBalance}
+ customPriceIsSafe={true}
+ isSpeedUp={false}
+ />
+ : null
+ }
</div>
<div>
<ConfirmDetailRow
diff --git a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
index 1e2270432..98cde4b03 100644
--- a/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -14,11 +14,17 @@ import {
GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../../constants/error-keys'
import { getHexGasTotal } from '../../../helpers/confirm-transaction/util'
-import { isBalanceSufficient } from '../../send/send.utils'
+import {
+ convertGasPriceForInputs,
+ convertGasLimitForInputs,
+ decimalToHex,
+ decGWEIToHexWEI,
+} from '../../../helpers/conversions.util'
+import { isBalanceSufficient, calcGasTotal } from '../../send/send.utils'
import { conversionGreaterThan } from '../../../conversion-util'
import { MIN_GAS_LIMIT_DEC } from '../../send/send.constants'
import { addressSlicer, valuesFor } from '../../../util'
-import { getMetaMaskAccounts } from '../../../selectors'
+import { getMetaMaskAccounts, getAdvancedInlineGasShown } from '../../../selectors'
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
return {
@@ -47,7 +53,13 @@ const mapStateToProps = (state, props) => {
nonce,
} = confirmTransaction
const { txParams = {}, lastGasPrice, id: transactionId } = txData
- const { from: fromAddress, to: txParamsToAddress } = txParams
+ const {
+ from: fromAddress,
+ to: txParamsToAddress,
+ gasPrice,
+ gas: gasLimit,
+ value: amount,
+ } = txParams
const accounts = getMetaMaskAccounts(state)
const {
conversionRate,
@@ -84,6 +96,13 @@ const mapStateToProps = (state, props) => {
)
const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length
+ const insufficientBalance = !isBalanceSufficient({
+ amount,
+ gasTotal: calcGasTotal(gasLimit, gasPrice),
+ balance,
+ conversionRate,
+ })
+
return {
balance,
fromAddress,
@@ -113,9 +132,13 @@ const mapStateToProps = (state, props) => {
unapprovedTxCount,
currentNetworkUnapprovedTxs,
customGas: {
- gasLimit: customGasLimit || txData.gasPrice,
- gasPrice: customGasPrice || txData.gasLimit,
+ gasLimit: customGasLimit || gasPrice,
+ gasPrice: customGasPrice || gasLimit,
},
+ advancedInlineGasShown: getAdvancedInlineGasShown(state),
+ gasPrice: convertGasPriceForInputs(gasPrice),
+ gasLimit: convertGasLimitForInputs(gasLimit),
+ insufficientBalance,
}
}
@@ -132,6 +155,12 @@ const mapDispatchToProps = dispatch => {
updateGasAndCalculate: ({ gasLimit, gasPrice }) => {
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
},
+ convertThenUpdateGasAndCalculate: ({ gasLimit, gasPrice }) => {
+ return dispatch(updateGasAndCalculate({
+ gasLimit: decimalToHex(gasLimit),
+ gasPrice: decGWEIToHexWEI(gasPrice),
+ }))
+ },
showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => {
return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount }))
},
diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
index ce1f72407..1c02b2507 100644
--- a/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
+++ b/ui/app/components/pages/settings/settings-tab/settings-tab.component.js
@@ -59,6 +59,8 @@ export default class SettingsTab extends PureComponent {
nativeCurrency: PropTypes.string,
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
+ setAdvancedInlineGasFeatureFlag: PropTypes.func,
+ advancedInlineGas: PropTypes.bool,
}
state = {
@@ -412,6 +414,32 @@ export default class SettingsTab extends PureComponent {
)
}
+ renderAdvancedGasInputInline () {
+ const { t } = this.context
+ const { advancedInlineGas, setAdvancedInlineGasFeatureFlag } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('showAdvancedGasInline') }</span>
+ <div className="settings-page__content-description">
+ { t('showAdvancedGasInlineDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <ToggleButton
+ value={advancedInlineGas}
+ onToggle={value => setAdvancedInlineGasFeatureFlag(!value)}
+ activeLabel=""
+ inactiveLabel=""
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+
renderUsePrimaryCurrencyOptions () {
const { t } = this.context
const {
@@ -508,6 +536,7 @@ export default class SettingsTab extends PureComponent {
{ this.renderClearApproval() }
{ this.renderPrivacyOptIn() }
{ this.renderHexDataOptIn() }
+ { this.renderAdvancedGasInputInline() }
{ this.renderBlockieOptIn() }
</div>
)
diff --git a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
index 92f645438..49da0db12 100644
--- a/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
+++ b/ui/app/components/pages/settings/settings-tab/settings-tab.container.js
@@ -25,6 +25,7 @@ const mapStateToProps = state => {
featureFlags: {
sendHexData,
privacyMode,
+ advancedInlineGas,
} = {},
provider = {},
currentLocale,
@@ -39,6 +40,7 @@ const mapStateToProps = state => {
nativeCurrency,
useBlockie,
sendHexData,
+ advancedInlineGas,
privacyMode,
provider,
useNativeCurrencyAsPrimaryCurrency,
@@ -54,6 +56,7 @@ const mapDispatchToProps = dispatch => {
setUseBlockie: value => dispatch(setUseBlockie(value)),
updateCurrentLocale: key => dispatch(updateCurrentLocale(key)),
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
+ setAdvancedInlineGasFeatureFlag: shouldShow => dispatch(setFeatureFlag('advancedInlineGas', shouldShow)),
setPrivacyMode: enabled => dispatch(setFeatureFlag('privacyMode', enabled)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js
index 8d305dd4f..50337e0bf 100644
--- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.component.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper/'
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
import GasPriceButtonGroup from '../../../gas-customization/gas-price-button-group'
+import AdvancedGasInputs from '../../../gas-customization/advanced-gas-inputs'
export default class SendGasRow extends Component {
@@ -13,54 +14,94 @@ export default class SendGasRow extends Component {
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
showCustomizeGasModal: PropTypes.func,
+ setGasPrice: PropTypes.func,
+ setGasLimit: PropTypes.func,
gasPriceButtonGroupProps: PropTypes.object,
gasButtonGroupShown: PropTypes.bool,
+ advancedInlineGasShown: PropTypes.bool,
resetGasButtons: PropTypes.func,
+ gasPrice: PropTypes.number,
+ gasLimit: PropTypes.number,
+ insufficientBalance: PropTypes.bool,
}
static contextTypes = {
t: PropTypes.func,
}
- render () {
+ renderAdvancedOptionsButton () {
+ const { showCustomizeGasModal } = this.props
+ return <div className="advanced-gas-options-btn" onClick={() => showCustomizeGasModal()}>
+ { this.context.t('advancedOptions') }
+ </div>
+ }
+
+ renderContent () {
const {
conversionRate,
convertedCurrency,
gasLoadingError,
gasTotal,
- gasFeeError,
showCustomizeGasModal,
gasPriceButtonGroupProps,
gasButtonGroupShown,
+ advancedInlineGasShown,
resetGasButtons,
+ setGasPrice,
+ setGasLimit,
+ gasPrice,
+ gasLimit,
+ insufficientBalance,
} = this.props
+ const gasPriceButtonGroup = <div>
+ <GasPriceButtonGroup
+ className="gas-price-button-group--small"
+ showCheck={false}
+ {...gasPriceButtonGroupProps}
+ />
+ { this.renderAdvancedOptionsButton() }
+ </div>
+ const gasFeeDisplay = <GasFeeDisplay
+ conversionRate={conversionRate}
+ convertedCurrency={convertedCurrency}
+ gasLoadingError={gasLoadingError}
+ gasTotal={gasTotal}
+ onReset={resetGasButtons}
+ onClick={() => showCustomizeGasModal()}
+ />
+ const advancedGasInputs = <div>
+ <AdvancedGasInputs
+ updateCustomGasPrice={newGasPrice => setGasPrice(newGasPrice, gasLimit)}
+ updateCustomGasLimit={newGasLimit => setGasLimit(newGasLimit, gasPrice)}
+ customGasPrice={gasPrice}
+ customGasLimit={gasLimit}
+ insufficientBalance={insufficientBalance}
+ customPriceIsSafe={true}
+ isSpeedUp={false}
+ />
+ { this.renderAdvancedOptionsButton() }
+ </div>
+
+ if (advancedInlineGasShown) {
+ return advancedGasInputs
+ } else if (gasButtonGroupShown) {
+ return gasPriceButtonGroup
+ } else {
+ return gasFeeDisplay
+ }
+ }
+
+ render () {
+ const { gasFeeError } = this.props
+
return (
<SendRowWrapper
label={`${this.context.t('transactionFee')}:`}
showError={gasFeeError}
errorType={'gasFee'}
>
- {gasButtonGroupShown
- ? <div>
- <GasPriceButtonGroup
- className="gas-price-button-group--small"
- showCheck={false}
- {...gasPriceButtonGroupProps}
- />
- <div className="advanced-gas-options-btn" onClick={() => showCustomizeGasModal()}>
- { this.context.t('advancedOptions') }
- </div>
- </div>
- : <GasFeeDisplay
- conversionRate={conversionRate}
- convertedCurrency={convertedCurrency}
- gasLoadingError={gasLoadingError}
- gasTotal={gasTotal}
- onReset={resetGasButtons}
- onClick={() => showCustomizeGasModal()}
- />}
-
+ { this.renderContent() }
</SendRowWrapper>
)
}
diff --git a/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js
index 977f8ab3c..b32928b75 100644
--- a/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js
+++ b/ui/app/components/send/send-content/send-gas-row/send-gas-row.container.js
@@ -4,32 +4,59 @@ import {
getCurrentCurrency,
getGasTotal,
getGasPrice,
+ getGasLimit,
+ getSendAmount,
} from '../../send.selectors.js'
import {
+ isBalanceSufficient,
+ calcGasTotal,
+} from '../../send.utils.js'
+import {
getBasicGasEstimateLoadingStatus,
getRenderableEstimateDataForSmallButtonsFromGWEI,
getDefaultActiveButtonIndex,
} from '../../../../selectors/custom-gas'
import {
+ decGWEIToHexWEI,
+ decimalToHex,
+ convertGasPriceForInputs,
+ convertGasLimitForInputs,
+} from '../../../../helpers/conversions.util'
+import {
showGasButtonGroup,
} from '../../../../ducks/send.duck'
import {
resetCustomData,
} from '../../../../ducks/gas.duck'
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
-import { showModal, setGasPrice } from '../../../../actions'
+import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../actions'
+import { getAdvancedInlineGasShown, getCurrentEthBalance } from '../../../../selectors'
import SendGasRow from './send-gas-row.component'
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
function mapStateToProps (state) {
const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
- const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, getGasPrice(state))
+ const gasPrice = getGasPrice(state)
+ const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
+ const renderableGasPrice = convertGasPriceForInputs(gasPrice)
+ const renderableGasLimit = convertGasLimitForInputs(getGasLimit(state))
+
+ const gasTotal = getGasTotal(state)
+ const conversionRate = getConversionRate(state)
+ const balance = getCurrentEthBalance(state)
+
+ const insufficientBalance = !isBalanceSufficient({
+ amount: getSendAmount(state),
+ gasTotal,
+ balance,
+ conversionRate,
+ })
return {
- conversionRate: getConversionRate(state),
+ conversionRate,
convertedCurrency: getCurrentCurrency(state),
- gasTotal: getGasTotal(state),
+ gasTotal,
gasFeeError: gasFeeIsInError(state),
gasLoadingError: getGasLoadingError(state),
gasPriceButtonGroupProps: {
@@ -39,13 +66,26 @@ function mapStateToProps (state) {
gasButtonInfo,
},
gasButtonGroupShown: getGasButtonGroupShown(state),
+ advancedInlineGasShown: getAdvancedInlineGasShown(state),
+ gasPrice: renderableGasPrice,
+ gasLimit: renderableGasLimit,
+ insufficientBalance,
}
}
function mapDispatchToProps (dispatch) {
return {
showCustomizeGasModal: () => dispatch(showModal({ name: 'CUSTOMIZE_GAS', hideBasic: true })),
- setGasPrice: newPrice => dispatch(setGasPrice(newPrice)),
+ setGasPrice: (newPrice, gasLimit) => {
+ newPrice = decGWEIToHexWEI(newPrice)
+ dispatch(setGasPrice(newPrice))
+ dispatch(setGasTotal(calcGasTotal(gasLimit, newPrice)))
+ },
+ setGasLimit: (newLimit, gasPrice) => {
+ newLimit = decimalToHex(newLimit)
+ dispatch(setGasLimit(newLimit))
+ dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
+ },
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
resetCustomData: () => dispatch(resetCustomData()),
}
@@ -74,5 +114,6 @@ function mergeProps (stateProps, dispatchProps, ownProps) {
dispatchSetGasPrice(gasButtonInfo[1].priceInHexWei)
dispatchShowGasButtonGroup()
},
+ setGasPrice: dispatchSetGasPrice,
}
}
diff --git a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
index f0c82e4f7..439f2ef6a 100644
--- a/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
+++ b/ui/app/components/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
@@ -9,6 +9,8 @@ let mergeProps
const actionSpies = {
showModal: sinon.spy(),
setGasPrice: sinon.spy(),
+ setGasTotal: sinon.spy(),
+ setGasLimit: sinon.spy(),
}
const sendDuckSpies = {
@@ -28,11 +30,26 @@ proxyquire('../send-gas-row.container.js', {
return () => ({})
},
},
+ '../../../../selectors': {
+ getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`,
+ getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`,
+ },
'../../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`,
getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`,
getGasTotal: (s) => `mockGasTotal:${s}`,
getGasPrice: (s) => `mockGasPrice:${s}`,
+ getGasLimit: (s) => `mockGasLimit:${s}`,
+ getSendAmount: (s) => `mockSendAmount:${s}`,
+ },
+ '../../send.utils.js': {
+ isBalanceSufficient: ({
+ amount,
+ gasTotal,
+ balance,
+ conversionRate,
+ }) => `${amount}:${gasTotal}:${balance}:${conversionRate}`,
+ calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
},
'./send-gas-row.selectors.js': {
getGasLoadingError: (s) => `mockGasLoadingError:${s}`,
@@ -47,6 +64,12 @@ proxyquire('../send-gas-row.container.js', {
},
'../../../../ducks/send.duck': sendDuckSpies,
'../../../../ducks/gas.duck': gasDuckSpies,
+ '../../../../helpers/conversions.util': {
+ convertGasPriceForInputs: str => str + '*',
+ convertGasLimitForInputs: str => str + '**',
+ decGWEIToHexWEI: str => '0x' + str + '000',
+ decimalToHex: str => '0x' + str,
+ },
})
describe('send-gas-row container', () => {
@@ -67,6 +90,10 @@ describe('send-gas-row container', () => {
gasButtonInfo: `mockGasButtonInfo:mockState`,
},
gasButtonGroupShown: `mockGetGasButtonGroupShown:mockState`,
+ advancedInlineGasShown: 'mockAdvancedInlineGasShown:mockState',
+ gasLimit: 'mockGasLimit:mockState**',
+ gasPrice: 'mockGasPrice:mockState*',
+ insufficientBalance: false,
})
})
@@ -79,6 +106,7 @@ describe('send-gas-row container', () => {
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
+ actionSpies.setGasTotal.resetHistory()
})
describe('showCustomizeGasModal()', () => {
@@ -94,10 +122,23 @@ describe('send-gas-row container', () => {
describe('setGasPrice()', () => {
it('should dispatch an action', () => {
- mapDispatchToPropsObject.setGasPrice('mockNewPrice')
- assert(dispatchSpy.calledOnce)
+ mapDispatchToPropsObject.setGasPrice('mockNewPrice', 'mockLimit')
+ assert(dispatchSpy.calledTwice)
assert(actionSpies.setGasPrice.calledOnce)
- assert.equal(actionSpies.setGasPrice.getCall(0).args[0], 'mockNewPrice')
+ assert.equal(actionSpies.setGasPrice.getCall(0).args[0], '0xmockNewPrice000')
+ assert(actionSpies.setGasTotal.calledOnce)
+ assert.equal(actionSpies.setGasTotal.getCall(0).args[0], 'mockLimit0xmockNewPrice000')
+ })
+ })
+
+ describe('setGasLimit()', () => {
+ it('should dispatch an action', () => {
+ mapDispatchToPropsObject.setGasLimit('mockNewLimit', 'mockPrice')
+ assert(dispatchSpy.calledTwice)
+ assert(actionSpies.setGasLimit.calledOnce)
+ assert.equal(actionSpies.setGasLimit.getCall(0).args[0], '0xmockNewLimit')
+ assert(actionSpies.setGasTotal.calledOnce)
+ assert.equal(actionSpies.setGasTotal.getCall(0).args[0], '0xmockNewLimitmockPrice')
})
})
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 4372f275c..07ab04613 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -560,6 +560,7 @@
&__form-field {
flex: 1 1 auto;
min-width: 0;
+ max-width: 277px;
.currency-display {
color: $tundora;
@@ -586,7 +587,7 @@
font-family: Roboto;
font-size: 16px;
line-height: 22px;
- width: 88px;
+ width: 95px;
font-weight: 400;
flex: 0 0 auto;
}
@@ -934,6 +935,7 @@
font-size: 14px;
color: #2f9ae0;
cursor: pointer;
+ margin-top: 16px;
}
.sliders-icon-container {
diff --git a/ui/app/ducks/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction.duck.js
index e228d2d39..c6e2c1be3 100644
--- a/ui/app/ducks/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction.duck.js
@@ -24,6 +24,7 @@ import {
import { getSymbolAndDecimals } from '../token-util'
import { conversionUtil } from '../conversion-util'
+import { addHexPrefix } from 'ethereumjs-util'
// Actions
const createActionType = action => `metamask/confirm-transaction/${action}`
@@ -256,6 +257,8 @@ export function setFetchingData (isFetching) {
}
export function updateGasAndCalculate ({ gasLimit, gasPrice }) {
+ gasLimit = addHexPrefix(gasLimit)
+ gasPrice = addHexPrefix(gasPrice)
return (dispatch, getState) => {
const { confirmTransaction: { txData } } = getState()
const newTxData = {
diff --git a/ui/app/helpers/conversions.util.js b/ui/app/helpers/conversions.util.js
index 065d67e8e..d2aaeca33 100644
--- a/ui/app/helpers/conversions.util.js
+++ b/ui/app/helpers/conversions.util.js
@@ -120,3 +120,11 @@ export function hexWEIToDecGWEI (decGWEI) {
toDenomination: 'GWEI',
})
}
+
+export function convertGasPriceForInputs (gasPriceInHexWEI) {
+ return Number(hexWEIToDecGWEI(gasPriceInHexWEI))
+}
+
+export function convertGasLimitForInputs (gasLimitInHexWEI) {
+ return parseInt(gasLimitInHexWEI, 16)
+}
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index 6e9bf6470..976342455 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -36,6 +36,7 @@ const selectors = {
getCurrentEthBalance,
getNetworkIdentifier,
isBalanceCached,
+ getAdvancedInlineGasShown,
}
module.exports = selectors
@@ -230,3 +231,7 @@ function getTotalUnapprovedCount ({ metamask }) {
function preferencesSelector ({ metamask }) {
return metamask.preferences
}
+
+function getAdvancedInlineGasShown (state) {
+ return Boolean(state.metamask.featureFlags.advancedInlineGas)
+}