1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +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
- Add support for calls to `eth.sign`.
## 1.7.0 2016-04-29
- 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 createMsgNotification = require('./lib/notifications.js').createMsgNotification
const configManager = require('./lib/config-manager-singleton')
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
@ -175,6 +176,8 @@ function setupControllerConnection(stream){
setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
approveTransaction: idStore.approveTransaction.bind(idStore),
cancelTransaction: idStore.cancelTransaction.bind(idStore),
signMessage: idStore.signMessage.bind(idStore),
cancelMessage: idStore.cancelMessage.bind(idStore),
setLocked: idStore.setLocked.bind(idStore),
clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
exportAccount: idStore.exportAccount.bind(idStore),
@ -206,7 +209,10 @@ idStore.on('update', updateBadge)
function updateBadge(state){
var label = ''
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) {
label = String(count)
}

View File

@ -211,73 +211,6 @@ ConfigManager.prototype.updateTx = function(tx) {
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

View File

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

View File

@ -42,6 +42,7 @@ var actions = {
SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL',
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
// account detail screen
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
showSendPage: showSendPage,
@ -57,7 +58,8 @@ var actions = {
NEXT_TX: 'NEXT_TX',
PREVIOUS_TX: 'PREV_TX',
setSelectedAddress: setSelectedAddress,
signTx: signTx,
signMsg: signMsg,
cancelMsg: cancelMsg,
sendTx: sendTx,
cancelTx: cancelTx,
completedTx: completedTx,
@ -111,7 +113,6 @@ function tryUnlockMetamask(password) {
dispatch(this.unlockFailed())
} else {
dispatch(this.unlockMetamask())
dispatch(this.showAccountDetail(selectedAccount))
}
})
}
@ -152,16 +153,15 @@ function setSelectedAddress(address) {
}
}
function signTx(txData) {
function signMsg(msgData) {
return (dispatch) => {
dispatch(this.showLoadingIndication())
web3.eth.sendTransaction(txData, (err, data) => {
_accountManager.signMessage(msgData, (err) => {
dispatch(this.hideLoadingIndication())
if (err) return dispatch(this.displayWarning(err.message))
dispatch(this.hideWarning())
dispatch(this.goHome())
dispatch(this.completedTx(msgData.metamaskId))
})
}
}
@ -193,9 +193,14 @@ function txError(err) {
}
}
function cancelMsg(msgData){
_accountManager.cancelMessage(msgData.id)
return this.completedTx(msgData.id)
}
function cancelTx(txData){
_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 InfoScreen = require('./info')
const LoadingIndicator = require('./loading')
const txHelper = require('../lib/tx-helper')
module.exports = connect(mapStateToProps)(App)
@ -39,6 +40,8 @@ function mapStateToProps(state) {
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
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'})
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){

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 AccountPanel = require('./components/account-panel')
const valuesFor = require('./util').valuesFor
const addressSummary = require('./util').addressSummary
const readableDate = require('./util').readableDate
const formatBalance = require('./util').formatBalance
const dataSize = require('./util').dataSize
const txHelper = require('../lib/tx-helper')
const ConfirmTx = require('./components/pending-tx')
const PendingMsg = require('./components/pending-msg')
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
@ -20,7 +20,9 @@ function mapStateToProps(state) {
accounts: state.metamask.accounts,
selectedAddress: state.metamask.selectedAddress,
unconfTxs: state.metamask.unconfTxs,
unconfMsgs: state.metamask.unconfMsgs,
index: state.appState.currentView.context,
warning: state.appState.warning,
}
}
@ -32,12 +34,12 @@ function ConfirmTxScreen() {
ConfirmTxScreen.prototype.render = function() {
var state = this.props
var unconfTxList = valuesFor(state.unconfTxs).sort(tx => tx.time)
var txData = unconfTxList[state.index] || {}
var txParams = txData.txParams || {}
var address = txParams.from || state.selectedAddress
var identity = state.identities[address] || { address: address }
var account = state.accounts[address] || { address: address }
var unconfTxs = state.unconfTxs
var unconfMsgs = state.unconfMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
var index = state.index !== undefined ? state.index : 0
var txData = unconfTxList[index] || {}
return (
@ -46,9 +48,9 @@ ConfirmTxScreen.prototype.render = function() {
// subtitle and nav
h('.section-title.flex-row.flex-center', [
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', {
@ -63,7 +65,7 @@ ConfirmTxScreen.prototype.render = function() {
},
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', {
style: {
display: state.index + 1 === unconfTxList.length ? 'none' : 'inline-block',
@ -72,58 +74,44 @@ ConfirmTxScreen.prototype.render = function() {
}),
]),
warningIfExists(state.warning),
h(ReactCSSTransitionGroup, {
transitionName: "main",
transitionEnterTimeout: 300,
transitionLeaveTimeout: 300,
}, [
h('.transaction', {
currentTxView({
// Properties
txData: txData,
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){
event.stopPropagation()
this.props.dispatch(actions.sendTx(txData))
@ -134,7 +122,25 @@ ConfirmTxScreen.prototype.cancelTransaction = function(txData, event){
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()
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 actions = require('../actions')
const valuesFor = require('../util').valuesFor
const txHelper = require('../../lib/tx-helper')
module.exports = reduceApp
@ -107,6 +108,7 @@ function reduceApp(state, action) {
case actions.UNLOCK_METAMASK:
return extend(appState, {
currentView: {},
transForward: true,
warning: null,
})
@ -127,10 +129,7 @@ function reduceApp(state, action) {
case actions.GO_HOME:
return extend(appState, {
currentView: {
name: 'accountDetail',
context: appState.currentView.context,
},
currentView: {},
accountDetail: {
accountExport: 'none',
privateKey: '',
@ -185,9 +184,24 @@ function reduceApp(state, action) {
warning: null,
})
case actions.SHOW_CONF_MSG_PAGE:
return extend(appState, {
currentView: {
name: 'confTx',
context: 0,
},
transForward: true,
warning: null,
})
case actions.COMPLETED_TX:
var unconfTxs = Object.keys(state.metamask.unconfTxs).filter(tx => tx !== tx.id)
if (unconfTxs && unconfTxs.length > 0) {
var unconfTxs = state.metamask.unconfTxs
var unconfMsgs = state.metamask.unconfMsgs
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
.filter(tx => tx !== tx.id)
if (unconfTxList && unconfTxList.length > 0) {
return extend(appState, {
transForward: false,
currentView: {
@ -202,7 +216,7 @@ function reduceApp(state, action) {
warning: null,
currentView: {
name: 'accountDetail',
context: appState.currentView.context,
context: state.metamask.selectedAddress,
},
})
}

View File

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

View File

@ -43,6 +43,11 @@ function startApp(metamaskState, accountManager, opts){
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){
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)
}