1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 02:10:12 +01:00

Merge pull request #169 from MetaMask/ImplementEthSign

implement eth_sign
This commit is contained in:
kumavis 2016-05-04 11:11:37 -07:00
commit 95582f8bde
16 changed files with 391 additions and 149 deletions

View File

@ -2,6 +2,8 @@
## Current Master ## Current Master
- Add support for calls to `eth.sign`.
## 1.7.0 2016-04-29 ## 1.7.0 2016-04-29
- Account detail view is now the primary view. - Account detail view is now the primary view.

View File

@ -10,6 +10,7 @@ const IdentityStore = require('./lib/idStore')
const createTxNotification = require('./lib/notifications.js').createTxNotification const createTxNotification = require('./lib/notifications.js').createTxNotification
const createMsgNotification = require('./lib/notifications.js').createMsgNotification const createMsgNotification = require('./lib/notifications.js').createMsgNotification
const configManager = require('./lib/config-manager-singleton') const configManager = require('./lib/config-manager-singleton')
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const HostStore = require('./lib/remote-store.js').HostStore const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3') const Web3 = require('web3')
@ -175,6 +176,8 @@ function setupControllerConnection(stream){
setSelectedAddress: idStore.setSelectedAddress.bind(idStore), setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
approveTransaction: idStore.approveTransaction.bind(idStore), approveTransaction: idStore.approveTransaction.bind(idStore),
cancelTransaction: idStore.cancelTransaction.bind(idStore), cancelTransaction: idStore.cancelTransaction.bind(idStore),
signMessage: idStore.signMessage.bind(idStore),
cancelMessage: idStore.cancelMessage.bind(idStore),
setLocked: idStore.setLocked.bind(idStore), setLocked: idStore.setLocked.bind(idStore),
clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
exportAccount: idStore.exportAccount.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore),
@ -206,7 +209,10 @@ idStore.on('update', updateBadge)
function updateBadge(state){ function updateBadge(state){
var label = '' var label = ''
var unconfTxs = configManager.unconfirmedTxs() var unconfTxs = configManager.unconfirmedTxs()
var count = Object.keys(unconfTxs).length var unconfTxLen = Object.keys(unconfTxs).length
var unconfMsgs = messageManager.unconfirmedMsgs()
var unconfMsgLen = Object.keys(unconfMsgs).length
var count = unconfTxLen + unconfMsgLen
if (count) { if (count) {
label = String(count) label = String(count)
} }

View File

@ -211,73 +211,6 @@ ConfigManager.prototype.updateTx = function(tx) {
this._saveTxList(transactions) this._saveTxList(transactions)
} }
//
// Msg
//
ConfigManager.prototype.getMsgList = function() {
var data = this.migrator.getData()
if (data.messages !== undefined) {
return data.messages
} else {
return []
}
}
ConfigManager.prototype.unconfirmedMsgs = function() {
var messages = this.getMsgList()
return messages.filter(msg => msg.status === 'unconfirmed')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
ConfigManager.prototype._saveMsgList = function(msgList) {
var data = this.migrator.getData()
data.messages = msgList
this.setData(data)
}
ConfigManager.prototype.addMsg = function(msg) {
var messages = this.getMsgList()
messages.push(msg)
this._saveMsgList(messages)
}
ConfigManager.prototype.getMsg = function(msgId) {
var messages = this.getMsgList()
var matching = messages.filter(msg => msg.id === msgId)
return matching.length > 0 ? matching[0] : null
}
ConfigManager.prototype.confirmMsg = function(msgId) {
this._setMsgStatus(msgId, 'confirmed')
}
ConfigManager.prototype.rejectMsg = function(msgId) {
this._setMsgStatus(msgId, 'rejected')
}
ConfigManager.prototype._setMsgStatus = function(msgId, status) {
var msg = this.getMsg(msgId)
msg.status = status
this.updateMsg(msg)
}
ConfigManager.prototype.updateMsg = function(msg) {
var messages = this.getMsgList()
var found, index
messages.forEach((otherMsg, i) => {
if (otherMsg.id === msg.id) {
found = true
index = i
}
})
if (found) {
messages[index] = msg
}
this._saveMsgList(messages)
}
// observable // observable

View File

@ -9,6 +9,7 @@ const extend = require('xtend')
const createId = require('web3-provider-engine/util/random-id') const createId = require('web3-provider-engine/util/random-id')
const autoFaucet = require('./auto-faucet') const autoFaucet = require('./auto-faucet')
const configManager = require('./config-manager-singleton') const configManager = require('./config-manager-singleton')
const messageManager = require('./message-manager')
const DEFAULT_RPC = 'https://testrpc.metamask.io/' const DEFAULT_RPC = 'https://testrpc.metamask.io/'
@ -32,6 +33,7 @@ function IdentityStore(opts = {}) {
selectedAddress: null, selectedAddress: null,
identities: {}, identities: {},
} }
// not part of serilized metamask state - only kept in memory // not part of serilized metamask state - only kept in memory
this._unconfTxCbs = {} this._unconfTxCbs = {}
this._unconfMsgCbs = {} this._unconfMsgCbs = {}
@ -85,6 +87,8 @@ IdentityStore.prototype.getState = function(){
seedWords: seedWords, seedWords: seedWords,
unconfTxs: configManager.unconfirmedTxs(), unconfTxs: configManager.unconfirmedTxs(),
transactions: configManager.getTxList(), transactions: configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
messages: messageManager.getMsgList(),
selectedAddress: configManager.getSelectedAccount(), selectedAddress: configManager.getSelectedAccount(),
})) }))
} }
@ -226,7 +230,7 @@ IdentityStore.prototype.addUnconfirmedMessage = function(msgParams, cb){
time: time, time: time,
status: 'unconfirmed', status: 'unconfirmed',
} }
configManager.addMsg(msgData) messageManager.addMsg(msgData)
console.log('addUnconfirmedMessage:', msgData) console.log('addUnconfirmedMessage:', msgData)
// keep the cb around for after approval (requires user interaction) // keep the cb around for after approval (requires user interaction)
@ -241,27 +245,27 @@ IdentityStore.prototype.addUnconfirmedMessage = function(msgParams, cb){
// comes from metamask ui // comes from metamask ui
IdentityStore.prototype.approveMessage = function(msgId, cb){ IdentityStore.prototype.approveMessage = function(msgId, cb){
var msgData = configManager.getMsg(msgId) var msgData = messageManager.getMsg(msgId)
var approvalCb = this._unconfMsgCbs[msgId] || noop var approvalCb = this._unconfMsgCbs[msgId] || noop
// accept msg // accept msg
cb() cb()
approvalCb(null, true) approvalCb(null, true)
// clean up // clean up
configManager.confirmMsg(msgId) messageManager.confirmMsg(msgId)
delete this._unconfMsgCbs[msgId] delete this._unconfMsgCbs[msgId]
this._didUpdate() this._didUpdate()
} }
// comes from metamask ui // comes from metamask ui
IdentityStore.prototype.cancelMessage = function(msgId){ IdentityStore.prototype.cancelMessage = function(msgId){
var txData = configManager.getMsg(msgId) var txData = messageManager.getMsg(msgId)
var approvalCb = this._unconfMsgCbs[msgId] || noop var approvalCb = this._unconfMsgCbs[msgId] || noop
// reject tx // reject tx
approvalCb(null, false) approvalCb(null, false)
// clean up // clean up
configManager.rejectMsg(msgId) messageManager.rejectMsg(msgId)
delete this._unconfTxCbs[msgId] delete this._unconfTxCbs[msgId]
this._didUpdate() this._didUpdate()
} }
@ -271,7 +275,14 @@ IdentityStore.prototype.signMessage = function(msgParams, cb){
try { try {
console.log('signing msg...', msgParams.data) console.log('signing msg...', msgParams.data)
var rawMsg = this._idmgmt.signMsg(msgParams.from, msgParams.data) var rawMsg = this._idmgmt.signMsg(msgParams.from, msgParams.data)
cb(null, rawMsg) if ('metamaskId' in msgParams) {
var id = msgParams.metamaskId
delete msgParams.metamaskId
this.approveMessage(id, cb)
} else {
cb(null, rawMsg)
}
} catch (err) { } catch (err) {
cb(err) cb(err)
} }
@ -426,7 +437,7 @@ function IdManagement(opts) {
var privKeyHex = this.exportPrivateKey(txParams.from) var privKeyHex = this.exportPrivateKey(txParams.from)
var privKey = ethUtil.toBuffer(privKeyHex) var privKey = ethUtil.toBuffer(privKeyHex)
tx.sign(privKey) tx.sign(privKey)
// Add the tx hash to the persisted meta-tx object // Add the tx hash to the persisted meta-tx object
var txHash = ethUtil.bufferToHex(tx.hash()) var txHash = ethUtil.bufferToHex(tx.hash())
var metaTx = configManager.getTx(txParams.metamaskId) var metaTx = configManager.getTx(txParams.metamaskId)
@ -472,4 +483,4 @@ function concatSig(v, r, s) {
s = ethUtil.toUnsigned(s).toString('hex') s = ethUtil.toUnsigned(s).toString('hex')
v = ethUtil.stripHexPrefix(ethUtil.intToHex(v)) v = ethUtil.stripHexPrefix(ethUtil.intToHex(v))
return ethUtil.addHexPrefix(r.concat(s, v).toString("hex")) return ethUtil.addHexPrefix(r.concat(s, v).toString("hex"))
} }

View File

@ -0,0 +1,61 @@
module.exports = new MessageManager()
function MessageManager(opts) {
this.messages = []
}
MessageManager.prototype.getMsgList = function() {
return this.messages
}
MessageManager.prototype.unconfirmedMsgs = function() {
var messages = this.getMsgList()
return messages.filter(msg => msg.status === 'unconfirmed')
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
}
MessageManager.prototype._saveMsgList = function(msgList) {
this.messages = msgList
}
MessageManager.prototype.addMsg = function(msg) {
var messages = this.getMsgList()
messages.push(msg)
this._saveMsgList(messages)
}
MessageManager.prototype.getMsg = function(msgId) {
var messages = this.getMsgList()
var matching = messages.filter(msg => msg.id === msgId)
return matching.length > 0 ? matching[0] : null
}
MessageManager.prototype.confirmMsg = function(msgId) {
this._setMsgStatus(msgId, 'confirmed')
}
MessageManager.prototype.rejectMsg = function(msgId) {
this._setMsgStatus(msgId, 'rejected')
}
MessageManager.prototype._setMsgStatus = function(msgId, status) {
var msg = this.getMsg(msgId)
if (msg) msg.status = status
this.updateMsg(msg)
}
MessageManager.prototype.updateMsg = function(msg) {
var messages = this.getMsgList()
var found, index
messages.forEach((otherMsg, i) => {
if (otherMsg.id === msg.id) {
found = true
index = i
}
})
if (found) {
messages[index] = msg
}
this._saveMsgList(messages)
}

View File

@ -13,7 +13,7 @@ function mapStateToProps(state) {
return { return {
identities: state.metamask.identities, identities: state.metamask.identities,
accounts: state.metamask.accounts, accounts: state.metamask.accounts,
address: state.appState.currentView.context, address: state.metamask.selectedAccount,
accountDetail: state.appState.accountDetail, accountDetail: state.appState.accountDetail,
transactions: state.metamask.transactions, transactions: state.metamask.transactions,
networkVersion: state.metamask.network, networkVersion: state.metamask.network,
@ -27,6 +27,7 @@ function AccountDetailScreen() {
AccountDetailScreen.prototype.render = function() { AccountDetailScreen.prototype.render = function() {
var state = this.props var state = this.props
var selected = state.address || Object.keys(state.accounts[0]).address
var identity = state.identities[state.address] var identity = state.identities[state.address]
var account = state.accounts[state.address] var account = state.accounts[state.address]
var accountDetail = state.accountDetail var accountDetail = state.accountDetail

View File

@ -42,6 +42,7 @@ var actions = {
SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL', SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL',
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE', SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE', SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
// account detail screen // account detail screen
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
showSendPage: showSendPage, showSendPage: showSendPage,
@ -57,7 +58,8 @@ var actions = {
NEXT_TX: 'NEXT_TX', NEXT_TX: 'NEXT_TX',
PREVIOUS_TX: 'PREV_TX', PREVIOUS_TX: 'PREV_TX',
setSelectedAddress: setSelectedAddress, setSelectedAddress: setSelectedAddress,
signTx: signTx, signMsg: signMsg,
cancelMsg: cancelMsg,
sendTx: sendTx, sendTx: sendTx,
cancelTx: cancelTx, cancelTx: cancelTx,
completedTx: completedTx, completedTx: completedTx,
@ -111,7 +113,6 @@ function tryUnlockMetamask(password) {
dispatch(this.unlockFailed()) dispatch(this.unlockFailed())
} else { } else {
dispatch(this.unlockMetamask()) dispatch(this.unlockMetamask())
dispatch(this.showAccountDetail(selectedAccount))
} }
}) })
} }
@ -152,16 +153,15 @@ function setSelectedAddress(address) {
} }
} }
function signTx(txData) { function signMsg(msgData) {
return (dispatch) => { return (dispatch) => {
dispatch(this.showLoadingIndication()) dispatch(this.showLoadingIndication())
web3.eth.sendTransaction(txData, (err, data) => { _accountManager.signMessage(msgData, (err) => {
dispatch(this.hideLoadingIndication()) dispatch(this.hideLoadingIndication())
if (err) return dispatch(this.displayWarning(err.message)) if (err) return dispatch(this.displayWarning(err.message))
dispatch(this.hideWarning()) dispatch(this.completedTx(msgData.metamaskId))
dispatch(this.goHome())
}) })
} }
} }
@ -193,9 +193,14 @@ function txError(err) {
} }
} }
function cancelMsg(msgData){
_accountManager.cancelMessage(msgData.id)
return this.completedTx(msgData.id)
}
function cancelTx(txData){ function cancelTx(txData){
_accountManager.cancelTransaction(txData.id) _accountManager.cancelTransaction(txData.id)
return this.goHome() return this.completedTx(txData.id)
} }
// //

