mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'master' into i1340-SynchronousInjection
This commit is contained in:
commit
d0d082d70c
13
CHANGELOG.md
13
CHANGELOG.md
@ -2,7 +2,20 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- Fix bug where web3 API was sometimes injected after the page loaded.
|
||||
|
||||
## 3.11.0 2017-10-11
|
||||
|
||||
- Add support for new eth_signTypedData method per EIP 712.
|
||||
- Fix bug where some transactions would be shown as pending forever, even after successfully mined.
|
||||
- Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined.
|
||||
- Fix link to support article on token addresses.
|
||||
|
||||
## 3.10.9 2017-10-5
|
||||
|
||||
- Only rebrodcast transactions for a day not a days worth of blocks
|
||||
- Remove Slack link from info page, since it is a big phishing target.
|
||||
- Stop computing balance based on pending transactions, to avoid edge case where users are unable to send transactions.
|
||||
|
||||
## 3.10.8 2017-9-28
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "3.10.8",
|
||||
"version": "3.11.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
|
@ -124,7 +124,8 @@ function setupController (initState) {
|
||||
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||
var unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
|
||||
var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs
|
||||
var unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
|
||||
var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs
|
||||
if (count) {
|
||||
label = String(count)
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
const assert = require('assert')
|
||||
const EventEmitter = require('events')
|
||||
const createMetamaskProvider = require('web3-provider-engine/zero.js')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ComposedStore = require('obs-store/lib/composed')
|
||||
const extend = require('xtend')
|
||||
const EthQuery = require('eth-query')
|
||||
const createEthRpcClient = require('eth-rpc-client')
|
||||
const createEventEmitterProxy = require('../lib/events-proxy.js')
|
||||
const createObjectProxy = require('../lib/obj-proxy.js')
|
||||
const RPC_ADDRESS_LIST = require('../config.js').network
|
||||
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
|
||||
|
||||
@ -17,7 +18,8 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
this.providerStore = new ObservableStore(config.provider)
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||
this._proxy = createEventEmitterProxy()
|
||||
this.providerProxy = createObjectProxy()
|
||||
this.blockTrackerProxy = createEventEmitterProxy()
|
||||
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
}
|
||||
@ -25,12 +27,11 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
initializeProvider (_providerParams) {
|
||||
this._baseProviderParams = _providerParams
|
||||
const rpcUrl = this.getCurrentRpcAddress()
|
||||
this._configureStandardProvider({ rpcUrl })
|
||||
this._proxy.on('block', this._logBlock.bind(this))
|
||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
||||
this.ethQuery = new EthQuery(this._proxy)
|
||||
this._configureStandardClient({ rpcUrl })
|
||||
this.blockTrackerProxy.on('block', this._logBlock.bind(this))
|
||||
this.blockTrackerProxy.on('error', this.verifyNetwork.bind(this))
|
||||
this.ethQuery = new EthQuery(this.providerProxy)
|
||||
this.lookupNetwork()
|
||||
return this._proxy
|
||||
}
|
||||
|
||||
verifyNetwork () {
|
||||
@ -76,8 +77,10 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
|
||||
// skip if type already matches
|
||||
if (type === this.getProviderConfig().type) return
|
||||
// lookup rpcTarget for typecreateMetamaskProvider
|
||||
const rpcTarget = this.getRpcAddressForType(type)
|
||||
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
|
||||
// update connectioncreateMetamaskProvider
|
||||
this.providerStore.updateState({ type, rpcTarget })
|
||||
this._switchNetwork({ rpcUrl: rpcTarget })
|
||||
}
|
||||
@ -97,32 +100,29 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
_switchNetwork (providerParams) {
|
||||
this.setNetworkState('loading')
|
||||
this._configureStandardProvider(providerParams)
|
||||
this._configureStandardClient(providerParams)
|
||||
this.emit('networkDidChange')
|
||||
}
|
||||
|
||||
_configureStandardProvider(_providerParams) {
|
||||
_configureStandardClient(_providerParams) {
|
||||
const providerParams = extend(this._baseProviderParams, _providerParams)
|
||||
const provider = createMetamaskProvider(providerParams)
|
||||
this._setProvider(provider)
|
||||
const client = createEthRpcClient(providerParams)
|
||||
this._setClient(client)
|
||||
}
|
||||
|
||||
_setProvider (provider) {
|
||||
// collect old block tracker events
|
||||
const oldProvider = this._provider
|
||||
let blockTrackerHandlers
|
||||
if (oldProvider) {
|
||||
// capture old block handlers
|
||||
blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers
|
||||
// tear down
|
||||
oldProvider.removeAllListeners()
|
||||
oldProvider.stop()
|
||||
_setClient (newClient) {
|
||||
// teardown old client
|
||||
const oldClient = this._currentClient
|
||||
if (oldClient) {
|
||||
oldClient.blockTracker.stop()
|
||||
// asyncEventEmitter lacks a "removeAllListeners" method
|
||||
// oldClient.blockTracker.removeAllListeners
|
||||
oldClient.blockTracker._events = {}
|
||||
}
|
||||
// override block tracler
|
||||
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
|
||||
// set as new provider
|
||||
this._provider = provider
|
||||
this._proxy.setTarget(provider)
|
||||
this._currentClient = newClient
|
||||
this.providerProxy.setTarget(newClient.provider)
|
||||
this.blockTrackerProxy.setTarget(newClient.blockTracker)
|
||||
}
|
||||
|
||||
_logBlock (block) {
|
||||
|
@ -46,6 +46,7 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
||||
this.nonceTracker = new NonceTracker({
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||
getConfirmedTransactions: (address) => {
|
||||
return this.txStateManager.getFilteredTxList({
|
||||
@ -59,9 +60,10 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
this.pendingTxTracker = new PendingTransactionTracker({
|
||||
provider: this.provider,
|
||||
nonceTracker: this.nonceTracker,
|
||||
retryLimit: 3500, // Retry 3500 blocks, or about 1 day.
|
||||
retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day.
|
||||
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||
})
|
||||
|
||||
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
||||
|
@ -1,6 +1,5 @@
|
||||
module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
|
||||
module.exports = function createEventEmitterProxy(eventEmitter, eventHandlers = {}) {
|
||||
let target = eventEmitter
|
||||
const eventHandlers = listeners || {}
|
||||
const proxy = new Proxy({}, {
|
||||
get: (obj, name) => {
|
||||
// intercept listeners
|
||||
@ -14,9 +13,12 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
|
||||
return true
|
||||
},
|
||||
})
|
||||
proxy.setTarget(eventEmitter)
|
||||
return proxy
|
||||
|
||||
function setTarget (eventEmitter) {
|
||||
target = eventEmitter
|
||||
// migrate listeners
|
||||
// migrate eventHandlers
|
||||
Object.keys(eventHandlers).forEach((name) => {
|
||||
eventHandlers[name].forEach((handler) => target.on(name, handler))
|
||||
})
|
||||
@ -26,6 +28,4 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
|
||||
eventHandlers[name].push(handler)
|
||||
target.on(name, handler)
|
||||
}
|
||||
if (listeners) proxy.setTarget(eventEmitter)
|
||||
return proxy
|
||||
}
|
@ -1,10 +1,18 @@
|
||||
const promiseToCallback = require('promise-to-callback')
|
||||
const noop = function(){}
|
||||
|
||||
module.exports = function nodeify (fn, context) {
|
||||
return function(){
|
||||
const args = [].slice.call(arguments)
|
||||
const callback = args.pop()
|
||||
if (typeof callback !== 'function') throw new Error('callback is not a function')
|
||||
const lastArg = args[args.length - 1]
|
||||
const lastArgIsCallback = typeof lastArg === 'function'
|
||||
let callback
|
||||
if (lastArgIsCallback) {
|
||||
callback = lastArg
|
||||
args.pop()
|
||||
} else {
|
||||
callback = noop
|
||||
}
|
||||
promiseToCallback(fn.apply(context, args))(callback)
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,9 @@ const Mutex = require('await-semaphore').Mutex
|
||||
|
||||
class NonceTracker {
|
||||
|
||||
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
|
||||
constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
|
||||
this.provider = provider
|
||||
this.blockTracker = blockTracker
|
||||
this.ethQuery = new EthQuery(provider)
|
||||
this.getPendingTransactions = getPendingTransactions
|
||||
this.getConfirmedTransactions = getConfirmedTransactions
|
||||
@ -53,7 +54,7 @@ class NonceTracker {
|
||||
}
|
||||
|
||||
async _getCurrentBlock () {
|
||||
const blockTracker = this._getBlockTracker()
|
||||
const blockTracker = this.blockTracker
|
||||
const currentBlock = blockTracker.getCurrentBlock()
|
||||
if (currentBlock) return currentBlock
|
||||
return await Promise((reject, resolve) => {
|
||||
@ -139,11 +140,6 @@ class NonceTracker {
|
||||
return { name: 'local', nonce: highest, details: { startPoint, highest } }
|
||||
}
|
||||
|
||||
// this is a hotfix for the fact that the blockTracker will
|
||||
// change when the network changes
|
||||
_getBlockTracker () {
|
||||
return this.provider._blockTracker
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NonceTracker
|
||||
|
19
app/scripts/lib/obj-proxy.js
Normal file
19
app/scripts/lib/obj-proxy.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = function createObjectProxy(obj) {
|
||||
let target = obj
|
||||
const proxy = new Proxy({}, {
|
||||
get: (obj, name) => {
|
||||
// intercept setTarget
|
||||
if (name === 'setTarget') return setTarget
|
||||
return target[name]
|
||||
},
|
||||
set: (obj, name, value) => {
|
||||
target[name] = value
|
||||
return true
|
||||
},
|
||||
})
|
||||
return proxy
|
||||
|
||||
function setTarget (obj) {
|
||||
target = obj
|
||||
}
|
||||
}
|
@ -22,9 +22,12 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
super()
|
||||
this.query = new EthQuery(config.provider)
|
||||
this.nonceTracker = config.nonceTracker
|
||||
this.retryLimit = config.retryLimit || Infinity
|
||||
// default is one day
|
||||
this.retryTimePeriod = config.retryTimePeriod || 86400000
|
||||
this.getPendingTransactions = config.getPendingTransactions
|
||||
this.getCompletedTransactions = config.getCompletedTransactions
|
||||
this.publishTransaction = config.publishTransaction
|
||||
this._checkPendingTxs()
|
||||
}
|
||||
|
||||
// checks if a signed tx is in a block and
|
||||
@ -99,8 +102,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
}
|
||||
|
||||
async _resubmitTx (txMeta) {
|
||||
if (txMeta.retryCount > this.retryLimit) {
|
||||
const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`)
|
||||
if (Date.now() > txMeta.time + this.retryTimePeriod) {
|
||||
const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1)
|
||||
const err = new Error(`Gave up submitting after ${hours} hours.`)
|
||||
return this.emit('tx:failed', txMeta.id, err)
|
||||
}
|
||||
|
||||
@ -118,6 +122,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
async _checkPendingTx (txMeta) {
|
||||
const txHash = txMeta.hash
|
||||
const txId = txMeta.id
|
||||
|
||||
// extra check in case there was an uncaught error during the
|
||||
// signature and submission process
|
||||
if (!txHash) {
|
||||
@ -126,6 +131,15 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
this.emit('tx:failed', txId, noTxHashErr)
|
||||
return
|
||||
}
|
||||
|
||||
// If another tx with the same nonce is mined, set as failed.
|
||||
const taken = await this._checkIfNonceIsTaken(txMeta)
|
||||
if (taken) {
|
||||
const nonceTakenErr = new Error('Another transaction with this nonce has been mined.')
|
||||
nonceTakenErr.name = 'NonceTakenErr'
|
||||
return this.emit('tx:failed', txId, nonceTakenErr)
|
||||
}
|
||||
|
||||
// get latest transaction status
|
||||
let txParams
|
||||
try {
|
||||
@ -157,4 +171,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
}
|
||||
nonceGlobalLock.releaseLock()
|
||||
}
|
||||
|
||||
async _checkIfNonceIsTaken (txMeta) {
|
||||
const completed = this.getCompletedTransactions()
|
||||
const sameNonce = completed.filter((otherMeta) => {
|
||||
return otherMeta.txParams.nonce === txMeta.txParams.nonce
|
||||
})
|
||||
return sameNonce.length > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,6 +46,12 @@ module.exports = class TransactionStateManger extends EventEmitter {
|
||||
return this.getFilteredTxList(opts)
|
||||
}
|
||||
|
||||
getConfirmedTransactions (address) {
|
||||
const opts = { status: 'confirmed' }
|
||||
if (address) opts.from = address
|
||||
return this.getFilteredTxList(opts)
|
||||
}
|
||||
|
||||
addTx (txMeta) {
|
||||
this.once(`${txMeta.id}:signed`, function (txId) {
|
||||
this.removeAllListeners(`${txMeta.id}:rejected`)
|
||||
@ -242,4 +248,4 @@ module.exports = class TransactionStateManger extends EventEmitter {
|
||||
_saveTxList (transactions) {
|
||||
this.store.updateState({ transactions })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
123
app/scripts/lib/typed-message-manager.js
Normal file
123
app/scripts/lib/typed-message-manager.js
Normal file
@ -0,0 +1,123 @@
|
||||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const createId = require('./random-id')
|
||||
const assert = require('assert')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
|
||||
|
||||
module.exports = class TypedMessageManager extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedTypedMessages: {},
|
||||
unapprovedTypedMessagesCount: 0,
|
||||
})
|
||||
this.messages = []
|
||||
}
|
||||
|
||||
get unapprovedTypedMessagesCount () {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length
|
||||
}
|
||||
|
||||
getUnapprovedMsgs () {
|
||||
return this.messages.filter(msg => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => { result[msg.id] = msg; return result }, {})
|
||||
}
|
||||
|
||||
addUnapprovedMessage (msgParams) {
|
||||
this.validateParams(msgParams)
|
||||
|
||||
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
|
||||
// create txData obj with parameters and meta data
|
||||
var time = (new Date()).getTime()
|
||||
var msgId = createId()
|
||||
var msgData = {
|
||||
id: msgId,
|
||||
msgParams: msgParams,
|
||||
time: time,
|
||||
status: 'unapproved',
|
||||
type: 'eth_signTypedData',
|
||||
}
|
||||
this.addMsg(msgData)
|
||||
|
||||
// signal update
|
||||
this.emit('update')
|
||||
return msgId
|
||||
}
|
||||
|
||||
validateParams (params) {
|
||||
assert.equal(typeof params, 'object', 'Params should ben an object.')
|
||||
assert.ok('data' in params, 'Params must include a data field.')
|
||||
assert.ok('from' in params, 'Params must include a from field.')
|
||||
assert.ok(Array.isArray(params.data), 'Data should be an array.')
|
||||
assert.equal(typeof params.from, 'string', 'From field must be a string.')
|
||||
assert.doesNotThrow(() => {
|
||||
sigUtil.typedSignatureHash(params.data)
|
||||
}, 'Expected EIP712 typed data')
|
||||
}
|
||||
|
||||
addMsg (msg) {
|
||||
this.messages.push(msg)
|
||||
this._saveMsgList()
|
||||
}
|
||||
|
||||
getMsg (msgId) {
|
||||
return this.messages.find(msg => msg.id === msgId)
|
||||
}
|
||||
|
||||
approveMessage (msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId)
|
||||
return this.prepMsgForSigning(msgParams)
|
||||
}
|
||||
|
||||
setMsgStatusApproved (msgId) {
|
||||
this._setMsgStatus(msgId, 'approved')
|
||||
}
|
||||
|
||||
setMsgStatusSigned (msgId, rawSig) {
|
||||
const msg = this.getMsg(msgId)
|
||||
msg.rawSig = rawSig
|
||||
this._updateMsg(msg)
|
||||
this._setMsgStatus(msgId, 'signed')
|
||||
}
|
||||
|
||||
prepMsgForSigning (msgParams) {
|
||||
delete msgParams.metamaskId
|
||||
return Promise.resolve(msgParams)
|
||||
}
|
||||
|
||||
rejectMsg (msgId) {
|
||||
this._setMsgStatus(msgId, 'rejected')
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
_setMsgStatus (msgId, status) {
|
||||
const msg = this.getMsg(msgId)
|
||||
if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".')
|
||||
msg.status = status
|
||||
this._updateMsg(msg)
|
||||
this.emit(`${msgId}:${status}`, msg)
|
||||
if (status === 'rejected' || status === 'signed') {
|
||||
this.emit(`${msgId}:finished`, msg)
|
||||
}
|
||||
}
|
||||
|
||||
_updateMsg (msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id)
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg
|
||||
}
|
||||
this._saveMsgList()
|
||||
}
|
||||
|
||||
_saveMsgList () {
|
||||
const unapprovedTypedMessages = this.getUnapprovedMsgs()
|
||||
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length
|
||||
this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount })
|
||||
this.emit('updateBadge')
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ const InfuraController = require('./controllers/infura')
|
||||
const BlacklistController = require('./controllers/blacklist')
|
||||
const MessageManager = require('./lib/message-manager')
|
||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||
const TypedMessageManager = require('./lib/typed-message-manager')
|
||||
const TransactionController = require('./controllers/transactions')
|
||||
const BalancesController = require('./controllers/computed-balances')
|
||||
const ConfigManager = require('./lib/config-manager')
|
||||
@ -80,9 +81,24 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
this.blacklistController.scheduleUpdates()
|
||||
|
||||
// rpc provider
|
||||
this.provider = this.initializeProvider()
|
||||
this.blockTracker = this.provider._blockTracker
|
||||
// rpc provider and block tracker
|
||||
this.networkController.initializeProvider({
|
||||
scaffold: {
|
||||
eth_syncing: false,
|
||||
web3_clientVersion: `MetaMask/v${version}`,
|
||||
},
|
||||
// account mgmt
|
||||
getAccounts: nodeify(this.getAccounts, this),
|
||||
// tx signing
|
||||
processTransaction: nodeify(this.newTransaction, this),
|
||||
// old style msg signing
|
||||
processMessage: this.newUnsignedMessage.bind(this),
|
||||
// personal_sign msg signing
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
||||
})
|
||||
this.provider = this.networkController.providerProxy
|
||||
this.blockTracker = this.networkController.blockTrackerProxy
|
||||
|
||||
// eth data query tools
|
||||
this.ethQuery = new EthQuery(this.provider)
|
||||
@ -161,6 +177,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.networkController.lookupNetwork()
|
||||
this.messageManager = new MessageManager()
|
||||
this.personalMessageManager = new PersonalMessageManager()
|
||||
this.typedMessageManager = new TypedMessageManager()
|
||||
this.publicConfigStore = this.initPublicConfigStore()
|
||||
|
||||
// manual disk state subscriptions
|
||||
@ -202,6 +219,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.balancesController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.addressBookController.store.subscribe(this.sendUpdate.bind(this))
|
||||
@ -215,35 +233,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// Constructor helpers
|
||||
//
|
||||
|
||||
initializeProvider () {
|
||||
const providerOpts = {
|
||||
static: {
|
||||
eth_syncing: false,
|
||||
web3_clientVersion: `MetaMask/v${version}`,
|
||||
},
|
||||
// account mgmt
|
||||
getAccounts: (cb) => {
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const result = []
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
|
||||
// only show address if account is unlocked
|
||||
if (isUnlocked && selectedAddress) {
|
||||
result.push(selectedAddress)
|
||||
}
|
||||
cb(null, result)
|
||||
},
|
||||
// tx signing
|
||||
processTransaction: nodeify(async (txParams) => await this.txController.newUnapprovedTransaction(txParams), this),
|
||||
// old style msg signing
|
||||
processMessage: this.newUnsignedMessage.bind(this),
|
||||
// personal_sign msg signing
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
}
|
||||
const providerProxy = this.networkController.initializeProvider(providerOpts)
|
||||
return providerProxy
|
||||
}
|
||||
|
||||
initPublicConfigStore () {
|
||||
// get init state
|
||||
const publicConfigStore = new ObservableStore()
|
||||
@ -283,6 +272,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.txController.memStore.getState(),
|
||||
this.messageManager.memStore.getState(),
|
||||
this.personalMessageManager.memStore.getState(),
|
||||
this.typedMessageManager.memStore.getState(),
|
||||
this.keyringController.memStore.getState(),
|
||||
this.balancesController.store.getState(),
|
||||
this.preferencesController.store.getState(),
|
||||
@ -364,6 +354,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
signPersonalMessage: nodeify(this.signPersonalMessage, this),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this),
|
||||
|
||||
// personalMessageManager
|
||||
signTypedMessage: nodeify(this.signTypedMessage, this),
|
||||
cancelTypedMessage: this.cancelTypedMessage.bind(this),
|
||||
|
||||
// notices
|
||||
checkNotices: noticeController.updateNoticesList.bind(noticeController),
|
||||
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
|
||||
@ -474,6 +468,18 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// Opinionated Keyring Management
|
||||
//
|
||||
|
||||
async getAccounts () {
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const result = []
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
|
||||
// only show address if account is unlocked
|
||||
if (isUnlocked && selectedAddress) {
|
||||
result.push(selectedAddress)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
addNewAccount (cb) {
|
||||
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
|
||||
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
|
||||
@ -520,6 +526,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// Identity Management
|
||||
//
|
||||
|
||||
// this function wrappper lets us pass the fn reference before txController is instantiated
|
||||
async newTransaction (txParams) {
|
||||
return await this.txController.newUnapprovedTransaction(txParams)
|
||||
}
|
||||
|
||||
newUnsignedMessage (msgParams, cb) {
|
||||
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
||||
this.sendUpdate()
|
||||
@ -556,6 +567,28 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
newUnsignedTypedMessage (msgParams, cb) {
|
||||
let msgId
|
||||
try {
|
||||
msgId = this.typedMessageManager.addUnapprovedMessage(msgParams)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedMessage()
|
||||
} catch (e) {
|
||||
return cb(e)
|
||||
}
|
||||
|
||||
this.typedMessageManager.once(`${msgId}:finished`, (data) => {
|
||||
switch (data.status) {
|
||||
case 'signed':
|
||||
return cb(null, data.rawSig)
|
||||
case 'rejected':
|
||||
return cb(new Error('MetaMask Message Signature: User denied message signature.'))
|
||||
default:
|
||||
return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
signMessage (msgParams, cb) {
|
||||
log.info('MetaMaskController - signMessage')
|
||||
const msgId = msgParams.metamaskId
|
||||
@ -618,6 +651,24 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
signTypedMessage (msgParams) {
|
||||
log.info('MetaMaskController - signTypedMessage')
|
||||
const msgId = msgParams.metamaskId
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for signing
|
||||
return this.typedMessageManager.approveMessage(msgParams)
|
||||
.then((cleanMsgParams) => {
|
||||
// signs the message
|
||||
return this.keyringController.signTypedMessage(cleanMsgParams)
|
||||
})
|
||||
.then((rawSig) => {
|
||||
// tells the listener that the message has been signed
|
||||
// and can be returned to the dapp
|
||||
this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
|
||||
return this.getState()
|
||||
})
|
||||
}
|
||||
|
||||
cancelPersonalMessage (msgId, cb) {
|
||||
const messageManager = this.personalMessageManager
|
||||
messageManager.rejectMsg(msgId)
|
||||
@ -626,6 +677,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
cancelTypedMessage (msgId, cb) {
|
||||
const messageManager = this.typedMessageManager
|
||||
messageManager.rejectMsg(msgId)
|
||||
if (cb && typeof cb === 'function') {
|
||||
cb(null, this.getState())
|
||||
}
|
||||
}
|
||||
|
||||
markAccountsFound (cb) {
|
||||
this.configManager.setLostAccounts([])
|
||||
this.sendUpdate()
|
||||
|
@ -151,7 +151,7 @@ gulp.task('copy:watch', function(){
|
||||
|
||||
gulp.task('lint', function () {
|
||||
// Ignoring node_modules, dist/firefox, and docs folders:
|
||||
return gulp.src(['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js'])
|
||||
return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js'])
|
||||
.pipe(eslint(fs.readFileSync(path.join(__dirname, '.eslintrc'))))
|
||||
// eslint.format() outputs the lint results to the console.
|
||||
// Alternatively use eslint.formatEach() (see Docs).
|
||||
|
@ -7,20 +7,32 @@ async function loadProvider() {
|
||||
const ethereumProvider = window.metamask.createDefaultProvider({ host: 'http://localhost:9001' })
|
||||
const ethQuery = new EthQuery(ethereumProvider)
|
||||
const accounts = await ethQuery.accounts()
|
||||
logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined')
|
||||
setupButton(ethQuery)
|
||||
window.METAMASK_ACCOUNT = accounts[0] || 'locked'
|
||||
logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account')
|
||||
setupButtons(ethQuery)
|
||||
}
|
||||
|
||||
|
||||
function logToDom(message){
|
||||
document.getElementById('account').innerText = message
|
||||
function logToDom(message, context){
|
||||
document.getElementById(context).innerText = message
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
function setupButton (ethQuery) {
|
||||
const button = document.getElementById('action-button-1')
|
||||
button.addEventListener('click', async () => {
|
||||
function setupButtons (ethQuery) {
|
||||
const accountButton = document.getElementById('action-button-1')
|
||||
accountButton.addEventListener('click', async () => {
|
||||
const accounts = await ethQuery.accounts()
|
||||
logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined')
|
||||
window.METAMASK_ACCOUNT = accounts[0] || 'locked'
|
||||
logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account')
|
||||
})
|
||||
const txButton = document.getElementById('action-button-2')
|
||||
txButton.addEventListener('click', async () => {
|
||||
if (!window.METAMASK_ACCOUNT || window.METAMASK_ACCOUNT === 'locked') return
|
||||
const txHash = await ethQuery.sendTransaction({
|
||||
from: window.METAMASK_ACCOUNT,
|
||||
to: window.METAMASK_ACCOUNT,
|
||||
data: '',
|
||||
})
|
||||
logToDom(txHash, 'cb-value')
|
||||
})
|
||||
}
|
@ -10,6 +10,8 @@
|
||||
<body>
|
||||
<button id="action-button-1">GET ACCOUNT</button>
|
||||
<div id="account"></div>
|
||||
<button id="action-button-2">SEND TRANSACTION</button>
|
||||
<div id="cb-value" ></div>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -5,7 +5,7 @@ const serveBundle = require('./util').serveBundle
|
||||
module.exports = createMetamascaraServer
|
||||
|
||||
|
||||
function createMetamascaraServer(){
|
||||
function createMetamascaraServer () {
|
||||
|
||||
// start bundlers
|
||||
const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js')
|
||||
@ -17,13 +17,13 @@ function createMetamascaraServer(){
|
||||
const server = express()
|
||||
// ui window
|
||||
serveBundle(server, '/ui.js', uiBundle)
|
||||
server.use(express.static(__dirname+'/../ui/'))
|
||||
server.use(express.static(__dirname+'/../../dist/chrome'))
|
||||
server.use(express.static(__dirname + '/../ui/'))
|
||||
server.use(express.static(__dirname + '/../../dist/chrome'))
|
||||
// metamascara
|
||||
serveBundle(server, '/metamascara.js', metamascaraBundle)
|
||||
// proxy
|
||||
serveBundle(server, '/proxy/proxy.js', proxyBundle)
|
||||
server.use('/proxy/', express.static(__dirname+'/../proxy'))
|
||||
server.use('/proxy/', express.static(__dirname + '/../proxy'))
|
||||
// background
|
||||
serveBundle(server, '/background.js', backgroundBuild)
|
||||
|
||||
|
@ -7,14 +7,14 @@ module.exports = {
|
||||
}
|
||||
|
||||
|
||||
function serveBundle(server, path, bundle){
|
||||
server.get(path, function(req, res){
|
||||
function serveBundle (server, path, bundle) {
|
||||
server.get(path, function (req, res) {
|
||||
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
|
||||
res.send(bundle.latest)
|
||||
})
|
||||
}
|
||||
|
||||
function createBundle(entryPoint){
|
||||
function createBundle (entryPoint) {
|
||||
|
||||
var bundleContainer = {}
|
||||
|
||||
@ -30,8 +30,8 @@ function createBundle(entryPoint){
|
||||
|
||||
return bundleContainer
|
||||
|
||||
function bundle() {
|
||||
bundler.bundle(function(err, result){
|
||||
function bundle () {
|
||||
bundler.bundle(function (err, result) {
|
||||
if (err) {
|
||||
console.log(`Bundle failed! (${entryPoint})`)
|
||||
console.error(err)
|
||||
|
@ -1,72 +1,60 @@
|
||||
global.window = global
|
||||
const self = global
|
||||
const pipe = require('pump')
|
||||
|
||||
const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js')
|
||||
const connectionListener = new SwGlobalListener(self)
|
||||
const connectionListener = new SwGlobalListener(global)
|
||||
const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex
|
||||
const PortStream = require('../../app/scripts/lib/port-stream.js')
|
||||
|
||||
const DbController = require('idb-global')
|
||||
|
||||
const SwPlatform = require('../../app/scripts/platforms/sw')
|
||||
const MetamaskController = require('../../app/scripts/metamask-controller')
|
||||
const extension = {} //require('../../app/scripts/lib/extension')
|
||||
|
||||
const storeTransform = require('obs-store/lib/transform')
|
||||
const Migrator = require('../../app/scripts/lib/migrator/')
|
||||
const migrations = require('../../app/scripts/migrations/')
|
||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
||||
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||
let popupIsOpen = false
|
||||
let connectedClientCount = 0
|
||||
global.metamaskPopupIsOpen = false
|
||||
|
||||
const log = require('loglevel')
|
||||
global.log = log
|
||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
|
||||
self.addEventListener('install', function(event) {
|
||||
event.waitUntil(self.skipWaiting())
|
||||
global.addEventListener('install', function (event) {
|
||||
event.waitUntil(global.skipWaiting())
|
||||
})
|
||||
self.addEventListener('activate', function(event) {
|
||||
event.waitUntil(self.clients.claim())
|
||||
global.addEventListener('activate', function (event) {
|
||||
event.waitUntil(global.clients.claim())
|
||||
})
|
||||
|
||||
console.log('inside:open')
|
||||
log.debug('inside:open')
|
||||
|
||||
|
||||
// // state persistence
|
||||
let diskStore
|
||||
const dbController = new DbController({
|
||||
key: STORAGE_KEY,
|
||||
})
|
||||
loadStateFromPersistence()
|
||||
.then((initState) => setupController(initState))
|
||||
.then(() => console.log('MetaMask initialization complete.'))
|
||||
.then(() => log.debug('MetaMask initialization complete.'))
|
||||
.catch((err) => console.error('WHILE SETTING UP:', err))
|
||||
|
||||
// initialization flow
|
||||
|
||||
//
|
||||
// State and Persistence
|
||||
//
|
||||
function loadStateFromPersistence() {
|
||||
async function loadStateFromPersistence () {
|
||||
// migrations
|
||||
let migrator = new Migrator({ migrations })
|
||||
const migrator = new Migrator({ migrations })
|
||||
const initialState = migrator.generateInitialState(firstTimeState)
|
||||
dbController.initialState = initialState
|
||||
return dbController.open()
|
||||
.then((versionedData) => migrator.migrateData(versionedData))
|
||||
.then((versionedData) => {
|
||||
dbController.put(versionedData)
|
||||
return Promise.resolve(versionedData)
|
||||
})
|
||||
.then((versionedData) => Promise.resolve(versionedData.data))
|
||||
const versionedData = await dbController.open()
|
||||
const migratedData = await migrator.migrateData(versionedData)
|
||||
await dbController.put(migratedData)
|
||||
return migratedData.data
|
||||
}
|
||||
|
||||
function setupController (initState, client) {
|
||||
async function setupController (initState, client) {
|
||||
|
||||
//
|
||||
// MetaMask Controller
|
||||
@ -86,19 +74,19 @@ function setupController (initState, client) {
|
||||
})
|
||||
global.metamaskController = controller
|
||||
|
||||
controller.store.subscribe((state) => {
|
||||
versionifyData(state)
|
||||
.then((versionedData) => dbController.put(versionedData))
|
||||
.catch((err) => {console.error(err)})
|
||||
controller.store.subscribe(async (state) => {
|
||||
try {
|
||||
const versionedData = await versionifyData(state)
|
||||
await dbController.put(versionedData)
|
||||
} catch (e) { console.error('METAMASK Error:', e) }
|
||||
})
|
||||
function versionifyData(state) {
|
||||
return dbController.get()
|
||||
.then((rawData) => {
|
||||
return Promise.resolve({
|
||||
data: state,
|
||||
meta: rawData.meta,
|
||||
})}
|
||||
)
|
||||
|
||||
async function versionifyData (state) {
|
||||
const rawData = await dbController.get()
|
||||
return {
|
||||
data: state,
|
||||
meta: rawData.meta,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -106,8 +94,7 @@ function setupController (initState, client) {
|
||||
//
|
||||
|
||||
connectionListener.on('remote', (portStream, messageEvent) => {
|
||||
console.log('REMOTE CONECTION FOUND***********')
|
||||
connectedClientCount += 1
|
||||
log.debug('REMOTE CONECTION FOUND***********')
|
||||
connectRemote(portStream, messageEvent.data.context)
|
||||
})
|
||||
|
||||
@ -116,7 +103,7 @@ function setupController (initState, client) {
|
||||
if (isMetaMaskInternalProcess) {
|
||||
// communication with popup
|
||||
controller.setupTrustedCommunication(connectionStream, 'MetaMask')
|
||||
popupIsOpen = true
|
||||
global.metamaskPopupIsOpen = true
|
||||
} else {
|
||||
// communication with page
|
||||
setupUntrustedCommunication(connectionStream, context)
|
||||
@ -130,25 +117,14 @@ function setupController (initState, client) {
|
||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||
controller.setupPublicConfig(mx.createStream('publicConfig'))
|
||||
}
|
||||
|
||||
function setupTrustedCommunication (connectionStream, originDomain) {
|
||||
// setup multiplexing
|
||||
var mx = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||
}
|
||||
//
|
||||
// User Interface setup
|
||||
//
|
||||
return Promise.resolve()
|
||||
|
||||
}
|
||||
// // this will be useful later but commented out for linting for now (liiiinting)
|
||||
// function sendMessageToAllClients (message) {
|
||||
// global.clients.matchAll().then(function (clients) {
|
||||
// clients.forEach(function (client) {
|
||||
// client.postMessage(message)
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
function sendMessageToAllClients (message) {
|
||||
self.clients.matchAll().then(function(clients) {
|
||||
clients.forEach(function(client) {
|
||||
client.postMessage(message)
|
||||
})
|
||||
})
|
||||
}
|
||||
function noop () {}
|
||||
|
@ -2,7 +2,7 @@ const createParentStream = require('iframe-stream').ParentStream
|
||||
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
|
||||
const SwStream = require('sw-stream/lib/sw-stream.js')
|
||||
|
||||
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
const background = new SWcontroller({
|
||||
fileName: '/background.js',
|
||||
letBeIdle: false,
|
||||
@ -12,7 +12,7 @@ const background = new SWcontroller({
|
||||
|
||||
const pageStream = createParentStream()
|
||||
background.on('ready', () => {
|
||||
let swStream = SwStream({
|
||||
const swStream = SwStream({
|
||||
serviceWorker: background.controller,
|
||||
context: 'dapp',
|
||||
})
|
||||
|
@ -17,17 +17,17 @@ var name = 'popup'
|
||||
window.METAMASK_UI_TYPE = name
|
||||
window.METAMASK_PLATFORM_TYPE = 'mascara'
|
||||
|
||||
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
||||
|
||||
const background = new SWcontroller({
|
||||
fileName: '/background.js',
|
||||
letBeIdle: false,
|
||||
intervalDelay,
|
||||
wakeUpInterval: 20000
|
||||
wakeUpInterval: 20000,
|
||||
})
|
||||
// Setup listener for when the service worker is read
|
||||
const connectApp = function (readSw) {
|
||||
let connectionStream = SwStream({
|
||||
const connectionStream = SwStream({
|
||||
serviceWorker: background.controller,
|
||||
context: name,
|
||||
})
|
||||
@ -57,7 +57,7 @@ background.on('updatefound', windowReload)
|
||||
|
||||
background.startWorker()
|
||||
|
||||
function windowReload() {
|
||||
function windowReload () {
|
||||
if (window.METAMASK_SKIP_RELOAD) return
|
||||
window.location.reload()
|
||||
}
|
||||
@ -66,4 +66,4 @@ function timeout (time) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time || 1500)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
10
package.json
10
package.json
@ -69,12 +69,14 @@
|
||||
"eth-bin-to-ops": "^1.0.1",
|
||||
"eth-block-tracker": "^2.2.0",
|
||||
"eth-contract-metadata": "^1.1.4",
|
||||
"eth-hd-keyring": "^1.1.1",
|
||||
"eth-hd-keyring": "^1.2.1",
|
||||
"eth-json-rpc-filters": "^1.2.2",
|
||||
"eth-keyring-controller": "^2.0.0",
|
||||
"eth-json-rpc-middleware": "^1.4.3",
|
||||
"eth-keyring-controller": "^2.1.0",
|
||||
"eth-phishing-detect": "^1.1.4",
|
||||
"eth-query": "^2.1.2",
|
||||
"eth-sig-util": "^1.2.2",
|
||||
"eth-rpc-client": "^1.1.3",
|
||||
"eth-sig-util": "^1.4.0",
|
||||
"eth-simple-keyring": "^1.1.1",
|
||||
"eth-token-tracker": "^1.1.4",
|
||||
"ethereumjs-tx": "^1.3.0",
|
||||
@ -141,7 +143,7 @@
|
||||
"valid-url": "^1.0.9",
|
||||
"vreme": "^3.0.2",
|
||||
"web3": "^0.20.1",
|
||||
"web3-provider-engine": "^13.3.1",
|
||||
"web3-provider-engine": "^13.3.2",
|
||||
"web3-stream-provider": "^3.0.1",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
|
@ -3,6 +3,9 @@ const PASSWORD = 'password123'
|
||||
QUnit.module('first time usage')
|
||||
|
||||
QUnit.test('render init screen', (assert) => {
|
||||
// intercept reload attempts
|
||||
window.onbeforeunload = () => true
|
||||
|
||||
const done = assert.async()
|
||||
runFirstTimeUsageTest(assert).then(done).catch((err) => {
|
||||
assert.notOk(err, `Error was thrown: ${err.stack}`)
|
||||
|
@ -48,4 +48,42 @@ describe('BnInput', function () {
|
||||
checkValidity () { return true } },
|
||||
})
|
||||
})
|
||||
|
||||
it('can tolerate wei precision', function (done) {
|
||||
const renderer = ReactTestUtils.createRenderer()
|
||||
|
||||
let valueStr = '1000000000'
|
||||
|
||||
const value = new BN(valueStr, 10)
|
||||
const inputStr = '1.000000001'
|
||||
|
||||
|
||||
let targetStr = '1000000001'
|
||||
|
||||
const target = new BN(targetStr, 10)
|
||||
|
||||
const precision = 9 // gwei precision
|
||||
const scale = 9
|
||||
|
||||
const props = {
|
||||
value,
|
||||
scale,
|
||||
precision,
|
||||
onChange: (newBn) => {
|
||||
assert.equal(newBn.toString(), target.toString(), 'should tolerate increase')
|
||||
const reInput = BnInput.prototype.downsize(newBn.toString(), 9, 9)
|
||||
assert.equal(reInput.toString(), inputStr, 'should tolerate increase')
|
||||
done()
|
||||
},
|
||||
}
|
||||
|
||||
const inputComponent = h(BnInput, props)
|
||||
const component = additions.renderIntoDocument(inputComponent)
|
||||
renderer.render(inputComponent)
|
||||
const input = additions.find(component, 'input.hex-input')[0]
|
||||
ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: {
|
||||
value: inputStr,
|
||||
checkValidity () { return true } },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -14,15 +14,15 @@ describe('# Network Controller', function () {
|
||||
},
|
||||
})
|
||||
|
||||
networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor)
|
||||
networkController.initializeProvider(networkControllerProviderInit)
|
||||
})
|
||||
describe('network', function () {
|
||||
describe('#provider', function () {
|
||||
it('provider should be updatable without reassignment', function () {
|
||||
networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor)
|
||||
const proxy = networkController._proxy
|
||||
proxy.setTarget({ test: true, on: () => {} })
|
||||
assert.ok(proxy.test)
|
||||
networkController.initializeProvider(networkControllerProviderInit)
|
||||
const providerProxy = networkController.providerProxy
|
||||
providerProxy.setTarget({ test: true })
|
||||
assert.ok(providerProxy.test)
|
||||
})
|
||||
})
|
||||
describe('#getNetworkState', function () {
|
||||
@ -66,19 +66,4 @@ describe('# Network Controller', function () {
|
||||
})
|
||||
})
|
||||
|
||||
function dummyProviderConstructor() {
|
||||
return {
|
||||
// provider
|
||||
sendAsync: noop,
|
||||
// block tracker
|
||||
_blockTracker: {},
|
||||
start: noop,
|
||||
stop: noop,
|
||||
on: noop,
|
||||
addListener: noop,
|
||||
once: noop,
|
||||
removeAllListeners: noop,
|
||||
}
|
||||
}
|
||||
|
||||
function noop() {}
|
@ -18,14 +18,13 @@ describe('nodeify', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw if the last argument is not a function', function (done) {
|
||||
it('should allow the last argument to not be a function', function (done) {
|
||||
const nodified = nodeify(obj.promiseFunc, obj)
|
||||
try {
|
||||
nodified('baz')
|
||||
done(new Error('should have thrown if the last argument is not a function'))
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'callback is not a function')
|
||||
done()
|
||||
} catch (err) {
|
||||
done(new Error('should not have thrown if the last argument is not a function'))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -190,12 +190,13 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
|
||||
providerResultStub.result = providerStub
|
||||
const provider = {
|
||||
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
|
||||
_blockTracker: {
|
||||
getCurrentBlock: () => '0x11b568',
|
||||
},
|
||||
}
|
||||
const blockTracker = {
|
||||
getCurrentBlock: () => '0x11b568',
|
||||
}
|
||||
return new NonceTracker({
|
||||
provider,
|
||||
blockTracker,
|
||||
getPendingTransactions,
|
||||
getConfirmedTransactions,
|
||||
})
|
||||
|
@ -5,6 +5,8 @@ const ObservableStore = require('obs-store')
|
||||
const clone = require('clone')
|
||||
const { createStubedProvider } = require('../stub/provider')
|
||||
const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker')
|
||||
const MockTxGen = require('../lib/mock-tx-gen')
|
||||
const sinon = require('sinon')
|
||||
const noop = () => true
|
||||
const currentNetworkId = 42
|
||||
const otherNetworkId = 36
|
||||
@ -46,10 +48,60 @@ describe('PendingTransactionTracker', function () {
|
||||
}
|
||||
},
|
||||
getPendingTransactions: () => {return []},
|
||||
getCompletedTransactions: () => {return []},
|
||||
publishTransaction: () => {},
|
||||
})
|
||||
})
|
||||
|
||||
describe('_checkPendingTx state management', function () {
|
||||
let stub
|
||||
|
||||
afterEach(function () {
|
||||
if (stub) {
|
||||
stub.restore()
|
||||
}
|
||||
})
|
||||
|
||||
it('should become failed if another tx with the same nonce succeeds', async function () {
|
||||
|
||||
// SETUP
|
||||
const txGen = new MockTxGen()
|
||||
|
||||
txGen.generate({
|
||||
id: '456',
|
||||
value: '0x01',
|
||||
hash: '0xbad',
|
||||
status: 'confirmed',
|
||||
nonce: '0x01',
|
||||
}, { count: 1 })
|
||||
|
||||
const pending = txGen.generate({
|
||||
id: '123',
|
||||
value: '0x02',
|
||||
hash: '0xfad',
|
||||
status: 'submitted',
|
||||
nonce: '0x01',
|
||||
}, { count: 1 })[0]
|
||||
|
||||
stub = sinon.stub(pendingTxTracker, 'getCompletedTransactions')
|
||||
.returns(txGen.txs)
|
||||
|
||||
// THE EXPECTATION
|
||||
const spy = sinon.spy()
|
||||
pendingTxTracker.on('tx:failed', (txId, err) => {
|
||||
assert.equal(txId, pending.id, 'should fail the pending tx')
|
||||
assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.')
|
||||
spy(txId, err)
|
||||
})
|
||||
|
||||
// THE METHOD
|
||||
await pendingTxTracker._checkPendingTx(pending)
|
||||
|
||||
// THE ASSERTION
|
||||
assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#checkForTxInBlock', function () {
|
||||
it('should return if no pending transactions', function () {
|
||||
// throw a type error if it trys to do anything on the block
|
||||
@ -239,4 +291,4 @@ describe('PendingTransactionTracker', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -46,7 +46,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
var selected = props.address || Object.keys(props.accounts)[0]
|
||||
var checksumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
var identity = props.identities[selected]
|
||||
var account = props.computedBalances[selected]
|
||||
var account = props.accounts[selected]
|
||||
const { network, conversionRate, currentCurrency } = props
|
||||
|
||||
return (
|
||||
@ -181,7 +181,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
}, [
|
||||
|
||||
h(EthBalance, {
|
||||
value: account && account.ethBalance,
|
||||
value: account && account.balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
style: {
|
||||
|
@ -97,6 +97,8 @@ var actions = {
|
||||
cancelMsg: cancelMsg,
|
||||
signPersonalMsg,
|
||||
cancelPersonalMsg,
|
||||
signTypedMsg,
|
||||
cancelTypedMsg,
|
||||
signTx: signTx,
|
||||
updateAndApproveTx,
|
||||
cancelTx: cancelTx,
|
||||
@ -392,6 +394,25 @@ function signPersonalMsg (msgData) {
|
||||
}
|
||||
}
|
||||
|
||||
function signTypedMsg (msgData) {
|
||||
log.debug('action - signTypedMsg')
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
log.debug(`actions calling background.signTypedMessage`)
|
||||
background.signTypedMessage(msgData, (err, newState) => {
|
||||
log.debug('signTypedMessage called back')
|
||||
dispatch(actions.updateMetamaskState(newState))
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
if (err) log.error(err)
|
||||
if (err) return dispatch(actions.displayWarning(err.message))
|
||||
|
||||
dispatch(actions.completedTx(msgData.metamaskId))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function signTx (txData) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
@ -446,6 +467,12 @@ function cancelPersonalMsg (msgData) {
|
||||
return actions.completedTx(id)
|
||||
}
|
||||
|
||||
function cancelTypedMsg (msgData) {
|
||||
const id = msgData.id
|
||||
background.cancelTypedMessage(id)
|
||||
return actions.completedTx(id)
|
||||
}
|
||||
|
||||
function cancelTx (txData) {
|
||||
return (dispatch) => {
|
||||
log.debug(`background.cancelTransaction`)
|
||||
|
@ -73,7 +73,7 @@ AddTokenScreen.prototype.render = function () {
|
||||
}, [
|
||||
h('a', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
|
||||
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
|
||||
target: '_blank',
|
||||
}, [
|
||||
h('span', 'Token Contract Address '),
|
||||
|
@ -319,7 +319,7 @@ App.prototype.renderNetworkDropdown = function () {
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
|
||||
providerType === 'localhost' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
|
@ -31,7 +31,7 @@ BnAsDecimalInput.prototype.render = function () {
|
||||
const suffix = props.suffix
|
||||
const style = props.style
|
||||
const valueString = value.toString(10)
|
||||
const newValue = this.downsize(valueString, scale, precision)
|
||||
const newValue = this.downsize(valueString, scale)
|
||||
|
||||
return (
|
||||
h('.flex-column', [
|
||||
@ -145,14 +145,17 @@ BnAsDecimalInput.prototype.constructWarning = function () {
|
||||
}
|
||||
|
||||
|
||||
BnAsDecimalInput.prototype.downsize = function (number, scale, precision) {
|
||||
BnAsDecimalInput.prototype.downsize = function (number, scale) {
|
||||
// if there is no scaling, simply return the number
|
||||
if (scale === 0) {
|
||||
return Number(number)
|
||||
} else {
|
||||
// if the scale is the same as the precision, account for this edge case.
|
||||
var decimals = (scale === precision) ? -1 : scale - precision
|
||||
return Number(number.slice(0, -scale) + '.' + number.slice(-scale, decimals))
|
||||
var adjustedNumber = number
|
||||
while (adjustedNumber.length < scale) {
|
||||
adjustedNumber = '0' + adjustedNumber
|
||||
}
|
||||
return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ function PendingTx () {
|
||||
|
||||
PendingTx.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { currentCurrency, blockGasLimit, computedBalances } = props
|
||||
const { currentCurrency, blockGasLimit } = props
|
||||
|
||||
const conversionRate = props.conversionRate
|
||||
const txMeta = this.gatherTxMeta()
|
||||
@ -42,8 +42,8 @@ PendingTx.prototype.render = function () {
|
||||
// Account Details
|
||||
const address = txParams.from || props.selectedAddress
|
||||
const identity = props.identities[address] || { address: address }
|
||||
const account = computedBalances[address]
|
||||
const balance = account ? account.ethBalance : '0x0'
|
||||
const account = props.accounts[address]
|
||||
const balance = account ? account.balance : '0x0'
|
||||
|
||||
// recipient check
|
||||
const isValidAddress = !txParams.to || util.isValidAddress(txParams.to)
|
||||
|
59
ui/app/components/pending-typed-msg-details.js
Normal file
59
ui/app/components/pending-typed-msg-details.js
Normal file
@ -0,0 +1,59 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const AccountPanel = require('./account-panel')
|
||||
const TypedMessageRenderer = require('./typed-message-renderer')
|
||||
|
||||
module.exports = PendingMsgDetails
|
||||
|
||||
inherits(PendingMsgDetails, Component)
|
||||
function PendingMsgDetails () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsgDetails.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 }
|
||||
|
||||
var { data } = msgParams
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
style: {
|
||||
margin: '10px 20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: identity,
|
||||
account: account,
|
||||
imageifyIdenticons: state.imageifyIdenticons,
|
||||
}),
|
||||
|
||||
// message data
|
||||
h('div', {
|
||||
style: {
|
||||
height: '260px',
|
||||
},
|
||||
}, [
|
||||
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'),
|
||||
h(TypedMessageRenderer, {
|
||||
value: data,
|
||||
style: {
|
||||
height: '215px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
46
ui/app/components/pending-typed-msg.js
Normal file
46
ui/app/components/pending-typed-msg.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const PendingTxDetails = require('./pending-typed-msg-details')
|
||||
|
||||
module.exports = PendingMsg
|
||||
|
||||
inherits(PendingMsg, Component)
|
||||
function PendingMsg () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsg.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
return (
|
||||
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
}, [
|
||||
|
||||
// header
|
||||
h('h3', {
|
||||
style: {
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, 'Sign Message'),
|
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state),
|
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [
|
||||
h('button', {
|
||||
onClick: state.cancelTypedMessage,
|
||||
}, 'Cancel'),
|
||||
h('button', {
|
||||
onClick: state.signTypedMessage,
|
||||
}, 'Sign'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
42
ui/app/components/typed-message-renderer.js
Normal file
42
ui/app/components/typed-message-renderer.js
Normal file
@ -0,0 +1,42 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = TypedMessageRenderer
|
||||
|
||||
inherits(TypedMessageRenderer, Component)
|
||||
function TypedMessageRenderer () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TypedMessageRenderer.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { value, style } = props
|
||||
const text = renderTypedData(value)
|
||||
|
||||
const defaultStyle = extend({
|
||||
width: '315px',
|
||||
maxHeight: '210px',
|
||||
resize: 'none',
|
||||
border: 'none',
|
||||
background: 'white',
|
||||
padding: '3px',
|
||||
overflow: 'scroll',
|
||||
}, style)
|
||||
|
||||
return (
|
||||
h('div.font-small', {
|
||||
style: defaultStyle,
|
||||
}, text)
|
||||
)
|
||||
}
|
||||
|
||||
function renderTypedData(values) {
|
||||
return values.map(function (value) {
|
||||
return h('div', {}, [
|
||||
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'),
|
||||
h('div', {}, value.value),
|
||||
])
|
||||
})
|
||||
}
|
@ -10,6 +10,7 @@ const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notific
|
||||
const PendingTx = require('./components/pending-tx')
|
||||
const PendingMsg = require('./components/pending-msg')
|
||||
const PendingPersonalMsg = require('./components/pending-personal-msg')
|
||||
const PendingTypedMsg = require('./components/pending-typed-msg')
|
||||
const Loading = require('./components/loading')
|
||||
|
||||
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
|
||||
@ -22,6 +23,7 @@ function mapStateToProps (state) {
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
|
||||
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
|
||||
index: state.appState.currentView.context,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
@ -41,9 +43,9 @@ function ConfirmTxScreen () {
|
||||
ConfirmTxScreen.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { network, provider, unapprovedTxs, currentCurrency, computedBalances,
|
||||
unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props
|
||||
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, conversionRate, blockGasLimit } = props
|
||||
|
||||
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network)
|
||||
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
|
||||
|
||||
var txData = unconfTxList[props.index] || {}
|
||||
var txParams = txData.params || {}
|
||||
@ -112,8 +114,10 @@ ConfirmTxScreen.prototype.render = function () {
|
||||
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
|
||||
signMessage: this.signMessage.bind(this, txData),
|
||||
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
|
||||
signTypedMessage: this.signTypedMessage.bind(this, txData),
|
||||
cancelMessage: this.cancelMessage.bind(this, txData),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
|
||||
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
|
||||
}),
|
||||
])
|
||||
)
|
||||
@ -136,6 +140,9 @@ function currentTxView (opts) {
|
||||
} else if (type === 'personal_sign') {
|
||||
log.debug('rendering personal_sign message')
|
||||
return h(PendingPersonalMsg, opts)
|
||||
} else if (type === 'eth_signTypedData') {
|
||||
log.debug('rendering eth_signTypedData message')
|
||||
return h(PendingTypedMsg, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -184,6 +191,14 @@ ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) {
|
||||
this.props.dispatch(actions.signPersonalMsg(params))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
|
||||
log.info('conf-tx.js: signing typed message')
|
||||
var params = msgData.msgParams
|
||||
params.metamaskId = msgData.id
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.signTypedMsg(params))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) {
|
||||
log.info('canceling message')
|
||||
this.stopPropagation(event)
|
||||
@ -196,6 +211,12 @@ ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) {
|
||||
this.props.dispatch(actions.cancelPersonalMsg(msgData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
|
||||
log.info('canceling typed message')
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.cancelTypedMsg(msgData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.goHome = function (event) {
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.goHome())
|
||||
|
@ -574,9 +574,9 @@ function checkUnconfActions (state) {
|
||||
|
||||
function getUnconfActionList (state) {
|
||||
const { unapprovedTxs, unapprovedMsgs,
|
||||
unapprovedPersonalMsgs, network } = state.metamask
|
||||
unapprovedPersonalMsgs, unapprovedTypedMessages, network } = state.metamask
|
||||
|
||||
const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network)
|
||||
const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
|
||||
return unconfActionList
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ function startApp (metamaskState, accountManager, opts) {
|
||||
})
|
||||
|
||||
// if unconfirmed txs, start on txConf page
|
||||
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.network)
|
||||
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
||||
if (unapprovedTxsAll.length > 0) {
|
||||
store.dispatch(actions.showConfTxPage())
|
||||
}
|
||||
|
@ -1,20 +1,27 @@
|
||||
const valuesFor = require('../app/util').valuesFor
|
||||
|
||||
module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) {
|
||||
module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) {
|
||||
log.debug('tx-helper called with params:')
|
||||
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network })
|
||||
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network })
|
||||
|
||||
const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs)
|
||||
log.debug(`tx helper found ${txValues.length} unapproved txs`)
|
||||
|
||||
const msgValues = valuesFor(unapprovedMsgs)
|
||||
log.debug(`tx helper found ${msgValues.length} unsigned messages`)
|
||||
let allValues = txValues.concat(msgValues)
|
||||
|
||||
const personalValues = valuesFor(personalMsgs)
|
||||
log.debug(`tx helper found ${personalValues.length} unsigned personal messages`)
|
||||
allValues = allValues.concat(personalValues)
|
||||
|
||||
const typedValues = valuesFor(typedMessages)
|
||||
log.debug(`tx helper found ${typedValues.length} unsigned typed messages`)
|
||||
allValues = allValues.concat(typedValues)
|
||||
|
||||
allValues = allValues.sort((a, b) => {
|
||||
return a.time > b.time
|
||||
})
|
||||
|
||||
return allValues
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user