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:
commit
95582f8bde
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
61
app/scripts/lib/message-manager.js
Normal file
61
app/scripts/lib/message-manager.js
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -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){
|
||||||
|
72
ui/app/components/pending-msg.js
Normal file
72
ui/app/components/pending-msg.js
Normal 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'),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
78
ui/app/components/pending-tx.js
Normal file
78
ui/app/components/pending-tx.js
Normal 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'),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
19
ui/app/components/template.js
Normal file
19
ui/app/components/template.js
Normal 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')
|
||||||
|
)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
8
ui/lib/tx-helper.js
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user