1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'master' into uiFixes

This commit is contained in:
Frankie 2016-06-06 10:20:05 -04:00
commit 2c2fcd60bf
23 changed files with 477 additions and 68 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ package
.DS_Store .DS_Store
builds/ builds/
notes.txt

View File

@ -2,9 +2,16 @@
## Current Master ## Current Master
- Show network status in title bar
- Added seed word recovery to config screen.
- Clicking network status indicator now reveals a provider menu.
## 2.2.0 2016-06-02
- Redesigned init, vault create, vault restore and seed confirmation screens. - Redesigned init, vault create, vault restore and seed confirmation screens.
- Added pending transactions to transaction list on account screen. - Added pending transactions to transaction list on account screen.
- Clicking a pending transaction takes you back to the transaction approval screen. - Clicking a pending transaction takes you back to the transaction approval screen.
- Update provider-engine to fix intermittent out of gas errors.
## 2.1.0 2016-05-26 ## 2.1.0 2016-05-26

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,7 +1,7 @@
{ {
"name": "__MSG_appName__", "name": "__MSG_appName__",
"short_name": "Metamask", "short_name": "Metamask",
"version": "2.1.0", "version": "2.2.0",
"manifest_version": 2, "manifest_version": 2,
"description": "__MSG_appDescription__", "description": "__MSG_appDescription__",
"icons": { "icons": {

View File

@ -76,13 +76,20 @@ var providerOpts = {
var provider = MetaMaskProvider(providerOpts) var provider = MetaMaskProvider(providerOpts)
var web3 = new Web3(provider) var web3 = new Web3(provider)
idStore.web3 = web3 idStore.web3 = web3
idStore.getNetwork(3) idStore.getNetwork()
// log new blocks // log new blocks
provider.on('block', function(block){ provider.on('block', function(block){
console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex')) console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex'))
// Check network when restoring connectivity:
if (idStore._currentState.network === 'loading') {
idStore.getNetwork()
}
}) })
provider.on('error', idStore.getNetwork.bind(idStore))
var ethStore = new EthStore(provider) var ethStore = new EthStore(provider)
idStore.setStore(ethStore) idStore.setStore(ethStore)
@ -145,7 +152,7 @@ function setupPublicConfig(stream){
} }
function setupProviderConnection(stream, originDomain){ function setupProviderConnection(stream, originDomain){
stream.on('data', function onRpcRequest(payload){ stream.on('data', function onRpcRequest(payload){
// Append origin to rpc payload // Append origin to rpc payload
payload.origin = originDomain payload.origin = originDomain
@ -195,6 +202,8 @@ function setupControllerConnection(stream){
exportAccount: idStore.exportAccount.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore),
revealAccount: idStore.revealAccount.bind(idStore), revealAccount: idStore.revealAccount.bind(idStore),
saveAccountLabel: idStore.saveAccountLabel.bind(idStore), saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
tryPassword: idStore.tryPassword.bind(idStore),
recoverSeed: idStore.recoverSeed.bind(idStore),
}) })
stream.pipe(dnode).pipe(stream) stream.pipe(dnode).pipe(stream)
dnode.on('remote', function(remote){ dnode.on('remote', function(remote){
@ -246,7 +255,7 @@ function newUnsignedTransaction(txParams, cb){
}) })
var txId = idStore.addUnconfirmedTransaction(txParams, cb) var txId = idStore.addUnconfirmedTransaction(txParams, cb)
} else { } else {
addUnconfirmedTx(txParams, cb) addUnconfirmedTx(txParams, cb)
} }
} }
@ -258,7 +267,7 @@ function newUnsignedMessage(msgParams, cb){
}) })
var msgId = idStore.addUnconfirmedMessage(msgParams, cb) var msgId = idStore.addUnconfirmedMessage(msgParams, cb)
} else { } else {
addUnconfirmedMsg(msgParams, cb) addUnconfirmedMsg(msgParams, cb)
} }
} }
@ -290,13 +299,13 @@ function addUnconfirmedMsg(msgParams, cb){
function setRpcTarget(rpcTarget){ function setRpcTarget(rpcTarget){
configManager.setRpcTarget(rpcTarget) configManager.setRpcTarget(rpcTarget)
chrome.runtime.reload() chrome.runtime.reload()
idStore.getNetwork(3) // 3 retry attempts idStore.getNetwork()
} }
function setProviderType(type) { function setProviderType(type) {
configManager.setProviderType(type) configManager.setProviderType(type)
chrome.runtime.reload() chrome.runtime.reload()
idStore.getNetwork(3) idStore.getNetwork()
} }
function useEtherscanProvider() { function useEtherscanProvider() {

View File

@ -59,6 +59,13 @@ IdentityStore.prototype.createNewVault = function(password, entropy, cb){
}) })
} }
IdentityStore.prototype.recoverSeed = function(cb){
configManager.setShowSeedWords(true)
if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.'))
var seedWords = this._idmgmt.getSeed()
cb(null, seedWords)
}
IdentityStore.prototype.recoverFromSeed = function(password, seed, cb){ IdentityStore.prototype.recoverFromSeed = function(password, seed, cb){
this._createIdmgmt(password, seed, null, (err) => { this._createIdmgmt(password, seed, null, (err) => {
if (err) return cb(err) if (err) return cb(err)
@ -130,16 +137,22 @@ IdentityStore.prototype.revealAccount = function(cb) {
cb(null) cb(null)
} }
IdentityStore.prototype.getNetwork = function(tries) { IdentityStore.prototype.getNetwork = function(err) {
if (tries === 0) {
this._currentState.network = 'error' if (err) {
return this._currentState.network = 'loading'
this._didUpdate()
} }
this.web3.version.getNetwork((err, network) => { this.web3.version.getNetwork((err, network) => {
if (err) { if (err) {
return this.getNetwork(tries - 1, cb) this._currentState.network = 'loading'
return this._didUpdate()
} }
console.log('web3.getNetwork returned ' + network)
this._currentState.network = network this._currentState.network = network
this._didUpdate()
}) })
} }
@ -150,7 +163,7 @@ IdentityStore.prototype.setLocked = function(cb){
} }
IdentityStore.prototype.submitPassword = function(password, cb){ IdentityStore.prototype.submitPassword = function(password, cb){
this._tryPassword(password, (err) => { this.tryPassword(password, (err) => {
if (err) return cb(err) if (err) return cb(err)
// load identities before returning... // load identities before returning...
this._loadIdentities() this._loadIdentities()
@ -366,7 +379,7 @@ IdentityStore.prototype._mayBeFauceting = function(i) {
// keyStore managment - unlocking + deserialization // keyStore managment - unlocking + deserialization
// //
IdentityStore.prototype._tryPassword = function(password, cb){ IdentityStore.prototype.tryPassword = function(password, cb){
this._createIdmgmt(password, null, null, cb) this._createIdmgmt(password, null, null, cb)
} }

View File

@ -8,24 +8,35 @@ module.exports = {
createMsgNotification: createMsgNotification, createMsgNotification: createMsgNotification,
} }
// notification button press setupListeners()
chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex){
var handlers = notificationHandlers[notificationId]
if (buttonIndex === 0) {
handlers.confirm()
} else {
handlers.cancel()
}
chrome.notifications.clear(notificationId)
})
// notification teardown function setupListeners(){
chrome.notifications.onClosed.addListener(function(notificationId){
delete notificationHandlers[notificationId] // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
}) if (!chrome.notifications) return console.error('Chrome notifications API missing...')
// notification button press
chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex){
var handlers = notificationHandlers[notificationId]
if (buttonIndex === 0) {
handlers.confirm()
} else {
handlers.cancel()
}
chrome.notifications.clear(notificationId)
})
// notification teardown
chrome.notifications.onClosed.addListener(function(notificationId){
delete notificationHandlers[notificationId]
})
}
// creation helper // creation helper
function createUnlockRequestNotification(opts){ function createUnlockRequestNotification(opts){
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
var message = 'An Ethereum app has requested a signature. Please unlock your account.' var message = 'An Ethereum app has requested a signature. Please unlock your account.'
var id = createId() var id = createId()
@ -39,6 +50,8 @@ function createUnlockRequestNotification(opts){
} }
function createTxNotification(opts){ function createTxNotification(opts){
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
var message = [ var message = [
'Submitted by '+opts.txParams.origin, 'Submitted by '+opts.txParams.origin,
'to: '+uiUtils.addressSummary(opts.txParams.to), 'to: '+uiUtils.addressSummary(opts.txParams.to),
@ -67,6 +80,8 @@ function createTxNotification(opts){
} }
function createMsgNotification(opts){ function createMsgNotification(opts){
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
var message = [ var message = [
'Submitted by '+opts.msgParams.origin, 'Submitted by '+opts.msgParams.origin,
'to be signed by: '+uiUtils.addressSummary(opts.msgParams.from), 'to be signed by: '+uiUtils.addressSummary(opts.msgParams.from),

View File

@ -62,7 +62,7 @@
"through2": "^2.0.1", "through2": "^2.0.1",
"vreme": "^3.0.2", "vreme": "^3.0.2",
"web3": "ethereum/web3.js#0.16.0", "web3": "ethereum/web3.js#0.16.0",
"web3-provider-engine": "^7.7.0", "web3-provider-engine": "^7.8.1",
"web3-stream-provider": "^2.0.1", "web3-stream-provider": "^2.0.1",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },

View File

@ -2,19 +2,9 @@ Chrome notifications allow you to show an SVG image via a data-uri
Taking advantage of this might allow us to show nicely formatted notifications Taking advantage of this might allow us to show nicely formatted notifications
Heres some utilities for preparing the data uri:
http://dopiaza.org/tools/datauri/index.php
provide text
no base64
specify mime type: image/svg+xml
result should look like:
data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%0D%0A%20%20width%3D%271000px%27%20height%3D%27500px%27%20viewBox%3D%270%200%20200%20100%27%3E%0D%0A%20%20%3Crect%20x%3D%270%27%20y%3D%270%27%20width%3D%27100%25%27%20height%3D%27100%25%27%20fill%3D%27white%27%20%2F%3E%0D%0A%20%20%3Ctext%20x%3D%270%27%20y%3D%2720%27%20font-family%3D%27monospace%27%20font-size%3D%276%27%20fill%3D%27black%27%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EDomain%3A%20https%3A%2F%2Fboardroom.to%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EFrom%3A%20%200xabcdef%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3ETo%3A%20%20%20%200xfedcba%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EValue%3A%201.025%20Ether%3C%2Ftspan%3E%0D%0A%20%20%20%20%3Ctspan%20x%3D%270%27%20dy%3D%271.2em%27%3EGas%3A%200.025%20Ether%3C%2Ftspan%3E%0D%0A%20%20%3C%2Ftext%3E%0D%0A%3C%2Fsvg%3E
build a template using pure svg: build a template using pure svg:
generate uri ```svg
'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svgSrc)
<svg xmlns='http://www.w3.org/2000/svg' <svg xmlns='http://www.w3.org/2000/svg'
width='1000px' height='500px' viewBox='0 0 200 100'> width='1000px' height='500px' viewBox='0 0 200 100'>
<rect x='0' y='0' width='100%' height='100%' fill='white' /> <rect x='0' y='0' width='100%' height='100%' fill='white' />
@ -26,9 +16,14 @@ generate uri
<tspan x='0' dy='1.2em'>Gas: 0.025 Ether</tspan> <tspan x='0' dy='1.2em'>Gas: 0.025 Ether</tspan>
</text> </text>
</svg> </svg>
```
generate uri
`'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svgSrc)`
or svg-embedded html: or svg-embedded html:
```svg
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500"> <svg xmlns="http://www.w3.org/2000/svg" width="800" height="500">
<rect x='0' y='0' width='100%' height='100%' fill='white' /> <rect x='0' y='0' width='100%' height='100%' fill='white' />
<foreignObject class="node" x="46" y="22" width="200" height="300"> <foreignObject class="node" x="46" y="22" width="200" height="300">
@ -39,4 +34,5 @@ or svg-embedded html:
</div> </div>
</body> </body>
</foreignObject> </foreignObject>
</svg> </svg>
```

View File

@ -77,6 +77,13 @@ describe('util', function() {
assert.ok(!result) assert.ok(!result)
}) })
it('should recognize this sample hashed address', function() {
const address = '0x5Fda30Bb72B8Dfe20e48A00dFc108d0915BE9Bb0'
const result = util.isValidAddress(address)
const hashed = ethUtil.toChecksumAddress(address.toLowerCase())
assert.equal(hashed, address, 'example is hashed correctly')
assert.ok(result, 'is valid by our check')
})
}) })
describe('numericBalance', function() { describe('numericBalance', function() {

View File

@ -6,6 +6,8 @@ var actions = {
toggleMenu: toggleMenu, toggleMenu: toggleMenu,
SET_MENU_STATE: 'SET_MENU_STATE', SET_MENU_STATE: 'SET_MENU_STATE',
closeMenu: closeMenu, closeMenu: closeMenu,
getNetworkStatus: 'getNetworkStatus',
// remote state // remote state
UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE', UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE',
updateMetamaskState: updateMetamaskState, updateMetamaskState: updateMetamaskState,
@ -29,6 +31,10 @@ var actions = {
createNewVaultInProgress: createNewVaultInProgress, createNewVaultInProgress: createNewVaultInProgress,
showNewVaultSeed: showNewVaultSeed, showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage, showInfoPage: showInfoPage,
// seed recovery actions
REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION',
revealSeedConfirmation: revealSeedConfirmation,
requestRevealSeed: requestRevealSeed,
// unlock screen // unlock screen
UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS', UNLOCK_IN_PROGRESS: 'UNLOCK_IN_PROGRESS',
UNLOCK_FAILED: 'UNLOCK_FAILED', UNLOCK_FAILED: 'UNLOCK_FAILED',
@ -131,6 +137,12 @@ function closeMenu() {
} }
} }
function getNetworkStatus(){
return {
type: actions.getNetworkStatus,
}
}
// async actions // async actions
function tryUnlockMetamask(password) { function tryUnlockMetamask(password) {
@ -155,6 +167,26 @@ function createNewVault(password, entropy) {
} }
} }
function revealSeedConfirmation() {
return {
type: this.REVEAL_SEED_CONFIRMATION,
}
}
function requestRevealSeed(password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
_accountManager.tryPassword(password, (err, seed) => {
dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message))
_accountManager.recoverSeed((err, seed) => {
if (err) return dispatch(actions.displayWarning(err.message))
dispatch(actions.showNewVaultSeed(seed))
})
})
}
}
function recoverFromSeed(password, seed) { function recoverFromSeed(password, seed) {
return (dispatch) => { return (dispatch) => {
// dispatch(actions.createNewVaultInProgress()) // dispatch(actions.createNewVaultInProgress())
@ -402,9 +434,10 @@ function previousTx() {
} }
} }
function showConfigPage() { function showConfigPage(transitionForward = true) {
return { return {
type: actions.SHOW_CONFIG_PAGE, type: actions.SHOW_CONFIG_PAGE,
value: transitionForward,
} }
} }

View File

@ -21,12 +21,14 @@ const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx') const ConfirmTxScreen = require('./conf-tx')
// other views // other views
const ConfigScreen = require('./config') const ConfigScreen = require('./config')
const RevealSeedConfirmation = require('./recover-seed/confirmation')
const InfoScreen = require('./info') const InfoScreen = require('./info')
const LoadingIndicator = require('./loading') const LoadingIndicator = require('./loading')
const txHelper = require('../lib/tx-helper') const txHelper = require('../lib/tx-helper')
const SandwichExpando = require('sandwich-expando') const SandwichExpando = require('sandwich-expando')
const MenuDroppo = require('menu-droppo') const MenuDroppo = require('menu-droppo')
const DropMenuItem = require('./components/drop-menu-item') const DropMenuItem = require('./components/drop-menu-item')
const NetworkIndicator = require('./components/network')
module.exports = connect(mapStateToProps)(App) module.exports = connect(mapStateToProps)(App)
@ -46,14 +48,14 @@ function mapStateToProps(state) {
unconfTxs: state.metamask.unconfTxs, unconfTxs: state.metamask.unconfTxs,
unconfMsgs: state.metamask.unconfMsgs, unconfMsgs: state.metamask.unconfMsgs,
menuOpen: state.appState.menuOpen, menuOpen: state.appState.menuOpen,
network: state.metamask.network,
} }
} }
App.prototype.render = function() { App.prototype.render = function() {
// const { selectedReddit, posts, isFetching, lastUpdated } = this.props var props = this.props
var state = this.props var view = props.currentView.name
var view = state.currentView.name var transForward = props.transForward
var transForward = state.transForward
return ( return (
@ -68,6 +70,7 @@ App.prototype.render = function() {
// app bar // app bar
this.renderAppBar(), this.renderAppBar(),
this.renderNetworkDropdown(),
this.renderDropdown(), this.renderDropdown(),
// panel content // panel content
@ -91,7 +94,9 @@ App.prototype.render = function() {
} }
App.prototype.renderAppBar = function(){ App.prototype.renderAppBar = function(){
var state = this.props const props = this.props
const state = this.state || {}
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
return ( return (
@ -100,30 +105,31 @@ App.prototype.renderAppBar = function(){
h('.app-header.flex-row.flex-space-between', { h('.app-header.flex-row.flex-space-between', {
style: { style: {
alignItems: 'center', alignItems: 'center',
visibility: state.isUnlocked ? 'visible' : 'none', visibility: props.isUnlocked ? 'visible' : 'none',
background: state.isUnlocked ? 'white' : 'none', background: props.isUnlocked ? 'white' : 'none',
height: '36px', height: '36px',
position: 'relative', position: 'relative',
zIndex: 1, zIndex: 1,
}, },
}, state.isUnlocked && [ }, props.isUnlocked && [
// mini logo h(NetworkIndicator, {
h('img', { network: this.props.network,
height: 24, onClick:(event) => {
width: 24, event.preventDefault()
src: '/images/icon-128.png', event.stopPropagation()
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
}
}), }),
// metamask name // metamask name
h('h1', 'MetaMask'), h('h1', 'MetaMask'),
// hamburger // hamburger
h(SandwichExpando, { h(SandwichExpando, {
width: 16, width: 16,
barHeight: 2, barHeight: 2,
padding: 0, padding: 0,
isOpen: state.menuOpen, isOpen: props.menuOpen,
color: 'rgb(247,146,30)', color: 'rgb(247,146,30)',
onClick: (event) => { onClick: (event) => {
event.preventDefault() event.preventDefault()
@ -136,6 +142,56 @@ App.prototype.renderAppBar = function(){
) )
} }
App.prototype.renderNetworkDropdown = function() {
const props = this.props
const state = this.state || {}
const isOpen = state.isNetworkMenuOpen
const checked = h('i.fa.fa-check.fa-lg', { ariaHidden: true })
return h(MenuDroppo, {
isOpen,
onClickOutside:(event) => {
this.setState({ isNetworkMenuOpen: !isOpen })
},
style: {
position: 'fixed',
left: 0,
zIndex: 0,
},
innerStyle: {
background: 'white',
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
},
}, [ // DROP MENU ITEMS
h('style', `
.drop-menu-item:hover { background:rgb(235, 235, 235); }
.drop-menu-item i { margin: 11px; }
`),
h(DropMenuItem, {
label: 'Main Ethereum Network',
closeMenu:() => this.setState({ isNetworkMenuOpen: false }),
action:() => props.dispatch(actions.setProviderType('mainnet')),
icon: h('.menu-icon.ether-icon'),
}),
h(DropMenuItem, {
label: 'Morden Test Network',
closeMenu:() => this.setState({ isNetworkMenuOpen: false }),
action:() => props.dispatch(actions.setProviderType('testnet')),
icon: h('.menu-icon.morden-icon'),
}),
h(DropMenuItem, {
label: 'Localhost 8545',
closeMenu:() => this.setState({ isNetworkMenuOpen: false }),
action:() => props.dispatch(actions.setRpcTarget('http://localhost:8545')),
icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }),
}),
])
}
App.prototype.renderDropdown = function() { App.prototype.renderDropdown = function() {
const props = this.props const props = this.props
return h(MenuDroppo, { return h(MenuDroppo, {
@ -232,6 +288,9 @@ App.prototype.renderPrimary = function(){
case 'config': case 'config':
return h(ConfigScreen, {key: 'config'}) return h(ConfigScreen, {key: 'config'})
case 'reveal-seed-conf':
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
case 'info': case 'info':
return h(InfoScreen, {key: 'info'}) return h(InfoScreen, {key: 'info'})

View File

@ -0,0 +1,65 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
module.exports = Network
inherits(Network, Component)
function Network() {
Component.call(this)
}
Network.prototype.render = function() {
const state = this.props
const networkNumber = state.network
let iconName, hoverText
const imagePath = "/images/"
if (networkNumber == 'loading') {
return h('img', {
title: 'Attempting to connect to blockchain.',
style: {
width: '27px',
marginRight: '-27px'
},
src: 'images/loading.svg',
})
} else if (parseInt(networkNumber) == 1) {
hoverText = 'Main Ethereum Network'
iconName = 'ethereum-network'
}else if (parseInt(networkNumber) == 2) {
hoverText = "Morden Test Network"
iconName = 'morden-test-network'
}else {
hoverText = "Unknown Private Network"
iconName = 'unknown-private-network'
}
return (
h('#network_component.flex-center.pointer', {
style: {
marginRight: '-27px',
marginLeft: '-3px',
},
title: hoverText,
onClick:(event) => this.props.onClick(event),
},[
function() {
switch (iconName) {
case 'ethereum-network':
return h('.menu-icon.ether-icon')
case 'morden-test-network':
return h('.menu-icon.morden-icon')
default:
return h('i.fa.fa-question-circle.fa-lg', {
ariaHidden: true,
style: {
margin: '10px',
color: 'rgb(125, 128, 130)',
},
})
}
}()
])
)
}

View File

@ -78,7 +78,7 @@ ConfigScreen.prototype.render = function() {
]), ]),
h('div', [ h('div', [
h('button', { h('button.spaced', {
style: { style: {
alignSelf: 'center', alignSelf: 'center',
}, },
@ -86,11 +86,11 @@ ConfigScreen.prototype.render = function() {
event.preventDefault() event.preventDefault()
state.dispatch(actions.setProviderType('mainnet')) state.dispatch(actions.setProviderType('mainnet'))
} }
}, 'Use Main Network') }, 'Use Main Network'),
]), ]),
h('div', [ h('div', [
h('button', { h('button.spaced', {
style: { style: {
alignSelf: 'center', alignSelf: 'center',
}, },
@ -98,11 +98,11 @@ ConfigScreen.prototype.render = function() {
event.preventDefault() event.preventDefault()
state.dispatch(actions.setProviderType('testnet')) state.dispatch(actions.setProviderType('testnet'))
} }
}, 'Use Morden Test Network') }, 'Use Morden Test Network'),
]), ]),
h('div', [ h('div', [
h('button', { h('button.spaced', {
style: { style: {
alignSelf: 'center', alignSelf: 'center',
}, },
@ -110,7 +110,25 @@ ConfigScreen.prototype.render = function() {
event.preventDefault() event.preventDefault()
state.dispatch(actions.setRpcTarget('http://localhost:8545/')) state.dispatch(actions.setRpcTarget('http://localhost:8545/'))
} }
}, 'Use http://localhost:8545') }, 'Use http://localhost:8545'),
]),
h('hr.horizontal-line'),
h('div', {
style: {
marginTop: '20px',
}
}, [
h('button', {
style: {
alignSelf: 'center',
},
onClick(event) {
event.preventDefault()
state.dispatch(actions.revealSeedConfirmation())
}
}, 'Reveal Seed Words')
]), ]),
]), ]),

View File

@ -45,6 +45,10 @@ button {
transition: transform 50ms ease-in; transition: transform 50ms ease-in;
} }
button.spaced {
margin: 2px;
}
button:hover { button:hover {
transform: scale(1.1); transform: scale(1.1);
} }

View File

@ -199,3 +199,22 @@ hr.horizontal-line {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.menu-icon {
display: inline-block;
width: 14px;
height: 14px;
margin: 13px;
}
.ether-icon {
background: rgb(0, 163, 68);
border-radius: 20px;
}
.morden-icon {
background: #2465E1;
}
.drop-menu-item {
display: flex;
align-items: center;
}

View File

@ -14,7 +14,7 @@ function CreateVaultCompleteScreen() {
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
seed: state.appState.currentView.context, seed: state.appState.currentView.seedWords,
cachedSeed: state.metamask.seedWords, cachedSeed: state.metamask.seedWords,
} }
} }

View File

@ -0,0 +1,149 @@
const inherits = require('util').inherits
const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../actions')
module.exports = connect(mapStateToProps)(RevealSeedConfirmatoin)
inherits(RevealSeedConfirmatoin, Component)
function RevealSeedConfirmatoin() {
Component.call(this)
}
function mapStateToProps(state) {
return {
warning: state.appState.warning,
}
}
RevealSeedConfirmatoin.prototype.confirmationPhrase = 'I understand'
RevealSeedConfirmatoin.prototype.render = function() {
const props = this.props
const state = this.state
return (
h('.initialize-screen.flex-column.flex-center.flex-grow', [
h('h3.flex-center.text-transform-uppercase', {
style: {
background: '#EBEBEB',
color: '#AEAEAE',
marginBottom: 24,
width: '100%',
fontSize: '20px',
padding: 6,
},
}, [
'Reveal Seed Words',
]),
h('.div', {
style: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
justifyContent: 'center',
}
}, [
h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
// confirmation
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'Enter your password to confirm',
onKeyPress: this.checkConfirmation.bind(this),
style: {
width: 260,
marginTop: '12px',
},
}),
h(`h4${state && state.confirmationWrong ? '.error' : ''}`, {
style: {
marginTop: '12px',
}
}, `Enter the phrase "I understand" to proceed.`),
// confirm confirmation
h('input.large-input.letter-spacey', {
type: 'text',
id: 'confirm-box',
placeholder: this.confirmationPhrase,
onKeyPress: this.checkConfirmation.bind(this),
style: {
width: 260,
marginTop: 16,
},
}),
h('.flex-row.flex-space-between', {
style: {
marginTop: 30,
width: '50%',
},
}, [
// cancel
h('button.primary', {
onClick: this.goHome.bind(this),
}, 'CANCEL'),
// submit
h('button.primary', {
onClick: this.revealSeedWords.bind(this),
}, 'OK'),
]),
(props.warning) && (
h('span.error', {
style: {
margin: '20px',
}
}, props.warning.split('-'))
),
props.inProgress && (
h('span.in-progress-notification', 'Generating Seed...')
),
]),
])
)
}
RevealSeedConfirmatoin.prototype.componentDidMount = function(){
document.getElementById('password-box').focus()
}
RevealSeedConfirmatoin.prototype.goHome = function() {
this.props.dispatch(actions.showConfigPage(false))
}
// create vault
RevealSeedConfirmatoin.prototype.checkConfirmation = function(event) {
if (event.key === 'Enter') {
event.preventDefault()
this.revealSeedWords()
}
}
RevealSeedConfirmatoin.prototype.revealSeedWords = function(){
this.setState({ confirmationWrong: false })
const confirmBox = document.getElementById('confirm-box')
const confirmation = confirmBox.value
if (confirmation !== this.confirmationPhrase) {
confirmBox.value = ''
return this.setState({ confirmationWrong: true })
}
var password = document.getElementById('password-box').value
this.props.dispatch(actions.requestRevealSeed(password))
}

View File

@ -25,10 +25,11 @@ function reduceApp(state, action) {
} }
// confirm seed words // confirm seed words
var seedWords = state.metamask.seedWords
var seedConfView = { var seedConfView = {
name: 'createVaultComplete', name: 'createVaultComplete',
seedWords,
} }
var seedWords = state.metamask.seedWords
var appState = extend({ var appState = extend({
menuOpen: false, menuOpen: false,
@ -85,7 +86,7 @@ function reduceApp(state, action) {
name: 'config', name: 'config',
context: appState.currentView.context, context: appState.currentView.context,
}, },
transForward: true, transForward: action.value,
}) })
case actions.SHOW_INFO_PAGE: case actions.SHOW_INFO_PAGE:
@ -111,7 +112,7 @@ function reduceApp(state, action) {
return extend(appState, { return extend(appState, {
currentView: { currentView: {
name: 'createVaultComplete', name: 'createVaultComplete',
context: action.value, seedWords: action.value,
}, },
transForward: true, transForward: true,
isLoading: false, isLoading: false,
@ -144,6 +145,18 @@ function reduceApp(state, action) {
warning: null, warning: null,
}) })
// reveal seed words
case actions.REVEAL_SEED_CONFIRMATION:
return extend(appState, {
currentView: {
name: 'reveal-seed-conf',
},
transForward: true,
warning: null,
})
// accounts // accounts
case actions.SET_SELECTED_ACCOUNT: case actions.SET_SELECTED_ACCOUNT:
@ -198,6 +211,7 @@ function reduceApp(state, action) {
return extend(appState, { return extend(appState, {
currentView: { currentView: {
name: seedWords ? 'createVaultComplete' : 'accounts', name: seedWords ? 'createVaultComplete' : 'accounts',
seedWords,
}, },
transForward: true, transForward: true,
isLoading: false, isLoading: false,

View File

@ -52,7 +52,7 @@ function addressSummary(address) {
function isValidAddress(address) { function isValidAddress(address) {
var prefixed = ethUtil.addHexPrefix(address) var prefixed = ethUtil.addHexPrefix(address)
return isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed) || ethUtil.isValidChecksumAddress(prefixed) return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
} }
function isAllOneCase(address) { function isAllOneCase(address) {