commit
27220b7bcd
@ -1 +1,4 @@ |
|||||||
{ "presets": ["es2015"] } |
{ |
||||||
|
"presets": ["es2015"], |
||||||
|
"plugins": ["transform-runtime"] |
||||||
|
} |
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
node_modules |
||||||
|
builds |
||||||
|
development |
@ -1 +1,5 @@ |
|||||||
app/scripts/lib/extension-instance.js |
app/scripts/lib/extension-instance.js |
||||||
|
test/integration/bundle.js |
||||||
|
test/integration/jquery-3.1.0.min.js |
||||||
|
test/integration/helpers.js |
||||||
|
test/integration/lib/first-time.js |
@ -0,0 +1,22 @@ |
|||||||
|
FROM node:7 |
||||||
|
MAINTAINER kumavis |
||||||
|
|
||||||
|
# setup app dir |
||||||
|
RUN mkdir -p /www/ |
||||||
|
WORKDIR /www/ |
||||||
|
|
||||||
|
# install dependencies |
||||||
|
COPY ./package.json /www/package.json |
||||||
|
RUN npm install |
||||||
|
|
||||||
|
# copy over app dir |
||||||
|
COPY ./ /www/ |
||||||
|
|
||||||
|
# run tests |
||||||
|
# RUN npm test |
||||||
|
|
||||||
|
# build app |
||||||
|
RUN npm run dist |
||||||
|
|
||||||
|
# start server |
||||||
|
CMD node mascara/example/server.js |
File diff suppressed because one or more lines are too long
@ -1,14 +1,15 @@ |
|||||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' |
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' |
||||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' |
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' |
||||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL |
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' |
||||||
|
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' |
||||||
|
|
||||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
||||||
|
|
||||||
module.exports = { |
module.exports = { |
||||||
network: { |
network: { |
||||||
default: DEFAULT_RPC_URL, |
|
||||||
mainnet: MAINET_RPC_URL, |
mainnet: MAINET_RPC_URL, |
||||||
testnet: TESTNET_RPC_URL, |
ropsten: ROPSTEN_RPC_URL, |
||||||
morden: TESTNET_RPC_URL, |
kovan: KOVAN_RPC_URL, |
||||||
|
rinkeby: RINKEBY_RPC_URL, |
||||||
}, |
}, |
||||||
} |
} |
||||||
|
@ -0,0 +1,129 @@ |
|||||||
|
const EventEmitter = require('events') |
||||||
|
const MetaMaskProvider = require('web3-provider-engine/zero.js') |
||||||
|
const ObservableStore = require('obs-store') |
||||||
|
const ComposedStore = require('obs-store/lib/composed') |
||||||
|
const extend = require('xtend') |
||||||
|
const EthQuery = require('eth-query') |
||||||
|
const RPC_ADDRESS_LIST = require('../config.js').network |
||||||
|
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] |
||||||
|
|
||||||
|
module.exports = class NetworkController extends EventEmitter { |
||||||
|
constructor (config) { |
||||||
|
super() |
||||||
|
this.networkStore = new ObservableStore('loading') |
||||||
|
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) |
||||||
|
this.providerStore = new ObservableStore(config.provider) |
||||||
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) |
||||||
|
this._providerListeners = {} |
||||||
|
|
||||||
|
this.on('networkDidChange', this.lookupNetwork) |
||||||
|
this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) |
||||||
|
} |
||||||
|
|
||||||
|
get provider () { |
||||||
|
return this._proxy |
||||||
|
} |
||||||
|
|
||||||
|
set provider (provider) { |
||||||
|
this._provider = provider |
||||||
|
} |
||||||
|
|
||||||
|
initializeProvider (opts) { |
||||||
|
this.providerInit = opts |
||||||
|
this._provider = MetaMaskProvider(opts) |
||||||
|
this._proxy = new Proxy(this._provider, { |
||||||
|
get: (obj, name) => { |
||||||
|
if (name === 'on') return this._on.bind(this) |
||||||
|
return this._provider[name] |
||||||
|
}, |
||||||
|
set: (obj, name, value) => { |
||||||
|
this._provider[name] = value |
||||||
|
}, |
||||||
|
}) |
||||||
|
this.provider.on('block', this._logBlock.bind(this)) |
||||||
|
this.provider.on('error', this.verifyNetwork.bind(this)) |
||||||
|
this.ethQuery = new EthQuery(this.provider) |
||||||
|
this.lookupNetwork() |
||||||
|
return this.provider |
||||||
|
} |
||||||
|
|
||||||
|
switchNetwork (providerInit) { |
||||||
|
this.setNetworkState('loading') |
||||||
|
const newInit = extend(this.providerInit, providerInit) |
||||||
|
this.providerInit = newInit |
||||||
|
|
||||||
|
this._provider.removeAllListeners() |
||||||
|
this._provider.stop() |
||||||
|
this.provider = MetaMaskProvider(newInit) |
||||||
|
// apply the listners created by other controllers
|
||||||
|
Object.keys(this._providerListeners).forEach((key) => { |
||||||
|
this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) |
||||||
|
}) |
||||||
|
this.emit('networkDidChange') |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
verifyNetwork () { |
||||||
|
// Check network when restoring connectivity:
|
||||||
|
if (this.isNetworkLoading()) this.lookupNetwork() |
||||||
|
} |
||||||
|
|
||||||
|
getNetworkState () { |
||||||
|
return this.networkStore.getState() |
||||||
|
} |
||||||
|
|
||||||
|
setNetworkState (network) { |
||||||
|
return this.networkStore.putState(network) |
||||||
|
} |
||||||
|
|
||||||
|
isNetworkLoading () { |
||||||
|
return this.getNetworkState() === 'loading' |
||||||
|
} |
||||||
|
|
||||||
|
lookupNetwork () { |
||||||
|
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { |
||||||
|
if (err) return this.setNetworkState('loading') |
||||||
|
log.info('web3.getNetwork returned ' + network) |
||||||
|
this.setNetworkState(network) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
setRpcTarget (rpcUrl) { |
||||||
|
this.providerStore.updateState({ |
||||||
|
type: 'rpc', |
||||||
|
rpcTarget: rpcUrl, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
getCurrentRpcAddress () { |
||||||
|
const provider = this.getProviderConfig() |
||||||
|
if (!provider) return null |
||||||
|
return this.getRpcAddressForType(provider.type) |
||||||
|
} |
||||||
|
|
||||||
|
setProviderType (type) { |
||||||
|
if (type === this.getProviderConfig().type) return |
||||||
|
const rpcTarget = this.getRpcAddressForType(type) |
||||||
|
this.providerStore.updateState({type, rpcTarget}) |
||||||
|
} |
||||||
|
|
||||||
|
getProviderConfig () { |
||||||
|
return this.providerStore.getState() |
||||||
|
} |
||||||
|
|
||||||
|
getRpcAddressForType (type, provider = this.getProviderConfig()) { |
||||||
|
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] |
||||||
|
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC |
||||||
|
} |
||||||
|
|
||||||
|
_logBlock (block) { |
||||||
|
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) |
||||||
|
this.verifyNetwork() |
||||||
|
} |
||||||
|
|
||||||
|
_on (event, handler) { |
||||||
|
if (!this._providerListeners[event]) this._providerListeners[event] = [] |
||||||
|
this._providerListeners[event].push(handler) |
||||||
|
this._provider.on(event, handler) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
module.exports = getBuyEthUrl |
||||||
|
|
||||||
|
function getBuyEthUrl ({ network, amount, address }) { |
||||||
|
let url |
||||||
|
switch (network) { |
||||||
|
case '1': |
||||||
|
url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` |
||||||
|
break |
||||||
|
|
||||||
|
case '3': |
||||||
|
url = 'https://faucet.metamask.io/' |
||||||
|
break |
||||||
|
|
||||||
|
case '4': |
||||||
|
url = 'https://www.rinkeby.io/' |
||||||
|
break |
||||||
|
|
||||||
|
case '42': |
||||||
|
url = 'https://github.com/kovan-testnet/faucet' |
||||||
|
break |
||||||
|
} |
||||||
|
return url |
||||||
|
} |
@ -1,68 +0,0 @@ |
|||||||
const apis = [ |
|
||||||
'alarms', |
|
||||||
'bookmarks', |
|
||||||
'browserAction', |
|
||||||
'commands', |
|
||||||
'contextMenus', |
|
||||||
'cookies', |
|
||||||
'downloads', |
|
||||||
'events', |
|
||||||
'extension', |
|
||||||
'extensionTypes', |
|
||||||
'history', |
|
||||||
'i18n', |
|
||||||
'idle', |
|
||||||
'notifications', |
|
||||||
'pageAction', |
|
||||||
'runtime', |
|
||||||
'storage', |
|
||||||
'tabs', |
|
||||||
'webNavigation', |
|
||||||
'webRequest', |
|
||||||
'windows', |
|
||||||
] |
|
||||||
|
|
||||||
function Extension () { |
|
||||||
const _this = this |
|
||||||
|
|
||||||
apis.forEach(function (api) { |
|
||||||
|
|
||||||
_this[api] = null |
|
||||||
|
|
||||||
try { |
|
||||||
if (chrome[api]) { |
|
||||||
_this[api] = chrome[api] |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
try { |
|
||||||
if (window[api]) { |
|
||||||
_this[api] = window[api] |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
try { |
|
||||||
if (browser[api]) { |
|
||||||
_this[api] = browser[api] |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
try { |
|
||||||
_this.api = browser.extension[api] |
|
||||||
} catch (e) {} |
|
||||||
}) |
|
||||||
|
|
||||||
try { |
|
||||||
if (browser && browser.runtime) { |
|
||||||
this.runtime = browser.runtime |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
try { |
|
||||||
if (browser && browser.browserAction) { |
|
||||||
this.browserAction = browser.browserAction |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
module.exports = Extension |
|
@ -1,14 +0,0 @@ |
|||||||
/* Extension.js |
|
||||||
* |
|
||||||
* A module for unifying browser differences in the WebExtension API. |
|
||||||
* |
|
||||||
* Initially implemented because Chrome hides all of their WebExtension API |
|
||||||
* behind a global `chrome` variable, but we'd like to start grooming |
|
||||||
* the code-base for cross-browser extension support. |
|
||||||
* |
|
||||||
* You can read more about the WebExtension API here: |
|
||||||
* https://developer.mozilla.org/en-US/Add-ons/WebExtensions
|
|
||||||
*/ |
|
||||||
|
|
||||||
const Extension = require('./extension-instance') |
|
||||||
module.exports = new Extension() |
|
@ -0,0 +1,7 @@ |
|||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
const BN = ethUtil.BN |
||||||
|
|
||||||
|
module.exports = function hexToBn (hex) { |
||||||
|
return new BN(ethUtil.stripHexPrefix(hex), 16) |
||||||
|
} |
||||||
|
|
@ -1,90 +0,0 @@ |
|||||||
/* ID Management |
|
||||||
* |
|
||||||
* This module exists to hold the decrypted credentials for the current session. |
|
||||||
* It therefore exposes sign methods, because it is able to perform these |
|
||||||
* with noa dditional authentication, because its very instantiation |
|
||||||
* means the vault is unlocked. |
|
||||||
*/ |
|
||||||
|
|
||||||
const ethUtil = require('ethereumjs-util') |
|
||||||
const Transaction = require('ethereumjs-tx') |
|
||||||
|
|
||||||
module.exports = IdManagement |
|
||||||
|
|
||||||
function IdManagement (opts) { |
|
||||||
if (!opts) opts = {} |
|
||||||
|
|
||||||
this.keyStore = opts.keyStore |
|
||||||
this.derivedKey = opts.derivedKey |
|
||||||
this.configManager = opts.configManager |
|
||||||
this.hdPathString = "m/44'/60'/0'/0" |
|
||||||
|
|
||||||
this.getAddresses = function () { |
|
||||||
return this.keyStore.getAddresses(this.hdPathString).map(function (address) { return '0x' + address }) |
|
||||||
} |
|
||||||
|
|
||||||
this.signTx = function (txParams) { |
|
||||||
|
|
||||||
// normalize values
|
|
||||||
txParams.gasPrice = ethUtil.intToHex(txParams.gasPrice) |
|
||||||
txParams.to = ethUtil.addHexPrefix(txParams.to) |
|
||||||
txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase()) |
|
||||||
txParams.value = ethUtil.addHexPrefix(txParams.value) |
|
||||||
txParams.data = ethUtil.addHexPrefix(txParams.data) |
|
||||||
txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas) |
|
||||||
txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) |
|
||||||
var tx = new Transaction(txParams) |
|
||||||
|
|
||||||
// sign tx
|
|
||||||
var privKeyHex = this.exportPrivateKey(txParams.from) |
|
||||||
var privKey = ethUtil.toBuffer(privKeyHex) |
|
||||||
tx.sign(privKey) |
|
||||||
|
|
||||||
// Add the tx hash to the persisted meta-tx object
|
|
||||||
var txHash = ethUtil.bufferToHex(tx.hash()) |
|
||||||
var metaTx = this.configManager.getTx(txParams.metamaskId) |
|
||||||
metaTx.hash = txHash |
|
||||||
this.configManager.updateTx(metaTx) |
|
||||||
|
|
||||||
// return raw serialized tx
|
|
||||||
var rawTx = ethUtil.bufferToHex(tx.serialize()) |
|
||||||
return rawTx |
|
||||||
} |
|
||||||
|
|
||||||
this.signMsg = function (address, message) { |
|
||||||
// sign message
|
|
||||||
var privKeyHex = this.exportPrivateKey(address.toLowerCase()) |
|
||||||
var privKey = ethUtil.toBuffer(privKeyHex) |
|
||||||
var msgSig = ethUtil.ecsign(new Buffer(message.replace('0x', ''), 'hex'), privKey) |
|
||||||
var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s)) |
|
||||||
return rawMsgSig |
|
||||||
} |
|
||||||
|
|
||||||
this.getSeed = function () { |
|
||||||
return this.keyStore.getSeed(this.derivedKey) |
|
||||||
} |
|
||||||
|
|
||||||
this.exportPrivateKey = function (address) { |
|
||||||
var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString)) |
|
||||||
return privKeyHex |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function padWithZeroes (number, length) { |
|
||||||
var myString = '' + number |
|
||||||
while (myString.length < length) { |
|
||||||
myString = '0' + myString |
|
||||||
} |
|
||||||
return myString |
|
||||||
} |
|
||||||
|
|
||||||
function concatSig (v, r, s) { |
|
||||||
const rSig = ethUtil.fromSigned(r) |
|
||||||
const sSig = ethUtil.fromSigned(s) |
|
||||||
const vSig = ethUtil.bufferToInt(v) |
|
||||||
const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64) |
|
||||||
const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64) |
|
||||||
const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig)) |
|
||||||
return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex') |
|
||||||
} |
|
||||||
|
|
@ -1,80 +0,0 @@ |
|||||||
const IdentityStore = require('./idStore') |
|
||||||
const HdKeyring = require('eth-hd-keyring') |
|
||||||
const sigUtil = require('eth-sig-util') |
|
||||||
const normalize = sigUtil.normalize |
|
||||||
const denodeify = require('denodeify') |
|
||||||
|
|
||||||
module.exports = class IdentityStoreMigrator { |
|
||||||
|
|
||||||
constructor ({ configManager }) { |
|
||||||
this.configManager = configManager |
|
||||||
const hasOldVault = this.hasOldVault() |
|
||||||
if (!hasOldVault) { |
|
||||||
this.idStore = new IdentityStore({ configManager }) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
migratedVaultForPassword (password) { |
|
||||||
const hasOldVault = this.hasOldVault() |
|
||||||
const configManager = this.configManager |
|
||||||
|
|
||||||
if (!this.idStore) { |
|
||||||
this.idStore = new IdentityStore({ configManager }) |
|
||||||
} |
|
||||||
|
|
||||||
if (!hasOldVault) { |
|
||||||
return Promise.resolve(null) |
|
||||||
} |
|
||||||
|
|
||||||
const idStore = this.idStore |
|
||||||
const submitPassword = denodeify(idStore.submitPassword.bind(idStore)) |
|
||||||
|
|
||||||
return submitPassword(password) |
|
||||||
.then(() => { |
|
||||||
const serialized = this.serializeVault() |
|
||||||
return this.checkForLostAccounts(serialized) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
serializeVault () { |
|
||||||
const mnemonic = this.idStore._idmgmt.getSeed() |
|
||||||
const numberOfAccounts = this.idStore._getAddresses().length |
|
||||||
|
|
||||||
return { |
|
||||||
type: 'HD Key Tree', |
|
||||||
data: { mnemonic, numberOfAccounts }, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
checkForLostAccounts (serialized) { |
|
||||||
const hd = new HdKeyring() |
|
||||||
return hd.deserialize(serialized.data) |
|
||||||
.then((hexAccounts) => { |
|
||||||
const newAccounts = hexAccounts.map(normalize) |
|
||||||
const oldAccounts = this.idStore._getAddresses().map(normalize) |
|
||||||
const lostAccounts = oldAccounts.reduce((result, account) => { |
|
||||||
if (newAccounts.includes(account)) { |
|
||||||
return result |
|
||||||
} else { |
|
||||||
result.push(account) |
|
||||||
return result |
|
||||||
} |
|
||||||
}, []) |
|
||||||
|
|
||||||
return { |
|
||||||
serialized, |
|
||||||
lostAccounts: lostAccounts.map((address) => { |
|
||||||
return { |
|
||||||
address, |
|
||||||
privateKey: this.idStore.exportAccount(address), |
|
||||||
} |
|
||||||
}), |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
hasOldVault () { |
|
||||||
const wallet = this.configManager.getWallet() |
|
||||||
return wallet |
|
||||||
} |
|
||||||
} |
|
@ -1,343 +0,0 @@ |
|||||||
const EventEmitter = require('events').EventEmitter |
|
||||||
const inherits = require('util').inherits |
|
||||||
const ethUtil = require('ethereumjs-util') |
|
||||||
const KeyStore = require('eth-lightwallet').keystore |
|
||||||
const clone = require('clone') |
|
||||||
const extend = require('xtend') |
|
||||||
const autoFaucet = require('./auto-faucet') |
|
||||||
const DEFAULT_RPC = 'https://testrpc.metamask.io/' |
|
||||||
const IdManagement = require('./id-management') |
|
||||||
|
|
||||||
|
|
||||||
module.exports = IdentityStore |
|
||||||
|
|
||||||
inherits(IdentityStore, EventEmitter) |
|
||||||
function IdentityStore (opts = {}) { |
|
||||||
EventEmitter.call(this) |
|
||||||
|
|
||||||
// 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
|
|
||||||
this._idmgmt = null |
|
||||||
|
|
||||||
this.hdPathString = "m/44'/60'/0'/0" |
|
||||||
|
|
||||||
this._currentState = { |
|
||||||
selectedAddress: null, |
|
||||||
identities: {}, |
|
||||||
} |
|
||||||
// not part of serilized metamask state - only kept in memory
|
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// public
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype.createNewVault = function (password, cb) { |
|
||||||
delete this._keyStore |
|
||||||
var serializedKeystore = this.configManager.getWallet() |
|
||||||
|
|
||||||
if (serializedKeystore) { |
|
||||||
this.configManager.setData({}) |
|
||||||
} |
|
||||||
|
|
||||||
this.purgeCache() |
|
||||||
this._createVault(password, null, (err) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this._autoFaucet() |
|
||||||
|
|
||||||
this.configManager.setShowSeedWords(true) |
|
||||||
var seedWords = this._idmgmt.getSeed() |
|
||||||
|
|
||||||
this._loadIdentities() |
|
||||||
|
|
||||||
cb(null, seedWords) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.recoverSeed = function (cb) { |
|
||||||
this.configManager.setShowSeedWords(true) |
|
||||||
if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.')) |
|
||||||
var seedWords = this._idmgmt.getSeed() |
|
||||||
cb(null, seedWords) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) { |
|
||||||
this.purgeCache() |
|
||||||
|
|
||||||
this._createVault(password, seed, (err) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this._loadIdentities() |
|
||||||
cb(null, this.getState()) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setStore = function (store) { |
|
||||||
this._ethStore = 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, |
|
||||||
isUnlocked: this._isUnlocked(), |
|
||||||
seedWords: seedWords, |
|
||||||
selectedAddress: configManager.getSelectedAccount(), |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getSeedIfUnlocked = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
var showSeed = configManager.getShouldShowSeedWords() |
|
||||||
var idmgmt = this._idmgmt |
|
||||||
var shouldShow = showSeed && !!idmgmt |
|
||||||
var seedWords = shouldShow ? idmgmt.getSeed() : null |
|
||||||
return seedWords |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getSelectedAddress = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
return configManager.getSelectedAccount() |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setSelectedAddressSync = function (address) { |
|
||||||
const configManager = this.configManager |
|
||||||
if (!address) { |
|
||||||
var addresses = this._getAddresses() |
|
||||||
address = addresses[0] |
|
||||||
} |
|
||||||
|
|
||||||
configManager.setSelectedAccount(address) |
|
||||||
return address |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setSelectedAddress = function (address, cb) { |
|
||||||
const resultAddress = this.setSelectedAddressSync(address) |
|
||||||
if (cb) return cb(null, resultAddress) |
|
||||||
} |
|
||||||
|
|
||||||
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) |
|
||||||
const addresses = keyStore.getAddresses() |
|
||||||
const address = addresses[ addresses.length - 1 ] |
|
||||||
|
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address)) |
|
||||||
|
|
||||||
configManager.setWallet(keyStore.serialize()) |
|
||||||
|
|
||||||
this._loadIdentities() |
|
||||||
this._didUpdate() |
|
||||||
cb(null) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getNetwork = function (err) { |
|
||||||
if (err) { |
|
||||||
this._currentState.network = 'loading' |
|
||||||
this._didUpdate() |
|
||||||
} |
|
||||||
|
|
||||||
this.web3.version.getNetwork((err, network) => { |
|
||||||
if (err) { |
|
||||||
this._currentState.network = 'loading' |
|
||||||
return this._didUpdate() |
|
||||||
} |
|
||||||
if (global.METAMASK_DEBUG) { |
|
||||||
console.log('web3.getNetwork returned ' + network) |
|
||||||
} |
|
||||||
this._currentState.network = network |
|
||||||
this._didUpdate() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setLocked = function (cb) { |
|
||||||
delete this._keyStore |
|
||||||
delete this._idmgmt |
|
||||||
cb() |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.submitPassword = function (password, cb) { |
|
||||||
const configManager = this.configManager |
|
||||||
this.tryPassword(password, (err) => { |
|
||||||
if (err) return cb(err) |
|
||||||
// load identities before returning...
|
|
||||||
this._loadIdentities() |
|
||||||
cb(null, configManager.getSelectedAccount()) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.exportAccount = function (address, cb) { |
|
||||||
var privateKey = this._idmgmt.exportPrivateKey(address) |
|
||||||
if (cb) cb(null, privateKey) |
|
||||||
return privateKey |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype._didUpdate = function () { |
|
||||||
this.emit('update', this.getState()) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._isUnlocked = function () { |
|
||||||
var result = Boolean(this._keyStore) && Boolean(this._idmgmt) |
|
||||||
return result |
|
||||||
} |
|
||||||
|
|
||||||
// load identities from keyStoreet
|
|
||||||
IdentityStore.prototype._loadIdentities = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
if (!this._isUnlocked()) throw new Error('not unlocked') |
|
||||||
|
|
||||||
var addresses = this._getAddresses() |
|
||||||
addresses.forEach((address, i) => { |
|
||||||
// // add to ethStore
|
|
||||||
if (this._ethStore) { |
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address)) |
|
||||||
} |
|
||||||
// add to identities
|
|
||||||
const defaultLabel = 'Account ' + (i + 1) |
|
||||||
const nickname = configManager.nicknameForWallet(address) |
|
||||||
var identity = { |
|
||||||
name: nickname || defaultLabel, |
|
||||||
address: address, |
|
||||||
mayBeFauceting: this._mayBeFauceting(i), |
|
||||||
} |
|
||||||
this._currentState.identities[address] = identity |
|
||||||
}) |
|
||||||
this._didUpdate() |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.saveAccountLabel = function (account, label, cb) { |
|
||||||
const configManager = this.configManager |
|
||||||
configManager.setNicknameForWallet(account, label) |
|
||||||
this._loadIdentities() |
|
||||||
cb(null, label) |
|
||||||
} |
|
||||||
|
|
||||||
// mayBeFauceting
|
|
||||||
// If on testnet, index 0 may be fauceting.
|
|
||||||
// The UI will have to check the balance to know.
|
|
||||||
// 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' && |
|
||||||
config.rpcTarget === DEFAULT_RPC) { |
|
||||||
return true |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// keyStore managment - unlocking + deserialization
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype.tryPassword = function (password, cb) { |
|
||||||
var serializedKeystore = this.configManager.getWallet() |
|
||||||
var keyStore = KeyStore.deserialize(serializedKeystore) |
|
||||||
|
|
||||||
keyStore.keyFromPassword(password, (err, pwDerivedKey) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey) |
|
||||||
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect')) |
|
||||||
|
|
||||||
this._keyStore = keyStore |
|
||||||
this._createIdMgmt(pwDerivedKey) |
|
||||||
cb() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._createVault = function (password, seedPhrase, cb) { |
|
||||||
const opts = { |
|
||||||
password, |
|
||||||
hdPathString: this.hdPathString, |
|
||||||
} |
|
||||||
|
|
||||||
if (seedPhrase) { |
|
||||||
opts.seedPhrase = seedPhrase |
|
||||||
} |
|
||||||
|
|
||||||
KeyStore.createVault(opts, (err, keyStore) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this._keyStore = keyStore |
|
||||||
|
|
||||||
keyStore.keyFromPassword(password, (err, derivedKey) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this.purgeCache() |
|
||||||
|
|
||||||
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'}) |
|
||||||
|
|
||||||
this._createFirstWallet(derivedKey) |
|
||||||
this._createIdMgmt(derivedKey) |
|
||||||
this.setSelectedAddressSync() |
|
||||||
|
|
||||||
cb() |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._createIdMgmt = function (derivedKey) { |
|
||||||
this._idmgmt = new IdManagement({ |
|
||||||
keyStore: this._keyStore, |
|
||||||
derivedKey: derivedKey, |
|
||||||
configManager: this.configManager, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.purgeCache = function () { |
|
||||||
this._currentState.identities = {} |
|
||||||
let accounts |
|
||||||
try { |
|
||||||
accounts = Object.keys(this._ethStore._currentState.accounts) |
|
||||||
} catch (e) { |
|
||||||
accounts = [] |
|
||||||
} |
|
||||||
accounts.forEach((address) => { |
|
||||||
this._ethStore.removeAccount(address) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._createFirstWallet = function (derivedKey) { |
|
||||||
const keyStore = this._keyStore |
|
||||||
keyStore.setDefaultHdDerivationPath(this.hdPathString) |
|
||||||
keyStore.generateNewAddress(derivedKey, 1) |
|
||||||
this.configManager.setWallet(keyStore.serialize()) |
|
||||||
var addresses = keyStore.getAddresses() |
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0])) |
|
||||||
} |
|
||||||
|
|
||||||
// get addresses and normalize address hexString
|
|
||||||
IdentityStore.prototype._getAddresses = function () { |
|
||||||
return this._keyStore.getAddresses(this.hdPathString).map((address) => { |
|
||||||
return ethUtil.addHexPrefix(address) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._autoFaucet = function () { |
|
||||||
var addresses = this._getAddresses() |
|
||||||
autoFaucet(addresses[0]) |
|
||||||
} |
|
||||||
|
|
||||||
// util
|
|
@ -0,0 +1,71 @@ |
|||||||
|
const extension = require('extensionizer') |
||||||
|
const height = 520 |
||||||
|
const width = 360 |
||||||
|
|
||||||
|
|
||||||
|
class NotificationManager { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
showPopup () { |
||||||
|
this._getPopup((err, popup) => { |
||||||
|
if (err) throw err |
||||||
|
|
||||||
|
if (popup) { |
||||||
|
// bring focus to existing popup
|
||||||
|
extension.windows.update(popup.id, { focused: true }) |
||||||
|
} else { |
||||||
|
// create new popup
|
||||||
|
extension.windows.create({ |
||||||
|
url: 'notification.html', |
||||||
|
type: 'popup', |
||||||
|
width, |
||||||
|
height, |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
closePopup () { |
||||||
|
this._getPopup((err, popup) => { |
||||||
|
if (err) throw err |
||||||
|
if (!popup) return |
||||||
|
extension.windows.remove(popup.id, console.error) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// Private
|
||||||
|
//
|
||||||
|
|
||||||
|
_getPopup (cb) { |
||||||
|
this._getWindows((err, windows) => { |
||||||
|
if (err) throw err |
||||||
|
cb(null, this._getPopupIn(windows)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
_getWindows (cb) { |
||||||
|
// Ignore in test environment
|
||||||
|
if (!extension.windows) { |
||||||
|
return cb() |
||||||
|
} |
||||||
|
|
||||||
|
extension.windows.getAll({}, (windows) => { |
||||||
|
cb(null, windows) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
_getPopupIn (windows) { |
||||||
|
return windows ? windows.find((win) => { |
||||||
|
return (win && win.type === 'popup' && |
||||||
|
win.height === height && |
||||||
|
win.width === width) |
||||||
|
}) : null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = NotificationManager |
@ -1,65 +0,0 @@ |
|||||||
const extension = require('./extension') |
|
||||||
const height = 520 |
|
||||||
const width = 360 |
|
||||||
|
|
||||||
const notifications = { |
|
||||||
show, |
|
||||||
getPopup, |
|
||||||
closePopup, |
|
||||||
} |
|
||||||
module.exports = notifications |
|
||||||
window.METAMASK_NOTIFIER = notifications |
|
||||||
|
|
||||||
function show () { |
|
||||||
getPopup((err, popup) => { |
|
||||||
if (err) throw err |
|
||||||
|
|
||||||
if (popup) { |
|
||||||
// bring focus to existing popup
|
|
||||||
extension.windows.update(popup.id, { focused: true }) |
|
||||||
} else { |
|
||||||
// create new popup
|
|
||||||
extension.windows.create({ |
|
||||||
url: 'notification.html', |
|
||||||
type: 'popup', |
|
||||||
focused: true, |
|
||||||
width, |
|
||||||
height, |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getWindows (cb) { |
|
||||||
// Ignore in test environment
|
|
||||||
if (!extension.windows) { |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
extension.windows.getAll({}, (windows) => { |
|
||||||
cb(null, windows) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getPopup (cb) { |
|
||||||
getWindows((err, windows) => { |
|
||||||
if (err) throw err |
|
||||||
cb(null, getPopupIn(windows)) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getPopupIn (windows) { |
|
||||||
return windows ? windows.find((win) => { |
|
||||||
return (win && win.type === 'popup' && |
|
||||||
win.height === height && |
|
||||||
win.width === width) |
|
||||||
}) : null |
|
||||||
} |
|
||||||
|
|
||||||
function closePopup () { |
|
||||||
getPopup((err, popup) => { |
|
||||||
if (err) throw err |
|
||||||
if (!popup) return |
|
||||||
extension.windows.remove(popup.id, console.error) |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1,36 @@ |
|||||||
|
const version = 12 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration modifies our notices to delete their body after being read. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
newState.NoticeController.noticesList.forEach((notice) => { |
||||||
|
if (notice.read) { |
||||||
|
notice.body = '' |
||||||
|
} |
||||||
|
}) |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
const version = 13 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration modifies the network config from ambiguous 'testnet' to explicit 'ropsten' |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
if (newState.config.provider.type === 'testnet') { |
||||||
|
newState.config.provider.type = 'ropsten' |
||||||
|
} |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
const version = 14 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration removes provider from config and moves it too NetworkController. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
newState.NetworkController = {} |
||||||
|
newState.NetworkController.provider = newState.config.provider |
||||||
|
delete newState.config.provider |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
const extension = require('extensionizer') |
||||||
|
|
||||||
|
class ExtensionPlatform { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () { |
||||||
|
extension.runtime.reload() |
||||||
|
} |
||||||
|
|
||||||
|
openWindow ({ url }) { |
||||||
|
extension.tabs.create({ url }) |
||||||
|
} |
||||||
|
|
||||||
|
getVersion () { |
||||||
|
return extension.runtime.getManifest().version |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = ExtensionPlatform |
@ -0,0 +1,24 @@ |
|||||||
|
|
||||||
|
class SwPlatform { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () { |
||||||
|
// you cant actually do this
|
||||||
|
global.location.reload() |
||||||
|
} |
||||||
|
|
||||||
|
openWindow ({ url }) { |
||||||
|
// this doesnt actually work
|
||||||
|
global.open(url, '_blank') |
||||||
|
} |
||||||
|
|
||||||
|
getVersion () { |
||||||
|
return '<unable to read version>' |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = SwPlatform |
@ -0,0 +1,22 @@ |
|||||||
|
|
||||||
|
class WindowPlatform { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () { |
||||||
|
global.location.reload() |
||||||
|
} |
||||||
|
|
||||||
|
openWindow ({ url }) { |
||||||
|
global.open(url, '_blank') |
||||||
|
} |
||||||
|
|
||||||
|
getVersion () { |
||||||
|
return '<unable to read version>' |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = WindowPlatform |
@ -0,0 +1,70 @@ |
|||||||
|
{ |
||||||
|
"metamask": { |
||||||
|
"isInitialized": true, |
||||||
|
"isUnlocked": true, |
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/", |
||||||
|
"identities": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"name": "Account 1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"unapprovedTxs": {}, |
||||||
|
"noActiveNotices": true, |
||||||
|
"frequentRpcList": [], |
||||||
|
"addressBook": [], |
||||||
|
"network": "3", |
||||||
|
"accounts": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"code": "0x", |
||||||
|
"nonce": "0x0", |
||||||
|
"balance": "0x0", |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"transactions": {}, |
||||||
|
"selectedAddressTxList": [], |
||||||
|
"unapprovedMsgs": {}, |
||||||
|
"unapprovedMsgCount": 0, |
||||||
|
"unapprovedPersonalMsgs": {}, |
||||||
|
"unapprovedPersonalMsgCount": 0, |
||||||
|
"keyringTypes": [ |
||||||
|
"Simple Key Pair", |
||||||
|
"HD Key Tree" |
||||||
|
], |
||||||
|
"keyrings": [ |
||||||
|
{ |
||||||
|
"type": "HD Key Tree", |
||||||
|
"accounts": [ |
||||||
|
"07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"currentCurrency": "USD", |
||||||
|
"conversionRate": 43.35903476, |
||||||
|
"conversionDate": 1490105102, |
||||||
|
"provider": { |
||||||
|
"type": "testnet" |
||||||
|
}, |
||||||
|
"shapeShiftTxList": [], |
||||||
|
"lostAccounts": [], |
||||||
|
"seedWords": null |
||||||
|
}, |
||||||
|
"appState": { |
||||||
|
"menuOpen": false, |
||||||
|
"currentView": { |
||||||
|
"name": "accountDetail", |
||||||
|
"context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
}, |
||||||
|
"accountDetail": { |
||||||
|
"subview": "export", |
||||||
|
"accountExport": "completed", |
||||||
|
"privateKey": "549c9638ad06432568969accacad4a02f8548cc358085938071745138ec134b7" |
||||||
|
}, |
||||||
|
"transForward": true, |
||||||
|
"isLoading": false, |
||||||
|
"warning": null |
||||||
|
}, |
||||||
|
"identities": {} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
{ |
||||||
|
"metamask": { |
||||||
|
"isInitialized": true, |
||||||
|
"isUnlocked": true, |
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/", |
||||||
|
"identities": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"name": "Account 1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"unapprovedTxs": {}, |
||||||
|
"noActiveNotices": true, |
||||||
|
"frequentRpcList": [], |
||||||
|
"addressBook": [], |
||||||
|
"network": "3", |
||||||
|
"accounts": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"code": "0x", |
||||||
|
"nonce": "0x0", |
||||||
|
"balance": "0x0", |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"transactions": {}, |
||||||
|
"selectedAddressTxList": [], |
||||||
|
"unapprovedMsgs": {}, |
||||||
|
"unapprovedMsgCount": 0, |
||||||
|
"unapprovedPersonalMsgs": {}, |
||||||
|
"unapprovedPersonalMsgCount": 0, |
||||||
|
"keyringTypes": [ |
||||||
|
"Simple Key Pair", |
||||||
|
"HD Key Tree" |
||||||
|
], |
||||||
|
"keyrings": [ |
||||||
|
{ |
||||||
|
"type": "HD Key Tree", |
||||||
|
"accounts": [ |
||||||
|
"07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"currentCurrency": "USD", |
||||||
|
"conversionRate": 43.35903476, |
||||||
|
"conversionDate": 1490105102, |
||||||
|
"provider": { |
||||||
|
"type": "testnet" |
||||||
|
}, |
||||||
|
"shapeShiftTxList": [], |
||||||
|
"lostAccounts": [], |
||||||
|
"seedWords": null |
||||||
|
}, |
||||||
|
"appState": { |
||||||
|
"menuOpen": false, |
||||||
|
"currentView": { |
||||||
|
"name": "accountDetail", |
||||||
|
"context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
}, |
||||||
|
"accountDetail": { |
||||||
|
"subview": "export", |
||||||
|
"accountExport": "requested" |
||||||
|
}, |
||||||
|
"transForward": true, |
||||||
|
"isLoading": false, |
||||||
|
"warning": null |
||||||
|
}, |
||||||
|
"identities": {} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
metamascara: |
||||||
|
build: ./ |
||||||
|
restart: always |
||||||
|
ports: |
||||||
|
- "9001" |
||||||
|
environment: |
||||||
|
MASCARA_ORIGIN: "https://zero.metamask.io" |
||||||
|
VIRTUAL_PORT: "9001" |
||||||
|
VIRTUAL_HOST: "zero.metamask.io" |
||||||
|
LETSENCRYPT_HOST: "zero.metamask.io" |
||||||
|
LETSENCRYPT_EMAIL: "admin@metamask.io" |
@ -0,0 +1,14 @@ |
|||||||
|
## Add Custom Build to Chrome |
||||||
|
|
||||||
|
Open `Settings` > `Extensions`. |
||||||
|
|
||||||
|
Check "Developer mode". |
||||||
|
|
||||||
|
At the top, click `Load Unpacked Extension`. |
||||||
|
|
||||||
|
Navigate to your `metamask-plugin/dist/chrome` folder. |
||||||
|
|
||||||
|
Click `Select`. |
||||||
|
|
||||||
|
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console. |
||||||
|
|
@ -0,0 +1,14 @@ |
|||||||
|
# Add Custom Build to Firefox |
||||||
|
|
||||||
|
Go to the url `about:debugging`. |
||||||
|
|
||||||
|
Click the button `Load Temporary Add-On`. |
||||||
|
|
||||||
|
Select the file `dist/firefox/manifest.json`. |
||||||
|
|
||||||
|
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console. |
||||||
|
|
||||||
|
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`. |
||||||
|
|
||||||
|
For longer questions, use the StackOverfow tag `firefox-addons`. |
||||||
|
|
@ -0,0 +1,25 @@ |
|||||||
|
## Adding Custom Networks |
||||||
|
|
||||||
|
To add another network to our dropdown menu, make sure the following files are adjusted properly: |
||||||
|
|
||||||
|
``` |
||||||
|
app/scripts/config.js |
||||||
|
app/scripts/lib/buy-eth-url.js |
||||||
|
app/scripts/lib/config-manager.js |
||||||
|
ui/app/app.js |
||||||
|
ui/app/components/buy-button-subview.js |
||||||
|
ui/app/components/drop-menu-item.js |
||||||
|
ui/app/components/network.js |
||||||
|
ui/app/components/transaction-list-item.js |
||||||
|
ui/app/config.js |
||||||
|
ui/app/css/lib.css |
||||||
|
ui/lib/account-link.js |
||||||
|
ui/lib/explorer-link.js |
||||||
|
``` |
||||||
|
|
||||||
|
You will need: |
||||||
|
+ The network ID |
||||||
|
+ An RPC Endpoint url |
||||||
|
+ An explorer link |
||||||
|
+ CSS for the display icon |
||||||
|
|
@ -0,0 +1,10 @@ |
|||||||
|
### Developing on Dependencies |
||||||
|
|
||||||
|
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: |
||||||
|
|
||||||
|
1. Clone the dependency locally. |
||||||
|
2. `npm install` in its folder. |
||||||
|
3. Run `npm link` in its folder. |
||||||
|
4. Run `npm link $DEP_NAME` in this project folder. |
||||||
|
5. Next time you `npm start` it will watch the dependency for changes as well! |
||||||
|
|
@ -0,0 +1,35 @@ |
|||||||
|
### Generate Development Visualization |
||||||
|
|
||||||
|
This will generate a video of the repo commit history. |
||||||
|
|
||||||
|
Install preqs: |
||||||
|
``` |
||||||
|
brew install gource |
||||||
|
brew install ffmpeg |
||||||
|
``` |
||||||
|
|
||||||
|
From the repo dir, pipe `gource` into `ffmpeg`: |
||||||
|
``` |
||||||
|
gource \ |
||||||
|
--seconds-per-day .1 \ |
||||||
|
--user-scale 1.5 \ |
||||||
|
--default-user-image "./images/icon-512.png" \ |
||||||
|
--viewport 1280x720 \ |
||||||
|
--auto-skip-seconds .1 \ |
||||||
|
--multi-sampling \ |
||||||
|
--stop-at-end \ |
||||||
|
--highlight-users \ |
||||||
|
--hide mouse,progress \ |
||||||
|
--file-idle-time 0 \ |
||||||
|
--max-files 0 \ |
||||||
|
--background-colour 000000 \ |
||||||
|
--font-size 18 \ |
||||||
|
--date-format "%b %d, %Y" \ |
||||||
|
--highlight-dirs \ |
||||||
|
--user-friction 0.1 \ |
||||||
|
--title "MetaMask Development History" \ |
||||||
|
--output-ppm-stream - \ |
||||||
|
--output-framerate 30 \ |
||||||
|
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4 |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,15 @@ |
|||||||
|
## Generating Notices |
||||||
|
|
||||||
|
To add a notice: |
||||||
|
``` |
||||||
|
npm run generateNotice |
||||||
|
``` |
||||||
|
Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window! |
||||||
|
Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made. |
||||||
|
|
||||||
|
To delete a notice: |
||||||
|
``` |
||||||
|
npm run deleteNotice |
||||||
|
``` |
||||||
|
A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
# Publishing Guide |
||||||
|
|
||||||
|
When publishing a new version of MetaMask, we follow this procedure: |
||||||
|
|
||||||
|
## Incrementing Version & Changelog |
||||||
|
|
||||||
|
You must be authorized already on the MetaMask plugin. |
||||||
|
|
||||||
|
1. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`. |
||||||
|
2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). |
||||||
|
|
||||||
|
## Publishing |
||||||
|
|
||||||
|
1. `npm run dist` to generate the latest build. |
||||||
|
2. Publish to chrome store. |
||||||
|
3. Publish to firefox addon marketplace. |
||||||
|
4. Post on Github releases page. |
||||||
|
5. `npm run announce`, post that announcement in our public places. |
||||||
|
|
@ -0,0 +1,6 @@ |
|||||||
|
# Running UI Dev Mode |
||||||
|
|
||||||
|
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI. |
||||||
|
|
||||||
|
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension. |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
### Developing on UI with Mocked Background Process |
||||||
|
|
||||||
|
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations. |
||||||
|
|
||||||
|
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work. |
||||||
|
|
||||||
|
You can reset the mock ui at any time with the `Reset` button at the top of the screen. |
||||||
|
|
@ -1,24 +0,0 @@ |
|||||||
start the dual servers (dapp + mascara) |
|
||||||
``` |
|
||||||
node server.js |
|
||||||
``` |
|
||||||
|
|
||||||
open the example dapp at `http://localhost:9002/` |
|
||||||
|
|
||||||
*You will need to build MetaMask in order for this to work* |
|
||||||
``` |
|
||||||
gulp dev |
|
||||||
``` |
|
||||||
to build MetaMask and have it live reload if you make changes |
|
||||||
|
|
||||||
|
|
||||||
## First time use: |
|
||||||
|
|
||||||
- navigate to: http://127.0.0.1:9001/popup/popup.html |
|
||||||
- Create an Account |
|
||||||
- go back to http://localhost:9002/ |
|
||||||
- open devTools |
|
||||||
- click Sync Tx |
|
||||||
|
|
||||||
### Todos |
|
||||||
- Look into using [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) |
|
@ -1,159 +0,0 @@ |
|||||||
const urlUtil = require('url') |
|
||||||
const extend = require('xtend') |
|
||||||
const Dnode = require('dnode') |
|
||||||
const eos = require('end-of-stream') |
|
||||||
const ParentStream = require('iframe-stream').ParentStream |
|
||||||
const PortStream = require('../app/scripts/lib/port-stream.js') |
|
||||||
const notification = require('../app/scripts/lib/notifications.js') |
|
||||||
const messageManager = require('../app/scripts/lib/message-manager') |
|
||||||
const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex |
|
||||||
const MetamaskController = require('../app/scripts/metamask-controller') |
|
||||||
const extension = require('../app/scripts/lib/extension') |
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config' |
|
||||||
|
|
||||||
|
|
||||||
initializeZeroClient() |
|
||||||
|
|
||||||
function initializeZeroClient() { |
|
||||||
|
|
||||||
const controller = new MetamaskController({ |
|
||||||
// User confirmation callbacks:
|
|
||||||
showUnconfirmedMessage, |
|
||||||
unlockAccountMessage, |
|
||||||
showUnapprovedTx, |
|
||||||
// Persistence Methods:
|
|
||||||
setData, |
|
||||||
loadData, |
|
||||||
}) |
|
||||||
const idStore = controller.idStore |
|
||||||
|
|
||||||
function unlockAccountMessage () { |
|
||||||
console.log('notif stub - unlockAccountMessage') |
|
||||||
} |
|
||||||
|
|
||||||
function showUnconfirmedMessage (msgParams, msgId) { |
|
||||||
console.log('notif stub - showUnconfirmedMessage') |
|
||||||
} |
|
||||||
|
|
||||||
function showUnapprovedTx (txParams, txData, onTxDoneCb) { |
|
||||||
console.log('notif stub - showUnapprovedTx') |
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// connect to other contexts
|
|
||||||
//
|
|
||||||
|
|
||||||
var connectionStream = new ParentStream() |
|
||||||
|
|
||||||
connectRemote(connectionStream, getParentHref()) |
|
||||||
|
|
||||||
function connectRemote (connectionStream, originDomain) { |
|
||||||
var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001') |
|
||||||
if (isMetaMaskInternalProcess) { |
|
||||||
// communication with popup
|
|
||||||
setupTrustedCommunication(connectionStream, 'MetaMask') |
|
||||||
} else { |
|
||||||
// communication with page
|
|
||||||
setupUntrustedCommunication(connectionStream, originDomain) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function setupUntrustedCommunication (connectionStream, originDomain) { |
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream) |
|
||||||
// connect features
|
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
|
||||||
controller.setupPublicConfig(mx.createStream('publicConfig')) |
|
||||||
} |
|
||||||
|
|
||||||
function setupTrustedCommunication (connectionStream, originDomain) { |
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream) |
|
||||||
// connect features
|
|
||||||
setupControllerConnection(mx.createStream('controller')) |
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// remote features
|
|
||||||
//
|
|
||||||
|
|
||||||
function setupControllerConnection (stream) { |
|
||||||
controller.stream = stream |
|
||||||
var api = controller.getApi() |
|
||||||
var dnode = Dnode(api) |
|
||||||
stream.pipe(dnode).pipe(stream) |
|
||||||
dnode.on('remote', (remote) => { |
|
||||||
// push updates to popup
|
|
||||||
controller.ethStore.on('update', controller.sendUpdate.bind(controller)) |
|
||||||
controller.listeners.push(remote) |
|
||||||
idStore.on('update', controller.sendUpdate.bind(controller)) |
|
||||||
|
|
||||||
// teardown on disconnect
|
|
||||||
eos(stream, () => { |
|
||||||
controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller)) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
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) |
|
||||||
} |
|
||||||
|
|
||||||
function getParentHref(){ |
|
||||||
try { |
|
||||||
var parentLocation = window.parent.location |
|
||||||
return parentLocation.hostname + ':' + parentLocation.port |
|
||||||
} catch (err) { |
|
||||||
return 'unknown' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
const Web3 = require('web3') |
|
||||||
const setupProvider = require('./lib/setup-provider.js') |
|
||||||
|
|
||||||
//
|
|
||||||
// setup web3
|
|
||||||
//
|
|
||||||
var provider = setupProvider() |
|
||||||
hijackProvider(provider) |
|
||||||
var web3 = new Web3(provider) |
|
||||||
web3.setProvider = function(){ |
|
||||||
console.log('MetaMask - overrode web3.setProvider') |
|
||||||
} |
|
||||||
console.log('metamask lib hijacked provider') |
|
||||||
|
|
||||||
//
|
|
||||||
// export web3
|
|
||||||
//
|
|
||||||
|
|
||||||
global.web3 = web3 |
|
||||||
|
|
||||||
//
|
|
||||||
// ui stuff
|
|
||||||
//
|
|
||||||
|
|
||||||
var shouldPop = false |
|
||||||
window.addEventListener('click', function(){ |
|
||||||
if (!shouldPop) return |
|
||||||
shouldPop = false |
|
||||||
window.open('http://127.0.0.1:9001/popup/popup.html', '', 'width=360 height=500') |
|
||||||
console.log('opening window...') |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
function hijackProvider(provider){ |
|
||||||
var _super = provider.sendAsync.bind(provider) |
|
||||||
provider.sendAsync = function(payload, cb){ |
|
||||||
if (payload.method === 'eth_sendTransaction') { |
|
||||||
console.log('saw send') |
|
||||||
shouldPop = true |
|
||||||
} |
|
||||||
_super(payload, cb) |
|
||||||
} |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
const injectCss = require('inject-css') |
|
||||||
const MetaMaskUiCss = require('../ui/css') |
|
||||||
const startPopup = require('../app/scripts/popup-core') |
|
||||||
const setupIframe = require('./lib/setup-iframe.js') |
|
||||||
|
|
||||||
|
|
||||||
var css = MetaMaskUiCss() |
|
||||||
injectCss(css) |
|
||||||
|
|
||||||
var name = 'popup' |
|
||||||
window.METAMASK_UI_TYPE = name |
|
||||||
|
|
||||||
var iframeStream = setupIframe({ |
|
||||||
zeroClientProvider: 'http://127.0.0.1:9001', |
|
||||||
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], |
|
||||||
container: document.body, |
|
||||||
}) |
|
||||||
|
|
||||||
startPopup(iframeStream) |
|
@ -1,96 +0,0 @@ |
|||||||
const express = require('express') |
|
||||||
const browserify = require('browserify') |
|
||||||
const watchify = require('watchify') |
|
||||||
const babelify = require('babelify') |
|
||||||
|
|
||||||
const zeroBundle = createBundle('./index.js') |
|
||||||
const controllerBundle = createBundle('./controller.js') |
|
||||||
const popupBundle = createBundle('./popup.js') |
|
||||||
const appBundle = createBundle('./example/index.js') |
|
||||||
|
|
||||||
//
|
|
||||||
// Iframe Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const iframeServer = express() |
|
||||||
|
|
||||||
// serve popup window
|
|
||||||
iframeServer.get('/popup/scripts/popup.js', function(req, res){ |
|
||||||
res.send(popupBundle.latest) |
|
||||||
}) |
|
||||||
iframeServer.use('/popup', express.static('../dist/chrome')) |
|
||||||
|
|
||||||
// serve controller bundle
|
|
||||||
iframeServer.get('/controller.js', function(req, res){ |
|
||||||
res.send(controllerBundle.latest) |
|
||||||
}) |
|
||||||
|
|
||||||
// serve background controller
|
|
||||||
iframeServer.use(express.static('./server')) |
|
||||||
|
|
||||||
// start the server
|
|
||||||
const mascaraPort = 9001 |
|
||||||
iframeServer.listen(mascaraPort) |
|
||||||
console.log(`Mascara service listening on port ${mascaraPort}`) |
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Dapp Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const dappServer = express() |
|
||||||
|
|
||||||
// serve metamask-lib bundle
|
|
||||||
dappServer.get('/zero.js', function(req, res){ |
|
||||||
res.send(zeroBundle.latest) |
|
||||||
}) |
|
||||||
|
|
||||||
// serve dapp bundle
|
|
||||||
dappServer.get('/app.js', function(req, res){ |
|
||||||
res.send(appBundle.latest) |
|
||||||
}) |
|
||||||
|
|
||||||
// serve static
|
|
||||||
dappServer.use(express.static('./example')) |
|
||||||
|
|
||||||
// start the server
|
|
||||||
const dappPort = '9002' |
|
||||||
dappServer.listen(dappPort) |
|
||||||
console.log(`Dapp listening on port ${dappPort}`) |
|
||||||
|
|
||||||
//
|
|
||||||
// util
|
|
||||||
//
|
|
||||||
|
|
||||||
function serveBundle(entryPoint){ |
|
||||||
const bundle = createBundle(entryPoint) |
|
||||||
return function(req, res){ |
|
||||||
res.send(bundle.latest) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function createBundle(entryPoint){ |
|
||||||
|
|
||||||
var bundleContainer = {} |
|
||||||
|
|
||||||
var bundler = browserify({ |
|
||||||
entries: [entryPoint], |
|
||||||
cache: {}, |
|
||||||
packageCache: {}, |
|
||||||
plugin: [watchify], |
|
||||||
}) |
|
||||||
|
|
||||||
bundler.on('update', bundle) |
|
||||||
bundle() |
|
||||||
|
|
||||||
return bundleContainer |
|
||||||
|
|
||||||
function bundle() { |
|
||||||
bundler.bundle(function(err, result){ |
|
||||||
if (err) throw err |
|
||||||
console.log(`Bundle updated! (${entryPoint})`) |
|
||||||
bundleContainer.latest = result.toString() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,33 @@ |
|||||||
|
start the dual servers (dapp + mascara) |
||||||
|
``` |
||||||
|
npm run mascara |
||||||
|
``` |
||||||
|
|
||||||
|
### First time use: |
||||||
|
|
||||||
|
- navigate to: http://localhost:9001 |
||||||
|
- Create an Account |
||||||
|
- go back to http://localhost:9002 |
||||||
|
- open devTools |
||||||
|
- click Sync Tx |
||||||
|
|
||||||
|
### Tests: |
||||||
|
|
||||||
|
``` |
||||||
|
npm run testMascara |
||||||
|
``` |
||||||
|
|
||||||
|
Test will run in browser, you will have to have these browsers installed: |
||||||
|
|
||||||
|
- Chrome |
||||||
|
- Firefox |
||||||
|
- Opera |
||||||
|
|
||||||
|
|
||||||
|
### Deploy: |
||||||
|
|
||||||
|
Will build and deploy mascara via docker |
||||||
|
|
||||||
|
``` |
||||||
|
docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f |
||||||
|
``` |
@ -0,0 +1,31 @@ |
|||||||
|
const express = require('express') |
||||||
|
const createMetamascaraServer = require('../server/') |
||||||
|
const createBundle = require('../server/util').createBundle |
||||||
|
const serveBundle = require('../server/util').serveBundle |
||||||
|
|
||||||
|
//
|
||||||
|
// Iframe Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const mascaraServer = createMetamascaraServer() |
||||||
|
|
||||||
|
// start the server
|
||||||
|
const mascaraPort = 9001 |
||||||
|
mascaraServer.listen(mascaraPort) |
||||||
|
console.log(`Mascara service listening on port ${mascaraPort}`) |
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Dapp Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const dappServer = express() |
||||||
|
|
||||||
|
// serve dapp bundle
|
||||||
|
serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js'))) |
||||||
|
dappServer.use(express.static(__dirname + '/app/')) |
||||||
|
|
||||||
|
// start the server
|
||||||
|
const dappPort = '9002' |
||||||
|
dappServer.listen(dappPort) |
||||||
|
console.log(`Dapp listening on port ${dappPort}`) |
@ -0,0 +1,32 @@ |
|||||||
|
const express = require('express') |
||||||
|
const createBundle = require('./util').createBundle |
||||||
|
const serveBundle = require('./util').serveBundle |
||||||
|
|
||||||
|
module.exports = createMetamascaraServer |
||||||
|
|
||||||
|
|
||||||
|
function createMetamascaraServer(){ |
||||||
|
|
||||||
|
// start bundlers
|
||||||
|
const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js') |
||||||
|
const proxyBundle = createBundle(__dirname + '/../src/proxy.js') |
||||||
|
const uiBundle = createBundle(__dirname + '/../src/ui.js') |
||||||
|
const backgroundBuild = createBundle(__dirname + '/../src/background.js') |
||||||
|
|
||||||
|
// serve bundles
|
||||||
|
const server = express() |
||||||
|
// ui window
|
||||||
|
serveBundle(server, '/ui.js', uiBundle) |
||||||
|
server.use(express.static(__dirname+'/../ui/')) |
||||||
|
server.use(express.static(__dirname+'/../../dist/chrome')) |
||||||
|
// metamascara
|
||||||
|
serveBundle(server, '/metamascara.js', metamascaraBundle) |
||||||
|
// proxy
|
||||||
|
serveBundle(server, '/proxy/proxy.js', proxyBundle) |
||||||
|
server.use('/proxy/', express.static(__dirname+'/../proxy')) |
||||||
|
// background
|
||||||
|
serveBundle(server, '/background.js', backgroundBuild) |
||||||
|
|
||||||
|
return server |
||||||
|
|
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
const browserify = require('browserify') |
||||||
|
const watchify = require('watchify') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
serveBundle, |
||||||
|
createBundle, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function serveBundle(server, path, bundle){ |
||||||
|
server.get(path, function(req, res){ |
||||||
|
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') |
||||||
|
res.send(bundle.latest) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function createBundle(entryPoint){ |
||||||
|
|
||||||
|
var bundleContainer = {} |
||||||
|
|
||||||
|
var bundler = browserify({ |
||||||
|
entries: [entryPoint], |
||||||
|
cache: {}, |
||||||
|
packageCache: {}, |
||||||
|
plugin: [watchify], |
||||||
|
}) |
||||||
|
|
||||||
|
bundler.on('update', bundle) |
||||||
|
bundle() |
||||||
|
|
||||||
|
return bundleContainer |
||||||
|
|
||||||
|
function bundle() { |
||||||
|
bundler.bundle(function(err, result){ |
||||||
|
if (err) { |
||||||
|
console.log(`Bundle failed! (${entryPoint})`) |
||||||
|
console.error(err) |
||||||
|
return |
||||||
|
} |
||||||
|
console.log(`Bundle updated! (${entryPoint})`) |
||||||
|
bundleContainer.latest = result.toString() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
global.window = global |
||||||
|
const self = global |
||||||
|
const pipe = require('pump') |
||||||
|
|
||||||
|
const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') |
||||||
|
const connectionListener = new SwGlobalListener(self) |
||||||
|
const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex |
||||||
|
const PortStream = require('../../app/scripts/lib/port-stream.js') |
||||||
|
|
||||||
|
const DbController = require('idb-global') |
||||||
|
|
||||||
|
const SwPlatform = require('../../app/scripts/platforms/sw') |
||||||
|
const MetamaskController = require('../../app/scripts/metamask-controller') |
||||||
|
const extension = {} //require('../../app/scripts/lib/extension')
|
||||||
|
|
||||||
|
const storeTransform = require('obs-store/lib/transform') |
||||||
|
const Migrator = require('../../app/scripts/lib/migrator/') |
||||||
|
const migrations = require('../../app/scripts/migrations/') |
||||||
|
const firstTimeState = require('../../app/scripts/first-time-state') |
||||||
|
|
||||||
|
const STORAGE_KEY = 'metamask-config' |
||||||
|
// const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||||
|
const METAMASK_DEBUG = true |
||||||
|
let popupIsOpen = false |
||||||
|
let connectedClientCount = 0 |
||||||
|
|
||||||
|
const log = require('loglevel') |
||||||
|
global.log = log |
||||||
|
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') |
||||||
|
|
||||||
|
self.addEventListener('install', function(event) { |
||||||
|
event.waitUntil(self.skipWaiting()) |
||||||
|
}) |
||||||
|
self.addEventListener('activate', function(event) { |
||||||
|
event.waitUntil(self.clients.claim()) |
||||||
|
}) |
||||||
|
|
||||||
|
console.log('inside:open') |
||||||
|
|
||||||
|
|
||||||
|
// // state persistence
|
||||||
|
let diskStore |
||||||
|
const dbController = new DbController({ |
||||||
|
key: STORAGE_KEY, |
||||||
|
}) |
||||||
|
loadStateFromPersistence() |
||||||
|
.then((initState) => setupController(initState)) |
||||||
|
.then(() => console.log('MetaMask initialization complete.')) |
||||||
|
.catch((err) => console.error('WHILE SETTING UP:', err)) |
||||||
|
|
||||||
|
// initialization flow
|
||||||
|
|
||||||
|
//
|
||||||
|
// State and Persistence
|
||||||
|
//
|
||||||
|
function loadStateFromPersistence() { |
||||||
|
// migrations
|
||||||
|
let migrator = new Migrator({ migrations }) |
||||||
|
const initialState = migrator.generateInitialState(firstTimeState) |
||||||
|
dbController.initialState = initialState |
||||||
|
return dbController.open() |
||||||
|
.then((versionedData) => migrator.migrateData(versionedData)) |
||||||
|
.then((versionedData) => { |
||||||
|
dbController.put(versionedData) |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}) |
||||||
|
.then((versionedData) => Promise.resolve(versionedData.data)) |
||||||
|
} |
||||||
|
|
||||||
|
function setupController (initState, client) { |
||||||
|
|
||||||
|
//
|
||||||
|
// MetaMask Controller
|
||||||
|
//
|
||||||
|
|
||||||
|
const platform = new SwPlatform() |
||||||
|
|
||||||
|
const controller = new MetamaskController({ |
||||||
|
// platform specific implementation
|
||||||
|
platform, |
||||||
|
// User confirmation callbacks:
|
||||||
|
showUnconfirmedMessage: noop, |
||||||
|
unlockAccountMessage: noop, |
||||||
|
showUnapprovedTx: noop, |
||||||
|
// initial state
|
||||||
|
initState, |
||||||
|
}) |
||||||
|
global.metamaskController = controller |
||||||
|
|
||||||
|
controller.store.subscribe((state) => { |
||||||
|
versionifyData(state) |
||||||
|
.then((versionedData) => dbController.put(versionedData)) |
||||||
|
.catch((err) => {console.error(err)}) |
||||||
|
}) |
||||||
|
function versionifyData(state) { |
||||||
|
return dbController.get() |
||||||
|
.then((rawData) => { |
||||||
|
return Promise.resolve({ |
||||||
|
data: state, |
||||||
|
meta: rawData.meta, |
||||||
|
})} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// connect to other contexts
|
||||||
|
//
|
||||||
|
|
||||||
|
connectionListener.on('remote', (portStream, messageEvent) => { |
||||||
|
console.log('REMOTE CONECTION FOUND***********') |
||||||
|
connectedClientCount += 1 |
||||||
|
connectRemote(portStream, messageEvent.data.context) |
||||||
|
}) |
||||||
|
|
||||||
|
function connectRemote (connectionStream, context) { |
||||||
|
var isMetaMaskInternalProcess = (context === 'popup') |
||||||
|
if (isMetaMaskInternalProcess) { |
||||||
|
// communication with popup
|
||||||
|
controller.setupTrustedCommunication(connectionStream, 'MetaMask') |
||||||
|
popupIsOpen = true |
||||||
|
} else { |
||||||
|
// communication with page
|
||||||
|
setupUntrustedCommunication(connectionStream, context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function setupUntrustedCommunication (connectionStream, originDomain) { |
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream) |
||||||
|
// connect features
|
||||||
|
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
||||||
|
controller.setupPublicConfig(mx.createStream('publicConfig')) |
||||||
|
} |
||||||
|
|
||||||
|
function setupTrustedCommunication (connectionStream, originDomain) { |
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream) |
||||||
|
// connect features
|
||||||
|
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
||||||
|
} |
||||||
|
//
|
||||||
|
// User Interface setup
|
||||||
|
//
|
||||||
|
return Promise.resolve() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function sendMessageToAllClients (message) { |
||||||
|
self.clients.matchAll().then(function(clients) { |
||||||
|
clients.forEach(function(client) { |
||||||
|
client.postMessage(message) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
function noop () {} |
@ -0,0 +1,47 @@ |
|||||||
|
const Web3 = require('web3') |
||||||
|
const setupProvider = require('./lib/setup-provider.js') |
||||||
|
const setupDappAutoReload = require('../../app/scripts/lib/auto-reload.js') |
||||||
|
const MASCARA_ORIGIN = process.env.MASCARA_ORIGIN || 'http://localhost:9001' |
||||||
|
console.log('MASCARA_ORIGIN:', MASCARA_ORIGIN) |
||||||
|
|
||||||
|
//
|
||||||
|
// setup web3
|
||||||
|
//
|
||||||
|
|
||||||
|
const provider = setupProvider({ |
||||||
|
mascaraUrl: MASCARA_ORIGIN + '/proxy/', |
||||||
|
}) |
||||||
|
instrumentForUserInteractionTriggers(provider) |
||||||
|
|
||||||
|
const web3 = new Web3(provider) |
||||||
|
setupDappAutoReload(web3, provider.publicConfigStore) |
||||||
|
//
|
||||||
|
// ui stuff
|
||||||
|
//
|
||||||
|
|
||||||
|
let shouldPop = false |
||||||
|
window.addEventListener('click', maybeTriggerPopup) |
||||||
|
|
||||||
|
//
|
||||||
|
// util
|
||||||
|
//
|
||||||
|
|
||||||
|
function maybeTriggerPopup(){ |
||||||
|
if (!shouldPop) return |
||||||
|
shouldPop = false |
||||||
|
window.open(MASCARA_ORIGIN, '', 'width=360 height=500') |
||||||
|
console.log('opening window...') |
||||||
|
} |
||||||
|
|
||||||
|
function instrumentForUserInteractionTriggers(provider){ |
||||||
|
const _super = provider.sendAsync.bind(provider) |
||||||
|
provider.sendAsync = function(payload, cb){ |
||||||
|
if (payload.method === 'eth_sendTransaction') { |
||||||
|
console.log('saw send') |
||||||
|
shouldPop = true |
||||||
|
} |
||||||
|
_super(payload, cb) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@ |
|||||||
|
const ParentStream = require('iframe-stream').ParentStream |
||||||
|
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') |
||||||
|
const SwStream = require('sw-stream/lib/sw-stream.js') |
||||||
|
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') |
||||||
|
|
||||||
|
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 |
||||||
|
const background = new SWcontroller({ |
||||||
|
fileName: '/background.js', |
||||||
|
letBeIdle: false, |
||||||
|
wakeUpInterval: 30000, |
||||||
|
intervalDelay, |
||||||
|
}) |
||||||
|
|
||||||
|
const pageStream = new ParentStream() |
||||||
|
background.on('ready', (_) => { |
||||||
|
let swStream = SwStream({ |
||||||
|
serviceWorker: background.controller, |
||||||
|
context: 'dapp', |
||||||
|
}) |
||||||
|
pageStream.pipe(swStream).pipe(pageStream) |
||||||
|
|
||||||
|
}) |
||||||
|
background.on('updatefound', () => window.location.reload()) |
||||||
|
|
||||||
|
background.on('error', console.error) |
||||||
|
background.startWorker() |
@ -0,0 +1,56 @@ |
|||||||
|
const injectCss = require('inject-css') |
||||||
|
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') |
||||||
|
const SwStream = require('sw-stream/lib/sw-stream.js') |
||||||
|
const MetaMaskUiCss = require('../../ui/css') |
||||||
|
const setupIframe = require('./lib/setup-iframe.js') |
||||||
|
const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') |
||||||
|
const MetamascaraPlatform = require('../../app/scripts/platforms/window') |
||||||
|
const startPopup = require('../../app/scripts/popup-core') |
||||||
|
|
||||||
|
// create platform global
|
||||||
|
global.platform = new MetamascaraPlatform() |
||||||
|
|
||||||
|
|
||||||
|
var css = MetaMaskUiCss() |
||||||
|
injectCss(css) |
||||||
|
const container = document.getElementById('app-content') |
||||||
|
|
||||||
|
var name = 'popup' |
||||||
|
window.METAMASK_UI_TYPE = name |
||||||
|
|
||||||
|
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 |
||||||
|
|
||||||
|
const background = new SWcontroller({ |
||||||
|
fileName: '/background.js', |
||||||
|
letBeIdle: false, |
||||||
|
intervalDelay, |
||||||
|
wakeUpInterval: 20000 |
||||||
|
}) |
||||||
|
// Setup listener for when the service worker is read
|
||||||
|
const connectApp = function (readSw) { |
||||||
|
let connectionStream = SwStream({ |
||||||
|
serviceWorker: background.controller, |
||||||
|
context: name, |
||||||
|
}) |
||||||
|
startPopup({container, connectionStream}, (err, store) => { |
||||||
|
if (err) return displayCriticalError(err) |
||||||
|
store.subscribe(() => { |
||||||
|
const state = store.getState() |
||||||
|
if (state.appState.shouldClose) window.close() |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
background.on('ready', (sw) => { |
||||||
|
background.removeListener('updatefound', connectApp) |
||||||
|
connectApp(sw) |
||||||
|
}) |
||||||
|
background.on('updatefound', () => window.location.reload()) |
||||||
|
|
||||||
|
background.startWorker() |
||||||
|
.then(() => { |
||||||
|
setTimeout(() => { |
||||||
|
const appContent = document.getElementById(`app-content`) |
||||||
|
if (!appContent.children.length) window.location.reload() |
||||||
|
}, 2000) |
||||||
|
}) |
||||||
|
console.log('hello from MetaMascara ui!') |
@ -0,0 +1,7 @@ |
|||||||
|
function wait(time) { |
||||||
|
return new Promise(function(resolve, reject) { |
||||||
|
setTimeout(function() { |
||||||
|
resolve() |
||||||
|
}, time * 3 || 1500) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta name="viewport" content="width=device-width"> |
||||||
|
<title>QUnit Example</title> |
||||||
|
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.0.css"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="qunit"></div> |
||||||
|
<div id="qunit-fixture"></div> |
||||||
|
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script> |
||||||
|
<script src="./jquery-3.1.0.min.js"></script> |
||||||
|
<script src="./helpers.js"></script> |
||||||
|
<script src="./test-bundle.js"></script> |
||||||
|
<script src="/testem.js"></script> |
||||||
|
|
||||||
|
<div id="app-content"></div> |
||||||
|
<script src="./bundle.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,22 @@ |
|||||||
|
var fs = require('fs') |
||||||
|
var path = require('path') |
||||||
|
var browserify = require('browserify'); |
||||||
|
var tests = fs.readdirSync(path.join(__dirname, 'lib')) |
||||||
|
var bundlePath = path.join(__dirname, 'test-bundle.js') |
||||||
|
var b = browserify(); |
||||||
|
|
||||||
|
// Remove old bundle
|
||||||
|
try { |
||||||
|
fs.unlinkSync(bundlePath) |
||||||
|
} catch (e) { |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
|
||||||
|
var writeStream = fs.createWriteStream(bundlePath) |
||||||
|
|
||||||
|
tests.forEach(function(fileName) { |
||||||
|
b.add(path.join(__dirname, 'lib', fileName)) |
||||||
|
}) |
||||||
|
|
||||||
|
b.bundle().pipe(writeStream); |
||||||
|
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue