aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--app/scripts/background.js6
-rw-r--r--app/scripts/controllers/token-rates.js6
-rw-r--r--app/scripts/lib/setupRaven.js37
-rw-r--r--old-ui/app/components/pending-tx.js3
-rw-r--r--old-ui/app/components/shapeshift-form.js2
-rw-r--r--package-lock.json144
-rw-r--r--package.json3
-rw-r--r--test/integration/lib/send-new-ui.js59
-rw-r--r--test/stub/provider.js24
-rw-r--r--test/unit/metamask-controller-test.js33
-rw-r--r--test/unit/tx-controller-test.js22
-rw-r--r--test/unit/tx-utils-test.js2
-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/components/shapeshift-form.js13
-rw-r--r--ui/app/send-v2.js2
-rw-r--r--ui/app/token-util.js46
21 files changed, 292 insertions, 174 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/background.js b/app/scripts/background.js
index 38b871bb5..69d549c85 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -261,7 +261,11 @@ function setupController (initState, initLangCode) {
controller.txController.on(`tx:status-update`, (txId, status) => {
if (status !== 'failed') return
const txMeta = controller.txController.txStateManager.getTx(txId)
- reportFailedTxToSentry({ raven, txMeta })
+ try {
+ reportFailedTxToSentry({ raven, txMeta })
+ } catch (e) {
+ console.error(e)
+ }
})
// setup state persistence
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/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js
index 9ec9a256f..48b941c3d 100644
--- a/app/scripts/lib/setupRaven.js
+++ b/app/scripts/lib/setupRaven.js
@@ -24,19 +24,20 @@ function setupRaven(opts) {
transport: function(opts) {
const report = opts.data
// simplify certain complex error messages
- report.exception.values.forEach(item => {
- let errorMessage = item.value
- // simplify ethjs error messages
- errorMessage = extractEthjsErrorMessage(errorMessage)
- // simplify 'Transaction Failed: known transaction'
- if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
- // cut the hash from the error message
- errorMessage = 'Transaction Failed: known transaction'
- }
- // finalize
- item.value = errorMessage
- })
-
+ if (report.exception && report.exception.values) {
+ report.exception.values.forEach(item => {
+ let errorMessage = item.value
+ // simplify ethjs error messages
+ errorMessage = extractEthjsErrorMessage(errorMessage)
+ // simplify 'Transaction Failed: known transaction'
+ if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
+ // cut the hash from the error message
+ errorMessage = 'Transaction Failed: known transaction'
+ }
+ // finalize
+ item.value = errorMessage
+ })
+ }
// modify report urls
rewriteReportUrls(report)
// make request normally
@@ -52,11 +53,13 @@ function rewriteReportUrls(report) {
// update request url
report.request.url = toMetamaskUrl(report.request.url)
// update exception stack trace
- report.exception.values.forEach(item => {
- item.stacktrace.frames.forEach(frame => {
- frame.filename = toMetamaskUrl(frame.filename)
+ if (report.exception && report.exception.values) {
+ report.exception.values.forEach(item => {
+ item.stacktrace.frames.forEach(frame => {
+ frame.filename = toMetamaskUrl(frame.filename)
+ })
})
- })
+ }
}
function toMetamaskUrl(origUrl) {
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/old-ui/app/components/shapeshift-form.js b/old-ui/app/components/shapeshift-form.js
index a54987c04..97068db0a 100644
--- a/old-ui/app/components/shapeshift-form.js
+++ b/old-ui/app/components/shapeshift-form.js
@@ -138,7 +138,7 @@ ShapeshiftForm.prototype.renderMain = function () {
width: '229px',
height: '82px',
},
- }, this.props.warning)
+ }, this.props.warning + '')
: this.renderInfo(),
this.renderRefundAddressForCoin(coin),
diff --git a/package-lock.json b/package-lock.json
index ae9bbb420..884f53ce8 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",
@@ -4358,7 +4377,7 @@
"requires": {
"file-type": "5.2.0",
"is-stream": "1.1.0",
- "tar-stream": "1.5.5"
+ "tar-stream": "1.6.0"
}
},
"decompress-tarbz2": {
@@ -5887,25 +5906,26 @@
"resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.0.0.tgz",
"integrity": "sha512-Ab6170AxlF4DK+HDImh52+AetwHPHstgg8uWtX4im26rqK7u4ziSfvUIUK2+/LK0pi0wbIFb8hZm5jPKAXDmBA==",
"requires": {
- "eth-json-rpc-middleware": "1.5.0",
+ "eth-json-rpc-middleware": "1.6.0",
"json-rpc-engine": "3.6.1",
"json-rpc-error": "2.0.0",
"tape": "4.8.0"
}
},
"eth-json-rpc-middleware": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.5.0.tgz",
- "integrity": "sha1-FrEFM4aqOAOxJXMqpt4H6t8Ghyk=",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz",
+ "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==",
"requires": {
"async": "2.6.0",
"eth-query": "2.1.2",
- "eth-tx-summary": "3.1.2",
+ "eth-tx-summary": "3.2.1",
"ethereumjs-block": "1.7.0",
"ethereumjs-tx": "1.3.3",
- "ethereumjs-util": "5.1.5",
+ "ethereumjs-util": "5.2.0",
"ethereumjs-vm": "2.3.2",
"fetch-ponyfill": "4.1.0",
+ "json-rpc-engine": "3.6.1",
"json-rpc-error": "2.0.0",
"json-stable-stringify": "1.0.1",
"promise-to-callback": "1.0.0",
@@ -5913,9 +5933,9 @@
},
"dependencies": {
"ethereumjs-util": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz",
- "integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
+ "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"requires": {
"bn.js": "4.11.8",
"create-hash": "1.1.3",
@@ -6243,11 +6263,12 @@
}
},
"eth-tx-summary": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.1.2.tgz",
- "integrity": "sha1-44g2/J+LVvFNdZUvD15XD4j7IiA=",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.1.tgz",
+ "integrity": "sha512-mu8g5tDkQxlFah58ggFhTzolE4OnYTj6j8SVsnGsiWT7WxN722RwnEsk/bco2foy+PLSEF2Mnoiw+wCqKoY72A==",
"requires": {
"async": "2.6.0",
+ "bn.js": "4.11.8",
"clone": "2.1.1",
"concat-stream": "1.6.0",
"end-of-stream": "1.4.0",
@@ -6255,12 +6276,46 @@
"ethereumjs-block": "1.7.0",
"ethereumjs-tx": "1.3.3",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
- "ethereumjs-vm": "2.3.2",
+ "ethereumjs-vm": "2.3.5",
"through2": "2.0.3",
"treeify": "1.1.0",
"web3-provider-engine": "13.8.0"
},
"dependencies": {
+ "ethereumjs-vm": {
+ "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",
+ "ethereum-common": "0.2.0",
+ "ethereumjs-account": "2.0.4",
+ "ethereumjs-block": "1.7.0",
+ "ethereumjs-util": "5.2.0",
+ "fake-merkle-patricia-tree": "1.0.1",
+ "functional-red-black-tree": "1.0.1",
+ "merkle-patricia-tree": "2.3.0",
+ "rustbn.js": "0.1.1",
+ "safe-buffer": "5.1.1"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
+ "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
+ "requires": {
+ "bn.js": "4.11.8",
+ "create-hash": "1.1.3",
+ "ethjs-util": "0.1.4",
+ "keccak": "1.4.0",
+ "rlp": "2.0.0",
+ "safe-buffer": "5.1.1",
+ "secp256k1": "3.4.0"
+ }
+ }
+ }
+ },
"web3-provider-engine": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz",
@@ -6272,8 +6327,8 @@
"eth-sig-util": "1.4.2",
"ethereumjs-block": "1.7.0",
"ethereumjs-tx": "1.3.3",
- "ethereumjs-util": "5.1.5",
- "ethereumjs-vm": "2.3.2",
+ "ethereumjs-util": "5.2.0",
+ "ethereumjs-vm": "2.3.5",
"fetch-ponyfill": "4.1.0",
"json-rpc-error": "2.0.0",
"json-stable-stringify": "1.0.1",
@@ -6288,9 +6343,9 @@
},
"dependencies": {
"ethereumjs-util": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz",
- "integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
+ "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"requires": {
"bn.js": "4.11.8",
"create-hash": "1.1.3",
@@ -7807,6 +7862,11 @@
"null-check": "1.0.0"
}
},
+ "fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
+ },
"fs-exists-sync": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
@@ -8888,7 +8948,7 @@
"ethereumjs-account": "2.0.4",
"ethereumjs-block": "1.2.2",
"ethereumjs-tx": "1.3.3",
- "ethereumjs-util": "5.1.5",
+ "ethereumjs-util": "5.2.0",
"ethereumjs-vm": "2.3.3",
"ethereumjs-wallet": "0.6.0",
"fake-merkle-patricia-tree": "1.0.1",
@@ -8909,7 +8969,7 @@
"tmp": "0.0.31",
"web3": "1.0.0-beta.34",
"web3-provider-engine": "13.8.0",
- "websocket": "1.0.25",
+ "websocket": "1.0.26",
"yargs": "7.1.0"
},
"dependencies": {
@@ -9009,9 +9069,9 @@
}
},
"ethereumjs-util": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz",
- "integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
+ "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"requires": {
"bn.js": "4.11.6",
"create-hash": "1.1.3",
@@ -9032,7 +9092,7 @@
"ethereum-common": "0.2.0",
"ethereumjs-account": "2.0.4",
"ethereumjs-block": "1.7.1",
- "ethereumjs-util": "5.1.5",
+ "ethereumjs-util": "5.2.0",
"fake-merkle-patricia-tree": "1.0.1",
"functional-red-black-tree": "1.0.1",
"merkle-patricia-tree": "2.3.0",
@@ -9053,7 +9113,7 @@
"async": "2.6.0",
"ethereum-common": "0.2.0",
"ethereumjs-tx": "1.3.3",
- "ethereumjs-util": "5.1.5",
+ "ethereumjs-util": "5.2.0",
"merkle-patricia-tree": "2.3.0"
}
}
@@ -9191,7 +9251,7 @@
"eth-sig-util": "1.4.2",
"ethereumjs-block": "1.2.2",
"ethereumjs-tx": "1.3.3",
- "ethereumjs-util": "5.1.5",
+ "ethereumjs-util": "5.2.0",
"ethereumjs-vm": "2.3.3",
"fetch-ponyfill": "4.1.0",
"json-rpc-error": "2.0.0",
@@ -24280,13 +24340,16 @@
}
},
"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.6.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.0.tgz",
+ "integrity": "sha512-lh2iAPG/BHNmN6WB9Ybdynk9rEJ5GD/dy4zscHmVlwa1dq2tpE+BH78i5vjYwYVWEaOXGBjzxr89aVACF17Cpw==",
"requires": {
"bl": "1.2.1",
+ "buffer-alloc": "1.1.0",
"end-of-stream": "1.4.0",
+ "fs-constants": "1.0.0",
"readable-stream": "2.3.3",
+ "to-buffer": "1.1.1",
"xtend": "4.0.1"
}
},
@@ -24674,6 +24737,11 @@
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M="
},
+ "to-buffer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
+ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
+ },
"to-fast-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
@@ -26276,7 +26344,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",
@@ -26309,7 +26377,7 @@
"integrity": "sha512-uMYkEP6fga8CyNo8TMoA/7cxi6bL3V8pTvjKQikOi9iYl6/AO5xlfgniyAMElSiq2mmXz3lYa/9VYDMzt/J5aA==",
"requires": {
"cross-fetch": "2.1.0",
- "eth-json-rpc-middleware": "1.5.0",
+ "eth-json-rpc-middleware": "1.6.0",
"json-rpc-engine": "3.6.1",
"json-rpc-error": "2.0.0",
"tape": "4.8.0"
@@ -26330,9 +26398,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",
@@ -27009,9 +27077,9 @@
}
},
"websocket": {
- "version": "1.0.25",
- "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.25.tgz",
- "integrity": "sha512-M58njvi6ZxVb5k7kpnHh2BvNKuBWiwIYvsToErBzWhvBZYwlEiLcyLrG41T1jRcrY9ettqPYEqduLI7ul54CVQ==",
+ "version": "1.0.26",
+ "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.26.tgz",
+ "integrity": "sha512-fjcrYDPIQxpTnqFQ9JjxUQcdvR89MFAOjPBlF+vjOt49w/XW4fJknUoMz/mDIn2eK1AdslVojcaOxOqyZZV8rw==",
"requires": {
"debug": "2.6.9",
"nan": "2.8.0",
diff --git a/package.json b/package.json
index 39182fba0..4528ca31b 100644
--- a/package.json
+++ b/package.json
@@ -217,9 +217,10 @@
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-mocha": "^5.0.0",
"eslint-plugin-react": "^7.4.0",
- "eth-json-rpc-middleware": "^1.2.7",
+ "eth-json-rpc-middleware": "^1.6.0",
"fs-promise": "^2.0.3",
"ganache-cli": "^6.1.0",
+ "ganache-core": "^2.1.0",
"gifencoder": "^1.1.0",
"gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed",
"gulp-babel": "^7.0.0",
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/stub/provider.js b/test/stub/provider.js
index e77db4e28..a1c70486d 100644
--- a/test/stub/provider.js
+++ b/test/stub/provider.js
@@ -1,14 +1,28 @@
const JsonRpcEngine = require('json-rpc-engine')
const scaffoldMiddleware = require('eth-json-rpc-middleware/scaffold')
-const TestBlockchain = require('eth-block-tracker/test/util/testBlockMiddleware')
+const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
+const GanacheCore = require('ganache-core')
module.exports = {
createEngineForTestData,
providerFromEngine,
scaffoldMiddleware,
createTestProviderTools,
+ getTestSeed,
+ getTestAccounts,
}
+function getTestSeed () {
+ return 'people carpet cluster attract ankle motor ozone mass dove original primary mask'
+}
+
+function getTestAccounts () {
+ return [
+ { address: '0x88bb7F89eB5e5b30D3e15a57C68DBe03C6aCCB21', key: Buffer.from('254A8D551474F35CCC816388B4ED4D20B945C96B7EB857A68064CB9E9FB2C092', 'hex') },
+ { address: '0x1fe9aAB565Be19629fF4e8541ca2102fb42D7724', key: Buffer.from('6BAB5A4F2A6911AF8EE2BD32C6C05F6643AC48EF6C939CDEAAAE6B1620805A9B', 'hex') },
+ { address: '0xbda5c89aa6bA1b352194291AD6822C92AbC87c7B', key: Buffer.from('9B11D7F833648F26CE94D544855558D7053ECD396E4F4563968C232C012879B0', 'hex') },
+ ]
+}
function createEngineForTestData () {
return new JsonRpcEngine()
@@ -21,11 +35,13 @@ function providerFromEngine (engine) {
function createTestProviderTools (opts = {}) {
const engine = createEngineForTestData()
- const testBlockchain = new TestBlockchain()
// handle provided hooks
engine.push(scaffoldMiddleware(opts.scaffold || {}))
// handle block tracker methods
- engine.push(testBlockchain.createMiddleware())
+ engine.push(providerAsMiddleware(GanacheCore.provider({
+ mnemonic: getTestSeed(),
+ })))
+ // wrap in standard provider interface
const provider = providerFromEngine(engine)
- return { provider, engine, testBlockchain }
+ return { provider, engine }
}
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/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js
index 20d6f8573..0b5c7226a 100644
--- a/test/unit/tx-controller-test.js
+++ b/test/unit/tx-controller-test.js
@@ -6,16 +6,15 @@ const ObservableStore = require('obs-store')
const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions')
const TxGasUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils')
-const { createTestProviderTools } = require('../stub/provider')
+const { createTestProviderTools, getTestAccounts } = require('../stub/provider')
const noop = () => true
const currentNetworkId = 42
const otherNetworkId = 36
-const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
describe('Transaction Controller', function () {
- let txController, provider, providerResultStub, testBlockchain
+ let txController, provider, providerResultStub, query, fromAccount
beforeEach(function () {
providerResultStub = {
@@ -24,9 +23,9 @@ describe('Transaction Controller', function () {
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0x',
}
- const providerTools = createTestProviderTools({ scaffold: providerResultStub })
- provider = providerTools.provider
- testBlockchain = providerTools.testBlockchain
+ provider = createTestProviderTools({ scaffold: providerResultStub }).provider
+ query = new EthjsQuery(provider)
+ fromAccount = getTestAccounts()[0]
txController = new TransactionController({
provider,
@@ -34,7 +33,7 @@ describe('Transaction Controller', function () {
txHistoryLimit: 10,
blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
signTransaction: (ethTx) => new Promise((resolve) => {
- ethTx.sign(privKey)
+ ethTx.sign(fromAccount.key)
resolve()
}),
})
@@ -298,12 +297,12 @@ describe('Transaction Controller', function () {
describe('#updateAndApproveTransaction', function () {
let txMeta
- beforeEach(function () {
+ beforeEach(() => {
txMeta = {
id: 1,
status: 'unapproved',
txParams: {
- from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
@@ -312,11 +311,12 @@ describe('Transaction Controller', function () {
metamaskNetworkId: currentNetworkId,
}
})
- it('should update and approve transactions', function () {
+ it('should update and approve transactions', async () => {
txController.txStateManager.addTx(txMeta)
- txController.updateAndApproveTransaction(txMeta)
+ const approvalPromise = txController.updateAndApproveTransaction(txMeta)
const tx = txController.txStateManager.getTx(1)
assert.equal(tx.status, 'approved')
+ await approvalPromise
})
})
diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js
index ae7afd285..be16225ba 100644
--- a/test/unit/tx-utils-test.js
+++ b/test/unit/tx-utils-test.js
@@ -95,4 +95,4 @@ describe('txUtils', function () {
txUtils.validateFrom(txParams)
})
})
-}) \ No newline at end of file
+})
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/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index fd4a80a4a..22ab64426 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -55,6 +55,10 @@ function ShapeshiftForm () {
}
}
+ShapeshiftForm.prototype.getCoinPair = function () {
+ return `${this.state.depositCoin.toUpperCase()}_ETH`
+}
+
ShapeshiftForm.prototype.componentWillMount = function () {
this.props.shapeShiftSubview()
}
@@ -120,14 +124,12 @@ ShapeshiftForm.prototype.renderMetadata = function (label, value) {
}
ShapeshiftForm.prototype.renderMarketInfo = function () {
- const { depositCoin } = this.state
- const coinPair = `${depositCoin}_eth`
const { tokenExchangeRates } = this.props
const {
limit,
rate,
minimum,
- } = tokenExchangeRates[coinPair] || {}
+ } = tokenExchangeRates[this.getCoinPair()] || {}
return h('div.shapeshift-form__metadata', {}, [
@@ -172,10 +174,9 @@ ShapeshiftForm.prototype.renderQrCode = function () {
ShapeshiftForm.prototype.render = function () {
const { coinOptions, btnClass, warning } = this.props
- const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state
- const coinPair = `${depositCoin}_eth`
+ const { errorMessage, showQrCode, depositAddress } = this.state
const { tokenExchangeRates } = this.props
- const token = tokenExchangeRates[coinPair]
+ const token = tokenExchangeRates[this.getCoinPair()]
return h('div.shapeshift-form-wrapper', [
showQrCode
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,
}