aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/metamask-controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/metamask-controller.js')
-rw-r--r--app/scripts/metamask-controller.js255
1 files changed, 255 insertions, 0 deletions
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
new file mode 100644
index 000000000..aef280310
--- /dev/null
+++ b/app/scripts/metamask-controller.js
@@ -0,0 +1,255 @@
+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 = MetamaskController
+
+class MetamaskController {
+
+ constructor (opts) {
+ this.configManager = new ConfigManager(opts)
+ this.idStore = new IdentityStore({ configManager })
+ this.messageManager = messageManager
+ this.provider = this.initializeProvider(opts)
+ this.ethStore = new EthStore(this.provider)
+ this.idStore.setStore(this.ethStore)
+ this.publicConfigStore = this.initPublicConfigStore()
+ }
+
+ getState () {
+ return extend(
+ this.ethStore.getState(),
+ this.idStore.getState(),
+ this.configManager.getConfig()
+ )
+ }
+
+ getApi () {
+ const idStore = this.idStore
+
+ return {
+ getState: function (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 () {
+ this.remote.sendUpdate(this.getState())
+ }
+
+ initializeProvider (opts) {
+ const idStore = this.idStore
+
+ var providerOpts = {
+ rpcUrl: this.configManager.getCurrentRpcAddress(),
+ // account mgmt
+ getAccounts: function (cb) {
+ var selectedAddress = idStore.getSelectedAddress()
+ var result = selectedAddress ? [selectedAddress] : []
+ cb(null, result)
+ },
+ // tx signing
+ approveTransaction: this.newUnsignedTransaction,
+ signTransaction: idStore.signTransaction.bind(idStore),
+ // msg signing
+ approveMessage: this.newUnsignedMessage,
+ signMessage: idStore.signMessage.bind(idStore),
+ }
+
+ var provider = MetaMaskProvider(providerOpts)
+ var web3 = new Web3(provider)
+ idStore.web3 = web3
+ idStore.getNetwork()
+
+ provider.on('block', this.processBlock)
+ 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 = 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
+ //
+
+ function agreeToDisclaimer (cb) {
+ try {
+ this.configManager.setConfirmed(true)
+ cb()
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ // called from popup
+ function setRpcTarget (rpcTarget) {
+ this.configManager.setRpcTarget(rpcTarget)
+ chrome.runtime.reload()
+ idStore.getNetwork()
+ }
+
+ function setProviderType (type) {
+ this.configManager.setProviderType(type)
+ chrome.runtime.reload()
+ idStore.getNetwork()
+ }
+
+ function useEtherscanProvider () {
+ this.configManager.useEtherscanProvider()
+ chrome.runtime.reload()
+ }
+
+}
+
+function noop () {}