aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrankie <frankie.pangilinan@consensys.net>2016-06-25 09:02:28 +0800
committerFrankie <frankie.pangilinan@consensys.net>2016-06-25 09:02:28 +0800
commitab55fefa1a4ebeaa9a42756a72d49b63f036b946 (patch)
treef30f51dd7dbe413b8944b230d14660a4c1a2fd61
parentef743499cb93dc8360be20db03c4ab0676aa57bb (diff)
parent36a183db94e66999421980f50c2ab2e8d1ce2c3b (diff)
downloadtangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.tar
tangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.tar.gz
tangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.tar.bz2
tangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.tar.lz
tangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.tar.xz
tangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.tar.zst
tangerine-wallet-browser-ab55fefa1a4ebeaa9a42756a72d49b63f036b946.zip
Merge branch 'master' into uiFixes
-rw-r--r--app/scripts/background.js343
-rw-r--r--app/scripts/contentscript.js6
-rw-r--r--app/scripts/lib/config-manager-singleton.js3
-rw-r--r--app/scripts/lib/config-manager.js56
-rw-r--r--app/scripts/lib/id-management.js6
-rw-r--r--app/scripts/lib/idStore.js28
-rw-r--r--app/scripts/lib/notifications.js126
-rw-r--r--app/scripts/metamask-controller.js257
-rw-r--r--test/lib/mock-config-manager.js57
-rw-r--r--test/unit/config-manager-test.js7
-rw-r--r--test/unit/idStore-test.js4
-rw-r--r--ui/app/components/account-panel.js2
-rw-r--r--ui/app/components/pending-msg-details.js53
-rw-r--r--ui/app/components/pending-msg.js37
-rw-r--r--ui/app/components/pending-tx-details.js65
-rw-r--r--ui/app/components/pending-tx.js69
-rw-r--r--ui/app/conf-tx.js4
17 files changed, 669 insertions, 454 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 6934e9d3e..97e3269ba 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -1,19 +1,51 @@
const urlUtil = require('url')
+const extend = require('xtend')
const Dnode = require('dnode')
const eos = require('end-of-stream')
-const extend = require('xtend')
-const EthStore = require('eth-store')
-const MetaMaskProvider = require('web3-provider-engine/zero.js')
const PortStream = require('./lib/port-stream.js')
-const IdentityStore = require('./lib/idStore')
const createUnlockRequestNotification = require('./lib/notifications.js').createUnlockRequestNotification
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')
+const MetamaskController = require('./metamask-controller')
+
+const STORAGE_KEY = 'metamask-config'
+
+const controller = new MetamaskController({
+ // User confirmation callbacks:
+ showUnconfirmedMessage,
+ unlockAccountMessage,
+ showUnconfirmedTx,
+ // Persistence Methods:
+ setData,
+ loadData,
+})
+const idStore = controller.idStore
+
+function unlockAccountMessage () {
+ createUnlockRequestNotification({
+ title: 'Account Unlock Request',
+ })
+}
+
+function showUnconfirmedMessage (msgParams, msgId) {
+ createMsgNotification({
+ title: 'New Unsigned Message',
+ msgParams: msgParams,
+ confirm: idStore.approveMessage.bind(idStore, msgId, noop),
+ cancel: idStore.cancelMessage.bind(idStore, msgId),
+ })
+}
+
+function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
+ createTxNotification({
+ title: 'New Unsigned Transaction',
+ txParams: txParams,
+ confirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
+ cancel: idStore.cancelTransaction.bind(idStore, txData.id),
+ })
+}
//
// connect to other contexts
@@ -37,8 +69,8 @@ function setupUntrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
- setupProviderConnection(mx.createStream('provider'), originDomain)
- setupPublicConfig(mx.createStream('publicConfig'))
+ controller.setupProviderConnection(mx.createStream('provider'), originDomain)
+ controller.setupPublicConfig(mx.createStream('publicConfig'))
}
function setupTrustedCommunication (connectionStream, originDomain) {
@@ -46,181 +78,28 @@ function setupTrustedCommunication (connectionStream, originDomain) {
var mx = setupMultiplex(connectionStream)
// connect features
setupControllerConnection(mx.createStream('controller'))
- setupProviderConnection(mx.createStream('provider'), originDomain)
-}
-
-//
-// state and network
-//
-
-var idStore = new IdentityStore()
-
-var providerOpts = {
- rpcUrl: configManager.getCurrentRpcAddress(),
- // account mgmt
- getAccounts: function (cb) {
- var selectedAddress = idStore.getSelectedAddress()
- var result = selectedAddress ? [selectedAddress] : []
- cb(null, result)
- },
- // tx signing
- approveTransaction: newUnsignedTransaction,
- signTransaction: idStore.signTransaction.bind(idStore),
- // msg signing
- approveMessage: newUnsignedMessage,
- signMessage: idStore.signMessage.bind(idStore),
-}
-var provider = MetaMaskProvider(providerOpts)
-var web3 = new Web3(provider)
-idStore.web3 = web3
-idStore.getNetwork()
-
-// log new blocks
-provider.on('block', function (block) {
- console.log('BLOCK CHANGED:', '#' + block.number.toString('hex'), '0x' + block.hash.toString('hex'))
-
- // Check network when restoring connectivity:
- if (idStore._currentState.network === 'loading') {
- idStore.getNetwork()
- }
-})
-
-provider.on('error', idStore.getNetwork.bind(idStore))
-
-var ethStore = new EthStore(provider)
-idStore.setStore(ethStore)
-
-function getState () {
- var state = extend(
- ethStore.getState(),
- idStore.getState(),
- configManager.getConfig()
- )
- return state
-}
-
-//
-// public store
-//
-
-// get init state
-var initPublicState = extend(
- idStoreToPublic(idStore.getState()),
- configToPublic(configManager.getConfig())
-)
-
-var publicConfigStore = new HostStore(initPublicState)
-
-// subscribe to changes
-configManager.subscribe(function (state) {
- storeSetFromObj(publicConfigStore, configToPublic(state))
-})
-idStore.on('update', function (state) {
- storeSetFromObj(publicConfigStore, idStoreToPublic(state))
-})
-
-// idStore substate
-function idStoreToPublic (state) {
- return {
- selectedAddress: state.selectedAddress,
- }
-}
-// config substate
-function configToPublic (state) {
- return {
- provider: state.provider,
- }
-}
-// dump obj into store
-function storeSetFromObj (store, obj) {
- Object.keys(obj).forEach(function (key) {
- store.set(key, obj[key])
- })
+ controller.setupProviderConnection(mx.createStream('provider'), originDomain)
}
//
// remote features
//
-function setupPublicConfig (stream) {
- var storeStream = publicConfigStore.createStream()
- stream.pipe(storeStream).pipe(stream)
-}
-
-function setupProviderConnection (stream, originDomain) {
- // decorate all payloads with origin domain
- stream.on('data', function onRpcRequest (request) {
- var payloads = Array.isArray(request) ? request : [request]
- payloads.forEach(function (payload) {
- // Append origin to rpc payload
- payload.origin = originDomain
- // Append origin to signature request
- if (payload.method === 'eth_sendTransaction') {
- payload.params[0].origin = originDomain
- } else if (payload.method === 'eth_sign') {
- payload.params.push({ origin: originDomain })
- }
- })
- // handle rpc request
- provider.sendAsync(request, function onPayloadHandled (err, response) {
- if (err) {
- return logger(err)
- }
- logger(null, request, response)
- try {
- stream.write(response)
- } catch (err) {
- logger(err)
- }
- })
- })
-
- function logger (err, request, response) {
- if (err) return console.error(err.stack)
- if (!request.isMetamaskInternal) {
- console.log(`RPC (${originDomain}):`, request, '->', response)
- if (response.error) console.error('Error in RPC response:\n' + response.error.message)
- }
- }
-}
-
function setupControllerConnection (stream) {
- var dnode = Dnode({
- getState: function (cb) { cb(null, getState()) },
- setRpcTarget: setRpcTarget,
- setProviderType: setProviderType,
- useEtherscanProvider: useEtherscanProvider,
- agreeToDisclaimer: agreeToDisclaimer,
- // forward directly to idStore
- createNewVault: idStore.createNewVault.bind(idStore),
- recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
- submitPassword: idStore.submitPassword.bind(idStore),
- 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),
- revealAccount: idStore.revealAccount.bind(idStore),
- saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
- tryPassword: idStore.tryPassword.bind(idStore),
- recoverSeed: idStore.recoverSeed.bind(idStore),
- })
+ controller.stream = stream
+ var api = controller.getApi()
+ var dnode = Dnode(api)
stream.pipe(dnode).pipe(stream)
- dnode.on('remote', function (remote) {
+ dnode.on('remote', (remote) => {
// push updates to popup
- ethStore.on('update', sendUpdate)
- idStore.on('update', sendUpdate)
+ controller.ethStore.on('update', controller.sendUpdate.bind(controller))
+ controller.remote = remote
+ idStore.on('update', controller.sendUpdate.bind(controller))
+
// teardown on disconnect
- eos(stream, function unsubscribe () {
- ethStore.removeListener('update', sendUpdate)
+ eos(stream, () => {
+ controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller))
})
- function sendUpdate () {
- var state = getState()
- remote.sendUpdate(state)
- }
})
}
@@ -232,7 +111,7 @@ idStore.on('update', updateBadge)
function updateBadge (state) {
var label = ''
- var unconfTxs = configManager.unconfirmedTxs()
+ var unconfTxs = controller.configManager.unconfirmedTxs()
var unconfTxLen = Object.keys(unconfTxs).length
var unconfMsgs = messageManager.unconfirmedMsgs()
var unconfMsgLen = Object.keys(unconfMsgs).length
@@ -244,86 +123,54 @@ function updateBadge (state) {
chrome.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
}
-//
-// Add unconfirmed Tx + Msg
-//
-
-function newUnsignedTransaction (txParams, onTxDoneCb) {
- var state = idStore.getState()
- if (!state.isUnlocked) {
- createUnlockRequestNotification({
- title: 'Account Unlock Request',
- })
- idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
- } else {
- addUnconfirmedTx(txParams, onTxDoneCb)
- }
-}
-
-function newUnsignedMessage (msgParams, cb) {
- var state = idStore.getState()
- if (!state.isUnlocked) {
- createUnlockRequestNotification({
- title: 'Account Unlock Request',
- })
- } else {
- addUnconfirmedMsg(msgParams, cb)
- }
-}
-
-function addUnconfirmedTx (txParams, onTxDoneCb) {
- idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, function (err, txData) {
- if (err) return onTxDoneCb(err)
- createTxNotification({
- title: 'New Unsigned Transaction',
- txParams: txParams,
- confirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
- cancel: idStore.cancelTransaction.bind(idStore, txData.id),
- })
- })
-}
-
-function addUnconfirmedMsg (msgParams, cb) {
- var msgId = idStore.addUnconfirmedMessage(msgParams, cb)
- createMsgNotification({
- title: 'New Unsigned Message',
- msgParams: msgParams,
- confirm: idStore.approveMessage.bind(idStore, msgId, noop),
- cancel: idStore.cancelMessage.bind(idStore, msgId),
- })
-}
-
-//
-// config
-//
-
-function agreeToDisclaimer (cb) {
+function loadData () {
+ var oldData = getOldStyleData()
+ var newData
try {
- configManager.setConfirmed(true)
- cb()
- } catch (e) {
- cb(e)
+ newData = JSON.parse(window.localStorage[STORAGE_KEY])
+ } catch (e) {}
+
+ var data = extend({
+ meta: {
+ version: 0,
+ },
+ data: {
+ config: {
+ provider: {
+ type: 'testnet',
+ },
+ },
+ },
+ }, oldData || null, newData || null)
+ return data
+}
+
+function getOldStyleData () {
+ var config, wallet, seedWords
+
+ var result = {
+ meta: { version: 0 },
+ data: {},
}
-}
-// called from popup
-function setRpcTarget (rpcTarget) {
- configManager.setRpcTarget(rpcTarget)
- chrome.runtime.reload()
- idStore.getNetwork()
-}
+ try {
+ config = JSON.parse(window.localStorage['config'])
+ result.data.config = config
+ } catch (e) {}
+ try {
+ wallet = JSON.parse(window.localStorage['lightwallet'])
+ result.data.wallet = wallet
+ } catch (e) {}
+ try {
+ seedWords = window.localStorage['seedWords']
+ result.data.seedWords = seedWords
+ } catch (e) {}
-function setProviderType (type) {
- configManager.setProviderType(type)
- chrome.runtime.reload()
- idStore.getNetwork()
+ return result
}
-function useEtherscanProvider () {
- configManager.useEtherscanProvider()
- chrome.runtime.reload()
+function setData (data) {
+ window.localStorage[STORAGE_KEY] = JSON.stringify(data)
}
-// util
-
function noop () {}
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 5d31e3c38..f4f064163 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -1,7 +1,7 @@
const LocalMessageDuplexStream = require('./lib/local-message-stream.js')
const PortStream = require('./lib/port-stream.js')
const ObjectMultiplex = require('./lib/obj-multiplex')
-const urlUtil = require('url')
+// const urlUtil = require('url')
if (shouldInjectWeb3()) {
setupInjection()
@@ -45,8 +45,6 @@ function setupInjection(){
}
function shouldInjectWeb3(){
- var urlData = urlUtil.parse(window.location.href)
- var extension = urlData.pathname.split('.').slice(-1)[0]
- var shouldInject = (extension !== 'pdf')
+ var shouldInject = (window.location.href.indexOf('.pdf') === -1)
return shouldInject
} \ No newline at end of file
diff --git a/app/scripts/lib/config-manager-singleton.js b/app/scripts/lib/config-manager-singleton.js
deleted file mode 100644
index 5915c401b..000000000
--- a/app/scripts/lib/config-manager-singleton.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var ConfigManager = require('./config-manager')
-
-module.exports = new ConfigManager()
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index a3ff0bdfb..0af82c89c 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -1,9 +1,7 @@
const Migrator = require('pojo-migrator')
-const extend = require('xtend')
const MetamaskConfig = require('../config.js')
const migrations = require('./migrations')
-const STORAGE_KEY = 'metamask-config'
const TESTNET_RPC = MetamaskConfig.network.testnet
const MAINNET_RPC = MetamaskConfig.network.mainnet
@@ -15,7 +13,7 @@ const MAINNET_RPC = MetamaskConfig.network.mainnet
* particular portions of the state.
*/
module.exports = ConfigManager
-function ConfigManager () {
+function ConfigManager (opts) {
// ConfigManager is observable and will emit updates
this._subs = []
@@ -37,12 +35,10 @@ function ConfigManager () {
// How to load initial config.
// Includes step on migrating pre-pojo-migrator data.
- loadData: loadData,
+ loadData: opts.loadData,
// How to persist migrated config.
- setData: function (data) {
- window.localStorage[STORAGE_KEY] = JSON.stringify(data)
- },
+ setData: opts.setData,
})
}
@@ -280,49 +276,3 @@ ConfigManager.prototype.getConfirmed = function () {
return ('isConfirmed' in data) && data.isConfirmed
}
-function loadData () {
- var oldData = getOldStyleData()
- var newData
- try {
- newData = JSON.parse(window.localStorage[STORAGE_KEY])
- } catch (e) {}
-
- var data = extend({
- meta: {
- version: 0,
- },
- data: {
- config: {
- provider: {
- type: 'testnet',
- },
- },
- },
- }, oldData || null, newData || null)
- return data
-}
-
-function getOldStyleData () {
- var config, wallet, seedWords
-
- var result = {
- meta: { version: 0 },
- data: {},
- }
-
- try {
- config = JSON.parse(window.localStorage['config'])
- result.data.config = config
- } catch (e) {}
- try {
- wallet = JSON.parse(window.localStorage['lightwallet'])
- result.data.wallet = wallet
- } catch (e) {}
- try {
- seedWords = window.localStorage['seedWords']
- result.data.seedWords = seedWords
- } catch (e) {}
-
- return result
-}
-
diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js
index cc50bd649..9b8ceb415 100644
--- a/app/scripts/lib/id-management.js
+++ b/app/scripts/lib/id-management.js
@@ -1,6 +1,5 @@
const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
-const configManager = require('./config-manager-singleton')
module.exports = IdManagement
@@ -9,6 +8,7 @@ function IdManagement (opts) {
this.keyStore = opts.keyStore
this.derivedKey = opts.derivedKey
+ this.configManager = opts.configManager
this.hdPathString = "m/44'/60'/0'/0"
this.getAddresses = function () {
@@ -32,9 +32,9 @@ function IdManagement (opts) {
// Add the tx hash to the persisted meta-tx object
var txHash = ethUtil.bufferToHex(tx.hash())
- var metaTx = configManager.getTx(txParams.metamaskId)
+ var metaTx = this.configManager.getTx(txParams.metamaskId)
metaTx.hash = txHash
- configManager.updateTx(metaTx)
+ this.configManager.updateTx(metaTx)
// return raw serialized tx
var rawTx = ethUtil.bufferToHex(tx.serialize())
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
index d9657dacf..f705c07a7 100644
--- a/app/scripts/lib/idStore.js
+++ b/app/scripts/lib/idStore.js
@@ -7,7 +7,6 @@ const extend = require('xtend')
const createId = require('web3-provider-engine/util/random-id')
const ethBinToOps = require('eth-bin-to-ops')
const autoFaucet = require('./auto-faucet')
-const configManager = require('./config-manager-singleton')
const messageManager = require('./message-manager')
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
const IdManagement = require('./id-management')
@@ -20,6 +19,7 @@ function IdentityStore (opts = {}) {
// we just use the ethStore to auto-add accounts
this._ethStore = opts.ethStore
+ this.configManager = opts.configManager
// lightwallet key store
this._keyStore = null
// lightwallet wrapper
@@ -43,7 +43,10 @@ function IdentityStore (opts = {}) {
IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
delete this._keyStore
- configManager.clearWallet()
+ if (this.configManager) {
+ this.configManager.clearWallet()
+ }
+
this._createIdmgmt(password, null, entropy, (err) => {
if (err) return cb(err)
@@ -51,14 +54,14 @@ IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
this._didUpdate()
this._autoFaucet()
- configManager.setShowSeedWords(true)
+ this.configManager.setShowSeedWords(true)
var seedWords = this._idmgmt.getSeed()
cb(null, seedWords)
})
}
IdentityStore.prototype.recoverSeed = function (cb) {
- configManager.setShowSeedWords(true)
+ this.configManager.setShowSeedWords(true)
if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.'))
var seedWords = this._idmgmt.getSeed()
cb(null, seedWords)
@@ -79,11 +82,13 @@ IdentityStore.prototype.setStore = function (store) {
}
IdentityStore.prototype.clearSeedWordCache = function (cb) {
+ const configManager = this.configManager
configManager.setShowSeedWords(false)
cb(null, configManager.getSelectedAccount())
}
IdentityStore.prototype.getState = function () {
+ const configManager = this.configManager
var seedWords = this.getSeedIfUnlocked()
return clone(extend(this._currentState, {
isInitialized: !!configManager.getWallet() && !seedWords,
@@ -99,6 +104,7 @@ IdentityStore.prototype.getState = function () {
}
IdentityStore.prototype.getSeedIfUnlocked = function () {
+ const configManager = this.configManager
var showSeed = configManager.getShouldShowSeedWords()
var idmgmt = this._idmgmt
var shouldShow = showSeed && !!idmgmt
@@ -107,10 +113,12 @@ IdentityStore.prototype.getSeedIfUnlocked = function () {
}
IdentityStore.prototype.getSelectedAddress = function () {
+ const configManager = this.configManager
return configManager.getSelectedAccount()
}
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
+ const configManager = this.configManager
if (!address) {
var addresses = this._getAddresses()
address = addresses[0]
@@ -123,6 +131,7 @@ IdentityStore.prototype.setSelectedAddress = function (address, cb) {
IdentityStore.prototype.revealAccount = function (cb) {
const derivedKey = this._idmgmt.derivedKey
const keyStore = this._keyStore
+ const configManager = this.configManager
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 1)
@@ -158,6 +167,7 @@ IdentityStore.prototype.setLocked = function (cb) {
}
IdentityStore.prototype.submitPassword = function (password, cb) {
+ const configManager = this.configManager
this.tryPassword(password, (err) => {
if (err) return cb(err)
// load identities before returning...
@@ -177,6 +187,7 @@ IdentityStore.prototype.exportAccount = function (address, cb) {
// comes from dapp via zero-client hooked-wallet provider
IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) {
+ const configManager = this.configManager
var self = this
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
@@ -227,6 +238,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
// comes from metamask ui
IdentityStore.prototype.approveTransaction = function (txId, cb) {
+ const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// accept tx
@@ -240,6 +252,7 @@ IdentityStore.prototype.approveTransaction = function (txId, cb) {
// comes from metamask ui
IdentityStore.prototype.cancelTransaction = function (txId) {
+ const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// reject tx
@@ -347,6 +360,7 @@ IdentityStore.prototype._isUnlocked = function () {
// load identities from keyStoreet
IdentityStore.prototype._loadIdentities = function () {
+ const configManager = this.configManager
if (!this._isUnlocked()) throw new Error('not unlocked')
var addresses = this._getAddresses()
@@ -367,6 +381,7 @@ IdentityStore.prototype._loadIdentities = function () {
}
IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
+ const configManager = this.configManager
configManager.setNicknameForWallet(account, label)
this._loadIdentities()
cb(null, label)
@@ -379,6 +394,7 @@ IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
// If there is no balance and it mayBeFauceting,
// then it is in fact fauceting.
IdentityStore.prototype._mayBeFauceting = function (i) {
+ const configManager = this.configManager
var config = configManager.getProvider()
if (i === 0 &&
config.type === 'rpc' &&
@@ -397,6 +413,7 @@ IdentityStore.prototype.tryPassword = function (password, cb) {
}
IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
+ const configManager = this.configManager
var keyStore = null
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
if (err) return cb(err)
@@ -425,6 +442,7 @@ IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
keyStore: keyStore,
derivedKey: derivedKey,
hdPathSTring: this.hdPathString,
+ configManager: this.configManager,
})
cb()
@@ -432,6 +450,7 @@ IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
}
IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey) {
+ const configManager = this.configManager
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
keyStore.setDefaultHdDerivationPath(this.hdPathString)
@@ -443,6 +462,7 @@ IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey)
}
IdentityStore.prototype._createFirstWallet = function (entropy, derivedKey) {
+ const configManager = this.configManager
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js
index 5762fd26b..a5746ae6e 100644
--- a/app/scripts/lib/notifications.js
+++ b/app/scripts/lib/notifications.js
@@ -1,10 +1,11 @@
const createId = require('hat')
+const extend = require('xtend')
const unmountComponentAtNode = require('react-dom').unmountComponentAtNode
const findDOMNode = require('react-dom').findDOMNode
const render = require('react-dom').render
const h = require('react-hyperscript')
-const uiUtils = require('../../../ui/app/util')
-const renderPendingTx = require('../../../ui/app/components/pending-tx').prototype.renderGeneric
+const PendingTxDetails = require('../../../ui/app/components/pending-tx-details')
+const PendingMsgDetails = require('../../../ui/app/components/pending-msg-details')
const MetaMaskUiCss = require('../../../ui/css')
var notificationHandlers = {}
@@ -56,29 +57,29 @@ function createTxNotification (opts) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
- renderTransactionNotificationSVG(opts, function(err, source){
+ var state = {
+ title: 'New Unsigned Transaction',
+ imageifyIdenticons: false,
+ txData: {
+ txParams: opts.txParams,
+ time: (new Date()).getTime(),
+ },
+ identities: {
+
+ },
+ accounts: {
+
+ },
+ onConfirm: opts.confirm,
+ onCancel: opts.cancel,
+ }
+
+ renderTxNotificationSVG(state, function(err, notificationSvgSource){
if (err) throw err
-
- var imageUrl = 'data:image/svg+xml;utf8,' + encodeURIComponent(source)
-
- var id = createId()
- chrome.notifications.create(id, {
- type: 'image',
- // requireInteraction: true,
- iconUrl: '/images/icon-128.png',
- imageUrl: imageUrl,
- title: opts.title,
- message: '',
- buttons: [{
- title: 'confirm',
- }, {
- title: 'cancel',
- }],
- })
- notificationHandlers[id] = {
- confirm: opts.confirm,
- cancel: opts.cancel,
- }
+
+ showNotification(extend(state, {
+ imageUrl: toSvgUri(notificationSvgSource),
+ }))
})
}
@@ -86,19 +87,46 @@ function createTxNotification (opts) {
function createMsgNotification (opts) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
- var message = [
- 'Submitted by ' + opts.msgParams.origin,
- 'to be signed by: ' + uiUtils.addressSummary(opts.msgParams.from),
- 'message:\n' + opts.msgParams.data,
- ].join('\n')
+
+ var state = {
+ title: 'New Unsigned Message',
+ imageifyIdenticons: false,
+ txData: {
+ msgParams: opts.msgParams,
+ time: (new Date()).getTime(),
+ },
+ identities: {
+
+ },
+ accounts: {
+
+ },
+ onConfirm: opts.confirm,
+ onCancel: opts.cancel,
+ }
+
+ renderMsgNotificationSVG(state, function(err, notificationSvgSource){
+ if (err) throw err
+
+ showNotification(extend(state, {
+ imageUrl: toSvgUri(notificationSvgSource),
+ }))
+
+ })
+}
+
+function showNotification (state) {
+ // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
+ if (!chrome.notifications) return console.error('Chrome notifications API missing...')
var id = createId()
chrome.notifications.create(id, {
- type: 'basic',
+ type: 'image',
requireInteraction: true,
iconUrl: '/images/icon-128.png',
- title: opts.title,
- message: message,
+ imageUrl: state.imageUrl,
+ title: state.title,
+ message: '',
buttons: [{
title: 'confirm',
}, {
@@ -106,27 +134,23 @@ function createMsgNotification (opts) {
}],
})
notificationHandlers[id] = {
- confirm: opts.confirm,
- cancel: opts.cancel,
+ confirm: state.onConfirm,
+ cancel: state.onCancel,
}
-}
-function renderTransactionNotificationSVG(opts, cb){
- var state = {
- nonInteractive: true,
- inlineIdenticons: true,
- txData: {
- txParams: opts.txParams,
- time: (new Date()).getTime(),
- },
- identities: {
+}
- },
- accounts: {
+function renderTxNotificationSVG(state, cb){
+ var content = h(PendingTxDetails, state)
+ renderNotificationSVG(content, cb)
+}
- },
- }
+function renderMsgNotificationSVG(state, cb){
+ var content = h(PendingMsgDetails, state)
+ renderNotificationSVG(content, cb)
+}
+function renderNotificationSVG(content, cb){
var container = document.createElement('div')
var confirmView = h('div.app-primary', {
style: {
@@ -138,7 +162,7 @@ function renderTransactionNotificationSVG(opts, cb){
},
}, [
h('style', MetaMaskUiCss()),
- renderPendingTx(h, state),
+ content,
])
render(confirmView, container, function ready(){
@@ -160,4 +184,8 @@ function svgWrapper(content){
</svg>
`
return wrapperSource.split('{{content}}').join(content)
+}
+
+function toSvgUri(content){
+ return 'data:image/svg+xml;utf8,' + encodeURIComponent(content)
} \ No newline at end of file
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
new file mode 100644
index 000000000..398086274
--- /dev/null
+++ b/app/scripts/metamask-controller.js
@@ -0,0 +1,257 @@
+const extend = require('xtend')
+const EthStore = require('eth-store')
+const MetaMaskProvider = require('web3-provider-engine/zero.js')
+const IdentityStore = require('./lib/idStore')
+const messageManager = require('./lib/message-manager')
+const HostStore = require('./lib/remote-store.js').HostStore
+const Web3 = require('web3')
+const ConfigManager = require('./lib/config-manager')
+
+module.exports = class MetamaskController {
+
+ constructor (opts) {
+ this.opts = opts
+ this.configManager = new ConfigManager(opts)
+ this.idStore = new IdentityStore({
+ configManager: this.configManager,
+ })
+ this.provider = this.initializeProvider(opts)
+ this.ethStore = new EthStore(this.provider)
+ this.idStore.setStore(this.ethStore)
+ this.messageManager = messageManager
+ this.publicConfigStore = this.initPublicConfigStore()
+ }
+
+ getState () {
+ return extend(
+ this.ethStore.getState(),
+ this.idStore.getState(),
+ this.configManager.getConfig()
+ )
+ }
+
+ getApi () {
+ const idStore = this.idStore
+
+ return {
+ getState: (cb) => { cb(null, this.getState()) },
+ setRpcTarget: this.setRpcTarget.bind(this),
+ setProviderType: this.setProviderType.bind(this),
+ useEtherscanProvider: this.useEtherscanProvider.bind(this),
+ agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
+ // forward directly to idStore
+ createNewVault: idStore.createNewVault.bind(idStore),
+ recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
+ submitPassword: idStore.submitPassword.bind(idStore),
+ 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),
+ revealAccount: idStore.revealAccount.bind(idStore),
+ saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
+ tryPassword: idStore.tryPassword.bind(idStore),
+ recoverSeed: idStore.recoverSeed.bind(idStore),
+ }
+ }
+
+ setupProviderConnection (stream, originDomain) {
+ stream.on('data', this.onRpcRequest.bind(this, stream, originDomain))
+ }
+
+ onRpcRequest (stream, originDomain, request) {
+ var payloads = Array.isArray(request) ? request : [request]
+ payloads.forEach(function (payload) {
+ // Append origin to rpc payload
+ payload.origin = originDomain
+ // Append origin to signature request
+ if (payload.method === 'eth_sendTransaction') {
+ payload.params[0].origin = originDomain
+ } else if (payload.method === 'eth_sign') {
+ payload.params.push({ origin: originDomain })
+ }
+ })
+
+ // handle rpc request
+ this.provider.sendAsync(request, function onPayloadHandled (err, response) {
+ if (err) {
+ return logger(err)
+ }
+ logger(null, request, response)
+ try {
+ stream.write(response)
+ } catch (err) {
+ logger(err)
+ }
+ })
+
+ function logger (err, request, response) {
+ if (err) return console.error(err.stack)
+ if (!request.isMetamaskInternal) {
+ console.log(`RPC (${originDomain}):`, request, '->', response)
+ if (response.error) console.error('Error in RPC response:\n' + response.error.message)
+ }
+ }
+ }
+
+ sendUpdate () {
+ if (this.remote) {
+ this.remote.sendUpdate(this.getState())
+ }
+ }
+
+ initializeProvider (opts) {
+ const idStore = this.idStore
+
+ var providerOpts = {
+ rpcUrl: this.configManager.getCurrentRpcAddress(),
+ // account mgmt
+ getAccounts: (cb) => {
+ var selectedAddress = idStore.getSelectedAddress()
+ var result = selectedAddress ? [selectedAddress] : []
+ cb(null, result)
+ },
+ // tx signing
+ approveTransaction: this.newUnsignedTransaction.bind(this),
+ signTransaction: idStore.signTransaction.bind(idStore),
+ // msg signing
+ approveMessage: this.newUnsignedMessage.bind(this),
+ signMessage: idStore.signMessage.bind(idStore),
+ }
+
+ var provider = MetaMaskProvider(providerOpts)
+ var web3 = new Web3(provider)
+ idStore.web3 = web3
+ idStore.getNetwork()
+
+ provider.on('block', this.processBlock.bind(this))
+ provider.on('error', idStore.getNetwork.bind(idStore))
+
+ return provider
+ }
+
+ initPublicConfigStore () {
+ // get init state
+ var initPublicState = extend(
+ idStoreToPublic(this.idStore.getState()),
+ configToPublic(this.configManager.getConfig())
+ )
+
+ var publicConfigStore = new HostStore(initPublicState)
+
+ // subscribe to changes
+ this.configManager.subscribe(function (state) {
+ storeSetFromObj(publicConfigStore, configToPublic(state))
+ })
+ this.idStore.on('update', function (state) {
+ storeSetFromObj(publicConfigStore, idStoreToPublic(state))
+ })
+
+ // idStore substate
+ function idStoreToPublic (state) {
+ return {
+ selectedAddress: state.selectedAddress,
+ }
+ }
+ // config substate
+ function configToPublic (state) {
+ return {
+ provider: state.provider,
+ }
+ }
+ // dump obj into store
+ function storeSetFromObj (store, obj) {
+ Object.keys(obj).forEach(function (key) {
+ store.set(key, obj[key])
+ })
+ }
+
+ return publicConfigStore
+ }
+
+ newUnsignedTransaction (txParams, onTxDoneCb) {
+ const idStore = this.idStore
+ var state = idStore.getState()
+
+ // It's locked
+ if (!state.isUnlocked) {
+ this.opts.unlockAccountMessage()
+ idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
+
+ // It's unlocked
+ } else {
+ idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
+ if (err) return onTxDoneCb(err)
+ this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
+ })
+ }
+ }
+
+ newUnsignedMessage (msgParams, cb) {
+ var state = this.idStore.getState()
+ if (!state.isUnlocked) {
+ this.opts.unlockAccountMessage()
+ } else {
+ this.addUnconfirmedMsg(msgParams, cb)
+ }
+ }
+
+ addUnconfirmedMessage (msgParams, cb) {
+ const idStore = this.idStore
+ const msgId = idStore.addUnconfirmedMessage(msgParams, cb)
+ this.opts.showUnconfirmedMessage(msgParams, msgId)
+ }
+
+ setupPublicConfig (stream) {
+ var storeStream = this.publicConfigStore.createStream()
+ stream.pipe(storeStream).pipe(stream)
+ }
+
+ // Log blocks
+ processBlock (block) {
+ console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
+ this.verifyNetwork()
+ }
+
+ verifyNetwork () {
+ // Check network when restoring connectivity:
+ if (this.idStore._currentState.network === 'loading') {
+ this.idStore.getNetwork()
+ }
+ }
+
+ // config
+ //
+
+ agreeToDisclaimer (cb) {
+ try {
+ this.configManager.setConfirmed(true)
+ cb()
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ // called from popup
+ setRpcTarget (rpcTarget) {
+ this.configManager.setRpcTarget(rpcTarget)
+ chrome.runtime.reload()
+ this.idStore.getNetwork()
+ }
+
+ setProviderType (type) {
+ this.configManager.setProviderType(type)
+ chrome.runtime.reload()
+ this.idStore.getNetwork()
+ }
+
+ useEtherscanProvider () {
+ this.configManager.useEtherscanProvider()
+ chrome.runtime.reload()
+ }
+}
+
+function noop () {}
diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js
new file mode 100644
index 000000000..fe841f455
--- /dev/null
+++ b/test/lib/mock-config-manager.js
@@ -0,0 +1,57 @@
+var ConfigManager = require('../../app/scripts/lib/config-manager')
+const STORAGE_KEY = 'metamask-persistance-key'
+const extend = require('xtend')
+
+module.exports = function() {
+ return new ConfigManager({ loadData, setData })
+}
+
+function loadData () {
+ var oldData = getOldStyleData()
+ var newData
+ try {
+ newData = JSON.parse(window.localStorage[STORAGE_KEY])
+ } catch (e) {}
+
+ var data = extend({
+ meta: {
+ version: 0,
+ },
+ data: {
+ config: {
+ provider: {
+ type: 'testnet',
+ },
+ },
+ },
+ }, oldData || null, newData || null)
+ return data
+}
+
+function getOldStyleData () {
+ var config, wallet, seedWords
+
+ var result = {
+ meta: { version: 0 },
+ data: {},
+ }
+
+ try {
+ config = JSON.parse(window.localStorage['config'])
+ result.data.config = config
+ } catch (e) {}
+ try {
+ wallet = JSON.parse(window.localStorage['lightwallet'])
+ result.data.wallet = wallet
+ } catch (e) {}
+ try {
+ seedWords = window.localStorage['seedWords']
+ result.data.seedWords = seedWords
+ } catch (e) {}
+
+ return result
+}
+
+function setData (data) {
+ window.localStorage[STORAGE_KEY] = JSON.stringify(data)
+}
diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js
index 130bde2ff..7891c5c9e 100644
--- a/test/unit/config-manager-test.js
+++ b/test/unit/config-manager-test.js
@@ -1,12 +1,14 @@
var assert = require('assert')
-var ConfigManager = require('../../app/scripts/lib/config-manager')
+const extend = require('xtend')
+const STORAGE_KEY = 'metamask-persistance-key'
+var configManagerGen = require('../lib/mock-config-manager')
var configManager
describe('config-manager', function() {
beforeEach(function() {
window.localStorage = {} // Hacking localStorage support into JSDom
- configManager = new ConfigManager()
+ configManager = configManagerGen()
})
describe('confirmation', function() {
@@ -209,3 +211,4 @@ describe('config-manager', function() {
})
})
})
+
diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js
index e9611d7e8..ee4613236 100644
--- a/test/unit/idStore-test.js
+++ b/test/unit/idStore-test.js
@@ -1,5 +1,6 @@
var assert = require('assert')
var IdentityStore = require('../../app/scripts/lib/idStore')
+var configManagerGen = require('../lib/mock-config-manager')
describe('IdentityStore', function() {
@@ -15,6 +16,7 @@ describe('IdentityStore', function() {
window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({
+ configManager: configManagerGen(),
ethStore: {
addAccount(acct) { accounts.push(acct) },
},
@@ -34,6 +36,7 @@ describe('IdentityStore', function() {
window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({
+ configManager: configManagerGen(),
ethStore: {
addAccount(acct) { newAccounts.push(acct) },
},
@@ -65,6 +68,7 @@ describe('IdentityStore', function() {
window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({
+ configManager: configManagerGen(),
ethStore: {
addAccount(acct) { accounts.push(acct) },
},
diff --git a/ui/app/components/account-panel.js b/ui/app/components/account-panel.js
index b98a8cb45..c69557d62 100644
--- a/ui/app/components/account-panel.js
+++ b/ui/app/components/account-panel.js
@@ -46,7 +46,7 @@ AccountPanel.prototype.render = function () {
h('.identicon-wrapper.flex-column.select-none', [
h(Identicon, {
address: panelState.identiconKey,
- imageify: !state.inlineIdenticons,
+ imageify: state.imageifyIdenticons,
}),
h('span.font-small', panelState.identiconLabel),
]),
diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js
new file mode 100644
index 000000000..adcec596e
--- /dev/null
+++ b/ui/app/components/pending-msg-details.js
@@ -0,0 +1,53 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+
+const AccountPanel = require('./account-panel')
+const readableDate = require('../util').readableDate
+
+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 }
+
+ return (
+ h('div', {
+ key: msgData.id,
+ }, [
+
+ // account that will sign
+ h(AccountPanel, {
+ showFullAddress: true,
+ identity: identity,
+ account: account,
+ imageifyIdenticons: state.imageifyIdenticons,
+ }),
+
+ // message 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),
+ ]),
+ ]),
+
+ ])
+ )
+}
+
diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js
index 7f3914d56..f4bde91dc 100644
--- a/ui/app/components/pending-msg.js
+++ b/ui/app/components/pending-msg.js
@@ -1,9 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
-
-const AccountPanel = require('./account-panel')
-const readableDate = require('../util').readableDate
+const PendingTxDetails = require('./pending-msg-details')
module.exports = PendingMsg
@@ -16,16 +14,13 @@ 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', {
+
+ h('div', {
key: msgData.id,
}, [
+ // header
h('h3', {
style: {
fontWeight: 'bold',
@@ -33,27 +28,10 @@ PendingMsg.prototype.render = function () {
},
}, '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)),
- ]),
+ // message details
+ h(PendingTxDetails, state),
- h('.flex-row.flex-space-between', [
- h('label.font-small', 'MESSAGE'),
- h('span.font-small', msgParams.data),
- ]),
- ]),
-
- // send + cancel
+ // sign + cancel
h('.flex-row.flex-space-around', [
h('button', {
onClick: state.cancelMessage,
@@ -63,6 +41,7 @@ PendingMsg.prototype.render = function () {
}, 'Sign'),
]),
])
+
)
}
diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js
new file mode 100644
index 000000000..2ba613f20
--- /dev/null
+++ b/ui/app/components/pending-tx-details.js
@@ -0,0 +1,65 @@
+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
+
+module.exports = PendingTxDetails
+
+inherits(PendingTxDetails, Component)
+function PendingTxDetails () {
+ Component.call(this)
+}
+
+PendingTxDetails.prototype.render = function () {
+ var state = this.props
+ return this.renderGeneric(h, state)
+}
+
+PendingTxDetails.prototype.renderGeneric = function (h, state) {
+ 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('div', [
+
+ // account that will sign
+ h(AccountPanel, {
+ showFullAddress: true,
+ identity: identity,
+ account: account,
+ imageifyIdenticons: state.imageifyIdenticons,
+ }),
+
+ // 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)),
+ ]),
+ ]),
+
+ ])
+
+ )
+
+}
diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js
index 484046827..197e0436c 100644
--- a/ui/app/components/pending-tx.js
+++ b/ui/app/components/pending-tx.js
@@ -1,11 +1,8 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const PendingTxDetails = require('./pending-tx-details')
-const AccountPanel = require('./account-panel')
-const addressSummary = require('../util').addressSummary
-const readableDate = require('../util').readableDate
-const formatBalance = require('../util').formatBalance
module.exports = PendingTx
@@ -16,23 +13,15 @@ function PendingTx () {
PendingTx.prototype.render = function () {
var state = this.props
- return this.renderGeneric(h, state)
-}
-
-PendingTx.prototype.renderGeneric = function (h, state) {
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', {
+ h('div', {
key: txData.id,
}, [
+ // header
h('h3', {
style: {
fontWeight: 'bold',
@@ -40,53 +29,21 @@ PendingTx.prototype.renderGeneric = function (h, state) {
},
}, 'Submit Transaction'),
- // account that will sign
- h(AccountPanel, {
- showFullAddress: true,
- identity: identity,
- account: account,
- inlineIdenticons: state.inlineIdenticons,
- }),
-
- // 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)),
- ]),
- ]),
+ // tx info
+ h(PendingTxDetails, state),
// send + cancel
- state.nonInteractive ? null : actionButtons(state),
+ h('.flex-row.flex-space-around', [
+ h('button', {
+ onClick: state.cancelTransaction,
+ }, 'Cancel'),
+ h('button', {
+ onClick: state.sendTransaction,
+ }, 'Send'),
+ ]),
])
)
}
-
-function actionButtons(state){
- return (
-
- h('.flex-row.flex-space-around', [
- h('button', {
- onClick: state.cancelTransaction,
- }, 'Cancel'),
- h('button', {
- onClick: state.sendTransaction,
- }, 'Send'),
- ])
-
- )
-} \ No newline at end of file
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 5c80939b9..8455826b8 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -6,7 +6,7 @@ const connect = require('react-redux').connect
const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
-const ConfirmTx = require('./components/pending-tx')
+const PendingTx = require('./components/pending-tx')
const PendingMsg = require('./components/pending-msg')
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
@@ -101,7 +101,7 @@ ConfirmTxScreen.prototype.render = function () {
function currentTxView (opts) {
if ('txParams' in opts.txData) {
// This is a pending transaction
- return h(ConfirmTx, opts)
+ return h(PendingTx, opts)
} else if ('msgParams' in opts.txData) {
// This is a pending message to sign
return h(PendingMsg, opts)