Merge branch 'master' of github.com:MetaMask/metamask-plugin into json-rpc-engine-bump

feature/default_network_editable
kumavis 7 years ago
commit 77e02d264a
  1. 11
      CHANGELOG.md
  2. 2
      app/manifest.json
  3. 12
      app/scripts/lib/pending-tx-tracker.js
  4. 2
      circle.yml
  5. 4
      development/index.html
  6. 5
      development/test.html
  7. 3
      mascara/src/proxy.js
  8. 49
      mascara/src/ui.js
  9. 21
      mascara/test/index.html
  10. 119
      mascara/test/lib/first-time.js
  11. 12
      mascara/test/test-ui.js
  12. 13
      mascara/test/testem.yml
  13. 5
      mascara/test/window-load.js
  14. 5
      mock-dev.js
  15. 45
      package.json
  16. 6
      test/base.conf.js
  17. 8
      test/flat.conf.js
  18. 27
      test/integration/lib/first-time.js
  19. 17
      test/mascara.conf.js
  20. 4
      ui-dev.js
  21. 65
      ui/app/components/pending-tx.js
  22. 2
      ui/app/components/tooltip.js
  23. 2
      ui/app/components/transaction-list-item-icon.js
  24. 2
      ui/app/components/transaction-list-item.js
  25. 5
      ui/app/send.js
  26. 7
      ui/app/util.js

@ -2,11 +2,22 @@
## Current Master ## Current Master
- Fix bug that would sometimes display transactions as failed that could be successfully mined.
## 3.10.2 2017-9-18
rollback to 3.10.0 due to bug
## 3.10.1 2017-9-18
- Add ability to export private keys as a file. - Add ability to export private keys as a file.
- Add ability to export seed words as a file. - Add ability to export seed words as a file.
- Changed state logs to a file download than a clipboard copy. - Changed state logs to a file download than a clipboard copy.
- Add specific error for failed recipient address checksum.
- Fixed a long standing memory leak associated with filters installed by dapps - Fixed a long standing memory leak associated with filters installed by dapps
- Fix link to support center. - Fix link to support center.
- Fixed tooltip icon locations to avoid overflow.
- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher)
## 3.10.0 2017-9-11 ## 3.10.0 2017-9-11

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "3.10.0", "version": "3.10.2",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

@ -76,6 +76,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
Dont marked as failed if the error is a "known" transaction warning Dont marked as failed if the error is a "known" transaction warning
"there is already a transaction with the same sender-nonce "there is already a transaction with the same sender-nonce
but higher/same gas price" but higher/same gas price"
Also don't mark as failed if it has ever been broadcast successfully.
A successful broadcast means it may still be mined.
*/ */
const errorMessage = err.message.toLowerCase() const errorMessage = err.message.toLowerCase()
const isKnownTx = ( const isKnownTx = (
@ -88,6 +91,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
// other // other
|| errorMessage.includes('gateway timeout') || errorMessage.includes('gateway timeout')
|| errorMessage.includes('nonce too low') || errorMessage.includes('nonce too low')
|| txMeta.retryCount > 1
) )
// ignore resubmit warnings, return early // ignore resubmit warnings, return early
if (isKnownTx) return if (isKnownTx) return
@ -117,10 +121,12 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
// Only auto-submit already-signed txs: // Only auto-submit already-signed txs:
if (!('rawTx' in txMeta)) return if (!('rawTx' in txMeta)) return
// Increment a try counter.
txMeta.retryCount++
const rawTx = txMeta.rawTx const rawTx = txMeta.rawTx
return await this.publishTransaction(rawTx) const txHash = await this.publishTransaction(rawTx)
// Increment successful tries:
txMeta.retryCount++
return txHash
} }
async _checkPendingTx (txMeta) { async _checkPendingTx (txMeta) {

@ -3,7 +3,7 @@ machine:
version: 8.1.4 version: 8.1.4
test: test:
override: override:
- "npm run ci" - "npm test"
dependencies: dependencies:
pre: pre:
- sudo apt-get update - sudo apt-get update

@ -14,13 +14,13 @@
</body> </body>
<style> <style>
html, body, #app-content, .super-dev-container { html, body, #test-container, .super-dev-container {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
background: white; background: white;
} }
.mock-app-root { #app-content {
background: #F7F7F7; background: #F7F7F7;
} }
</style> </style>

