mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge branch 'master' of github.com:MetaMask/metamask-plugin into library
This commit is contained in:
commit
850b6d1440
30
CHANGELOG.md
30
CHANGELOG.md
@ -2,11 +2,37 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- Fix bug where pending transactions from Test net (or other networks) show up In Main net.
|
||||
- Add fiat conversion values to more views.
|
||||
- On fresh install, open a new tab with the MetaMask Introduction video. Does not open on update.
|
||||
- Block negative values from transactions.
|
||||
- Fixed a memory leak.
|
||||
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
|
||||
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experience slow unlocks.
|
||||
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
|
||||
|
||||
## 2.10.2 2016-09-02
|
||||
|
||||
- Fix bug where notification popup would not display.
|
||||
|
||||
## 2.10.1 2016-09-02
|
||||
|
||||
- Fix bug where provider menu did not allow switching to custom network from a custom network.
|
||||
- Sending a transaction from within MetaMask no longer triggers a popup.
|
||||
- The ability to build without livereload features (such as for production) can be enabled with the gulp --disableLiveReload flag.
|
||||
- Fix Ethereum JSON RPC Filters bug.
|
||||
|
||||
## 2.10.0 2016-08-29
|
||||
|
||||
- Changed transaction approval from notifications system to popup system.
|
||||
- Add a back button to locked screen to allow restoring vault from seed words when password is forgotten.
|
||||
- Forms now retain their values even when closing the popup and reopening it.
|
||||
- Fixed a spelling error in provider menu.
|
||||
|
||||
## 2.9.2 2016-08-24
|
||||
|
||||
- Fixed shortcut bug from preventing installation.
|
||||
|
||||
|
||||
## 2.9.1 2016-08-24
|
||||
|
||||
- Added static image as fallback for when WebGL isn't supported.
|
||||
@ -27,7 +53,7 @@
|
||||
## 2.8.0 2016-08-15
|
||||
|
||||
- Integrate ShapeShift
|
||||
- Add a for for Coinbase to specify amount to buy
|
||||
- Add a form for Coinbase to specify amount to buy
|
||||
- Fix various typos.
|
||||
- Make dapp-metamask connection more reliable
|
||||
- Remove Ethereum Classic from provider menu.
|
||||
|
@ -1,5 +1,12 @@
|
||||
# MetaMask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-plugin.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-plugin)
|
||||
|
||||
## Developing Compatible Dapps
|
||||
|
||||
If you're a web dapp developer, we've got two types of guides for you:
|
||||
|
||||
- If you've never built a Dapp before, we've got a gentle introduction on [Developing Dapps with Truffle and MetaMask](https://blog.metamask.io/developing-for-metamask-with-truffle/).
|
||||
- If you have a Dapp, and you want to ensure compatibility, [here is our guide on building MetaMask-compatible Dapps](https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md)
|
||||
|
||||
## Building locally
|
||||
|
||||
- Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later.
|
||||
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "2.9.2",
|
||||
"version": "2.10.2",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
"commands": {
|
||||
"_execute_browser_action": {
|
||||
@ -28,7 +29,8 @@
|
||||
"scripts": [
|
||||
"scripts/chromereload.js",
|
||||
"scripts/background.js"
|
||||
]
|
||||
],
|
||||
"persistent": true
|
||||
},
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
|
16
app/notification.html
Normal file
16
app/notification.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MetaMask Notification</title>
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
@ -3,9 +3,7 @@ const extend = require('xtend')
|
||||
const Dnode = require('dnode')
|
||||
const eos = require('end-of-stream')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const createUnlockRequestNotification = require('./lib/notifications.js').createUnlockRequestNotification
|
||||
const createTxNotification = require('./lib/notifications.js').createTxNotification
|
||||
const createMsgNotification = require('./lib/notifications.js').createMsgNotification
|
||||
const notification = require('./lib/notifications.js')
|
||||
const messageManager = require('./lib/message-manager')
|
||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||
const MetamaskController = require('./metamask-controller')
|
||||
@ -26,50 +24,32 @@ const controller = new MetamaskController({
|
||||
const idStore = controller.idStore
|
||||
|
||||
function unlockAccountMessage () {
|
||||
createUnlockRequestNotification({
|
||||
title: 'Account Unlock Request',
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
function showUnconfirmedMessage (msgParams, msgId) {
|
||||
var controllerState = controller.getState()
|
||||
|
||||
createMsgNotification({
|
||||
imageifyIdenticons: false,
|
||||
txData: {
|
||||
msgParams: msgParams,
|
||||
time: (new Date()).getTime(),
|
||||
},
|
||||
identities: controllerState.identities,
|
||||
accounts: controllerState.accounts,
|
||||
onConfirm: idStore.approveMessage.bind(idStore, msgId, noop),
|
||||
onCancel: idStore.cancelMessage.bind(idStore, msgId),
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
|
||||
var controllerState = controller.getState()
|
||||
|
||||
createTxNotification({
|
||||
imageifyIdenticons: false,
|
||||
txData: {
|
||||
txParams: txParams,
|
||||
time: (new Date()).getTime(),
|
||||
},
|
||||
identities: controllerState.identities,
|
||||
accounts: controllerState.accounts,
|
||||
onConfirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
|
||||
onCancel: idStore.cancelTransaction.bind(idStore, txData.id),
|
||||
})
|
||||
notification.show()
|
||||
}
|
||||
|
||||
// On first install, open a window to MetaMask website to how-it-works.
|
||||
|
||||
extension.runtime.onInstalled.addListener(function (details) {
|
||||
if (details.reason === 'install') {
|
||||
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
// connect to other contexts
|
||||
//
|
||||
|
||||
extension.runtime.onConnect.addListener(connectRemote)
|
||||
function connectRemote (remotePort) {
|
||||
var isMetaMaskInternalProcess = (remotePort.name === 'popup')
|
||||
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
||||
var portStream = new PortStream(remotePort)
|
||||
if (isMetaMaskInternalProcess) {
|
||||
// communication with popup
|
||||
@ -109,7 +89,7 @@ function setupControllerConnection (stream) {
|
||||
dnode.on('remote', (remote) => {
|
||||
// push updates to popup
|
||||
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
|
||||
controller.remote = remote
|
||||
controller.listeners.push(remote)
|
||||
idStore.on('update', controller.sendUpdate.bind(controller))
|
||||
|
||||
// teardown on disconnect
|
||||
@ -188,5 +168,3 @@ function getOldStyleData () {
|
||||
function setData (data) {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
||||
}
|
||||
|
||||
function noop () {}
|
||||
|
@ -324,13 +324,13 @@ window.LiveReloadOptions = { host: 'localhost' };
|
||||
this.pluginIdentifiers = {}
|
||||
this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.location.href.match(/LR-verbose/) ? this.window.console : {
|
||||
log: function () {},
|
||||
error: this.window.console.error.bind(this.window.console),
|
||||
error: console.error,
|
||||
} : {
|
||||
log: function () {},
|
||||
error: function () {},
|
||||
}
|
||||
if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
|
||||
this.console.error('LiveReload disabled because the browser does not seem to support web sockets')
|
||||
console.error('LiveReload disabled because the browser does not seem to support web sockets')
|
||||
return
|
||||
}
|
||||
if ('LiveReloadOptions' in window) {
|
||||
@ -344,7 +344,7 @@ window.LiveReloadOptions = { host: 'localhost' };
|
||||
} else {
|
||||
this.options = Options.extract(this.window.document)
|
||||
if (!this.options) {
|
||||
this.console.error('LiveReload disabled because it could not find its own <SCRIPT> tag')
|
||||
console.error('LiveReload disabled because it could not find its own <SCRIPT> tag')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/'
|
||||
const TESTNET_RPC_URL = 'https://morden.infura.io/'
|
||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
||||
const TESTNET_RPC_URL = 'https://morden.infura.io/metamask'
|
||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL
|
||||
|
||||
global.METAMASK_DEBUG = false
|
||||
|
@ -43,20 +43,20 @@ function setupStreams(){
|
||||
name: 'contentscript',
|
||||
target: 'inpage',
|
||||
})
|
||||
pageStream.on('error', console.error.bind(console))
|
||||
pageStream.on('error', console.error)
|
||||
var pluginPort = extension.runtime.connect({name: 'contentscript'})
|
||||
var pluginStream = new PortStream(pluginPort)
|
||||
pluginStream.on('error', console.error.bind(console))
|
||||
pluginStream.on('error', console.error)
|
||||
|
||||
// forward communication plugin->inpage
|
||||
pageStream.pipe(pluginStream).pipe(pageStream)
|
||||
|
||||
// connect contentscript->inpage reload stream
|
||||
var mx = ObjectMultiplex()
|
||||
mx.on('error', console.error.bind(console))
|
||||
mx.on('error', console.error)
|
||||
mx.pipe(pageStream)
|
||||
var reloadStream = mx.createStream('reload')
|
||||
reloadStream.on('error', console.error.bind(console))
|
||||
reloadStream.on('error', console.error)
|
||||
|
||||
// if we lose connection with the plugin, trigger tab refresh
|
||||
pluginStream.on('close', function () {
|
||||
|
@ -41,11 +41,28 @@ function Extension () {
|
||||
}
|
||||
} 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
|
||||
|
@ -45,7 +45,11 @@ function IdentityStore (opts = {}) {
|
||||
|
||||
IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
|
||||
delete this._keyStore
|
||||
var serializedKeystore = this.configManager.getWallet()
|
||||
|
||||
if (serializedKeystore) {
|
||||
this.configManager.setData({})
|
||||
}
|
||||
this._createIdmgmt(password, null, entropy, (err) => {
|
||||
if (err) return cb(err)
|
||||
|
||||
@ -437,6 +441,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)
|
||||
@ -478,7 +483,7 @@ IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey)
|
||||
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
|
||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
||||
|
||||
keyStore.generateNewAddress(derivedKey, 3)
|
||||
keyStore.generateNewAddress(derivedKey, 1)
|
||||
configManager.setWallet(keyStore.serialize())
|
||||
if (global.METAMASK_DEBUG) {
|
||||
console.log('restored from seed. saved to keystore')
|
||||
|
@ -33,15 +33,29 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
})
|
||||
asyncProvider.on('error', console.error.bind(console))
|
||||
self.asyncProvider = asyncProvider
|
||||
|
||||
self.idMap = {}
|
||||
// handle sendAsync requests via asyncProvider
|
||||
self.sendAsync = function(payload, cb){
|
||||
// rewrite request ids
|
||||
var request = jsonrpcMessageTransform(payload, (message) => {
|
||||
message.id = createRandomId()
|
||||
var request = eachJsonMessage(payload, (message) => {
|
||||
var newId = createRandomId()
|
||||
self.idMap[newId] = message.id
|
||||
message.id = newId
|
||||
return message
|
||||
})
|
||||
// forward to asyncProvider
|
||||
asyncProvider.sendAsync(request, cb)
|
||||
asyncProvider.sendAsync(request, function(err, res){
|
||||
if (err) return cb(err)
|
||||
// transform messages to original ids
|
||||
eachJsonMessage(res, (message) => {
|
||||
var oldId = self.idMap[message.id]
|
||||
delete self.idMap[message.id]
|
||||
message.id = oldId
|
||||
return message
|
||||
})
|
||||
cb(null, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +80,8 @@ MetamaskInpageProvider.prototype.send = function (payload) {
|
||||
|
||||
// throw not-supported Error
|
||||
default:
|
||||
var message = 'The MetaMask Web3 object does not support synchronous methods. See https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#all-async---think-of-metamask-as-a-light-client for details.'
|
||||
var message = 'The MetaMask Web3 object does not support synchronous methods like ' + payload.method +
|
||||
'. See https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#all-async---think-of-metamask-as-a-light-client for details.'
|
||||
throw new Error(message)
|
||||
|
||||
}
|
||||
@ -111,10 +126,10 @@ function createRandomId(){
|
||||
return datePart + extraPart
|
||||
}
|
||||
|
||||
function jsonrpcMessageTransform(payload, transformFn){
|
||||
function eachJsonMessage(payload, transformFn){
|
||||
if (Array.isArray(payload)) {
|
||||
return payload.map(transformFn)
|
||||
} else {
|
||||
return transformFn(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
app/scripts/lib/is-popup-or-notification.js
Normal file
8
app/scripts/lib/is-popup-or-notification.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = function isPopupOrNotification() {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
return 'popup'
|
||||
} else {
|
||||
return 'notification'
|
||||
}
|
||||
}
|
@ -1,159 +1,63 @@
|
||||
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 PendingTxDetails = require('../../../ui/app/components/pending-tx-details')
|
||||
const PendingMsgDetails = require('../../../ui/app/components/pending-msg-details')
|
||||
const MetaMaskUiCss = require('../../../ui/css')
|
||||
const extension = require('./extension')
|
||||
var notificationHandlers = {}
|
||||
|
||||
const notifications = {
|
||||
createUnlockRequestNotification: createUnlockRequestNotification,
|
||||
createTxNotification: createTxNotification,
|
||||
createMsgNotification: createMsgNotification,
|
||||
show,
|
||||
getPopup,
|
||||
closePopup,
|
||||
}
|
||||
module.exports = notifications
|
||||
window.METAMASK_NOTIFIER = notifications
|
||||
|
||||
setupListeners()
|
||||
function show () {
|
||||
getPopup((err, popup) => {
|
||||
if (err) throw err
|
||||
|
||||
function setupListeners () {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
||||
if (popup) {
|
||||
|
||||
// bring focus to existing popup
|
||||
extension.windows.update(popup.id, { focused: true })
|
||||
|
||||
// notification button press
|
||||
extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
|
||||
var handlers = notificationHandlers[notificationId]
|
||||
if (buttonIndex === 0) {
|
||||
handlers.confirm()
|
||||
} else {
|
||||
handlers.cancel()
|
||||
|
||||
// create new popup
|
||||
extension.windows.create({
|
||||
url: 'notification.html',
|
||||
type: 'popup',
|
||||
focused: true,
|
||||
width: 360,
|
||||
height: 500,
|
||||
})
|
||||
|
||||
}
|
||||
extension.notifications.clear(notificationId)
|
||||
})
|
||||
|
||||
// notification teardown
|
||||
extension.notifications.onClosed.addListener(function (notificationId) {
|
||||
delete notificationHandlers[notificationId]
|
||||
})
|
||||
}
|
||||
|
||||
// creation helper
|
||||
function createUnlockRequestNotification (opts) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
||||
var message = 'An Ethereum app has requested a signature. Please unlock your account.'
|
||||
|
||||
var id = createId()
|
||||
extension.notifications.create(id, {
|
||||
type: 'basic',
|
||||
iconUrl: '/images/icon-128.png',
|
||||
title: opts.title,
|
||||
message: message,
|
||||
})
|
||||
}
|
||||
|
||||
function createTxNotification (state) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
||||
|
||||
renderTxNotificationSVG(state, function (err, notificationSvgSource) {
|
||||
if (err) throw err
|
||||
|
||||
showNotification(extend(state, {
|
||||
title: 'New Unsigned Transaction',
|
||||
imageUrl: toSvgUri(notificationSvgSource),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function createMsgNotification (state) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
||||
|
||||
renderMsgNotificationSVG(state, function (err, notificationSvgSource) {
|
||||
if (err) throw err
|
||||
|
||||
showNotification(extend(state, {
|
||||
title: 'New Unsigned Message',
|
||||
imageUrl: toSvgUri(notificationSvgSource),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
function showNotification (state) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
||||
|
||||
var id = createId()
|
||||
extension.notifications.create(id, {
|
||||
type: 'image',
|
||||
requireInteraction: true,
|
||||
iconUrl: '/images/icon-128.png',
|
||||
imageUrl: state.imageUrl,
|
||||
title: state.title,
|
||||
message: '',
|
||||
buttons: [{
|
||||
title: 'Approve',
|
||||
}, {
|
||||
title: 'Reject',
|
||||
}],
|
||||
})
|
||||
notificationHandlers[id] = {
|
||||
confirm: state.onConfirm,
|
||||
cancel: state.onCancel,
|
||||
function getWindows(cb) {
|
||||
// Ignore in test environment
|
||||
if (!extension.windows) {
|
||||
return cb()
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
width: '360px',
|
||||
height: '240px',
|
||||
padding: '16px',
|
||||
// background: '#F7F7F7',
|
||||
background: 'white',
|
||||
},
|
||||
}, [
|
||||
h('style', MetaMaskUiCss()),
|
||||
content,
|
||||
])
|
||||
|
||||
render(confirmView, container, function ready() {
|
||||
var rootElement = findDOMNode(this)
|
||||
var viewSource = rootElement.outerHTML
|
||||
unmountComponentAtNode(container)
|
||||
var svgSource = svgWrapper(viewSource)
|
||||
// insert content into svg wrapper
|
||||
cb(null, svgSource)
|
||||
extension.windows.getAll({}, (windows) => {
|
||||
cb(null, windows)
|
||||
})
|
||||
}
|
||||
|
||||
function svgWrapper (content) {
|
||||
var wrapperSource = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="240">
|
||||
<foreignObject x="0" y="0" width="100%" height="100%">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" height="100%">{{content}}</body>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
`
|
||||
return wrapperSource.split('{{content}}').join(content)
|
||||
function getPopup(cb) {
|
||||
getWindows((err, windows) => {
|
||||
if (err) throw err
|
||||
cb(null, getPopupIn(windows))
|
||||
})
|
||||
}
|
||||
|
||||
function toSvgUri (content) {
|
||||
return 'data:image/svg+xml;utf8,' + encodeURIComponent(content)
|
||||
function getPopupIn(windows) {
|
||||
return windows ? windows.find((win) => win.type === 'popup') : null
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
getPopup((err, popup) => {
|
||||
if (err) throw err
|
||||
if (!popup) return
|
||||
extension.windows.remove(popup.id, console.error)
|
||||
})
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ module.exports = class MetamaskController {
|
||||
|
||||
constructor (opts) {
|
||||
this.opts = opts
|
||||
this.listeners = []
|
||||
this.configManager = new ConfigManager(opts)
|
||||
this.idStore = new IdentityStore({
|
||||
configManager: this.configManager,
|
||||
@ -112,9 +113,9 @@ module.exports = class MetamaskController {
|
||||
}
|
||||
|
||||
sendUpdate () {
|
||||
if (this.remote) {
|
||||
this.remote.sendUpdate(this.getState())
|
||||
}
|
||||
this.listeners.forEach((remote) => {
|
||||
remote.sendUpdate(this.getState())
|
||||
})
|
||||
}
|
||||
|
||||
initializeProvider (opts) {
|
||||
@ -130,10 +131,17 @@ module.exports = class MetamaskController {
|
||||
},
|
||||
// tx signing
|
||||
approveTransaction: this.newUnsignedTransaction.bind(this),
|
||||
signTransaction: idStore.signTransaction.bind(idStore),
|
||||
signTransaction: (...args) => {
|
||||
idStore.signTransaction(...args)
|
||||
this.sendUpdate()
|
||||
},
|
||||
|
||||
// msg signing
|
||||
approveMessage: this.newUnsignedMessage.bind(this),
|
||||
signMessage: idStore.signMessage.bind(idStore),
|
||||
signMessage: (...args) => {
|
||||
idStore.signMessage(...args)
|
||||
this.sendUpdate()
|
||||
},
|
||||
}
|
||||
|
||||
var provider = MetaMaskProvider(providerOpts)
|
||||
@ -191,8 +199,13 @@ module.exports = class MetamaskController {
|
||||
const idStore = this.idStore
|
||||
var state = idStore.getState()
|
||||
|
||||
let err = this.enforceTxValidations(txParams)
|
||||
if (err) return onTxDoneCb(err)
|
||||
|
||||
// It's locked
|
||||
if (!state.isUnlocked) {
|
||||
|
||||
// Allow the environment to define an unlock message.
|
||||
this.opts.unlockAccountMessage()
|
||||
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
|
||||
|
||||
@ -200,11 +213,19 @@ module.exports = class MetamaskController {
|
||||
} else {
|
||||
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
|
||||
if (err) return onTxDoneCb(err)
|
||||
this.sendUpdate()
|
||||
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enforceTxValidations (txParams) {
|
||||
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
|
||||
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
|
||||
return new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
newUnsignedMessage (msgParams, cb) {
|
||||
var state = this.idStore.getState()
|
||||
if (!state.isUnlocked) {
|
||||
@ -212,6 +233,7 @@ module.exports = class MetamaskController {
|
||||
this.opts.unlockAccountMessage()
|
||||
} else {
|
||||
this.addUnconfirmedMessage(msgParams, cb)
|
||||
this.sendUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,9 @@ const injectCss = require('inject-css')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const StreamProvider = require('web3-stream-provider')
|
||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||
const extension = require('./lib/extension')
|
||||
const notification = require('./lib/notifications')
|
||||
|
||||
// setup app
|
||||
var css = MetaMaskUiCss()
|
||||
@ -22,7 +24,11 @@ async.parallel({
|
||||
|
||||
function connectToAccountManager (cb) {
|
||||
// setup communication with background
|
||||
var pluginPort = extension.runtime.connect({name: 'popup'})
|
||||
|
||||
var name = isPopupOrNotification()
|
||||
closePopupIfOpen(name)
|
||||
window.METAMASK_UI_TYPE = name
|
||||
var pluginPort = extension.runtime.connect({ name })
|
||||
var portStream = new PortStream(pluginPort)
|
||||
// setup multiplexing
|
||||
var mx = setupMultiplex(portStream)
|
||||
@ -68,22 +74,12 @@ function getCurrentDomain (cb) {
|
||||
})
|
||||
}
|
||||
|
||||
function clearNotifications(){
|
||||
extension.notifications.getAll(function (object) {
|
||||
for (let notification in object){
|
||||
extension.notifications.clear(notification)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setupApp (err, opts) {
|
||||
if (err) {
|
||||
alert(err.stack)
|
||||
throw err
|
||||
}
|
||||
|
||||
clearNotifications()
|
||||
|
||||
var container = document.getElementById('app-content')
|
||||
|
||||
MetaMaskUi({
|
||||
@ -93,3 +89,9 @@ function setupApp (err, opts) {
|
||||
networkVersion: opts.networkVersion,
|
||||
})
|
||||
}
|
||||
|
||||
function closePopupIfOpen(name) {
|
||||
if (name !== 'notification') {
|
||||
notification.closePopup()
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,4 @@ machine:
|
||||
dependencies:
|
||||
pre:
|
||||
- "npm i -g testem"
|
||||
override:
|
||||
- sudo apt-get install libxss1 libappindicator1 libindicator7 lsb-base
|
||||
- curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
- sudo dpkg -i google-chrome.deb
|
||||
- sudo sed -i 's|HERE/chrome\"|HERE/chrome\" --disable-setuid-sandbox|g' /opt/google/chrome/google-chrome
|
||||
- rm google-chrome.deb
|
||||
- "npm i -g mocha"
|
||||
|
File diff suppressed because one or more lines are too long
@ -158,7 +158,7 @@
|
||||
}
|
||||
],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "loading",
|
||||
"network": "166",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"isEthConfirmed": true,
|
||||
|
@ -1,84 +1,44 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isUnlocked": false,
|
||||
"isEthConfirmed": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
|
||||
"name": "Wallet 1",
|
||||
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0x843963b837841dad3b0f5969ff271108776616df": {
|
||||
"name": "Wallet 2",
|
||||
"address": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
|
||||
"name": "Wallet 3",
|
||||
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
|
||||
"name": "Wallet 4",
|
||||
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1",
|
||||
"mayBeFauceting": false
|
||||
}
|
||||
},
|
||||
"identities": {},
|
||||
"unconfTxs": {},
|
||||
"accounts": {
|
||||
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"code": "0x",
|
||||
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6"
|
||||
},
|
||||
"0x843963b837841dad3b0f5969ff271108776616df": {
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"code": "0x",
|
||||
"address": "0x843963b837841dad3b0f5969ff271108776616df"
|
||||
},
|
||||
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"code": "0x",
|
||||
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a"
|
||||
},
|
||||
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"code": "0x",
|
||||
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1"
|
||||
}
|
||||
},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 11.4379398,
|
||||
"conversionDate": 1473358355,
|
||||
"accounts": {},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
|
||||
"network": "2",
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "1473186153102",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
"type": "rpc",
|
||||
"rpcTarget": "http://localhost:8545"
|
||||
},
|
||||
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df"
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail"
|
||||
"name": "accountDetail",
|
||||
"detailView": null,
|
||||
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions",
|
||||
"accountExport": "none",
|
||||
"privateKey": ""
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "testfaucet.metamask.io",
|
||||
"transForward": false,
|
||||
"currentDomain": "127.0.0.1:9966",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null,
|
||||
"scrollToBottom": false
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
||||
}
|
||||
|
39
development/states/restore-vault.json
Normal file
39
development/states/restore-vault.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": false,
|
||||
"isUnlocked": false,
|
||||
"isEthConfirmed": false,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 0,
|
||||
"conversionDate": "N/A",
|
||||
"accounts": {},
|
||||
"transactions": [],
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"network": "2"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "restoreVault"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "extensions",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
76
development/states/send.json
Normal file
76
development/states/send.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isEthConfirmed": false,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"name": "Wallet 1",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
|
||||
"name": "Wallet 2",
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
|
||||
"name": "Wallet 3",
|
||||
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d",
|
||||
"mayBeFauceting": false
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 11.21283484,
|
||||
"conversionDate": 1472158984,
|
||||
"accounts": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"code": "0x",
|
||||
"balance": "0x34693f54a1e25900",
|
||||
"nonce": "0x100013",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
|
||||
"code": "0x",
|
||||
"nonce": "0x100000",
|
||||
"balance": "0x18af912cee770000",
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
|
||||
},
|
||||
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
|
||||
"code": "0x",
|
||||
"nonce": "0x100000",
|
||||
"balance": "0x2386f26fc10000",
|
||||
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "sendTransaction"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "127.0.0.1:9966",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null,
|
||||
"detailView": {}
|
||||
},
|
||||
"identities": {}
|
||||
}
|
348
development/states/shapeshift.json
Normal file
348
development/states/shapeshift.json
Normal file
@ -0,0 +1,348 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isEthConfirmed": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"name": "Wallet 1",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
|
||||
"name": "Wallet 2",
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
|
||||
"name": "Wallet 3",
|
||||
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d",
|
||||
"mayBeFauceting": false
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 11.21274318,
|
||||
"conversionDate": 1472159644,
|
||||
"accounts": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"code": "0x",
|
||||
"nonce": "0x13",
|
||||
"balance": "0x461d4a64e937d3d1",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
|
||||
"code": "0x",
|
||||
"nonce": "0x0",
|
||||
"balance": "0x0",
|
||||
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
|
||||
},
|
||||
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"network": "1",
|
||||
"seedWords": null,
|
||||
"isConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"provider": {
|
||||
"type": "mainnet"
|
||||
},
|
||||
"selectedAccount": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "buyEth",
|
||||
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "127.0.0.1:9966",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"detailView": {},
|
||||
"buyView": {
|
||||
"subview": "buyForm",
|
||||
"formView": {
|
||||
"coinbase": false,
|
||||
"shapeshift": true,
|
||||
"marketinfo": {
|
||||
"pair": "btc_eth",
|
||||
"rate": 51.14252949,
|
||||
"minerFee": 0.01,
|
||||
"limit": 2.60306578,
|
||||
"minimum": 0.00038935,
|
||||
"maxLimit": 8.67688592
|
||||
},
|
||||
"coinOptions": {
|
||||
"BTC": {
|
||||
"name": "Bitcoin",
|
||||
"symbol": "BTC",
|
||||
"image": "https://shapeshift.io/images/coins/bitcoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"BCY": {
|
||||
"name": "BitCrystals",
|
||||
"symbol": "BCY",
|
||||
"image": "https://shapeshift.io/images/coins/bitcrystals.png",
|
||||
"status": "available"
|
||||
},
|
||||
"BLK": {
|
||||
"name": "Blackcoin",
|
||||
"symbol": "BLK",
|
||||
"image": "https://shapeshift.io/images/coins/blackcoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"BTS": {
|
||||
"name": "Bitshares",
|
||||
"symbol": "BTS",
|
||||
"specialReturn": false,
|
||||
"specialOutgoing": true,
|
||||
"specialIncoming": true,
|
||||
"fieldName": "destTag",
|
||||
"fieldKey": "destTag",
|
||||
"image": "https://shapeshift.io/images/coins/bitshares.png",
|
||||
"status": "available"
|
||||
},
|
||||
"CLAM": {
|
||||
"name": "Clams",
|
||||
"symbol": "CLAM",
|
||||
"image": "https://shapeshift.io/images/coins/clams.png",
|
||||
"status": "available"
|
||||
},
|
||||
"DASH": {
|
||||
"name": "Dash",
|
||||
"symbol": "DASH",
|
||||
"image": "https://shapeshift.io/images/coins/dash.png",
|
||||
"status": "available"
|
||||
},
|
||||
"DGB": {
|
||||
"name": "Digibyte",
|
||||
"symbol": "DGB",
|
||||
"image": "https://shapeshift.io/images/coins/digibyte.png",
|
||||
"status": "available"
|
||||
},
|
||||
"DAO": {
|
||||
"name": "TheDao",
|
||||
"symbol": "DAO",
|
||||
"image": "https://shapeshift.io/images/coins/thedao.png",
|
||||
"status": "available"
|
||||
},
|
||||
"DGD": {
|
||||
"name": "DigixDao",
|
||||
"symbol": "DGD",
|
||||
"image": "https://shapeshift.io/images/coins/digixdao.png",
|
||||
"status": "available"
|
||||
},
|
||||
"DOGE": {
|
||||
"name": "Dogecoin",
|
||||
"symbol": "DOGE",
|
||||
"image": "https://shapeshift.io/images/coins/dogecoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"EMC": {
|
||||
"name": "Emercoin",
|
||||
"symbol": "EMC",
|
||||
"image": "https://shapeshift.io/images/coins/emercoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"ETH": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"image": "https://shapeshift.io/images/coins/ether.png",
|
||||
"status": "available"
|
||||
},
|
||||
"ETC": {
|
||||
"name": "Ether Classic",
|
||||
"symbol": "ETC",
|
||||
"image": "https://shapeshift.io/images/coins/etherclassic.png",
|
||||
"status": "available"
|
||||
},
|
||||
"FCT": {
|
||||
"name": "Factoids",
|
||||
"symbol": "FCT",
|
||||
"image": "https://shapeshift.io/images/coins/factoids.png",
|
||||
"status": "available"
|
||||
},
|
||||
"LBC": {
|
||||
"name": "LBRY Credits",
|
||||
"symbol": "LBC",
|
||||
"image": "https://shapeshift.io/images/coins/lbry.png",
|
||||
"status": "available"
|
||||
},
|
||||
"LSK": {
|
||||
"name": "Lisk",
|
||||
"symbol": "LSK",
|
||||
"image": "https://shapeshift.io/images/coins/lisk.png",
|
||||
"status": "available"
|
||||
},
|
||||
"LTC": {
|
||||
"name": "Litecoin",
|
||||
"symbol": "LTC",
|
||||
"image": "https://shapeshift.io/images/coins/litecoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"MAID": {
|
||||
"name": "Maidsafe",
|
||||
"symbol": "MAID",
|
||||
"image": "https://shapeshift.io/images/coins/maidsafe.png",
|
||||
"status": "available"
|
||||
},
|
||||
"MINT": {
|
||||
"name": "Mintcoin",
|
||||
"symbol": "MINT",
|
||||
"image": "https://shapeshift.io/images/coins/mintcoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"MONA": {
|
||||
"name": "Monacoin",
|
||||
"symbol": "MONA",
|
||||
"image": "https://shapeshift.io/images/coins/monacoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"MSC": {
|
||||
"name": "Omni",
|
||||
"symbol": "MSC",
|
||||
"image": "https://shapeshift.io/images/coins/mastercoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"NBT": {
|
||||
"name": "Nubits",
|
||||
"symbol": "NBT",
|
||||
"image": "https://shapeshift.io/images/coins/nubits.png",
|
||||
"status": "available"
|
||||
},
|
||||
"NMC": {
|
||||
"name": "Namecoin",
|
||||
"symbol": "NMC",
|
||||
"image": "https://shapeshift.io/images/coins/namecoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"NVC": {
|
||||
"name": "Novacoin",
|
||||
"symbol": "NVC",
|
||||
"image": "https://shapeshift.io/images/coins/novacoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"NXT": {
|
||||
"name": "Nxt",
|
||||
"symbol": "NXT",
|
||||
"specialReturn": false,
|
||||
"specialOutgoing": true,
|
||||
"specialIncoming": true,
|
||||
"specialIncomingStatus": false,
|
||||
"fieldName": "Public Key (only for unfunded accounts!)",
|
||||
"fieldKey": "rsAddress",
|
||||
"image": "https://shapeshift.io/images/coins/nxt.png",
|
||||
"status": "available"
|
||||
},
|
||||
"PPC": {
|
||||
"name": "Peercoin",
|
||||
"symbol": "PPC",
|
||||
"image": "https://shapeshift.io/images/coins/peercoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"RDD": {
|
||||
"name": "Reddcoin",
|
||||
"symbol": "RDD",
|
||||
"image": "https://shapeshift.io/images/coins/reddcoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"SDC": {
|
||||
"name": "Shadowcash",
|
||||
"symbol": "SDC",
|
||||
"image": "https://shapeshift.io/images/coins/shadowcash.png",
|
||||
"status": "available"
|
||||
},
|
||||
"SC": {
|
||||
"name": "Siacoin",
|
||||
"symbol": "SC",
|
||||
"image": "https://shapeshift.io/images/coins/siacoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"SJCX": {
|
||||
"name": "StorjX",
|
||||
"symbol": "SJCX",
|
||||
"image": "https://shapeshift.io/images/coins/storjcoinx.png",
|
||||
"status": "available"
|
||||
},
|
||||
"START": {
|
||||
"name": "Startcoin",
|
||||
"symbol": "START",
|
||||
"image": "https://shapeshift.io/images/coins/startcoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"STEEM": {
|
||||
"name": "Steem",
|
||||
"symbol": "STEEM",
|
||||
"specialReturn": false,
|
||||
"specialOutgoing": true,
|
||||
"specialIncoming": true,
|
||||
"fieldName": "destTag",
|
||||
"fieldKey": "destTag",
|
||||
"image": "https://shapeshift.io/images/coins/steem.png",
|
||||
"status": "available"
|
||||
},
|
||||
"USDT": {
|
||||
"name": "Tether",
|
||||
"symbol": "USDT",
|
||||
"image": "https://shapeshift.io/images/coins/tether.png",
|
||||
"status": "available"
|
||||
},
|
||||
"VOX": {
|
||||
"name": "Voxels",
|
||||
"symbol": "VOX",
|
||||
"image": "https://shapeshift.io/images/coins/voxels.png",
|
||||
"status": "available"
|
||||
},
|
||||
"VRC": {
|
||||
"name": "Vericoin",
|
||||
"symbol": "VRC",
|
||||
"image": "https://shapeshift.io/images/coins/vericoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"VTC": {
|
||||
"name": "Vertcoin",
|
||||
"symbol": "VTC",
|
||||
"image": "https://shapeshift.io/images/coins/vertcoin.png",
|
||||
"status": "available"
|
||||
},
|
||||
"XCP": {
|
||||
"name": "Counterparty",
|
||||
"symbol": "XCP",
|
||||
"image": "https://shapeshift.io/images/coins/counterparty.png",
|
||||
"status": "available"
|
||||
},
|
||||
"XMR": {
|
||||
"name": "Monero",
|
||||
"symbol": "XMR",
|
||||
"specialReturn": false,
|
||||
"specialOutgoing": true,
|
||||
"specialIncoming": true,
|
||||
"fieldName": "Payment Id",
|
||||
"qrName": "tx_payment_id",
|
||||
"fieldKey": "paymentId",
|
||||
"image": "https://shapeshift.io/images/coins/monero.png",
|
||||
"status": "available"
|
||||
}
|
||||
}
|
||||
},
|
||||
"buyAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"amount": "5.00",
|
||||
"warning": null
|
||||
},
|
||||
"isSubLoading": false
|
||||
},
|
||||
"identities": {}
|
||||
}
|
28
docs/form_persisting_architecture.md
Normal file
28
docs/form_persisting_architecture.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Form Persisting Architecture
|
||||
|
||||
Since:
|
||||
- The popup is torn down completely on every click outside of it.
|
||||
- We have forms with multiple fields (like passwords & seed phrases) that might encourage a user to leave our panel to refer to a password manager.
|
||||
|
||||
We cause user friction when we lose the contents of certain forms.
|
||||
|
||||
This calls for an architecture of a form component that can completely persist its values to LocalStorage on every relevant change, and restore those values on reopening.
|
||||
|
||||
To achieve this, we have defined a class, a subclass of `React.Component`, called `PersistentForm`, and it's stored at `ui/lib/persistent-form.js`.
|
||||
|
||||
To use this class, simply take your form component (the component that renders `input`, `select`, or `textarea` elements), and make it subclass from `PersistentForm` instead of `React.Component`.
|
||||
|
||||
You can see an example of this in use in `ui/app/first-time/restore-vault.js`.
|
||||
|
||||
Additionally, any field whose value should be persisted, should have a `persistentFormId` attribute, which needs to be assigned under a `dataset` key on the main `attributes` hash. For example:
|
||||
|
||||
```javascript
|
||||
return h('textarea.twelve-word-phrase.letter-spacey', {
|
||||
dataset: {
|
||||
persistentFormId: 'wallet-seed',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
That's it! This field should be persisted to `localStorage` on each `keyUp`, those values should be restored on view load, and the cached values should be cleared when navigating deliberately away from the form.
|
||||
|
66
gulpfile.js
66
gulpfile.js
@ -16,6 +16,10 @@ var eslint = require('gulp-eslint')
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var manifest = require('./app/manifest.json')
|
||||
var gulpif = require('gulp-if')
|
||||
var replace = require('gulp-replace')
|
||||
|
||||
var disableLiveReload = gutil.env.disableLiveReload
|
||||
|
||||
// browser reload
|
||||
|
||||
@ -34,6 +38,7 @@ gulp.task('copy:locales', copyTask({
|
||||
destinations: [
|
||||
'./dist/firefox/_locales',
|
||||
'./dist/chrome/_locales',
|
||||
'./dist/edge/_locales',
|
||||
]
|
||||
}))
|
||||
gulp.task('copy:images', copyTask({
|
||||
@ -41,6 +46,7 @@ gulp.task('copy:images', copyTask({
|
||||
destinations: [
|
||||
'./dist/firefox/images',
|
||||
'./dist/chrome/images',
|
||||
'./dist/edge/images',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:fonts', copyTask({
|
||||
@ -48,6 +54,7 @@ gulp.task('copy:fonts', copyTask({
|
||||
destinations: [
|
||||
'./dist/firefox/fonts',
|
||||
'./dist/chrome/fonts',
|
||||
'./dist/edge/fonts',
|
||||
],
|
||||
}))
|
||||
gulp.task('copy:reload', copyTask({
|
||||
@ -55,6 +62,7 @@ gulp.task('copy:reload', copyTask({
|
||||
destinations: [
|
||||
'./dist/firefox/scripts',
|
||||
'./dist/chrome/scripts',
|
||||
'./dist/edge/scripts',
|
||||
],
|
||||
pattern: '/chromereload.js',
|
||||
}))
|
||||
@ -63,12 +71,13 @@ gulp.task('copy:root', copyTask({
|
||||
destinations: [
|
||||
'./dist/firefox',
|
||||
'./dist/chrome',
|
||||
'./dist/edge',
|
||||
],
|
||||
pattern: '/*',
|
||||
}))
|
||||
|
||||
gulp.task('manifest:cleanup', function() {
|
||||
return gulp.src('./dist/firefox/manifest.json')
|
||||
gulp.task('manifest:chrome', function() {
|
||||
return gulp.src('./dist/chrome/manifest.json')
|
||||
.pipe(jsoneditor(function(json) {
|
||||
delete json.applications
|
||||
return json
|
||||
@ -76,7 +85,33 @@ gulp.task('manifest:cleanup', function() {
|
||||
.pipe(gulp.dest('./dist/chrome', { overwrite: true }))
|
||||
})
|
||||
|
||||
gulp.task('copy', gulp.series(gulp.parallel('copy:locales','copy:images','copy:fonts','copy:reload','copy:root'), 'manifest:cleanup'))
|
||||
gulp.task('manifest:production', function() {
|
||||
return gulp.src([
|
||||
'./dist/firefox/manifest.json',
|
||||
'./dist/chrome/manifest.json',
|
||||
'./dist/edge/manifest.json',
|
||||
],{base: './dist/'})
|
||||
.pipe(gulpif(disableLiveReload,jsoneditor(function(json) {
|
||||
json.background.scripts = ["scripts/background.js"]
|
||||
return json
|
||||
})))
|
||||
.pipe(gulp.dest('./dist/', { overwrite: true }))
|
||||
})
|
||||
|
||||
const staticFiles = [
|
||||
'locales',
|
||||
'images',
|
||||
'fonts',
|
||||
'root'
|
||||
]
|
||||
|
||||
var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`)
|
||||
|
||||
if (!disableLiveReload) {
|
||||
copyStrings.push('copy:reload')
|
||||
}
|
||||
|
||||
gulp.task('copy', gulp.series(gulp.parallel(...copyStrings), 'manifest:production', 'manifest:chrome'))
|
||||
gulp.task('copy:watch', function(){
|
||||
gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy'))
|
||||
})
|
||||
@ -110,14 +145,18 @@ const jsFiles = [
|
||||
'popup',
|
||||
]
|
||||
|
||||
var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`)
|
||||
var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`)
|
||||
|
||||
jsFiles.forEach((jsFile) => {
|
||||
gulp.task(`dev:js:${jsFile}`, bundleTask({ watch: true, filename: `${jsFile}.js` }))
|
||||
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, filename: `${jsFile}.js` }))
|
||||
})
|
||||
|
||||
gulp.task('dev:js', gulp.parallel('dev:js:inpage','dev:js:contentscript','dev:js:background','dev:js:popup'))
|
||||
gulp.task('dev:js', gulp.parallel(...jsDevStrings))
|
||||
|
||||
gulp.task('build:js', gulp.parallel(...jsBuildStrings))
|
||||
|
||||
gulp.task('build:js', gulp.parallel('build:js:inpage','build:js:contentscript','build:js:background','build:js:popup'))
|
||||
|
||||
// clean dist
|
||||
|
||||
@ -131,17 +170,23 @@ gulp.task('zip:chrome', () => {
|
||||
return gulp.src('dist/chrome/**')
|
||||
.pipe(zip(`metamask-chrome-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
});
|
||||
})
|
||||
gulp.task('zip:firefox', () => {
|
||||
return gulp.src('dist/firefox/**')
|
||||
.pipe(zip(`metamask-firefox-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
});
|
||||
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox'))
|
||||
})
|
||||
gulp.task('zip:edge', () => {
|
||||
return gulp.src('dist/edge/**')
|
||||
.pipe(zip(`metamask-edge-${manifest.version}.zip`))
|
||||
.pipe(gulp.dest('builds'));
|
||||
})
|
||||
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge'))
|
||||
|
||||
// high level tasks
|
||||
|
||||
gulp.task('dev', gulp.series('dev:js', 'copy', gulp.parallel('copy:watch', 'dev:reload')))
|
||||
|
||||
gulp.task('build', gulp.series('clean', gulp.parallel('build:js', 'copy')))
|
||||
gulp.task('dist', gulp.series('build', 'zip'))
|
||||
|
||||
@ -160,7 +205,7 @@ function copyTask(opts){
|
||||
destinations.forEach(function(destination) {
|
||||
stream = stream.pipe(gulp.dest(destination))
|
||||
})
|
||||
stream.pipe(livereload())
|
||||
stream.pipe(gulpif(!disableLiveReload,livereload()))
|
||||
|
||||
return stream
|
||||
}
|
||||
@ -200,7 +245,8 @@ function bundleTask(opts) {
|
||||
.pipe(sourcemaps.write('./')) // writes .map file
|
||||
.pipe(gulp.dest('./dist/firefox/scripts'))
|
||||
.pipe(gulp.dest('./dist/chrome/scripts'))
|
||||
.pipe(livereload())
|
||||
.pipe(gulp.dest('./dist/edge/scripts'))
|
||||
.pipe(gulpif(!disableLiveReload,livereload()))
|
||||
|
||||
)
|
||||
}
|
||||
|
@ -54,7 +54,7 @@
|
||||
"inject-css": "^0.1.1",
|
||||
"jazzicon": "^1.1.3",
|
||||
"menu-droppo": "^1.1.0",
|
||||
"metamask-logo": "^1.3.1",
|
||||
"metamask-logo": "^2.1.2",
|
||||
"mississippi": "^1.2.0",
|
||||
"multiplex": "^6.7.0",
|
||||
"once": "^1.3.3",
|
||||
@ -97,8 +97,10 @@
|
||||
"del": "^2.2.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-brfs": "^0.1.0",
|
||||
"gulp-if": "^2.0.1",
|
||||
"gulp-json-editor": "^2.2.1",
|
||||
"gulp-livereload": "^3.8.1",
|
||||
"gulp-replace": "^0.5.4",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.5",
|
||||
|
@ -9,6 +9,34 @@ var Extension = require(path.join(__dirname, '..', '..', 'app', 'scripts', 'lib'
|
||||
|
||||
describe('extension', function() {
|
||||
|
||||
describe('extension.getURL', function() {
|
||||
const desiredResult = 'http://the-desired-result.io'
|
||||
|
||||
describe('in Chrome or Firefox', function() {
|
||||
GLOBAL.chrome.extension = {
|
||||
getURL: () => desiredResult
|
||||
}
|
||||
|
||||
it('returns the desired result', function() {
|
||||
const extension = new Extension()
|
||||
const result = extension.extension.getURL()
|
||||
assert.equal(result, desiredResult)
|
||||
})
|
||||
})
|
||||
|
||||
describe('in Microsoft Edge', function() {
|
||||
GLOBAL.browser.extension = {
|
||||
getURL: () => desiredResult
|
||||
}
|
||||
|
||||
it('returns the desired result', function() {
|
||||
const extension = new Extension()
|
||||
const result = extension.extension.getURL()
|
||||
assert.equal(result, desiredResult)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with chrome global', function() {
|
||||
let extension
|
||||
|
||||
@ -45,4 +73,5 @@ describe('extension', function() {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
97
test/unit/metamask-controller-test.js
Normal file
97
test/unit/metamask-controller-test.js
Normal file
@ -0,0 +1,97 @@
|
||||
var assert = require('assert')
|
||||
var MetaMaskController = require('../../app/scripts/metamask-controller')
|
||||
var sinon = require('sinon')
|
||||
var extend = require('xtend')
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
|
||||
describe('MetaMaskController', function() {
|
||||
const noop = () => {}
|
||||
let controller = new MetaMaskController({
|
||||
showUnconfirmedMessage: noop,
|
||||
unlockAccountMessage: noop,
|
||||
showUnconfirmedTx: noop,
|
||||
setData,
|
||||
loadData,
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
// sinon allows stubbing methods that are easily verified
|
||||
this.sinon = sinon.sandbox.create()
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
// sinon requires cleanup otherwise it will overwrite context
|
||||
this.sinon.restore()
|
||||
})
|
||||
|
||||
describe('#enforceTxValidations', function () {
|
||||
it('returns null for positive values', function() {
|
||||
var sample = {
|
||||
value: '0x01'
|
||||
}
|
||||
var res = controller.enforceTxValidations(sample)
|
||||
assert.equal(res, null, 'no error')
|
||||
})
|
||||
|
||||
|
||||
it('returns error for negative values', function() {
|
||||
var sample = {
|
||||
value: '-0x01'
|
||||
}
|
||||
var res = controller.enforceTxValidations(sample)
|
||||
assert.ok(res, 'error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
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)
|
||||
}
|
@ -10,7 +10,7 @@ const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const valuesFor = require('./util').valuesFor
|
||||
|
||||
const Identicon = require('./components/identicon')
|
||||
const AccountEtherBalance = require('./components/account-eth-balance')
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const TransactionList = require('./components/transaction-list')
|
||||
const ExportAccountView = require('./components/account-export')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
@ -168,7 +168,7 @@ AccountDetailScreen.prototype.render = function () {
|
||||
},
|
||||
}, [
|
||||
|
||||
h(AccountEtherBalance, {
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
@ -235,7 +235,7 @@ AccountDetailScreen.prototype.subview = function () {
|
||||
AccountDetailScreen.prototype.transactionList = function () {
|
||||
const { transactions, unconfTxs, unconfMsgs, address, network, shapeShiftTxList } = this.props
|
||||
|
||||
var txsToRender = transactions
|
||||
var txsToRender = transactions.concat(unconfTxs)
|
||||
// only transactions that are from the current address
|
||||
.filter(tx => tx.txParams.from === address)
|
||||
// only transactions that are on the current network
|
||||
|
@ -3,7 +3,7 @@ const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
const AccountEtherBalance = require('../components/account-eth-balance')
|
||||
const EthBalance = require('../components/eth-balance')
|
||||
const CopyButton = require('../components/copyButton')
|
||||
const Identicon = require('../components/identicon')
|
||||
|
||||
@ -50,7 +50,7 @@ NewComponent.prototype.render = function () {
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}, ethUtil.toChecksumAddress(identity.address)),
|
||||
h(AccountEtherBalance, {
|
||||
h(EthBalance, {
|
||||
value: account.balance,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
|
@ -11,6 +11,7 @@ module.exports = connect(mapStateToProps)(AccountsScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const pendingTxs = valuesFor(state.metamask.unconfTxs)
|
||||
.filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network)
|
||||
const pendingMsgs = valuesFor(state.metamask.unconfMsgs)
|
||||
const pending = pendingTxs.concat(pendingMsgs)
|
||||
|
||||
|
@ -137,6 +137,12 @@ var actions = {
|
||||
getQr: getQr,
|
||||
reshowQrCode: reshowQrCode,
|
||||
SHOW_QR_VIEW: 'SHOW_QR_VIEW',
|
||||
// FORGOT PASSWORD:
|
||||
BACK_TO_INIT_MENU: 'BACK_TO_INIT_MENU',
|
||||
goBackToInitView: goBackToInitView,
|
||||
RECOVERY_IN_PROGRESS: 'RECOVERY_IN_PROGRESS',
|
||||
BACK_TO_UNLOCK_VIEW: 'BACK_TO_UNLOCK_VIEW',
|
||||
backToUnlockView: backToUnlockView,
|
||||
}
|
||||
|
||||
module.exports = actions
|
||||
@ -156,8 +162,10 @@ function goHome () {
|
||||
|
||||
function tryUnlockMetamask (password) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
dispatch(actions.unlockInProgress())
|
||||
_accountManager.submitPassword(password, (err, selectedAccount) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
if (err) {
|
||||
dispatch(actions.unlockFailed())
|
||||
} else {
|
||||
@ -270,8 +278,6 @@ function signMsg (msgData) {
|
||||
|
||||
function signTx (txData) {
|
||||
return (dispatch) => {
|
||||
dispatch(actions.showLoadingIndication())
|
||||
|
||||
web3.eth.sendTransaction(txData, (err, data) => {
|
||||
dispatch(actions.hideLoadingIndication())
|
||||
|
||||
@ -279,6 +285,7 @@ function signTx (txData) {
|
||||
dispatch(actions.hideWarning())
|
||||
dispatch(actions.goHome())
|
||||
})
|
||||
dispatch(this.showConfTxPage())
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,6 +377,12 @@ function showNewVaultSeed (seed) {
|
||||
}
|
||||
}
|
||||
|
||||
function backToUnlockView () {
|
||||
return {
|
||||
type: actions.BACK_TO_UNLOCK_VIEW,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// unlock screen
|
||||
//
|
||||
@ -498,6 +511,12 @@ function showConfigPage (transitionForward = true) {
|
||||
}
|
||||
}
|
||||
|
||||
function goBackToInitView () {
|
||||
return {
|
||||
type: actions.BACK_TO_INIT_MENU,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// config
|
||||
//
|
||||
|
121
ui/app/app.js
121
ui/app/app.js
@ -51,6 +51,7 @@ function mapStateToProps (state) {
|
||||
menuOpen: state.appState.menuOpen,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +90,7 @@ App.prototype.render = function () {
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
this.renderPrimary(),
|
||||
this.renderBackToInitButton(),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
@ -96,6 +98,11 @@ App.prototype.render = function () {
|
||||
}
|
||||
|
||||
App.prototype.renderAppBar = function () {
|
||||
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
||||
@ -238,10 +245,17 @@ App.prototype.renderNetworkDropdown = function () {
|
||||
label: 'Localhost 8545',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setRpcTarget('http://localhost:8545')),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: props.provider.rpcTarget,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
}),
|
||||
|
||||
this.renderCustomOption(props.provider.rpcTarget),
|
||||
])
|
||||
}
|
||||
@ -275,24 +289,109 @@ App.prototype.renderDropdown = function () {
|
||||
label: 'Settings',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-gear.fa-lg', { ariaHidden: true }),
|
||||
icon: h('i.fa.fa-gear.fa-lg'),
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Lock',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.lockMetamask()),
|
||||
icon: h('i.fa.fa-lock.fa-lg', { ariaHidden: true }),
|
||||
icon: h('i.fa.fa-lock.fa-lg'),
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Help',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showInfoPage()),
|
||||
icon: h('i.fa.fa-question.fa-lg', { ariaHidden: true }),
|
||||
icon: h('i.fa.fa-question.fa-lg'),
|
||||
}),
|
||||
])
|
||||
}
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
var props = this.props
|
||||
return (
|
||||
h('.flex-row', {
|
||||
key: 'leftArrow',
|
||||
style: style,
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.cursor-pointer'),
|
||||
justArrow ? null : h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginLeft: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, 'BACK'),
|
||||
])
|
||||
)
|
||||
|
||||
}
|
||||
App.prototype.renderBackToInitButton = function () {
|
||||
var props = this.props
|
||||
var button = null
|
||||
if (!props.isUnlocked) {
|
||||
if (props.currentView.name === 'InitMenu') {
|
||||
button = props.forgottenPassword ? h('.flex-row', {
|
||||
key: 'rightArrow',
|
||||
style: {
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
right: '15px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
color: '#7F8082',
|
||||
width: '77.578px',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
}, [
|
||||
h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginRight: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.backToUnlockView()),
|
||||
}, 'LOGIN'),
|
||||
h('i.fa.fa-arrow-right.cursor-pointer'),
|
||||
]) : null
|
||||
} else if (props.isInitialized) {
|
||||
var style
|
||||
switch (props.currentView.name) {
|
||||
case 'createVault':
|
||||
style = {
|
||||
position: 'absolute',
|
||||
top: '41px',
|
||||
left: '80px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Bold',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
}
|
||||
return this.renderBackButton(style, true)
|
||||
case 'restoreVault':
|
||||
style = {
|
||||
position: 'absolute',
|
||||
top: '41px',
|
||||
left: '70px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Bold',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
}
|
||||
return this.renderBackButton(style, true)
|
||||
default:
|
||||
style = {
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
left: '15px',
|
||||
fontSize: '21px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
color: '#7F8082',
|
||||
width: '71.969px',
|
||||
alignItems: 'flex-end',
|
||||
}
|
||||
return this.renderBackButton(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
return button
|
||||
}
|
||||
|
||||
App.prototype.renderPrimary = function () {
|
||||
var props = this.props
|
||||
@ -306,7 +405,7 @@ App.prototype.renderPrimary = function () {
|
||||
}
|
||||
|
||||
// show initialize screen
|
||||
if (!props.isInitialized) {
|
||||
if (!props.isInitialized || props.forgottenPassword) {
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
@ -408,25 +507,21 @@ App.prototype.toggleMetamaskActive = function () {
|
||||
App.prototype.renderCustomOption = function (rpcTarget) {
|
||||
switch (rpcTarget) {
|
||||
case undefined:
|
||||
return h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }),
|
||||
})
|
||||
return null
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
})
|
||||
|
||||
default:
|
||||
return h(DropMenuItem, {
|
||||
label: `${rpcTarget}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: 'custom',
|
||||
})
|
||||
}
|
||||
|
@ -1,140 +0,0 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
|
||||
module.exports = connect(mapStateToProps)(EthBalanceComponent)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
conversionDate: state.metamask.conversionDate,
|
||||
currentFiat: state.metamask.currentFiat,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var state = this.props
|
||||
var style = state.style
|
||||
|
||||
const value = formatBalance(state.value, 6)
|
||||
var width = state.width
|
||||
|
||||
return (
|
||||
|
||||
h('.ether-balance', {
|
||||
style: style,
|
||||
}, [
|
||||
h('.ether-balance-amount', {
|
||||
style: {
|
||||
display: 'inline',
|
||||
width: width,
|
||||
},
|
||||
}, this.renderBalance(value, state)),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value, state) {
|
||||
if (value === 'None') return value
|
||||
var balanceObj = generateBalanceObject(value, state.shorten ? 1 : 3)
|
||||
var balance, fiatDisplayNumber, fiatTooltipNumber
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
|
||||
|
||||
if (state.conversionRate !== 0) {
|
||||
fiatTooltipNumber = Number(splitBalance[0]) * state.conversionRate
|
||||
fiatDisplayNumber = fiatTooltipNumber.toFixed(2)
|
||||
} else {
|
||||
fiatDisplayNumber = 'N/A'
|
||||
}
|
||||
|
||||
var fiatSuffix = state.currentFiat
|
||||
|
||||
if (state.shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
}
|
||||
|
||||
var label = balanceObj.label
|
||||
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
marginBottom: '5px',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
},
|
||||
}, balance),
|
||||
h('div', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
]),
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${fiatTooltipNumber} ${fiatSuffix}`,
|
||||
}, [
|
||||
fiatDisplay(fiatDisplayNumber, fiatSuffix),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
|
||||
if (fiatDisplayNumber !== 'N/A') {
|
||||
return h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
fontSize: '12px',
|
||||
color: '#333333',
|
||||
},
|
||||
}, fiatDisplayNumber),
|
||||
h('div', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
marginLeft: '5px',
|
||||
fontSize: '12px',
|
||||
},
|
||||
}, fiatSuffix),
|
||||
])
|
||||
} else {
|
||||
return h('div')
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ function AccountInfoLink () {
|
||||
|
||||
AccountInfoLink.prototype.render = function () {
|
||||
const { selected, network } = this.props
|
||||
const title = 'View account on etherscan'
|
||||
const title = 'View account on Etherscan'
|
||||
const url = genAccountLink(selected, network)
|
||||
|
||||
if (!url) {
|
||||
|
@ -106,7 +106,7 @@ BuyButtonSubview.prototype.formVersionSubview = function () {
|
||||
style: {
|
||||
width: '225px',
|
||||
},
|
||||
}, 'In order to access this feature please switch too the Main Network'),
|
||||
}, 'In order to access this feature please switch to the Main Network'),
|
||||
h('h3.text-transform-uppercase', 'or:'),
|
||||
this.props.network === '2' ? h('button.text-transform-uppercase', {
|
||||
onClick: () => this.props.dispatch(actions.buyEth()),
|
||||
|
@ -4,6 +4,7 @@ const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
|
||||
@ -13,11 +14,11 @@ function EthBalanceComponent () {
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var state = this.props
|
||||
var style = state.style
|
||||
var props = this.props
|
||||
var style = props.style
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
const value = formatBalance(state.value, 6, needsParse)
|
||||
var width = state.width
|
||||
const value = formatBalance(props.value, 6, needsParse)
|
||||
var width = props.width
|
||||
|
||||
return (
|
||||
|
||||
@ -35,15 +36,16 @@ EthBalanceComponent.prototype.render = function () {
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var state = this.props
|
||||
var props = this.props
|
||||
if (value === 'None') return value
|
||||
var balanceObj = generateBalanceObject(value, state.shorten ? 1 : 3)
|
||||
var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (state.shorten) {
|
||||
if (props.shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
@ -55,8 +57,8 @@ EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, [
|
||||
h('.flex-column', {
|
||||
}, h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
@ -74,9 +76,12 @@ EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
])
|
||||
|
||||
showFiat ? h(FiatValue, { value: props.value }) : null,
|
||||
]))
|
||||
)
|
||||
}
|
||||
|
71
ui/app/components/fiat-value.js
Normal file
71
ui/app/components/fiat-value.js
Normal file
@ -0,0 +1,71 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const formatBalance = require('../util').formatBalance
|
||||
|
||||
module.exports = connect(mapStateToProps)(FiatValue)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentFiat: state.metamask.currentFiat,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(FiatValue, Component)
|
||||
function FiatValue () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
FiatValue.prototype.render = function () {
|
||||
const props = this.props
|
||||
const value = formatBalance(props.value, 6)
|
||||
|
||||
if (value === 'None') return value
|
||||
var fiatDisplayNumber, fiatTooltipNumber
|
||||
var splitBalance = value.split(' ')
|
||||
|
||||
if (props.conversionRate !== 0) {
|
||||
fiatTooltipNumber = Number(splitBalance[0]) * props.conversionRate
|
||||
fiatDisplayNumber = fiatTooltipNumber.toFixed(2)
|
||||
} else {
|
||||
fiatDisplayNumber = 'N/A'
|
||||
fiatTooltipNumber = 'Unknown'
|
||||
}
|
||||
|
||||
var fiatSuffix = props.currentFiat
|
||||
|
||||
return fiatDisplay(fiatDisplayNumber, fiatSuffix)
|
||||
}
|
||||
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
|
||||
if (fiatDisplayNumber !== 'N/A') {
|
||||
return h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
fontSize: '12px',
|
||||
color: '#333333',
|
||||
},
|
||||
}, fiatDisplayNumber),
|
||||
h('div', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
marginLeft: '5px',
|
||||
fontSize: '12px',
|
||||
},
|
||||
}, fiatSuffix),
|
||||
])
|
||||
} else {
|
||||
return h('div')
|
||||
}
|
||||
}
|
@ -14,9 +14,8 @@ function Mascot () {
|
||||
pxNotRatio: true,
|
||||
width: 200,
|
||||
height: 200,
|
||||
staticImage: './images/icon-512.png',
|
||||
})
|
||||
if (!this.logo.webGLSupport) return
|
||||
|
||||
this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000)
|
||||
this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false)
|
||||
}
|
||||
@ -27,32 +26,25 @@ Mascot.prototype.render = function () {
|
||||
// and we dont get that until render
|
||||
this.handleAnimationEvents()
|
||||
|
||||
return (
|
||||
|
||||
h('#metamask-mascot-container')
|
||||
|
||||
)
|
||||
return h('#metamask-mascot-container', {
|
||||
style: { zIndex: 2 },
|
||||
})
|
||||
}
|
||||
|
||||
Mascot.prototype.componentDidMount = function () {
|
||||
var targetDivId = 'metamask-mascot-container'
|
||||
var container = document.getElementById(targetDivId)
|
||||
if (!this.logo.webGLSupport) {
|
||||
var staticLogo = this.logo.staticLogo
|
||||
staticLogo.style.marginBottom = '40px'
|
||||
container.appendChild(staticLogo)
|
||||
} else {
|
||||
container.appendChild(this.logo.canvas)
|
||||
}
|
||||
container.appendChild(this.logo.container)
|
||||
}
|
||||
|
||||
Mascot.prototype.componentWillUnmount = function () {
|
||||
if (!this.logo.webGLSupport) return
|
||||
this.logo.canvas.remove()
|
||||
this.animations = this.props.animationEventEmitter
|
||||
this.animations.removeAllListeners()
|
||||
this.logo.container.remove()
|
||||
this.logo.stopAnimation()
|
||||
}
|
||||
|
||||
Mascot.prototype.handleAnimationEvents = function () {
|
||||
if (!this.logo.webGLSupport) return
|
||||
// only setup listeners once
|
||||
if (this.animations) return
|
||||
this.animations = this.props.animationEventEmitter
|
||||
@ -61,7 +53,6 @@ Mascot.prototype.handleAnimationEvents = function () {
|
||||
}
|
||||
|
||||
Mascot.prototype.lookAt = function (target) {
|
||||
if (!this.logo.webGLSupport) return
|
||||
this.unfollowMouse()
|
||||
this.logo.lookAt(target)
|
||||
this.refollowMouse()
|
||||
|
@ -61,7 +61,7 @@ Network.prototype.render = function () {
|
||||
style: {
|
||||
color: '#039396',
|
||||
}},
|
||||
'Etherum Main Net'),
|
||||
'Ethereum Main Net'),
|
||||
])
|
||||
case 'morden-test-network':
|
||||
return h('.network-indicator', [
|
||||
@ -75,7 +75,6 @@ Network.prototype.render = function () {
|
||||
default:
|
||||
return h('.network-indicator', [
|
||||
h('i.fa.fa-question-circle.fa-lg', {
|
||||
ariaHidden: true,
|
||||
style: {
|
||||
margin: '10px',
|
||||
color: 'rgb(125, 128, 130)',
|
||||
|
@ -4,9 +4,8 @@ const inherits = require('util').inherits
|
||||
const carratInline = require('fs').readFileSync('./images/forward-carrat.svg', 'utf8')
|
||||
|
||||
const MiniAccountPanel = require('./mini-account-panel')
|
||||
const EtherBalance = require('./eth-balance')
|
||||
const EthBalance = require('./eth-balance')
|
||||
const addressSummary = require('../util').addressSummary
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const nameForAddress = require('../../lib/contract-namer')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
@ -70,7 +69,7 @@ PTXP.render = function () {
|
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
|
||||
},
|
||||
}, [
|
||||
h(EtherBalance, {
|
||||
h(EthBalance, {
|
||||
value: balance,
|
||||
inline: true,
|
||||
labelColor: '#F7861C',
|
||||
@ -107,12 +106,12 @@ PTXP.render = function () {
|
||||
|
||||
h('.row', [
|
||||
h('.cell.label', 'Amount'),
|
||||
h('.cell.value', formatBalance(txParams.value)),
|
||||
h(EthBalance, { value: txParams.value }),
|
||||
]),
|
||||
|
||||
h('.cell.row', [
|
||||
h('.cell.label', 'Max Transaction Fee'),
|
||||
h('.cell.value', formatBalance(txFee.toString(16))),
|
||||
h(EthBalance, { value: txFee.toString(16) }),
|
||||
]),
|
||||
|
||||
h('.cell.row', {
|
||||
@ -129,7 +128,7 @@ PTXP.render = function () {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h(EtherBalance, {
|
||||
h(EthBalance, {
|
||||
value: maxCost.toString(16),
|
||||
inline: true,
|
||||
labelColor: 'black',
|
||||
|
@ -1,4 +1,4 @@
|
||||
const Component = require('react').Component
|
||||
const PersistentForm = require('../../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
@ -17,12 +17,15 @@ function mapStateToProps(state) {
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ShapeshiftForm, Component)
|
||||
inherits(ShapeshiftForm, PersistentForm)
|
||||
|
||||
function ShapeshiftForm () {
|
||||
Component.call(this)
|
||||
PersistentForm.call(this)
|
||||
this.persistentFormParentId = 'shapeshift-buy-form'
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.render = function () {
|
||||
|
||||
return h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
@ -66,6 +69,9 @@ ShapeshiftForm.prototype.renderMain = function () {
|
||||
h('input#fromCoin.buy-inputs.ex-coins', {
|
||||
type: 'text',
|
||||
list: 'coinList',
|
||||
dataset: {
|
||||
persistentFormId: 'input-coin',
|
||||
},
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
@ -159,6 +165,9 @@ ShapeshiftForm.prototype.renderMain = function () {
|
||||
h('input#fromCoinAddress.buy-inputs', {
|
||||
type: 'text',
|
||||
placeholder: `Your ${coin} Refund Address`,
|
||||
dataset: {
|
||||
persistentFormId: 'refund-address',
|
||||
},
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
width: '278px',
|
||||
|
@ -11,12 +11,14 @@ function Tooltip () {
|
||||
}
|
||||
|
||||
Tooltip.prototype.render = function () {
|
||||
|
||||
const props = this.props
|
||||
const { position, title, children } = props
|
||||
|
||||
return h(ReactTooltip, {
|
||||
position: props.position ? props.position : 'left',
|
||||
title: props.title,
|
||||
position: position || 'left',
|
||||
title,
|
||||
fixed: false,
|
||||
}, props.children)
|
||||
}, children)
|
||||
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ TransactionListItem.prototype.render = function () {
|
||||
value: txParams.value,
|
||||
width: '55px',
|
||||
shorten: true,
|
||||
showFiat: false,
|
||||
style: {fontSize: '15px'},
|
||||
}) : h('.flex-column'),
|
||||
])
|
||||
|
@ -5,6 +5,7 @@ const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
const txHelper = require('../lib/tx-helper')
|
||||
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
|
||||
|
||||
const PendingTx = require('./components/pending-tx')
|
||||
const PendingMsg = require('./components/pending-msg')
|
||||
@ -20,6 +21,7 @@ function mapStateToProps (state) {
|
||||
unconfMsgs: state.metamask.unconfMsgs,
|
||||
index: state.appState.currentView.context,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,11 +33,13 @@ function ConfirmTxScreen () {
|
||||
ConfirmTxScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
|
||||
var network = state.network
|
||||
var unconfTxs = state.unconfTxs
|
||||
var unconfMsgs = state.unconfMsgs
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network)
|
||||
var index = state.index !== undefined ? state.index : 0
|
||||
var txData = unconfTxList[index] || unconfTxList[0] || {}
|
||||
var isNotification = isPopupOrNotification() === 'notification'
|
||||
|
||||
return (
|
||||
|
||||
@ -43,9 +47,9 @@ ConfirmTxScreen.prototype.render = function () {
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: this.goHome.bind(this),
|
||||
}),
|
||||
}) : null,
|
||||
h('h2.page-subtitle', 'Confirm Transaction'),
|
||||
]),
|
||||
|
||||
|
@ -35,10 +35,9 @@ EthStoreWarning.prototype.render = function () {
|
||||
margin: '10px 10px 10px 10px',
|
||||
},
|
||||
},
|
||||
`The MetaMask team would like to
|
||||
remind you that MetaMask is currently in beta - so
|
||||
don't store large
|
||||
amounts of ether in MetaMask.
|
||||
`MetaMask is currently in beta; use
|
||||
caution in storing large
|
||||
amounts of ether.
|
||||
`),
|
||||
|
||||
h('i.fa.fa-exclamation-triangle.fa-4', {
|
||||
|
@ -73,9 +73,7 @@ InitializeMenuScreen.prototype.renderMenu = function () {
|
||||
margin: 12,
|
||||
},
|
||||
}, 'Restore Existing Vault'),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const PersistentForm = require('../../lib/persistent-form')
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(RestoreVaultScreen)
|
||||
|
||||
inherits(RestoreVaultScreen, Component)
|
||||
inherits(RestoreVaultScreen, PersistentForm)
|
||||
function RestoreVaultScreen () {
|
||||
Component.call(this)
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
@ -19,6 +19,8 @@ function mapStateToProps (state) {
|
||||
|
||||
RestoreVaultScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
this.persistentFormParentId = 'restore-vault-form'
|
||||
|
||||
return (
|
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||
@ -39,6 +41,9 @@ RestoreVaultScreen.prototype.render = function () {
|
||||
// wallet seed entry
|
||||
h('h3', 'Wallet Seed'),
|
||||
h('textarea.twelve-word-phrase.letter-spacey', {
|
||||
dataset: {
|
||||
persistentFormId: 'wallet-seed',
|
||||
},
|
||||
placeholder: 'Enter your secret twelve word phrase here to restore your vault.',
|
||||
}),
|
||||
|
||||
@ -47,6 +52,9 @@ RestoreVaultScreen.prototype.render = function () {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'New Password (min 8 chars)',
|
||||
dataset: {
|
||||
persistentFormId: 'password',
|
||||
},
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
@ -59,6 +67,9 @@ RestoreVaultScreen.prototype.render = function () {
|
||||
id: 'password-box-confirm',
|
||||
placeholder: 'Confirm Password',
|
||||
onKeyPress: this.onMaybeCreate.bind(this),
|
||||
dataset: {
|
||||
persistentFormId: 'password-confirmation',
|
||||
},
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 16,
|
||||
|
@ -67,7 +67,7 @@ InfoScreen.prototype.render = function () {
|
||||
`For more information on MetaMask
|
||||
you can visit our web site. If you want to
|
||||
contact us with questions or just
|
||||
say 'Hi', you can find us on theise platforms:`),
|
||||
say 'Hi', you can find us on these platforms:`),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
|
@ -1,6 +1,7 @@
|
||||
const extend = require('xtend')
|
||||
const actions = require('../actions')
|
||||
const txHelper = require('../../lib/tx-helper')
|
||||
const notification = require('../../../app/scripts/lib/notifications')
|
||||
|
||||
module.exports = reduceApp
|
||||
|
||||
@ -123,6 +124,7 @@ function reduceApp (state, action) {
|
||||
|
||||
case actions.UNLOCK_METAMASK:
|
||||
return extend(appState, {
|
||||
forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
|
||||
detailView: {},
|
||||
transForward: true,
|
||||
isLoading: false,
|
||||
@ -136,6 +138,25 @@ function reduceApp (state, action) {
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.BACK_TO_INIT_MENU:
|
||||
return extend(appState, {
|
||||
warning: null,
|
||||
transForward: false,
|
||||
forgottenPassword: true,
|
||||
currentView: {
|
||||
name: 'InitMenu',
|
||||
},
|
||||
})
|
||||
|
||||
case actions.BACK_TO_UNLOCK_VIEW:
|
||||
return extend(appState, {
|
||||
warning: null,
|
||||
transForward: true,
|
||||
forgottenPassword: !appState.forgottenPassword,
|
||||
currentView: {
|
||||
name: 'UnlockScreen',
|
||||
},
|
||||
})
|
||||
// reveal seed words
|
||||
|
||||
case actions.REVEAL_SEED_CONFIRMATION:
|
||||
@ -170,6 +191,7 @@ function reduceApp (state, action) {
|
||||
|
||||
case actions.SHOW_ACCOUNT_DETAIL:
|
||||
return extend(appState, {
|
||||
forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
|
||||
currentView: {
|
||||
name: 'accountDetail',
|
||||
context: action.value,
|
||||
@ -236,8 +258,9 @@ function reduceApp (state, action) {
|
||||
case actions.COMPLETED_TX:
|
||||
var unconfTxs = state.metamask.unconfTxs
|
||||
var unconfMsgs = state.metamask.unconfMsgs
|
||||
var network = state.metamask.network
|
||||
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network)
|
||||
.filter(tx => tx !== tx.id)
|
||||
|
||||
if (unconfTxList && unconfTxList.length > 0) {
|
||||
@ -250,6 +273,9 @@ function reduceApp (state, action) {
|
||||
warning: null,
|
||||
})
|
||||
} else {
|
||||
|
||||
notification.closePopup()
|
||||
|
||||
return extend(appState, {
|
||||
transForward: false,
|
||||
warning: null,
|
||||
@ -498,14 +524,16 @@ function reduceApp (state, action) {
|
||||
function hasPendingTxs (state) {
|
||||
var unconfTxs = state.metamask.unconfTxs
|
||||
var unconfMsgs = state.metamask.unconfMsgs
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
|
||||
var network = state.metamask.network
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network)
|
||||
return unconfTxList.length > 0
|
||||
}
|
||||
|
||||
function indexForPending (state, txId) {
|
||||
var unconfTxs = state.metamask.unconfTxs
|
||||
var unconfMsgs = state.metamask.unconfMsgs
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs)
|
||||
var network = state.metamask.network
|
||||
var unconfTxList = txHelper(unconfTxs, unconfMsgs, network)
|
||||
let idx
|
||||
unconfTxList.forEach((tx, i) => {
|
||||
if (tx.id === txId) {
|
||||
@ -515,4 +543,3 @@ function indexForPending (state, txId) {
|
||||
return idx
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const PersistentForm = require('../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const Identicon = require('./components/identicon')
|
||||
@ -7,7 +7,7 @@ const actions = require('./actions')
|
||||
const util = require('./util')
|
||||
const numericBalance = require('./util').numericBalance
|
||||
const addressSummary = require('./util').addressSummary
|
||||
const EtherBalance = require('./components/eth-balance')
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
||||
@ -29,12 +29,14 @@ function mapStateToProps (state) {
|
||||
return result
|
||||
}
|
||||
|
||||
inherits(SendTransactionScreen, Component)
|
||||
inherits(SendTransactionScreen, PersistentForm)
|
||||
function SendTransactionScreen () {
|
||||
Component.call(this)
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.render = function () {
|
||||
this.persistentFormParentId = 'send-tx-form'
|
||||
|
||||
var state = this.props
|
||||
var address = state.address
|
||||
var account = state.account
|
||||
@ -105,8 +107,7 @@ SendTransactionScreen.prototype.render = function () {
|
||||
// balance
|
||||
h('.flex-row.flex-center', [
|
||||
|
||||
// h('div', formatBalance(account && account.balance)),
|
||||
h(EtherBalance, {
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
}),
|
||||
|
||||
@ -137,6 +138,9 @@ SendTransactionScreen.prototype.render = function () {
|
||||
h('input.large-input', {
|
||||
name: 'address',
|
||||
placeholder: 'Recipient Address',
|
||||
dataset: {
|
||||
persistentFormId: 'recipient-address',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
@ -150,6 +154,9 @@ SendTransactionScreen.prototype.render = function () {
|
||||
style: {
|
||||
marginRight: 6,
|
||||
},
|
||||
dataset: {
|
||||
persistentFormId: 'tx-amount',
|
||||
},
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
@ -185,11 +192,12 @@ SendTransactionScreen.prototype.render = function () {
|
||||
width: '100%',
|
||||
resize: 'none',
|
||||
},
|
||||
dataset: {
|
||||
persistentFormId: 'tx-data',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@ -227,7 +235,6 @@ SendTransactionScreen.prototype.onSubmit = function () {
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.hideWarning())
|
||||
this.props.dispatch(actions.showLoadingIndication())
|
||||
|
||||
var txParams = {
|
||||
from: this.props.address,
|
||||
|
@ -26,47 +26,46 @@ UnlockScreen.prototype.render = function () {
|
||||
const state = this.props
|
||||
const warning = state.warning
|
||||
return (
|
||||
h('.flex-column.hey-im-here', [
|
||||
h('.unlock-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
h('.unlock-screen.flex-column.flex-center.flex-grow', [
|
||||
h(Mascot, {
|
||||
animationEventEmitter: this.animationEventEmitter,
|
||||
}),
|
||||
|
||||
h(Mascot, {
|
||||
animationEventEmitter: this.animationEventEmitter,
|
||||
}),
|
||||
h('h1', {
|
||||
style: {
|
||||
fontSize: '1.4em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#7F8082',
|
||||
},
|
||||
}, 'MetaMask'),
|
||||
|
||||
h('h1', {
|
||||
style: {
|
||||
fontSize: '1.4em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#7F8082',
|
||||
},
|
||||
}, 'MetaMask'),
|
||||
h('input.large-input', {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'enter password',
|
||||
style: {
|
||||
|
||||
h('input.large-input', {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'enter password',
|
||||
style: {
|
||||
},
|
||||
onKeyPress: this.onKeyPress.bind(this),
|
||||
onInput: this.inputChanged.bind(this),
|
||||
}),
|
||||
|
||||
},
|
||||
onKeyPress: this.onKeyPress.bind(this),
|
||||
onInput: this.inputChanged.bind(this),
|
||||
}),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
h('button.primary.cursor-pointer', {
|
||||
onClick: this.onSubmit.bind(this),
|
||||
style: {
|
||||
margin: 10,
|
||||
},
|
||||
}, 'Unlock'),
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
h('button.primary.cursor-pointer', {
|
||||
onClick: this.onSubmit.bind(this),
|
||||
style: {
|
||||
margin: 10,
|
||||
},
|
||||
}, 'Unlock'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ const h = require('react-hyperscript')
|
||||
const Root = require('./app/root')
|
||||
const actions = require('./app/actions')
|
||||
const configureStore = require('./app/store')
|
||||
|
||||
const txHelper = require('./lib/tx-helper')
|
||||
module.exports = launchApp
|
||||
|
||||
function launchApp (opts) {
|
||||
@ -34,7 +34,8 @@ function startApp (metamaskState, accountManager, opts) {
|
||||
})
|
||||
|
||||
// if unconfirmed txs, start on txConf page
|
||||
if (Object.keys(metamaskState.unconfTxs || {}).length) {
|
||||
var unconfirmedTxsAll = txHelper(metamaskState.unconfTxs, metamaskState.unconfMsgs, metamaskState.network)
|
||||
if (unconfirmedTxsAll.length > 0) {
|
||||
store.dispatch(actions.showConfTxPage())
|
||||
}
|
||||
|
||||
|
61
ui/lib/persistent-form.js
Normal file
61
ui/lib/persistent-form.js
Normal file
@ -0,0 +1,61 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const defaultKey = 'persistent-form-default'
|
||||
const eventName = 'keyup'
|
||||
|
||||
module.exports = PersistentForm
|
||||
|
||||
function PersistentForm () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
inherits(PersistentForm, Component)
|
||||
|
||||
PersistentForm.prototype.componentDidMount = function () {
|
||||
const fields = document.querySelectorAll('[data-persistent-formid]')
|
||||
const store = this.getPersistentStore()
|
||||
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
const field = fields[i]
|
||||
const key = field.getAttribute('data-persistent-formid')
|
||||
const cached = store[key]
|
||||
if (cached !== undefined) {
|
||||
field.value = cached
|
||||
}
|
||||
|
||||
field.addEventListener(eventName, this.persistentFieldDidUpdate.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
PersistentForm.prototype.getPersistentStore = function () {
|
||||
let store = window.localStorage[this.persistentFormParentId || defaultKey]
|
||||
if (store && store !== 'null') {
|
||||
store = JSON.parse(store)
|
||||
} else {
|
||||
store = {}
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
PersistentForm.prototype.setPersistentStore = function (newStore) {
|
||||
window.localStorage[this.persistentFormParentId || defaultKey] = JSON.stringify(newStore)
|
||||
}
|
||||
|
||||
PersistentForm.prototype.persistentFieldDidUpdate = function (event) {
|
||||
const field = event.target
|
||||
const store = this.getPersistentStore()
|
||||
const key = field.getAttribute('data-persistent-formid')
|
||||
const val = field.value
|
||||
store[key] = val
|
||||
this.setPersistentStore(store)
|
||||
}
|
||||
|
||||
PersistentForm.prototype.componentWillUnmount = function () {
|
||||
const fields = document.querySelectorAll('[data-persistent-formid]')
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
const field = fields[i]
|
||||
field.removeEventListener(eventName, this.persistentFieldDidUpdate.bind(this))
|
||||
}
|
||||
this.setPersistentStore({})
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const valuesFor = require('../app/util').valuesFor
|
||||
|
||||
module.exports = function (unconfTxs, unconfMsgs) {
|
||||
var txValues = valuesFor(unconfTxs)
|
||||
module.exports = function (unconfTxs, unconfMsgs, network) {
|
||||
var txValues = network ? valuesFor(unconfTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unconfTxs)
|
||||
var msgValues = valuesFor(unconfMsgs)
|
||||
var allValues = txValues.concat(msgValues)
|
||||
return allValues.sort(tx => tx.time)
|
||||
|
Loading…
Reference in New Issue
Block a user