diff options
author | Frankie <frankie.pangilinan@consensys.net> | 2016-06-25 09:02:28 +0800 |
---|---|---|
committer | Frankie <frankie.pangilinan@consensys.net> | 2016-06-25 09:02:28 +0800 |
commit | ab55fefa1a4ebeaa9a42756a72d49b63f036b946 (patch) | |
tree | f30f51dd7dbe413b8944b230d14660a4c1a2fd61 | |
parent | ef743499cb93dc8360be20db03c4ab0676aa57bb (diff) | |
parent | 36a183db94e66999421980f50c2ab2e8d1ce2c3b (diff) | |
download | tangerine-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.js | 343 | ||||
-rw-r--r-- | app/scripts/contentscript.js | 6 | ||||
-rw-r--r-- | app/scripts/lib/config-manager-singleton.js | 3 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 56 | ||||
-rw-r--r-- | app/scripts/lib/id-management.js | 6 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 28 | ||||
-rw-r--r-- | app/scripts/lib/notifications.js | 126 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 257 | ||||
-rw-r--r-- | test/lib/mock-config-manager.js | 57 | ||||
-rw-r--r-- | test/unit/config-manager-test.js | 7 | ||||
-rw-r--r-- | test/unit/idStore-test.js | 4 | ||||
-rw-r--r-- | ui/app/components/account-panel.js | 2 | ||||
-rw-r--r-- | ui/app/components/pending-msg-details.js | 53 | ||||
-rw-r--r-- | ui/app/components/pending-msg.js | 37 | ||||
-rw-r--r-- | ui/app/components/pending-tx-details.js | 65 | ||||
-rw-r--r-- | ui/app/components/pending-tx.js | 69 | ||||
-rw-r--r-- | ui/app/conf-tx.js | 4 |
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) |