@ -18,13 +18,14 @@
</body> </body>
<style> <style>
html, body, #app-content, .super-dev-container { html, body, #test-container, .super-dev-container {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
background: white; background: white;
} }
.mock-app-root {
#app-content {
background: #F7F7F7; background: #F7F7F7;
} }
</style> </style>

@ -1,7 +1,6 @@
const createParentStream = require('iframe-stream').ParentStream const createParentStream = require('iframe-stream').ParentStream
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
const SwStream = require('sw-stream/lib/sw-stream.js') const SwStream = require('sw-stream/lib/sw-stream.js')
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js')
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
const background = new SWcontroller({ const background = new SWcontroller({
@ -12,7 +11,7 @@ const background = new SWcontroller({
}) })
const pageStream = createParentStream() const pageStream = createParentStream()
background.on('ready', (_) => { background.on('ready', () => {
let swStream = SwStream({ let swStream = SwStream({
serviceWorker: background.controller, serviceWorker: background.controller,
context: 'dapp', context: 'dapp',

@ -2,8 +2,6 @@ const injectCss = require('inject-css')
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
const SwStream = require('sw-stream/lib/sw-stream.js') const SwStream = require('sw-stream/lib/sw-stream.js')
const MetaMaskUiCss = require('../../ui/css') const MetaMaskUiCss = require('../../ui/css')
const setupIframe = require('./lib/setup-iframe.js')
const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js')
const MetamascaraPlatform = require('../../app/scripts/platforms/window') const MetamascaraPlatform = require('../../app/scripts/platforms/window')
const startPopup = require('../../app/scripts/popup-core') const startPopup = require('../../app/scripts/popup-core')
@ -17,6 +15,7 @@ const container = document.getElementById('app-content')
var name = 'popup' var name = 'popup'
window.METAMASK_UI_TYPE = name window.METAMASK_UI_TYPE = name
window.METAMASK_PLATFORM_TYPE = 'mascara'
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
@ -32,25 +31,39 @@ const connectApp = function (readSw) {
serviceWorker: background.controller, serviceWorker: background.controller,
context: name, context: name,
}) })
startPopup({container, connectionStream}, (err, store) => { return new Promise((resolve, reject) => {
if (err) return displayCriticalError(err) startPopup({ container, connectionStream }, (err, store) => {
store.subscribe(() => { console.log('hello from MetaMascara ui!')
const state = store.getState() if (err) reject(err)
if (state.appState.shouldClose) window.close() store.subscribe(() => {
const state = store.getState()
if (state.appState.shouldClose) window.close()
})
resolve()
}) })
}) })
} }
background.on('ready', (sw) => { background.on('ready', async (sw) => {
background.removeListener('updatefound', connectApp) try {
connectApp(sw) background.removeListener('updatefound', connectApp)
await timeout(1000)
await connectApp(sw)
console.log('hello from cb ready event!')
} catch (e) {
console.error(e)
}
}) })
background.on('updatefound', () => window.location.reload()) background.on('updatefound', windowReload)
background.startWorker() background.startWorker()
.then(() => {
setTimeout(() => { function windowReload() {
const appContent = document.getElementById(`app-content`) if (window.METAMASK_SKIP_RELOAD) return
if (!appContent.children.length) window.location.reload() window.location.reload()
}, 2000) }
})
console.log('hello from MetaMascara ui!') function timeout (time) {
return new Promise((resolve) => {
setTimeout(resolve, time || 1500)
})
}

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script>
<script src="./jquery-3.1.0.min.js"></script>
<script src="./helpers.js"></script>
<script src="./test-bundle.js"></script>
<script src="/testem.js"></script>
<div id="app-content"></div>
<script src="./bundle.js"></script>
</body>
</html>

@ -1,119 +0,0 @@
const PASSWORD = 'password123'
QUnit.module('first time usage')
QUnit.test('render init screen', function (assert) {
var done = assert.async()
let app
wait(1000).then(function() {
app = $('#app-content').contents()
const recurseNotices = function () {
let button = app.find('button')
if (button.html() === 'Accept') {
let termsPage = app.find('.markdown')[0]
termsPage.scrollTop = termsPage.scrollHeight
return wait().then(() => {
button.click()
return wait()
}).then(() => {
return recurseNotices()
})
} else {
return wait()
}
}
return recurseNotices()
}).then(function() {
// Scroll through terms
var title = app.find('h1').text()
assert.equal(title, 'MetaMask', 'title screen')
// enter password
var pwBox = app.find('#password-box')[0]
var confBox = app.find('#password-box-confirm')[0]
pwBox.value = PASSWORD
confBox.value = PASSWORD
return wait()
}).then(function() {
// create vault
var createButton = app.find('button.primary')[0]
createButton.click()
return wait(1500)
}).then(function() {
var created = app.find('h3')[0]
assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
// Agree button
var button = app.find('button')[0]
assert.ok(button, 'button present')
button.click()
return wait(1000)
}).then(function() {
var detail = app.find('.account-detail-section')[0]
assert.ok(detail, 'Account detail section loaded.')
var sandwich = app.find('.sandwich-expando')[0]
sandwich.click()
return wait()
}).then(function() {
var sandwich = app.find('.menu-droppo')[0]
var children = sandwich.children
var lock = children[children.length - 2]
assert.ok(lock, 'Lock menu item found')
lock.click()
return wait(1000)
}).then(function() {
var pwBox = app.find('#password-box')[0]
pwBox.value = PASSWORD
var createButton = app.find('button.primary')[0]
createButton.click()
return wait(1000)
}).then(function() {
var detail = app.find('.account-detail-section')[0]
assert.ok(detail, 'Account detail section loaded again.')
return wait()
}).then(function (){
var qrButton = app.find('.fa.fa-qrcode')[0]
qrButton.click()
return wait(1000)
}).then(function (){
var qrHeader = app.find('.qr-header')[0]
var qrContainer = app.find('#qr-container')[0]
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
assert.ok(qrContainer, 'QR Container found')
return wait()
}).then(function (){
var networkMenu = app.find('.network-indicator')[0]
networkMenu.click()
return wait()
}).then(function (){
var networkMenu = app.find('.network-indicator')[0]
var children = networkMenu.children
children.length[3]
assert.ok(children, 'All network options present')
done()
})
})

@ -0,0 +1,12 @@
const Helper = require('./util/mascara-test-helper.js')
window.addEventListener('load', () => {
window.METAMASK_SKIP_RELOAD = true
// inject app container
const body = document.body
const container = document.createElement('div')
container.id = 'app-content'
body.appendChild(container)
// start ui
require('../src/ui.js')
})

@ -1,13 +0,0 @@
launch_in_dev:
- Chrome
- Firefox
- Opera
launch_in_ci:
- Chrome
- Firefox
- Opera
framework:
- qunit
before_tests: "npm run mascaraCi"
after_tests: "rm ./background.js ./test-bundle.js ./bundle.js"
test_page: "./index.html"

@ -1,5 +0,0 @@
const Helper = require('./util/mascara-test-helper.js')
window.addEventListener('load', () => {
require('../src/ui.js')
})

@ -94,9 +94,8 @@ startApp()
function startApp(){ function startApp(){
const body = document.body const body = document.body
const container = document.createElement('div') const container = document.createElement('div')
container.id = 'app-content' container.id = 'test-container'
body.appendChild(container) body.appendChild(container)
console.log('container', container)
render( render(
h('.super-dev-container', [ h('.super-dev-container', [
@ -113,7 +112,7 @@ function startApp(){
h(Selector, { actions, selectedKey: selectedView, states, store }), h(Selector, { actions, selectedKey: selectedView, states, store }),
h('.mock-app-root', { h('#app-content', {
style: { style: {
height: '500px', height: '500px',
width: '360px', width: '360px',

@ -6,30 +6,33 @@
"scripts": { "scripts": {
"start": "npm run dev", "start": "npm run dev",
"dev": "gulp dev --debug", "dev": "gulp dev --debug",
"disc": "gulp disc --debug", "ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"dist": "npm run clear && npm install && gulp dist",
"test": "npm run lint && npm run test-unit && npm run test-integration",
"test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
"single-test": "METAMASK_ENV=test mocha --require test/helper.js",
"test-integration": "npm run buildMock && npm run buildCiUnits && karma start",
"test-coverage": "nyc npm run test-unit && if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi",
"ci": "npm run lint && npm run test-coverage && npm run test-integration",
"lint": "gulp lint",
"buildCiUnits": "node test/integration/index.js",
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
"genStates": "node development/genStates.js",
"ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js", "watch": "mocha watch --recursive \"test/unit/**/*.js\"",
"mascara": "node ./mascara/example/server",
"dist": "npm run dist:clear && npm install && gulp dist",
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"test": "npm run lint && npm run test:coverage && npm run test:integration",
"test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:single": "METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "npm run test:flat && npm run test:mascara",
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
"test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi",
"test:flat": "npm run test:flat:build && karma start test/flat.conf.js",
"test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests",
"test:flat:build:tests": "node test/integration/index.js",
"test:flat:build:states": "node development/genStates.js",
"test:flat:build:ui": "npm run test:flat:build:states && browserify ./mock-dev.js -o ./development/bundle.js",
"test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js",
"test:mascara:build": "mkdir -p dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests",
"test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js",
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
"lint": "gulp lint",
"disc": "gulp disc --debug",
"announce": "node development/announcer.js", "announce": "node development/announcer.js",
"generateNotice": "node notices/notice-generator.js", "generateNotice": "node notices/notice-generator.js",
"deleteNotice": "node notices/notice-delete.js", "deleteNotice": "node notices/notice-delete.js"
"mascara": "node ./mascara/example/server",
"buildMascaraCi": "browserify mascara/test/window-load.js -o mascara/test/bundle.js",
"buildMascaraSWCi": "browserify mascara/src/background.js -o mascara/test/background.js",
"mascaraCi": "npm run buildMascaraCi && npm run buildMascaraSWCi && node mascara/test/index.js",
"testMascara": "cd mascara/test && npm run mascaraCi && testem ci -P 3"
}, },
"browserify": { "browserify": {
"transform": [ "transform": [

@ -2,7 +2,7 @@
// Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT) // Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT)
module.exports = function(config) { module.exports = function(config) {
config.set({ return {
// base path that will be used to resolve all patterns (eg. files, exclude) // base path that will be used to resolve all patterns (eg. files, exclude)
basePath: process.cwd(), basePath: process.cwd(),
@ -16,9 +16,7 @@ module.exports = function(config) {
// list of files / patterns to load in the browser // list of files / patterns to load in the browser
files: [ files: [
'development/bundle.js',
'test/integration/jquery-3.1.0.min.js', 'test/integration/jquery-3.1.0.min.js',
'test/integration/bundle.js',
{ pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true }, { pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true },
{ pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true }, { pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true },
], ],
@ -57,5 +55,5 @@ module.exports = function(config) {
// Concurrency level // Concurrency level
// how many browser should be started simultaneous // how many browser should be started simultaneous
concurrency: Infinity concurrency: Infinity
}) }
} }

@ -0,0 +1,8 @@
const getBaseConfig = require('./base.conf.js')
module.exports = function(config) {
const settings = getBaseConfig(config)
settings.files.push('development/bundle.js')
settings.files.push('test/integration/bundle.js')
config.set(settings)
}

@ -10,19 +10,12 @@ QUnit.test('render init screen', (assert) => {
}) })
}) })
// QUnit.testDone(({ module, name, total, passed, failed, skipped, todo, runtime }) => {
// if (failed > 0) {
// const app = $('iframe').contents()[0].documentElement
// console.warn('Test failures - dumping DOM:')
// console.log(app.innerHTML)
// }
// })
async function runFirstTimeUsageTest(assert, done) { async function runFirstTimeUsageTest(assert, done) {
let waitTime = 0
if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 4000
await timeout(waitTime)
await timeout() const app = $('#app-content')
const app = $('#app-content .mock-app-root')
// recurse notices // recurse notices
while (true) { while (true) {
@ -32,10 +25,12 @@ async function runFirstTimeUsageTest(assert, done) {
const termsPage = app.find('.markdown')[0] const termsPage = app.find('.markdown')[0]
termsPage.scrollTop = termsPage.scrollHeight termsPage.scrollTop = termsPage.scrollHeight
await timeout() await timeout()
console.log('Clearing notice')
button.click() button.click()
await timeout() await timeout()
} else { } else {
// exit loop // exit loop
console.log('No more notices...')
break break
} }
} }
@ -58,7 +53,7 @@ async function runFirstTimeUsageTest(assert, done) {
const createButton = app.find('button.primary')[0] const createButton = app.find('button.primary')[0]
createButton.click() createButton.click()
await timeout(1500) await timeout(3000)
const created = app.find('h3')[0] const created = app.find('h3')[0]
assert.equal(created.textContent, 'Vault Created', 'Vault created screen') assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
@ -129,10 +124,8 @@ async function runFirstTimeUsageTest(assert, done) {
assert.ok(children2, 'All network options present') assert.ok(children2, 'All network options present')
} }
function timeout(time) { function timeout (time) {
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
setTimeout(function () { setTimeout(resolve, time || 1500)
resolve()
}, time * 3 || 1500)
}) })
} }

@ -0,0 +1,17 @@
const getBaseConfig = require('./base.conf.js')
module.exports = function(config) {
const settings = getBaseConfig(config)
// ui and tests
settings.files.push('dist/mascara/ui.js')
settings.files.push('dist/mascara/tests.js')
// service worker background
settings.files.push({ pattern: 'dist/mascara/background.js', watched: false, included: false, served: true }),
settings.proxies['/background.js'] = '/base/dist/mascara/background.js'
// use this to keep the browser open for debugging
settings.browserNoActivityTimeout = 10000000
config.set(settings)
}

@ -61,7 +61,7 @@ const actions = {
var css = MetaMaskUiCss() var css = MetaMaskUiCss()
injectCss(css) injectCss(css)
const container = document.querySelector('#app-content') const container = document.querySelector('#test-container')
// parse opts // parse opts
var store = configureStore(states[selectedView]) var store = configureStore(states[selectedView])
@ -72,7 +72,7 @@ render(
h(Selector, { actions, selectedKey: selectedView, states, store }), h(Selector, { actions, selectedKey: selectedView, states, store }),
h('.mock-app-root', { h('#app-content', {
style: { style: {
height: '500px', height: '500px',
width: '360px', width: '360px',

@ -52,7 +52,9 @@ PendingTx.prototype.render = function () {
const gas = txParams.gas const gas = txParams.gas
const gasBn = hexToBn(gas) const gasBn = hexToBn(gas)
const gasLimit = new BN(parseInt(blockGasLimit)) const gasLimit = new BN(parseInt(blockGasLimit))
const safeGasLimit = this.bnMultiplyByFraction(gasLimit, 19, 20).toString(10) const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
const safeGasLimit = safeGasLimitBN.toString(10)
// Gas Price // Gas Price
const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16) const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16)
@ -66,6 +68,8 @@ PendingTx.prototype.render = function () {
const balanceBn = hexToBn(balance) const balanceBn = hexToBn(balance)
const insufficientBalance = balanceBn.lt(maxCost) const insufficientBalance = balanceBn.lt(maxCost)
const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
const gasLimitSpecified = txMeta.gasLimitSpecified
const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
const showRejectAll = props.unconfTxListLength > 1 const showRejectAll = props.unconfTxListLength > 1
@ -263,33 +267,44 @@ PendingTx.prototype.render = function () {
text-transform: uppercase; text-transform: uppercase;
} }
`), `),
h('.cell.row', {
style: {
textAlign: 'center',
},
}, [
txMeta.simulationFails ?
h('.error', {
style: {
fontSize: '0.9em',
},
}, 'Transaction Error. Exception thrown in contract code.')
: null,
txMeta.simulationFails ? !isValidAddress ?
h('.error', { h('.error', {
style: { style: {
marginLeft: 50, fontSize: '0.9em',
fontSize: '0.9em', },
}, }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
}, 'Transaction Error. Exception thrown in contract code.') : null,
: null,
!isValidAddress ? insufficientBalance ?
h('.error', { h('span.error', {
style: { style: {
marginLeft: 50, fontSize: '0.9em',
fontSize: '0.9em', },
}, }, 'Insufficient balance for transaction')
}, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') : null,
: null,
(dangerousGasLimit && !gasLimitSpecified) ?
h('span.error', {
style: {
fontSize: '0.9em',
},
}, 'Gas limit set dangerously high. Approving this transaction is likely to fail.')
: null,
]),
insufficientBalance ?
h('span.error', {
style: {
marginLeft: 50,
fontSize: '0.9em',
},
}, 'Insufficient balance for transaction')
: null,
// send + cancel // send + cancel
h('.flex-row.flex-space-around.conf-buttons', { h('.flex-row.flex-space-around.conf-buttons', {

@ -17,6 +17,6 @@ Tooltip.prototype.render = function () {
return h(ReactTooltip, { return h(ReactTooltip, {
position: position || 'left', position: position || 'left',
title, title,
fixed: false, fixed: true,
}, children) }, children)
} }

@ -35,7 +35,7 @@ TransactionIcon.prototype.render = function () {
case 'submitted': case 'submitted':
return h(Tooltip, { return h(Tooltip, {
title: 'Pending', title: 'Pending',
position: 'bottom', position: 'right',
}, [ }, [
h('i.fa.fa-ellipsis-h', { h('i.fa.fa-ellipsis-h', {
style: { style: {

@ -65,7 +65,7 @@ TransactionListItem.prototype.render = function () {
h(Tooltip, { h(Tooltip, {
title: 'Transaction Number', title: 'Transaction Number',
position: 'bottom', position: 'right',
}, [ }, [
h('span', { h('span', {
style: { style: {

@ -262,6 +262,11 @@ SendTransactionScreen.prototype.onSubmit = function () {
return this.props.dispatch(actions.displayWarning(message)) return this.props.dispatch(actions.displayWarning(message))
} }
if ((util.isInvalidChecksumAddress(recipient))) {
message = 'Recipient address checksum is invalid.'
return this.props.dispatch(actions.displayWarning(message))
}
if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) { if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) {
message = 'Recipient address is invalid.' message = 'Recipient address is invalid.'
return this.props.dispatch(actions.displayWarning(message)) return this.props.dispatch(actions.displayWarning(message))

@ -37,6 +37,7 @@ module.exports = {
bnTable: bnTable, bnTable: bnTable,
isHex: isHex, isHex: isHex,
exportAsFile: exportAsFile, exportAsFile: exportAsFile,
isInvalidChecksumAddress,
} }
function valuesFor (obj) { function valuesFor (obj) {
@ -66,6 +67,12 @@ function isValidAddress (address) {
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
} }
function isInvalidChecksumAddress (address) {
var prefixed = ethUtil.addHexPrefix(address)
if (address === '0x0000000000000000000000000000000000000000') return false
return !isAllOneCase(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) && ethUtil.isValidAddress(prefixed)
}
function isAllOneCase (address) { function isAllOneCase (address) {
if (!address) return true if (!address) return true
var lower = address.toLowerCase() var lower = address.toLowerCase()

Loading…
Cancel
Save