View File

@ -23,6 +23,7 @@ const ConfirmTxScreen = require('./conf-tx')
const ConfigScreen = require('./config') const ConfigScreen = require('./config')
const InfoScreen = require('./info') const InfoScreen = require('./info')
const LoadingIndicator = require('./loading') const LoadingIndicator = require('./loading')
const txHelper = require('../lib/tx-helper')
module.exports = connect(mapStateToProps)(App) module.exports = connect(mapStateToProps)(App)
@ -39,6 +40,8 @@ function mapStateToProps(state) {
activeAddress: state.appState.activeAddress, activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward, transForward: state.appState.transForward,
seedWords: state.metamask.seedWords, seedWords: state.metamask.seedWords,
unconfTxs: state.metamask.unconfTxs,
unconfMsgs: state.metamask.unconfMsgs,
} }
} }
@ -202,8 +205,20 @@ App.prototype.renderPrimary = function(state){
return h(CreateVaultScreen, {key: 'createVault'}) return h(CreateVaultScreen, {key: 'createVault'})
default: default:
return h(AccountsScreen, {key: 'accounts'}) if (this.hasPendingTxs()) {
} return h(ConfirmTxScreen, {key: 'confirm-tx'})
} else {
return h(AccountDetailScreen, {key: 'account-detail'})
}
}
}
App.prototype.hasPendingTxs = function() {
var state = this.props
var unconfTxs = state.unconfTxs
var unconfMsgs = state.unconfMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
return unconfTxList.length > 0
} }
function onOffToggle(state){ function onOffToggle(state){

View File

@ -0,0 +1,72 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountPanel = require('./account-panel')
const addressSummary = require('../util').addressSummary
const readableDate = require('../util').readableDate
const formatBalance = require('../util').formatBalance
const dataSize = require('../util').dataSize
module.exports = PendingMsg
inherits(PendingMsg, Component)
function PendingMsg() {
Component.call(this)
}
PendingMsg.prototype.render = function() {
var state = this.props
var msgData = state.txData
var msgParams = msgData.msgParams || {}
var address = msgParams.from || state.selectedAddress
var identity = state.identities[address] || { address: address }
var account = state.accounts[address] || { address: address }
return (
h('.transaction', {
key: msgData.id,
}, [
h('h3', {
style: {
fontWeight: 'bold',
textAlign: 'center',
}
}, 'Sign Message'),
// account that will sign
h(AccountPanel, {
showFullAddress: true,
identity: identity,
account: account,
}),
// tx data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
h('.flex-row.flex-space-between', [
h('label.font-small', 'DATE'),
h('span.font-small', readableDate(msgData.time)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'MESSAGE'),
h('span.font-small', msgParams.data),
]),
]),
// send + cancel
h('.flex-row.flex-space-around', [
h('button', {
onClick: state.cancelMessage,
}, 'Cancel'),
h('button', {
onClick: state.signMessage,
}, 'Sign'),
]),
])
)
}

