aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--app/scripts/controllers/token-rates.js6
-rw-r--r--old-ui/app/components/pending-tx.js3
-rw-r--r--package-lock.json42
-rw-r--r--test/integration/lib/send-new-ui.js59
-rw-r--r--test/unit/metamask-controller-test.js33
-rw-r--r--ui/app/components/customize-gas-modal/index.js4
-rw-r--r--ui/app/components/pages/add-token.js2
-rw-r--r--ui/app/components/pending-tx/index.js38
-rw-r--r--ui/app/components/send/currency-display.js15
-rw-r--r--ui/app/components/send/send-constants.js4
-rw-r--r--ui/app/send-v2.js2
-rw-r--r--ui/app/token-util.js46
13 files changed, 153 insertions, 102 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16a9523b3..0bb50fd5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
- Fetch token prices based on contract address, not symbol
- Fix bug that prevents setting language locale in settings.
- Show checksum addresses throughout the UI
+- Allow transactions with a 0 gwei gas price
## 4.5.5 Fri Apr 06 2018
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
index abeec4cc0..87d716aa6 100644
--- a/app/scripts/controllers/token-rates.js
+++ b/app/scripts/controllers/token-rates.js
@@ -1,4 +1,5 @@
const ObservableStore = require('obs-store')
+const { warn } = require('loglevel')
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000
@@ -42,7 +43,10 @@ class TokenRatesController {
const response = await fetch(`https://metamask.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
const json = await response.json()
return json && json.length ? json[0].averagePrice : 0
- } catch (error) { }
+ } catch (error) {
+ warn(`MetaMask - TokenRatesController exchange rate fetch failed for ${address}.`, error)
+ return 0
+ }
}
/**
diff --git a/old-ui/app/components/pending-tx.js b/old-ui/app/components/pending-tx.js
index cd4189fc4..c8132539c 100644
--- a/old-ui/app/components/pending-tx.js
+++ b/old-ui/app/components/pending-tx.js
@@ -16,8 +16,7 @@ const addressSummary = util.addressSummary
const nameForAddress = require('../../lib/contract-namer')
const BNInput = require('./bn-as-decimal-input')
-// corresponds with 0.1 GWEI
-const MIN_GAS_PRICE_BN = new BN('100000000')
+const MIN_GAS_PRICE_BN = new BN('0')
const MIN_GAS_LIMIT_BN = new BN('21000')
module.exports = PendingTx
diff --git a/package-lock.json b/package-lock.json
index 5b5b82450..fd381e993 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2821,6 +2821,20 @@
"ieee754": "1.1.8"
}
},
+ "buffer-alloc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.1.0.tgz",
+ "integrity": "sha1-BVFNM78WVtNUDGhPZbEgLpDsowM=",
+ "requires": {
+ "buffer-alloc-unsafe": "0.1.1",
+ "buffer-fill": "0.1.1"
+ }
+ },
+ "buffer-alloc-unsafe": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-0.1.1.tgz",
+ "integrity": "sha1-/+H2dVHdBVc33iUzN7/oU9+rGmo="
+ },
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -2831,6 +2845,11 @@
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
"integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74="
},
+ "buffer-fill": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-0.1.1.tgz",
+ "integrity": "sha512-YgBMBzdRLEfgxJIGu2wrvI2E03tMCFU1p7d1KhB4BOoMN0VxmTFjSyN5JtKt9z8Z9JajMHruI6SE25W96wNv7Q=="
+ },
"buffer-from": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
@@ -4350,7 +4369,7 @@
"requires": {
"file-type": "5.2.0",
"is-stream": "1.1.0",
- "tar-stream": "1.5.5"
+ "tar-stream": "1.5.6"
}
},
"decompress-tarbz2": {
@@ -24245,13 +24264,15 @@
}
},
"tar-stream": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
- "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.6.tgz",
+ "integrity": "sha512-tFG9xPbc4Y7CubEwriTss87tdcBQDsw81ejJyCbT4ALNYkNsdPqCfCD6Gkg3OpRkUkq6VO7qpNfwoQAuk/aeNQ==",
"requires": {
"bl": "1.2.1",
+ "buffer-alloc": "1.1.0",
"end-of-stream": "1.4.0",
"readable-stream": "2.3.3",
+ "to-buffer": "1.1.0",
"xtend": "4.0.1"
}
},
@@ -24639,6 +24660,11 @@
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M="
},
+ "to-buffer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.0.tgz",
+ "integrity": "sha1-N1vAPtrlw1qPoLP+laHzmF2x3Po="
+ },
"to-fast-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
@@ -26284,7 +26310,7 @@
"ethereumjs-block": "1.7.0",
"ethereumjs-tx": "1.3.3",
"ethereumjs-util": "5.1.5",
- "ethereumjs-vm": "2.3.4",
+ "ethereumjs-vm": "2.3.5",
"json-rpc-error": "2.0.0",
"json-stable-stringify": "1.0.1",
"promise-to-callback": "1.0.0",
@@ -26338,9 +26364,9 @@
}
},
"ethereumjs-vm": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.3.4.tgz",
- "integrity": "sha512-Y4SlzNDqxrCO58jhp98HdnZVdjOqB+HC0hoU+N/DEp1aU+hFkRX/nru5F7/HkQRPIlA6aJlQp/xIA6xZs1kspw==",
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.3.5.tgz",
+ "integrity": "sha512-AJ7x44+xqyE5+UO3Nns19WkTdZfyqFZ+sEjIEpvme7Ipbe3iBU1uwCcHEdiu/yY9bdhr3IfSa/NfIKNeXPaRVQ==",
"requires": {
"async": "2.6.0",
"async-eventemitter": "0.2.4",
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
index 09a074750..3da3f4f95 100644
--- a/test/integration/lib/send-new-ui.js
+++ b/test/integration/lib/send-new-ui.js
@@ -23,6 +23,37 @@ global.ethQuery = {
global.ethereumProvider = {}
+async function customizeGas (assert, price, limit, ethFee, usdFee) {
+ const sendGasOpenCustomizeModalButton = await queryAsync($, '.sliders-icon-container')
+ sendGasOpenCustomizeModalButton[0].click()
+
+ const customizeGasModal = await queryAsync($, '.send-v2__customize-gas')
+ assert.ok(customizeGasModal[0], 'should render the customize gas modal')
+
+ const customizeGasPriceInput = (await queryAsync($, '.send-v2__gas-modal-card')).first().find('input')
+ customizeGasPriceInput.val(price)
+ reactTriggerChange(customizeGasPriceInput[0])
+ const customizeGasLimitInput = (await queryAsync($, '.send-v2__gas-modal-card')).last().find('input')
+ customizeGasLimitInput.val(limit)
+ reactTriggerChange(customizeGasLimitInput[0])
+
+ const customizeGasSaveButton = await queryAsync($, '.send-v2__customize-gas__save')
+ customizeGasSaveButton[0].click()
+ const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
+
+ assert.equal(
+ (await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
+ ethFee,
+ 'send gas field should show customized gas total'
+ )
+
+ assert.equal(
+ (await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent,
+ usdFee,
+ 'send gas field should show customized gas total converted to USD'
+ )
+}
+
async function runSendFlowTest(assert, done) {
console.log('*** start runSendFlowTest')
const selectState = await queryAsync($, 'select')
@@ -95,32 +126,8 @@ async function runSendFlowTest(assert, done) {
'send gas field should show estimated gas total converted to USD'
)
- const sendGasOpenCustomizeModalButton = await queryAsync($, '.sliders-icon-container')
- sendGasOpenCustomizeModalButton[0].click()
-
- const customizeGasModal = await queryAsync($, '.send-v2__customize-gas')
- assert.ok(customizeGasModal[0], 'should render the customize gas modal')
-
- const customizeGasPriceInput = (await queryAsync($, '.send-v2__gas-modal-card')).first().find('input')
- customizeGasPriceInput.val(50)
- reactTriggerChange(customizeGasPriceInput[0])
- const customizeGasLimitInput = (await queryAsync($, '.send-v2__gas-modal-card')).last().find('input')
- customizeGasLimitInput.val(60000)
- reactTriggerChange(customizeGasLimitInput[0])
-
- const customizeGasSaveButton = await queryAsync($, '.send-v2__customize-gas__save')
- customizeGasSaveButton[0].click()
-
- assert.equal(
- (await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
- '0.003',
- 'send gas field should show customized gas total'
- )
- assert.equal(
- (await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent,
- '$3.60 USD',
- 'send gas field should show customized gas total converted to USD'
- )
+ await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
+ await customizeGas(assert, 500, 60000, '0.003', '$3.60 USD')
const sendButton = await queryAsync($, 'button.btn-primary--lg.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js
index adeca9b5f..18c3f9ab9 100644
--- a/test/unit/metamask-controller-test.js
+++ b/test/unit/metamask-controller-test.js
@@ -6,6 +6,12 @@ const MetaMaskController = require('../../app/scripts/metamask-controller')
const blacklistJSON = require('../stub/blacklist')
const firstTimeState = require('../../app/scripts/first-time-state')
+const DEFAULT_LABEL = 'Account 1'
+const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
+const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'
+const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle'
+const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'
+
describe('MetaMaskController', function () {
let metamaskController
const sandbox = sinon.sandbox.create()
@@ -87,17 +93,28 @@ describe('MetaMaskController', function () {
describe('#createNewVaultAndRestore', function () {
it('should be able to call newVaultAndRestore despite a mistake.', async function () {
-
const password = 'what-what-what'
- const wrongSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadiu'
- const rightSeed = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium'
- await metamaskController.createNewVaultAndRestore(password, wrongSeed)
- .catch((e) => {
- return
- })
- await metamaskController.createNewVaultAndRestore(password, rightSeed)
+ await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
+ await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
})
+
+ it('should clear previous identities after vault restoration', async () => {
+ await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED)
+ assert.deepEqual(metamaskController.getState().identities, {
+ [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL },
+ })
+
+ await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo')
+ assert.deepEqual(metamaskController.getState().identities, {
+ [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' },
+ })
+
+ await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT)
+ assert.deepEqual(metamaskController.getState().identities, {
+ [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL },
+ })
+ })
})
})
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 4c693d1c3..1ff8eea87 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -280,8 +280,7 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, {
value: convertedGasPrice,
min: forceGasMin || MIN_GAS_PRICE_GWEI,
- // max: 1000,
- step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10),
+ step: 1,
onChange: value => this.convertAndSetGasPrice(value),
title: this.context.t('gasPrice'),
copy: this.context.t('gasPriceCalculation'),
@@ -290,7 +289,6 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, {
value: convertedGasLimit,
min: 1,
- // max: 100000,
step: 1,
onChange: value => this.convertAndSetGasLimit(value),
title: this.context.t('gasLimit'),
diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js
index 566e42450..8d52571d0 100644
--- a/ui/app/components/pages/add-token.js
+++ b/ui/app/components/pages/add-token.js
@@ -192,7 +192,7 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address)
if (symbol && decimals) {
this.setState({
customSymbol: symbol,
- customDecimals: decimals.toString(),
+ customDecimals: decimals,
autoFilled: true,
})
}
diff --git a/ui/app/components/pending-tx/index.js b/ui/app/components/pending-tx/index.js
index 6ee83ba7e..fb409cb92 100644
--- a/ui/app/components/pending-tx/index.js
+++ b/ui/app/components/pending-tx/index.js
@@ -8,7 +8,7 @@ const abiDecoder = require('abi-decoder')
abiDecoder.addABI(abi)
const inherits = require('util').inherits
const actions = require('../../actions')
-const util = require('../../util')
+const { getSymbolAndDecimals } = require('../../token-util')
const ConfirmSendEther = require('./confirm-send-ether')
const ConfirmSendToken = require('./confirm-send-token')
const ConfirmDeployContract = require('./confirm-deploy-contract')
@@ -26,6 +26,7 @@ function mapStateToProps (state) {
const {
conversionRate,
identities,
+ tokens: existingTokens,
} = state.metamask
const accounts = state.metamask.accounts
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
@@ -33,6 +34,7 @@ function mapStateToProps (state) {
conversionRate,
identities,
selectedAddress,
+ existingTokens,
}
}
@@ -66,6 +68,7 @@ PendingTx.prototype.componentDidUpdate = function (prevProps, prevState) {
}
PendingTx.prototype.setTokenData = async function () {
+ const { existingTokens } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
@@ -89,30 +92,15 @@ PendingTx.prototype.setTokenData = async function () {
}
if (isTokenTransaction) {
- const token = util.getContractAtAddress(txParams.to)
- const results = await Promise.all([
- token.symbol(),
- token.decimals(),
- ])
- const [ symbol, decimals ] = results
-
- if (symbol[0] && decimals[0]) {
- this.setState({
- transactionType: TX_TYPES.SEND_TOKEN,
- tokenAddress: txParams.to,
- tokenSymbol: symbol[0],
- tokenDecimals: decimals[0],
- isFetching: false,
- })
- } else {
- this.setState({
- transactionType: TX_TYPES.SEND_TOKEN,
- tokenAddress: txParams.to,
- tokenSymbol: null,
- tokenDecimals: null,
- isFetching: false,
- })
- }
+ const { symbol, decimals } = await getSymbolAndDecimals(txParams.to, existingTokens)
+
+ this.setState({
+ transactionType: TX_TYPES.SEND_TOKEN,
+ tokenAddress: txParams.to,
+ tokenSymbol: symbol,
+ tokenDecimals: decimals,
+ isFetching: false,
+ })
} else {
this.setState({
transactionType: TX_TYPES.SEND_ETHER,
diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js
index a7bd5d7ea..90fb2b66c 100644
--- a/ui/app/components/send/currency-display.js
+++ b/ui/app/components/send/currency-display.js
@@ -89,7 +89,6 @@ CurrencyDisplay.prototype.render = function () {
} = this.props
const valueToRender = this.getValueToRender()
-
const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
return h('div', {
@@ -97,22 +96,24 @@ CurrencyDisplay.prototype.render = function () {
style: {
borderColor: inError ? 'red' : null,
},
- onClick: () => this.currencyInput.focus(),
+ onClick: () => this.currencyInput && this.currencyInput.focus(),
}, [
h('div.currency-display__primary-row', [
h('div.currency-display__input-wrapper', [
- h(CurrencyInput, {
+ h(readOnly ? 'input' : CurrencyInput, {
className: primaryBalanceClassName,
value: `${valueToRender}`,
placeholder: '0',
readOnly,
- onInputChange: newValue => {
- handleChange(this.getAmount(newValue))
- },
- inputRef: input => { this.currencyInput = input },
+ ...(!readOnly ? {
+ onInputChange: newValue => {
+ handleChange(this.getAmount(newValue))
+ },
+ inputRef: input => { this.currencyInput = input },
+ } : {}),
}),
h('span.currency-display__currency-symbol', primaryCurrency),
diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js
index b3ee0899a..5d89c74aa 100644
--- a/ui/app/components/send/send-constants.js
+++ b/ui/app/components/send/send-constants.js
@@ -1,8 +1,8 @@
const ethUtil = require('ethereumjs-util')
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
-const MIN_GAS_PRICE_HEX = (100000000).toString(16)
-const MIN_GAS_PRICE_DEC = '100000000'
+const MIN_GAS_PRICE_DEC = '0'
+const MIN_GAS_PRICE_HEX = (parseInt(MIN_GAS_PRICE_DEC)).toString(16)
const MIN_GAS_LIMIT_DEC = '21000'
const MIN_GAS_LIMIT_HEX = (parseInt(MIN_GAS_LIMIT_DEC)).toString(16)
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 30d3d3152..bd00b186e 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -493,7 +493,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
history,
} = this.props
- const missingTokenBalance = selectedToken && !tokenBalance
+ const missingTokenBalance = selectedToken && (tokenBalance === null || tokenBalance === undefined)
const noErrors = !amountError && toError === null
return h('div.page-container__footer', [
diff --git a/ui/app/token-util.js b/ui/app/token-util.js
index f84051ef5..920442bfc 100644
--- a/ui/app/token-util.js
+++ b/ui/app/token-util.js
@@ -1,14 +1,6 @@
-const abi = require('human-standard-token-abi')
-const Eth = require('ethjs-query')
-const EthContract = require('ethjs-contract')
-
-const tokenInfoGetter = function () {
- if (typeof global.ethereumProvider === 'undefined') return
-
- const eth = new Eth(global.ethereumProvider)
- const contract = new EthContract(eth)
- const TokenContract = contract(abi)
+const util = require('./util')
+function tokenInfoGetter () {
const tokens = {}
return async (address) => {
@@ -16,18 +8,35 @@ const tokenInfoGetter = function () {
return tokens[address]
}
- const contract = TokenContract.at(address)
+ tokens[address] = await getSymbolAndDecimals(address)
- const result = await Promise.all([
- contract.symbol(),
- contract.decimals(),
- ])
+ return tokens[address]
+ }
+}
- const [ symbol = [], decimals = [] ] = result
+async function getSymbolAndDecimals (tokenAddress, existingTokens = []) {
+ const existingToken = existingTokens.find(({ address }) => tokenAddress === address)
+ if (existingToken) {
+ return existingToken
+ }
+
+ let result = []
+ try {
+ const token = util.getContractAtAddress(tokenAddress)
+
+ result = await Promise.all([
+ token.symbol(),
+ token.decimals(),
+ ])
+ } catch (err) {
+ console.log(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, err)
+ }
- tokens[address] = { symbol: symbol[0], decimals: decimals[0] }
+ const [ symbol = [], decimals = [] ] = result
- return tokens[address]
+ return {
+ symbol: symbol[0] || null,
+ decimals: decimals[0] && decimals[0].toString() || null,
}
}
@@ -42,4 +51,5 @@ function calcTokenAmount (value, decimals) {
module.exports = {
tokenInfoGetter,
calcTokenAmount,
+ getSymbolAndDecimals,
}