View File

@ -0,0 +1,78 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountPanel = require('./account-panel')
const addressSummary = require('../util').addressSummary
const readableDate = require('../util').readableDate
const formatBalance = require('../util').formatBalance
const dataSize = require('../util').dataSize
module.exports = PendingTx
inherits(PendingTx, Component)
function PendingTx() {
Component.call(this)
}
PendingTx.prototype.render = function() {
var state = this.props
var txData = state.txData
var txParams = txData.txParams || {}
var address = txParams.from || state.selectedAddress
var identity = state.identities[address] || { address: address }
var account = state.accounts[address] || { address: address }
return (
h('.transaction', {
key: txData.id,
}, [
h('h3', {
style: {
fontWeight: 'bold',
textAlign: 'center',
}
}, 'Submit Transaction'),
// account that will sign
h(AccountPanel, {
showFullAddress: true,
identity: identity,
account: account,
}),
// tx data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
h('.flex-row.flex-space-between', [
h('label.font-small', 'TO ADDRESS'),
h('span.font-small', addressSummary(txParams.to)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'DATE'),
h('span.font-small', readableDate(txData.time)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'AMOUNT'),
h('span.font-small', formatBalance(txParams.value)),
]),
]),
// send + cancel
h('.flex-row.flex-space-around', [
h('button', {
onClick: state.cancelTransaction,
}, 'Cancel'),
h('button', {
onClick: state.sendTransaction,
}, 'Send'),
]),
])
)
}

View File

@ -0,0 +1,19 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
module.exports = NewComponent
inherits(NewComponent, Component)
function NewComponent() {
Component.call(this)
}
NewComponent.prototype.render = function() {
var state = this.props
return (
h('span', 'Placeholder component')
)
}

View File

@ -7,10 +7,10 @@ const copyToClipboard = require('copy-to-clipboard')
const actions = require('./actions') const actions = require('./actions')
const AccountPanel = require('./components/account-panel') const AccountPanel = require('./components/account-panel')
const valuesFor = require('./util').valuesFor const valuesFor = require('./util').valuesFor
const addressSummary = require('./util').addressSummary const txHelper = require('../lib/tx-helper')
const readableDate = require('./util').readableDate
const formatBalance = require('./util').formatBalance const ConfirmTx = require('./components/pending-tx')
const dataSize = require('./util').dataSize const PendingMsg = require('./components/pending-msg')
module.exports = connect(mapStateToProps)(ConfirmTxScreen) module.exports = connect(mapStateToProps)(ConfirmTxScreen)
@ -20,7 +20,9 @@ function mapStateToProps(state) {
accounts: state.metamask.accounts, accounts: state.metamask.accounts,
selectedAddress: state.metamask.selectedAddress, selectedAddress: state.metamask.selectedAddress,
unconfTxs: state.metamask.unconfTxs, unconfTxs: state.metamask.unconfTxs,
unconfMsgs: state.metamask.unconfMsgs,
index: state.appState.currentView.context, index: state.appState.currentView.context,
warning: state.appState.warning,
} }
} }
@ -32,12 +34,12 @@ function ConfirmTxScreen() {
ConfirmTxScreen.prototype.render = function() { ConfirmTxScreen.prototype.render = function() {
var state = this.props var state = this.props
var unconfTxList = valuesFor(state.unconfTxs).sort(tx => tx.time)
var txData = unconfTxList[state.index] || {} var unconfTxs = state.unconfTxs
var txParams = txData.txParams || {} var unconfMsgs = state.unconfMsgs
var address = txParams.from || state.selectedAddress var unconfTxList = txHelper(unconfTxs, unconfMsgs)
var identity = state.identities[address] || { address: address } var index = state.index !== undefined ? state.index : 0
var account = state.accounts[address] || { address: address } var txData = unconfTxList[index] || {}
return ( return (
@ -46,9 +48,9 @@ ConfirmTxScreen.prototype.render = function() {
// subtitle and nav // subtitle and nav
h('.section-title.flex-row.flex-center', [ h('.section-title.flex-row.flex-center', [
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', { h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
onClick: this.navigateToAccounts.bind(this), onClick: this.goHome.bind(this),
}), }),
h('h2.page-subtitle', 'Confirm Transaction'), h('h2.page-subtitle', 'Confirmation'),
]), ]),
h('h3', { h('h3', {
@ -63,7 +65,7 @@ ConfirmTxScreen.prototype.render = function() {
}, },
onClick: () => state.dispatch(actions.previousTx()), onClick: () => state.dispatch(actions.previousTx()),
}), }),
` Transaction ${state.index + 1} of ${unconfTxList.length} `, ` ${state.index + 1} of ${unconfTxList.length} `,
h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', { h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', {
style: { style: {
display: state.index + 1 === unconfTxList.length ? 'none' : 'inline-block', display: state.index + 1 === unconfTxList.length ? 'none' : 'inline-block',
@ -72,58 +74,44 @@ ConfirmTxScreen.prototype.render = function() {
}), }),
]), ]),
warningIfExists(state.warning),
h(ReactCSSTransitionGroup, { h(ReactCSSTransitionGroup, {
transitionName: "main", transitionName: "main",
transitionEnterTimeout: 300, transitionEnterTimeout: 300,
transitionLeaveTimeout: 300, transitionLeaveTimeout: 300,
}, [ }, [
h('.transaction', { currentTxView({
// Properties
txData: txData,
key: txData.id, key: txData.id,
}, [ selectedAddress: state.selectedAddress,
accounts: state.accounts,
identities: state.identities,
// Actions
sendTransaction: this.sendTransaction.bind(this, txData),
cancelTransaction: this.cancelTransaction.bind(this, txData),
signMessage: this.signMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
}),
// account that will sign
h(AccountPanel, {
showFullAddress: true,
identity: identity,
account: account,
}),
// tx data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
h('.flex-row.flex-space-between', [
h('label.font-small', 'TO ADDRESS'),
h('span.font-small', addressSummary(txParams.to)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'DATE'),
h('span.font-small', readableDate(txData.time)),
]),
h('.flex-row.flex-space-between', [
h('label.font-small', 'AMOUNT'),
h('span.font-small', formatBalance(txParams.value)),
]),
]),
// send + cancel
h('.flex-row.flex-space-around', [
h('button', {
onClick: this.cancelTransaction.bind(this, txData),
}, 'Cancel'),
h('button', {
onClick: this.sendTransaction.bind(this, txData),
}, 'Send'),
]),
]),
]), ]),
]) // No comma or semicolon can go here ])
) )
} }
function currentTxView (opts) {
if ('txParams' in opts.txData) {
// This is a pending transaction
return h(ConfirmTx, opts)
} else if ('msgParams' in opts.txData) {
// This is a pending message to sign
return h(PendingMsg, opts)
}
}
ConfirmTxScreen.prototype.sendTransaction = function(txData, event){ ConfirmTxScreen.prototype.sendTransaction = function(txData, event){
event.stopPropagation() event.stopPropagation()
this.props.dispatch(actions.sendTx(txData)) this.props.dispatch(actions.sendTx(txData))
@ -134,7 +122,25 @@ ConfirmTxScreen.prototype.cancelTransaction = function(txData, event){
this.props.dispatch(actions.cancelTx(txData)) this.props.dispatch(actions.cancelTx(txData))
} }
ConfirmTxScreen.prototype.navigateToAccounts = function(event){ ConfirmTxScreen.prototype.signMessage = function(msgData, event){
var params = msgData.msgParams
params.metamaskId = msgData.id
event.stopPropagation() event.stopPropagation()
this.props.dispatch(actions.showAccountsPage()) this.props.dispatch(actions.signMsg(params))
}
ConfirmTxScreen.prototype.cancelMessage = function(msgData, event){
event.stopPropagation()
this.props.dispatch(actions.cancelMsg(msgData))
}
ConfirmTxScreen.prototype.goHome = function(event){
event.stopPropagation()
this.props.dispatch(actions.goHome())
}
function warningIfExists(warning) {
if (warning) {
return h('span.error', { style: { margin: 'auto' } }, warning)
}
} }

View File

@ -1,6 +1,7 @@
const extend = require('xtend') const extend = require('xtend')
const actions = require('../actions') const actions = require('../actions')
const valuesFor = require('../util').valuesFor const valuesFor = require('../util').valuesFor
const txHelper = require('../../lib/tx-helper')
module.exports = reduceApp module.exports = reduceApp
@ -107,6 +108,7 @@ function reduceApp(state, action) {
case actions.UNLOCK_METAMASK: case actions.UNLOCK_METAMASK:
return extend(appState, { return extend(appState, {
currentView: {},
transForward: true, transForward: true,
warning: null, warning: null,
}) })
@ -127,10 +129,7 @@ function reduceApp(state, action) {
case actions.GO_HOME: case actions.GO_HOME:
return extend(appState, { return extend(appState, {
currentView: { currentView: {},
name: 'accountDetail',
context: appState.currentView.context,
},
accountDetail: { accountDetail: {
accountExport: 'none', accountExport: 'none',
privateKey: '', privateKey: '',
@ -185,9 +184,24 @@ function reduceApp(state, action) {
warning: null, warning: null,
}) })
case actions.SHOW_CONF_MSG_PAGE:
return extend(appState, {
currentView: {
name: 'confTx',
context: 0,
},
transForward: true,
warning: null,
})
case actions.COMPLETED_TX: case actions.COMPLETED_TX:
var unconfTxs = Object.keys(state.metamask.unconfTxs).filter(tx => tx !== tx.id) var unconfTxs = state.metamask.unconfTxs
if (unconfTxs && unconfTxs.length > 0) { var unconfMsgs = state.metamask.unconfMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
.filter(tx => tx !== tx.id)
if (unconfTxList && unconfTxList.length > 0) {
return extend(appState, { return extend(appState, {
transForward: false, transForward: false,
currentView: { currentView: {
@ -202,7 +216,7 @@ function reduceApp(state, action) {
warning: null, warning: null,
currentView: { currentView: {
name: 'accountDetail', name: 'accountDetail',
context: appState.currentView.context, context: state.metamask.selectedAddress,
}, },
}) })
} }

View File

@ -44,13 +44,19 @@ function reduceMetamask(state, action) {
case actions.COMPLETED_TX: case actions.COMPLETED_TX:
var stringId = String(action.id) var stringId = String(action.id)
var newState = extend(metamaskState, { var newState = extend(metamaskState, {
unconfTxs: {} unconfTxs: {},
unconfMsgs: {},
}) })
for (var id in metamaskState.unconfTxs) { for (var id in metamaskState.unconfTxs) {
if (id !== stringId) { if (id !== stringId) {
newState.unconfTxs[id] = metamaskState.unconfTxs[id] newState.unconfTxs[id] = metamaskState.unconfTxs[id]
} }
} }
for (var id in metamaskState.unconfMsgs) {
if (id !== stringId) {
newState.unconfMsgs[id] = metamaskState.unconfMsgs[id]
}
}
return newState return newState
case actions.CLEAR_SEED_WORD_CACHE: case actions.CLEAR_SEED_WORD_CACHE:

View File

@ -43,6 +43,11 @@ function startApp(metamaskState, accountManager, opts){
store.dispatch(actions.showConfTxPage()) store.dispatch(actions.showConfTxPage())
} }
// if unconfirmed messages, start on msgConf page
if (Object.keys(metamaskState.unconfMsgs || {}).length) {
store.dispatch(actions.showConfTxPage())
}
accountManager.on('update', function(metamaskState){ accountManager.on('update', function(metamaskState){
store.dispatch(actions.updateMetamaskState(metamaskState)) store.dispatch(actions.updateMetamaskState(metamaskState))
}) })

8
ui/lib/tx-helper.js Normal file
View File

@ -0,0 +1,8 @@
const valuesFor = require('../app/util').valuesFor
module.exports = function(unconfTxs, unconfMsgs) {
var txValues = valuesFor(unconfTxs)
var msgValues = valuesFor(unconfMsgs)
var allValues = txValues.concat(msgValues)
return allValues.sort(tx => tx.time)
}