mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Resolve merge conflicts
This commit is contained in:
commit
1e9c0a9db2
2
.babelrc
2
.babelrc
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"presets": ["es2015", "stage-0"],
|
"presets": ["es2015", "stage-0", "react"],
|
||||||
"plugins": ["transform-runtime", "transform-async-to-generator"]
|
"plugins": ["transform-runtime", "transform-async-to-generator"]
|
||||||
}
|
}
|
||||||
|
12
.eslintrc
12
.eslintrc
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaVersion": 2017,
|
"ecmaVersion": 2017,
|
||||||
@ -10,10 +11,14 @@
|
|||||||
"arrowFunctions": true,
|
"arrowFunctions": true,
|
||||||
"objectLiteralShorthandMethods": true,
|
"objectLiteralShorthandMethods": true,
|
||||||
"objectLiteralShorthandProperties": true,
|
"objectLiteralShorthandProperties": true,
|
||||||
"templateStrings": true
|
"templateStrings": true,
|
||||||
|
"classes": true,
|
||||||
|
"jsx": true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"extends": ["plugin:react/recommended"],
|
||||||
|
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true,
|
"node": true,
|
||||||
@ -23,7 +28,8 @@
|
|||||||
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"mocha",
|
"mocha",
|
||||||
"chai"
|
"chai",
|
||||||
|
"react"
|
||||||
],
|
],
|
||||||
|
|
||||||
"globals": {
|
"globals": {
|
||||||
@ -51,7 +57,7 @@
|
|||||||
"generator-star-spacing": [2, { "before": true, "after": true }],
|
"generator-star-spacing": [2, { "before": true, "after": true }],
|
||||||
"handle-callback-err": [1, "^(err|error)$" ],
|
"handle-callback-err": [1, "^(err|error)$" ],
|
||||||
"indent": "off",
|
"indent": "off",
|
||||||
"jsx-quotes": [2, "prefer-single"],
|
"jsx-quotes": [2, "prefer-double"],
|
||||||
"key-spacing": 1,
|
"key-spacing": 1,
|
||||||
"keyword-spacing": [2, { "before": true, "after": true }],
|
"keyword-spacing": [2, { "before": true, "after": true }],
|
||||||
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||||
|
23
CHANGELOG.md
23
CHANGELOG.md
@ -2,10 +2,31 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
- Add new support for new eth_signTypedData method per EIP 712.
|
## 3.12.0 2017-10-25
|
||||||
|
|
||||||
|
- Add support for alternative ENS TLDs (Ethereum Name Service Top-Level Domains).
|
||||||
|
- Lower minimum gas price to 0.1 GWEI.
|
||||||
|
- Remove web3 injection message from production (thanks to @ChainsawBaby)
|
||||||
|
|
||||||
|
## 3.11.2 2017-10-21
|
||||||
|
|
||||||
|
- Fix bug where reject button would sometimes not work.
|
||||||
|
- Fixed bug where sometimes MetaMask's connection to a page would be unreliable.
|
||||||
|
|
||||||
|
## 3.11.1 2017-10-20
|
||||||
|
|
||||||
|
- Fix bug where log filters were not populated correctly
|
||||||
|
- Fix bug where web3 API was sometimes injected after the page loaded.
|
||||||
|
- Fix bug where first account was sometimes not selected correctly after creating or restoring a vault.
|
||||||
|
- Fix bug where imported accounts could not use new eth_signTypedData method.
|
||||||
|
|
||||||
|
## 3.11.0 2017-10-11
|
||||||
|
|
||||||
|
- Add support for new eth_signTypedData method per EIP 712.
|
||||||
- Fix bug where some transactions would be shown as pending forever, even after successfully mined.
|
- Fix bug where some transactions would be shown as pending forever, even after successfully mined.
|
||||||
- Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined.
|
- Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined.
|
||||||
- Add OS and browser version information to state log dump (for debugging purposes only).
|
- Add OS and browser version information to state log dump (for debugging purposes only).
|
||||||
|
- Fix link to support article on token addresses.
|
||||||
|
|
||||||
## 3.10.9 2017-10-5
|
## 3.10.9 2017-10-5
|
||||||
|
|
||||||
|
76
app/images/caret-right.svg
Normal file
76
app/images/caret-right.svg
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#231F20;}
|
||||||
|
.st1{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||||
|
|
||||||
|
.st2{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:25,61;}
|
||||||
|
.st3{display:none;}
|
||||||
|
.st4{display:inline;}
|
||||||
|
.st5{fill:#EC008C;}
|
||||||
|
.st6{display:inline;fill:#FFF200;}
|
||||||
|
</style>
|
||||||
|
<g id="Layer_4">
|
||||||
|
</g>
|
||||||
|
<g id="Layer_1">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M380.4,756.7c-4.5,0-9-1.7-12.4-5.1c-6.8-6.8-6.8-17.9,0-24.7L594.9,500L368,273.2
|
||||||
|
c-6.8-6.8-6.8-17.9,0-24.7c6.8-6.8,17.9-6.8,24.7,0L632,487.6c6.8,6.8,6.8,17.9,0,24.7L392.8,751.6
|
||||||
|
C389.3,755,384.9,756.7,380.4,756.7z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Layer_2" class="st3">
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "MetaMask",
|
"name": "MetaMask",
|
||||||
"short_name": "Metamask",
|
"short_name": "Metamask",
|
||||||
"version": "3.10.9",
|
"version": "3.12.0",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "Ethereum Browser Extension",
|
"description": "Ethereum Browser Extension",
|
||||||
|
@ -7,7 +7,9 @@ const ObjectMultiplex = require('obj-multiplex')
|
|||||||
const extension = require('extensionizer')
|
const extension = require('extensionizer')
|
||||||
const PortStream = require('./lib/port-stream.js')
|
const PortStream = require('./lib/port-stream.js')
|
||||||
|
|
||||||
const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString()
|
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString()
|
||||||
|
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n'
|
||||||
|
const inpageBundle = inpageContent + inpageSuffix
|
||||||
|
|
||||||
// Eventually this streaming injection could be replaced with:
|
// Eventually this streaming injection could be replaced with:
|
||||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
||||||
@ -25,8 +27,7 @@ function setupInjection () {
|
|||||||
try {
|
try {
|
||||||
// inject in-page script
|
// inject in-page script
|
||||||
var scriptTag = document.createElement('script')
|
var scriptTag = document.createElement('script')
|
||||||
scriptTag.src = extension.extension.getURL('scripts/inpage.js')
|
scriptTag.textContent = inpageBundle
|
||||||
scriptTag.textContent = inpageText
|
|
||||||
scriptTag.onload = function () { this.parentNode.removeChild(this) }
|
scriptTag.onload = function () { this.parentNode.removeChild(this) }
|
||||||
var container = document.head || document.documentElement
|
var container = document.head || document.documentElement
|
||||||
// append as first child
|
// append as first child
|
||||||
|
@ -5,7 +5,9 @@ const BN = require('ethereumjs-util').BN
|
|||||||
class BalanceController {
|
class BalanceController {
|
||||||
|
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
|
this._validateParams(opts)
|
||||||
const { address, accountTracker, txController, blockTracker } = opts
|
const { address, accountTracker, txController, blockTracker } = opts
|
||||||
|
|
||||||
this.address = address
|
this.address = address
|
||||||
this.accountTracker = accountTracker
|
this.accountTracker = accountTracker
|
||||||
this.txController = txController
|
this.txController = txController
|
||||||
@ -65,6 +67,14 @@ class BalanceController {
|
|||||||
return pending
|
return pending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_validateParams (opts) {
|
||||||
|
const { address, accountTracker, txController, blockTracker } = opts
|
||||||
|
if (!address || !accountTracker || !txController || !blockTracker) {
|
||||||
|
const error = 'Cannot construct a balance checker without address, accountTracker, txController, and blockTracker.'
|
||||||
|
throw new Error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BalanceController
|
module.exports = BalanceController
|
||||||
|
@ -20,23 +20,34 @@ class ComputedbalancesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateAllBalances () {
|
updateAllBalances () {
|
||||||
for (let address in this.accountTracker.store.getState().accounts) {
|
Object.keys(this.balances).forEach((balance) => {
|
||||||
|
const address = balance.address
|
||||||
this.balances[address].updateBalance()
|
this.balances[address].updateBalance()
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_initBalanceUpdating () {
|
_initBalanceUpdating () {
|
||||||
const store = this.accountTracker.store.getState()
|
const store = this.accountTracker.store.getState()
|
||||||
this.addAnyAccountsFromStore(store)
|
this.syncAllAccountsFromStore(store)
|
||||||
this.accountTracker.store.subscribe(this.addAnyAccountsFromStore.bind(this))
|
this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
addAnyAccountsFromStore(store) {
|
syncAllAccountsFromStore (store) {
|
||||||
const balances = store.accounts
|
const upstream = Object.keys(store.accounts)
|
||||||
|
const balances = Object.keys(this.balances)
|
||||||
|
.map(address => this.balances[address])
|
||||||
|
|
||||||
for (let address in balances) {
|
// Follow new addresses
|
||||||
|
for (const address in balances) {
|
||||||
this.trackAddressIfNotAlready(address)
|
this.trackAddressIfNotAlready(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unfollow old ones
|
||||||
|
balances.forEach(({ address }) => {
|
||||||
|
if (!upstream.includes(address)) {
|
||||||
|
delete this.balances[address]
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
trackAddressIfNotAlready (address) {
|
trackAddressIfNotAlready (address) {
|
||||||
@ -47,14 +58,14 @@ class ComputedbalancesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trackAddress (address) {
|
trackAddress (address) {
|
||||||
let updater = new BalanceController({
|
const updater = new BalanceController({
|
||||||
address,
|
address,
|
||||||
accountTracker: this.accountTracker,
|
accountTracker: this.accountTracker,
|
||||||
txController: this.txController,
|
txController: this.txController,
|
||||||
blockTracker: this.blockTracker,
|
blockTracker: this.blockTracker,
|
||||||
})
|
})
|
||||||
updater.store.subscribe((accountBalance) => {
|
updater.store.subscribe((accountBalance) => {
|
||||||
let newState = this.store.getState()
|
const newState = this.store.getState()
|
||||||
newState.computedBalances[address] = accountBalance
|
newState.computedBalances[address] = accountBalance
|
||||||
this.store.updateState(newState)
|
this.store.updateState(newState)
|
||||||
})
|
})
|
||||||
|
@ -51,6 +51,10 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookupNetwork () {
|
lookupNetwork () {
|
||||||
|
// Prevent firing when provider is not defined.
|
||||||
|
if (!this.ethQuery || !this.ethQuery.sendAsync) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||||
if (err) return this.setNetworkState('loading')
|
if (err) return this.setNetworkState('loading')
|
||||||
log.info('web3.getNetwork returned ' + network)
|
log.info('web3.getNetwork returned ' + network)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/*global Web3*/
|
/*global Web3*/
|
||||||
cleanContextForImports()
|
cleanContextForImports()
|
||||||
require('web3/dist/web3.min.js')
|
require('web3/dist/web3.min.js')
|
||||||
|
const log = require('loglevel')
|
||||||
const LocalMessageDuplexStream = require('post-message-stream')
|
const LocalMessageDuplexStream = require('post-message-stream')
|
||||||
// const PingStream = require('ping-pong-stream/ping')
|
// const PingStream = require('ping-pong-stream/ping')
|
||||||
// const endOfStream = require('end-of-stream')
|
// const endOfStream = require('end-of-stream')
|
||||||
@ -8,6 +9,10 @@ const setupDappAutoReload = require('./lib/auto-reload.js')
|
|||||||
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
|
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
|
||||||
restoreContextAfterImports()
|
restoreContextAfterImports()
|
||||||
|
|
||||||
|
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||||
|
window.log = log
|
||||||
|
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// setup plugin communication
|
// setup plugin communication
|
||||||
@ -28,9 +33,9 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
|||||||
|
|
||||||
var web3 = new Web3(inpageProvider)
|
var web3 = new Web3(inpageProvider)
|
||||||
web3.setProvider = function () {
|
web3.setProvider = function () {
|
||||||
console.log('MetaMask - overrode web3.setProvider')
|
log.debug('MetaMask - overrode web3.setProvider')
|
||||||
}
|
}
|
||||||
console.log('MetaMask - injected web3')
|
log.debug('MetaMask - injected web3')
|
||||||
// export global web3, with usage-detection
|
// export global web3, with usage-detection
|
||||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||||
|
|
||||||
@ -65,4 +70,3 @@ function restoreContextAfterImports () {
|
|||||||
console.warn('MetaMask - global.define could not be overwritten.')
|
console.warn('MetaMask - global.define could not be overwritten.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,29 @@ class AccountTracker extends EventEmitter {
|
|||||||
// public
|
// public
|
||||||
//
|
//
|
||||||
|
|
||||||
|
syncWithAddresses (addresses) {
|
||||||
|
const accounts = this.store.getState().accounts
|
||||||
|
const locals = Object.keys(accounts)
|
||||||
|
|
||||||
|
const toAdd = []
|
||||||
|
addresses.forEach((upstream) => {
|
||||||
|
if (!locals.includes(upstream)) {
|
||||||
|
toAdd.push(upstream)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const toRemove = []
|
||||||
|
locals.forEach((local) => {
|
||||||
|
if (!addresses.includes(local)) {
|
||||||
|
toRemove.push(local)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
toAdd.forEach(upstream => this.addAccount(upstream))
|
||||||
|
toRemove.forEach(local => this.removeAccount(local))
|
||||||
|
this._updateAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
addAccount (address) {
|
addAccount (address) {
|
||||||
const accounts = this.store.getState().accounts
|
const accounts = this.store.getState().accounts
|
||||||
accounts[address] = {}
|
accounts[address] = {}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
module.exports = createProviderMiddleware
|
module.exports = createProviderMiddleware
|
||||||
|
|
||||||
// forward requests to provider
|
// forward requests to provider
|
||||||
|
@ -81,14 +81,14 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
|||||||
const errorMessage = err.message.toLowerCase()
|
const errorMessage = err.message.toLowerCase()
|
||||||
const isKnownTx = (
|
const isKnownTx = (
|
||||||
// geth
|
// geth
|
||||||
errorMessage.includes('replacement transaction underpriced')
|
errorMessage.includes('replacement transaction underpriced') ||
|
||||||
|| errorMessage.includes('known transaction')
|
errorMessage.includes('known transaction') ||
|
||||||
// parity
|
// parity
|
||||||
|| errorMessage.includes('gas price too low to replace')
|
errorMessage.includes('gas price too low to replace') ||
|
||||||
|| errorMessage.includes('transaction with the same hash was already imported')
|
errorMessage.includes('transaction with the same hash was already imported') ||
|
||||||
// other
|
// other
|
||||||
|| errorMessage.includes('gateway timeout')
|
errorMessage.includes('gateway timeout') ||
|
||||||
|| errorMessage.includes('nonce too low')
|
errorMessage.includes('nonce too low')
|
||||||
)
|
)
|
||||||
// ignore resubmit warnings, return early
|
// ignore resubmit warnings, return early
|
||||||
if (isKnownTx) return
|
if (isKnownTx) return
|
||||||
|
@ -91,7 +91,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
|
|||||||
updateTx (txMeta, note) {
|
updateTx (txMeta, note) {
|
||||||
if (txMeta.txParams) {
|
if (txMeta.txParams) {
|
||||||
Object.keys(txMeta.txParams).forEach((key) => {
|
Object.keys(txMeta.txParams).forEach((key) => {
|
||||||
let value = txMeta.txParams[key]
|
const value = txMeta.txParams[key]
|
||||||
if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`)
|
if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`)
|
||||||
if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed')
|
if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed')
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const promiseToCallback = require('promise-to-callback')
|
|
||||||
const pump = require('pump')
|
const pump = require('pump')
|
||||||
const Dnode = require('dnode')
|
const Dnode = require('dnode')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
@ -96,25 +95,20 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// key mgmt
|
// key mgmt
|
||||||
this.keyringController = new KeyringController({
|
this.keyringController = new KeyringController({
|
||||||
initState: initState.KeyringController,
|
initState: initState.KeyringController,
|
||||||
accountTracker: this.accountTracker,
|
|
||||||
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
|
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
|
||||||
encryptor: opts.encryptor || undefined,
|
encryptor: opts.encryptor || undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
// If only one account exists, make sure it is selected.
|
// If only one account exists, make sure it is selected.
|
||||||
this.keyringController.store.subscribe((state) => {
|
this.keyringController.memStore.subscribe((state) => {
|
||||||
const addresses = Object.keys(state.walletNicknames || {})
|
const addresses = state.keyrings.reduce((res, keyring) => {
|
||||||
|
return res.concat(keyring.accounts)
|
||||||
|
}, [])
|
||||||
if (addresses.length === 1) {
|
if (addresses.length === 1) {
|
||||||
const address = addresses[0]
|
const address = addresses[0]
|
||||||
this.preferencesController.setSelectedAddress(address)
|
this.preferencesController.setSelectedAddress(address)
|
||||||
}
|
}
|
||||||
})
|
this.accountTracker.syncWithAddresses(addresses)
|
||||||
this.keyringController.on('newAccount', (address) => {
|
|
||||||
this.preferencesController.setSelectedAddress(address)
|
|
||||||
this.accountTracker.addAccount(address)
|
|
||||||
})
|
|
||||||
this.keyringController.on('removedAccount', (address) => {
|
|
||||||
this.accountTracker.removeAccount(address)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// address book controller
|
// address book controller
|
||||||
@ -329,13 +323,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
||||||
|
|
||||||
// primary HD keyring management
|
// primary HD keyring management
|
||||||
addNewAccount: this.addNewAccount.bind(this),
|
addNewAccount: nodeify(this.addNewAccount, this),
|
||||||
placeSeedWords: this.placeSeedWords.bind(this),
|
placeSeedWords: this.placeSeedWords.bind(this),
|
||||||
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
clearSeedWordCache: this.clearSeedWordCache.bind(this),
|
||||||
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
|
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
|
||||||
|
|
||||||
// vault management
|
// vault management
|
||||||
submitPassword: this.submitPassword.bind(this),
|
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
||||||
|
|
||||||
// network management
|
// network management
|
||||||
setProviderType: nodeify(networkController.setProviderType, networkController),
|
setProviderType: nodeify(networkController.setProviderType, networkController),
|
||||||
@ -351,8 +345,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
// KeyringController
|
// KeyringController
|
||||||
setLocked: nodeify(keyringController.setLocked, keyringController),
|
setLocked: nodeify(keyringController.setLocked, keyringController),
|
||||||
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain, keyringController),
|
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
|
||||||
createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore, keyringController),
|
createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
|
||||||
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
|
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
|
||||||
saveAccountLabel: nodeify(keyringController.saveAccountLabel, keyringController),
|
saveAccountLabel: nodeify(keyringController.saveAccountLabel, keyringController),
|
||||||
exportAccount: nodeify(keyringController.exportAccount, keyringController),
|
exportAccount: nodeify(keyringController.exportAccount, keyringController),
|
||||||
@ -473,20 +467,43 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
// Vault Management
|
// Vault Management
|
||||||
//
|
//
|
||||||
|
|
||||||
submitPassword (password, cb) {
|
async createNewVaultAndKeychain (password, cb) {
|
||||||
return this.keyringController.submitPassword(password)
|
const vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||||
.then((newState) => { cb(null, newState) })
|
this.selectFirstIdentity(vault)
|
||||||
.catch((reason) => { cb(reason) })
|
return vault
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNewVaultAndRestore (password, seed, cb) {
|
||||||
|
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||||
|
this.selectFirstIdentity(vault)
|
||||||
|
return vault
|
||||||
|
}
|
||||||
|
|
||||||
|
selectFirstIdentity (vault) {
|
||||||
|
const { identities } = vault
|
||||||
|
const address = Object.keys(identities)[0]
|
||||||
|
this.preferencesController.setSelectedAddress(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Opinionated Keyring Management
|
// Opinionated Keyring Management
|
||||||
//
|
//
|
||||||
|
|
||||||
addNewAccount (cb) {
|
async addNewAccount (cb) {
|
||||||
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
|
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
|
||||||
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
|
if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found'))
|
||||||
promiseToCallback(this.keyringController.addNewAccount(primaryKeyring))(cb)
|
const keyringController = this.keyringController
|
||||||
|
const oldAccounts = await keyringController.getAccounts()
|
||||||
|
const keyState = await keyringController.addNewAccount(primaryKeyring)
|
||||||
|
const newAccounts = await keyringController.getAccounts()
|
||||||
|
|
||||||
|
newAccounts.forEach((address) => {
|
||||||
|
if (!oldAccounts.includes(address)) {
|
||||||
|
this.preferencesController.setSelectedAddress(address)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return keyState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds the current vault's seed words to the UI's state tree.
|
// Adds the current vault's seed words to the UI's state tree.
|
||||||
|
16
gulpfile.js
16
gulpfile.js
@ -161,6 +161,13 @@ gulp.task('lint', function () {
|
|||||||
.pipe(eslint.failAfterError())
|
.pipe(eslint.failAfterError())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('lint:fix', function () {
|
||||||
|
return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js'])
|
||||||
|
.pipe(eslint(Object.assign(fs.readFileSync(path.join(__dirname, '.eslintrc')), {fix: true})))
|
||||||
|
.pipe(eslint.format())
|
||||||
|
.pipe(eslint.failAfterError())
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
gulp.task('default', ['lint'], function () {
|
gulp.task('default', ['lint'], function () {
|
||||||
// This will only run if the lint task is successful...
|
// This will only run if the lint task is successful...
|
||||||
@ -186,8 +193,13 @@ jsFiles.forEach((jsFile) => {
|
|||||||
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, label: jsFile, filename: `${jsFile}.js` }))
|
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, label: jsFile, filename: `${jsFile}.js` }))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('dev:js', gulp.parallel(...jsDevStrings))
|
// inpage must be built before all other scripts:
|
||||||
gulp.task('build:js', gulp.parallel(...jsBuildStrings))
|
const firstDevString = jsDevStrings.shift()
|
||||||
|
gulp.task('dev:js', gulp.series(firstDevString, gulp.parallel(...jsDevStrings)))
|
||||||
|
|
||||||
|
// inpage must be built before all other scripts:
|
||||||
|
const firstBuildString = jsBuildStrings.shift()
|
||||||
|
gulp.task('build:js', gulp.series(firstBuildString, gulp.parallel(...jsBuildStrings)))
|
||||||
|
|
||||||
// disc bundle analyzer tasks
|
// disc bundle analyzer tasks
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const path = require('path')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const createBundle = require('./util').createBundle
|
const createBundle = require('./util').createBundle
|
||||||
const serveBundle = require('./util').serveBundle
|
const serveBundle = require('./util').serveBundle
|
||||||
@ -8,22 +9,22 @@ module.exports = createMetamascaraServer
|
|||||||
function createMetamascaraServer () {
|
function createMetamascaraServer () {
|
||||||
|
|
||||||
// start bundlers
|
// start bundlers
|
||||||
const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js')
|
const metamascaraBundle = createBundle(path.join(__dirname, '/../src/mascara.js'))
|
||||||
const proxyBundle = createBundle(__dirname + '/../src/proxy.js')
|
const proxyBundle = createBundle(path.join(__dirname, '/../src/proxy.js'))
|
||||||
const uiBundle = createBundle(__dirname + '/../src/ui.js')
|
const uiBundle = createBundle(path.join(__dirname, '/../src/ui.js'))
|
||||||
const backgroundBuild = createBundle(__dirname + '/../src/background.js')
|
const backgroundBuild = createBundle(path.join(__dirname, '/../src/background.js'))
|
||||||
|
|
||||||
// serve bundles
|
// serve bundles
|
||||||
const server = express()
|
const server = express()
|
||||||
// ui window
|
// ui window
|
||||||
serveBundle(server, '/ui.js', uiBundle)
|
serveBundle(server, '/ui.js', uiBundle)
|
||||||
server.use(express.static(__dirname + '/../ui/'))
|
server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))
|
||||||
server.use(express.static(__dirname + '/../../dist/chrome'))
|
server.use(express.static(path.join(__dirname, '/../../dist/chrome')))
|
||||||
// metamascara
|
// metamascara
|
||||||
serveBundle(server, '/metamascara.js', metamascaraBundle)
|
serveBundle(server, '/metamascara.js', metamascaraBundle)
|
||||||
// proxy
|
// proxy
|
||||||
serveBundle(server, '/proxy/proxy.js', proxyBundle)
|
serveBundle(server, '/proxy/proxy.js', proxyBundle)
|
||||||
server.use('/proxy/', express.static(__dirname + '/../proxy'))
|
server.use('/proxy/', express.static(path.join(__dirname, '/../proxy')))
|
||||||
// background
|
// background
|
||||||
serveBundle(server, '/background.js', backgroundBuild)
|
serveBundle(server, '/background.js', backgroundBuild)
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ function createBundle (entryPoint) {
|
|||||||
cache: {},
|
cache: {},
|
||||||
packageCache: {},
|
packageCache: {},
|
||||||
plugin: [watchify],
|
plugin: [watchify],
|
||||||
})
|
}).transform('babelify')
|
||||||
|
|
||||||
bundler.on('update', bundle)
|
bundler.on('update', bundle)
|
||||||
bundle()
|
bundle()
|
||||||
|
254
mascara/src/app/first-time/backup-phrase-screen.js
Normal file
254
mascara/src/app/first-time/backup-phrase-screen.js
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import shuffle from 'lodash.shuffle'
|
||||||
|
import {compose, onlyUpdateForPropTypes} from 'recompose'
|
||||||
|
import Identicon from '../../../../ui/app/components/identicon'
|
||||||
|
import {confirmSeedWords} from '../../../../ui/app/actions'
|
||||||
|
import Breadcrumbs from './breadcrumbs'
|
||||||
|
import LoadingScreen from './loading-screen'
|
||||||
|
|
||||||
|
const LockIcon = props => (
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Capa_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="401.998px"
|
||||||
|
height="401.998px"
|
||||||
|
viewBox="0 0 401.998 401.998"
|
||||||
|
style={{enableBackground: 'new 0 0 401.998 401.998'}}
|
||||||
|
xmlSpace="preserve"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
|
||||||
|
C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
|
||||||
|
h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
|
||||||
|
c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
|
||||||
|
C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
|
||||||
|
c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
|
||||||
|
z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
class BackupPhraseScreen extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
address: PropTypes.string.isRequired,
|
||||||
|
seedWords: PropTypes.string.isRequired,
|
||||||
|
next: PropTypes.func.isRequired,
|
||||||
|
confirmSeedWords: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
seedWords: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
static PAGE = {
|
||||||
|
SECRET: 'secret',
|
||||||
|
CONFIRM: 'confirm'
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
const {seedWords} = props
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
isShowingSecret: false,
|
||||||
|
page: BackupPhraseScreen.PAGE.SECRET,
|
||||||
|
selectedSeeds: [],
|
||||||
|
shuffledSeeds: seedWords && shuffle(seedWords.split(' ')),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSecretWordsContainer () {
|
||||||
|
const { isShowingSecret } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="backup-phrase__secret">
|
||||||
|
<div className={classnames('backup-phrase__secret-words', {
|
||||||
|
'backup-phrase__secret-words--hidden': !isShowingSecret
|
||||||
|
})}>
|
||||||
|
{this.props.seedWords}
|
||||||
|
</div>
|
||||||
|
{!isShowingSecret && (
|
||||||
|
<div className="backup-phrase__secret-blocker">
|
||||||
|
<LockIcon width="28px" height="35px" fill="#FFFFFF" />
|
||||||
|
<button
|
||||||
|
className="backup-phrase__reveal-button"
|
||||||
|
onClick={() => this.setState({ isShowingSecret: true })}
|
||||||
|
>
|
||||||
|
Click here to reveal secret words
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSecretScreen() {
|
||||||
|
const { isShowingSecret } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="backup-phrase__content-wrapper">
|
||||||
|
<div>
|
||||||
|
<div className="backup-phrase__title">Secret Backup Phrase</div>
|
||||||
|
<div className="backup-phrase__body-text">
|
||||||
|
Your secret backup phrase makes it easy to back up and restore your account.
|
||||||
|
</div>
|
||||||
|
<div className="backup-phrase__body-text">
|
||||||
|
WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever.
|
||||||
|
</div>
|
||||||
|
{this.renderSecretWordsContainer()}
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
onClick={() => isShowingSecret && this.setState({
|
||||||
|
isShowingSecret: false,
|
||||||
|
page: BackupPhraseScreen.PAGE.CONFIRM
|
||||||
|
})}
|
||||||
|
disabled={!isShowingSecret}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
<Breadcrumbs total={3} currentIndex={1} />
|
||||||
|
</div>
|
||||||
|
<div className="backup-phrase__tips">
|
||||||
|
<div className="backup-phrase__tips-text">Tips:</div>
|
||||||
|
<div className="backup-phrase__tips-text">
|
||||||
|
Store this phrase in a password manager like 1password.
|
||||||
|
</div>
|
||||||
|
<div className="backup-phrase__tips-text">
|
||||||
|
Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
|
||||||
|
</div>
|
||||||
|
<div className="backup-phrase__tips-text">
|
||||||
|
Memorize this phrase.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConfirmationScreen() {
|
||||||
|
const { seedWords, confirmSeedWords, next } = this.props;
|
||||||
|
const { selectedSeeds, shuffledSeeds } = this.state;
|
||||||
|
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="backup-phrase__content-wrapper">
|
||||||
|
<div>
|
||||||
|
<div className="backup-phrase__title">Confirm your Secret Backup Phrase</div>
|
||||||
|
<div className="backup-phrase__body-text">
|
||||||
|
Please select each phrase in order to make sure it is correct.
|
||||||
|
</div>
|
||||||
|
<div className="backup-phrase__confirm-secret">
|
||||||
|
{selectedSeeds.map(([_, word], i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
className="backup-phrase__confirm-seed-option"
|
||||||
|
>
|
||||||
|
{word}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="backup-phrase__confirm-seed-options">
|
||||||
|
{shuffledSeeds.map((word, i) => {
|
||||||
|
const isSelected = selectedSeeds
|
||||||
|
.filter(([index, seed]) => seed === word && index === i)
|
||||||
|
.length
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
className={classnames('backup-phrase__confirm-seed-option', {
|
||||||
|
'backup-phrase__confirm-seed-option--selected': isSelected
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isSelected) {
|
||||||
|
this.setState({
|
||||||
|
selectedSeeds: [...selectedSeeds, [i, word]]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
selectedSeeds: selectedSeeds
|
||||||
|
.filter(([index, seed]) => !(seed === word && index === i))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{word}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
onClick={() => isValid && confirmSeedWords().then(next)}
|
||||||
|
disabled={!isValid}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBack () {
|
||||||
|
return this.state.page === BackupPhraseScreen.PAGE.CONFIRM
|
||||||
|
? (
|
||||||
|
<a
|
||||||
|
className="backup-phrase__back-button"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
page: BackupPhraseScreen.PAGE.SECRET
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
{`< Back`}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent () {
|
||||||
|
switch (this.state.page) {
|
||||||
|
case BackupPhraseScreen.PAGE.CONFIRM:
|
||||||
|
return this.renderConfirmationScreen()
|
||||||
|
case BackupPhraseScreen.PAGE.SECRET:
|
||||||
|
default:
|
||||||
|
return this.renderSecretScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return this.props.isLoading
|
||||||
|
? <LoadingScreen loadingMessage="Creating your new account" />
|
||||||
|
: (
|
||||||
|
<div className="backup-phrase">
|
||||||
|
{this.renderBack()}
|
||||||
|
<Identicon address={this.props.address} diameter={70} />
|
||||||
|
{this.renderContent()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
onlyUpdateForPropTypes,
|
||||||
|
connect(
|
||||||
|
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
|
||||||
|
seedWords,
|
||||||
|
isLoading,
|
||||||
|
address: selectedAddress,
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
confirmSeedWords: () => dispatch(confirmSeedWords()),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)(BackupPhraseScreen)
|
25
mascara/src/app/first-time/breadcrumbs.js
Normal file
25
mascara/src/app/first-time/breadcrumbs.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
|
||||||
|
export default class Breadcrumbs extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
total: PropTypes.number,
|
||||||
|
currentIndex: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {total, currentIndex} = this.props
|
||||||
|
return (
|
||||||
|
<div className="breadcrumbs">
|
||||||
|
{Array(total).fill().map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="breadcrumb"
|
||||||
|
style={{backgroundColor: i === currentIndex ? '#D8D8D8' : '#FFFFFF'}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
199
mascara/src/app/first-time/buy-ether-screen.js
Normal file
199
mascara/src/app/first-time/buy-ether-screen.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import {qrcode} from 'qrcode-npm'
|
||||||
|
import copyToClipboard from 'copy-to-clipboard'
|
||||||
|
import ShapeShiftForm from '../shapeshift-form'
|
||||||
|
import Identicon from '../../../../ui/app/components/identicon'
|
||||||
|
import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
|
||||||
|
|
||||||
|
class BuyEtherScreen extends Component {
|
||||||
|
static OPTION_VALUES = {
|
||||||
|
COINBASE: 'coinbase',
|
||||||
|
SHAPESHIFT: 'shapeshift',
|
||||||
|
QR_CODE: 'qr_code',
|
||||||
|
};
|
||||||
|
|
||||||
|
static OPTIONS = [
|
||||||
|
{
|
||||||
|
name: 'Direct Deposit',
|
||||||
|
value: BuyEtherScreen.OPTION_VALUES.QR_CODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Buy with Dollars',
|
||||||
|
value: BuyEtherScreen.OPTION_VALUES.COINBASE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Buy with Cryptos',
|
||||||
|
value: BuyEtherScreen.OPTION_VALUES.SHAPESHIFT,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
address: PropTypes.string,
|
||||||
|
goToCoinbase: PropTypes.func.isRequired,
|
||||||
|
showAccountDetail: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selectedOption: BuyEtherScreen.OPTION_VALUES.QR_CODE,
|
||||||
|
justCopied: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard = () => {
|
||||||
|
const { address } = this.props
|
||||||
|
|
||||||
|
this.setState({ justCopied: true }, () => copyToClipboard(address))
|
||||||
|
|
||||||
|
setTimeout(() => this.setState({ justCopied: false }), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSkip () {
|
||||||
|
const {showAccountDetail, address} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='buy-ether__do-it-later'
|
||||||
|
onClick={() => showAccountDetail(address)}
|
||||||
|
>
|
||||||
|
Do it later
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCoinbaseLogo () {
|
||||||
|
return (
|
||||||
|
<svg width='140px' height='49px' viewBox='0 0 579 126' version='1.1'>
|
||||||
|
<g id='Page-1' stroke='none' strokeWidth={1} fill='none' fillRule='evenodd'>
|
||||||
|
<g id='Imported-Layers' fill='#0081C9'>
|
||||||
|
<path d='M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873' id='Fill-1' />
|
||||||
|
<path d='M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z' id='Fill-2' />
|
||||||
|
<path d='M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z' id='Fill-3' />
|
||||||
|
<path d='M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137' id='Fill-4' />
|
||||||
|
<path d='M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z' id='Fill-5' />
|
||||||
|
<path d='M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z' id='Fill-6' />
|
||||||
|
<path d='M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873' id='Fill-7' />
|
||||||
|
<path d='M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z' id='Fill-8' />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCoinbaseForm () {
|
||||||
|
const {goToCoinbase, address} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='buy-ether__action-content-wrapper'>
|
||||||
|
<div>{this.renderCoinbaseLogo()}</div>
|
||||||
|
<div className='buy-ether__body-text'>Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
|
||||||
|
<a className='first-time-flow__link buy-ether__faq-link'>What is Ethereum?</a>
|
||||||
|
<div className='buy-ether__buttons'>
|
||||||
|
<button
|
||||||
|
className='first-time-flow__button'
|
||||||
|
onClick={() => goToCoinbase(address)}
|
||||||
|
>
|
||||||
|
Buy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent () {
|
||||||
|
const { OPTION_VALUES } = BuyEtherScreen
|
||||||
|
const { address } = this.props
|
||||||
|
const { justCopied } = this.state
|
||||||
|
const qrImage = qrcode(4, 'M')
|
||||||
|
qrImage.addData(address)
|
||||||
|
qrImage.make()
|
||||||
|
|
||||||
|
switch (this.state.selectedOption) {
|
||||||
|
case OPTION_VALUES.COINBASE:
|
||||||
|
return this.renderCoinbaseForm()
|
||||||
|
case OPTION_VALUES.SHAPESHIFT:
|
||||||
|
return (
|
||||||
|
<div className='buy-ether__action-content-wrapper'>
|
||||||
|
<div className='shapeshift-logo' />
|
||||||
|
<div className='buy-ether__body-text'>
|
||||||
|
Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
|
||||||
|
</div>
|
||||||
|
<ShapeShiftForm btnClass='first-time-flow__button' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
case OPTION_VALUES.QR_CODE:
|
||||||
|
return (
|
||||||
|
<div className='buy-ether__action-content-wrapper'>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
|
||||||
|
<div className='buy-ether__body-text'>Deposit Ether directly into your account.</div>
|
||||||
|
<div className='buy-ether__small-body-text'>(This is the account address that MetaMask created for you to recieve funds.)</div>
|
||||||
|
<div className='buy-ether__buttons'>
|
||||||
|
<button
|
||||||
|
className='first-time-flow__button'
|
||||||
|
onClick={this.copyToClipboard}
|
||||||
|
disabled={justCopied}
|
||||||
|
>
|
||||||
|
{ justCopied ? 'Copied' : 'Copy' }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { OPTIONS } = BuyEtherScreen
|
||||||
|
const { selectedOption } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='buy-ether'>
|
||||||
|
<Identicon address={this.props.address} diameter={70} />
|
||||||
|
<div className='buy-ether__title'>Deposit Ether</div>
|
||||||
|
<div className='buy-ether__body-text'>
|
||||||
|
MetaMask works best if you have Ether in your account to pay for transaction gas fees and more. To get Ether, choose from one of these methods.
|
||||||
|
</div>
|
||||||
|
<div className='buy-ether__content-wrapper'>
|
||||||
|
<div className='buy-ether__content-headline-wrapper'>
|
||||||
|
<div className='buy-ether__content-headline'>Deposit Options</div>
|
||||||
|
{this.renderSkip()}
|
||||||
|
</div>
|
||||||
|
<div className='buy-ether__content'>
|
||||||
|
<div className='buy-ether__side-panel'>
|
||||||
|
{OPTIONS.map(({ name, value }) => (
|
||||||
|
<div
|
||||||
|
key={value}
|
||||||
|
className={classnames('buy-ether__side-panel-item', {
|
||||||
|
'buy-ether__side-panel-item--selected': value === selectedOption,
|
||||||
|
})}
|
||||||
|
onClick={() => this.setState({ selectedOption: value })}
|
||||||
|
>
|
||||||
|
<div className='buy-ether__side-panel-item-name'>{name}</div>
|
||||||
|
{value === selectedOption && (
|
||||||
|
<svg viewBox='0 0 574 1024' id='si-ant-right' width='15px' height='15px'>
|
||||||
|
<path d='M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z' />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='buy-ether__action-content'>
|
||||||
|
{this.renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ metamask: { selectedAddress } }) => ({
|
||||||
|
address: selectedAddress,
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
|
||||||
|
showAccountDetail: address => dispatch(showAccountDetail(address)),
|
||||||
|
})
|
||||||
|
)(BuyEtherScreen)
|
109
mascara/src/app/first-time/create-password-screen.js
Normal file
109
mascara/src/app/first-time/create-password-screen.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {createNewVaultAndKeychain} from '../../../../ui/app/actions'
|
||||||
|
import LoadingScreen from './loading-screen'
|
||||||
|
import Breadcrumbs from './breadcrumbs'
|
||||||
|
|
||||||
|
class CreatePasswordScreen extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
createAccount: PropTypes.func.isRequired,
|
||||||
|
goToImportWithSeedPhrase: PropTypes.func.isRequired,
|
||||||
|
goToImportAccount: PropTypes.func.isRequired,
|
||||||
|
next: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
const {password, confirmPassword} = this.state;
|
||||||
|
|
||||||
|
if (!password || !confirmPassword) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return password === confirmPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
createAccount = () => {
|
||||||
|
if (!this.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {password} = this.state;
|
||||||
|
const {createAccount, next} = this.props;
|
||||||
|
|
||||||
|
createAccount(password)
|
||||||
|
.then(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isLoading, goToImportAccount, goToImportWithSeedPhrase } = this.props
|
||||||
|
|
||||||
|
return isLoading
|
||||||
|
? <LoadingScreen loadingMessage="Creating your new account" />
|
||||||
|
: (
|
||||||
|
<div className="create-password">
|
||||||
|
<div className="create-password__title">
|
||||||
|
Create Password
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
className="first-time-flow__input"
|
||||||
|
type="password"
|
||||||
|
placeholder="New Password (min 8 characters)"
|
||||||
|
onChange={e => this.setState({password: e.target.value})}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="first-time-flow__input create-password__confirm-input"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
onChange={e => this.setState({confirmPassword: e.target.value})}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
disabled={!this.isValid()}
|
||||||
|
onClick={this.createAccount}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
className="first-time-flow__link create-password__import-link"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault()
|
||||||
|
goToImportWithSeedPhrase()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Import with seed phrase
|
||||||
|
</a>
|
||||||
|
{ /* }
|
||||||
|
<a
|
||||||
|
href=""
|
||||||
|
className="first-time-flow__link create-password__import-link"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault()
|
||||||
|
goToImportAccount()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Import an account
|
||||||
|
</a>
|
||||||
|
{ */ }
|
||||||
|
<Breadcrumbs total={3} currentIndex={0} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ appState: { isLoading } }) => ({ isLoading }),
|
||||||
|
dispatch => ({
|
||||||
|
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
|
||||||
|
})
|
||||||
|
)(CreatePasswordScreen)
|
203
mascara/src/app/first-time/import-account-screen.js
Normal file
203
mascara/src/app/first-time/import-account-screen.js
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import LoadingScreen from './loading-screen'
|
||||||
|
import {importNewAccount, hideWarning} from '../../../../ui/app/actions'
|
||||||
|
|
||||||
|
const Input = ({ label, placeholder, onChange, errorMessage, type = 'text' }) => (
|
||||||
|
<div className="import-account__input-wrapper">
|
||||||
|
<div className="import-account__input-label">{label}</div>
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={classnames('first-time-flow__input import-account__input', {
|
||||||
|
'first-time-flow__input--error': errorMessage,
|
||||||
|
})}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<div className="import-account__input-error-message">{errorMessage}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
Input.prototype.propTypes = {
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
placeholder: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
errorMessage: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImportAccountScreen extends Component {
|
||||||
|
static OPTIONS = {
|
||||||
|
PRIVATE_KEY: 'private_key',
|
||||||
|
JSON_FILE: 'json_file',
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
warning: PropTypes.string,
|
||||||
|
back: PropTypes.func.isRequired,
|
||||||
|
next: PropTypes.func.isRequired,
|
||||||
|
importNewAccount: PropTypes.func.isRequired,
|
||||||
|
hideWarning: PropTypes.func.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
selectedOption: ImportAccountScreen.OPTIONS.PRIVATE_KEY,
|
||||||
|
privateKey: '',
|
||||||
|
jsonFile: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid () {
|
||||||
|
const { OPTIONS } = ImportAccountScreen
|
||||||
|
const { privateKey, jsonFile, password } = this.state
|
||||||
|
|
||||||
|
switch (this.state.selectedOption) {
|
||||||
|
case OPTIONS.JSON_FILE:
|
||||||
|
return Boolean(jsonFile && password)
|
||||||
|
case OPTIONS.PRIVATE_KEY:
|
||||||
|
default:
|
||||||
|
return Boolean(privateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = () => {
|
||||||
|
const { OPTIONS } = ImportAccountScreen
|
||||||
|
const { importNewAccount, next } = this.props
|
||||||
|
const { privateKey, jsonFile, password } = this.state
|
||||||
|
|
||||||
|
switch (this.state.selectedOption) {
|
||||||
|
case OPTIONS.JSON_FILE:
|
||||||
|
return importNewAccount('JSON File', [ jsonFile, password ])
|
||||||
|
.then(next)
|
||||||
|
case OPTIONS.PRIVATE_KEY:
|
||||||
|
default:
|
||||||
|
return importNewAccount('Private Key', [ privateKey ])
|
||||||
|
.then(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPrivateKey () {
|
||||||
|
return Input({
|
||||||
|
label: 'Add Private Key String',
|
||||||
|
placeholder: 'Enter private key',
|
||||||
|
onChange: e => this.setState({ privateKey: e.target.value }),
|
||||||
|
errorMessage: this.props.warning && 'Something went wrong. Please make sure your private key is correct.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderJsonFile () {
|
||||||
|
const { jsonFile: { name } } = this.state
|
||||||
|
const { warning } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<div className="import-account__input-wrapper">
|
||||||
|
<div className="import-account__input-label">Upload File</div>
|
||||||
|
<div className="import-account__file-picker-wrapper">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="file"
|
||||||
|
className="import-account__file-input"
|
||||||
|
onChange={e => this.setState({ jsonFile: e.target.files[0] })}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="file"
|
||||||
|
className={classnames('import-account__file-input-label', {
|
||||||
|
'import-account__file-input-label--error': warning,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Choose File
|
||||||
|
</label>
|
||||||
|
<div className="import-account__file-name">{name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="import-account__input-error-message">
|
||||||
|
{warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{Input({
|
||||||
|
label: 'Enter Password',
|
||||||
|
placeholder: 'Enter Password',
|
||||||
|
type: 'password',
|
||||||
|
onChange: e => this.setState({ password: e.target.value }),
|
||||||
|
errorMessage: warning && 'Please make sure your password is correct.',
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent () {
|
||||||
|
const { OPTIONS } = ImportAccountScreen
|
||||||
|
|
||||||
|
switch (this.state.selectedOption) {
|
||||||
|
case OPTIONS.JSON_FILE:
|
||||||
|
return this.renderJsonFile()
|
||||||
|
case OPTIONS.PRIVATE_KEY:
|
||||||
|
default:
|
||||||
|
return this.renderPrivateKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { OPTIONS } = ImportAccountScreen
|
||||||
|
const { selectedOption } = this.state
|
||||||
|
|
||||||
|
return this.props.isLoading
|
||||||
|
? <LoadingScreen loadingMessage="Creating your new account" />
|
||||||
|
: (
|
||||||
|
<div className="import-account">
|
||||||
|
<a
|
||||||
|
className="import-account__back-button"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.back()
|
||||||
|
}}
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
{`< Back`}
|
||||||
|
</a>
|
||||||
|
<div className="import-account__title">
|
||||||
|
Import an Account
|
||||||
|
</div>
|
||||||
|
<div className="import-account__selector-label">
|
||||||
|
How would you like to import your account?
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className="import-account__dropdown"
|
||||||
|
value={selectedOption}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ selectedOption: e.target.value })
|
||||||
|
this.props.hideWarning()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={OPTIONS.PRIVATE_KEY}>Private Key</option>
|
||||||
|
<option value={OPTIONS.JSON_FILE}>JSON File</option>
|
||||||
|
</select>
|
||||||
|
{this.renderContent()}
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
disabled={!this.isValid()}
|
||||||
|
onClick={this.onClick}
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href="https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file"
|
||||||
|
className="first-time-flow__link import-account__faq-link"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
File import not working?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ appState: { isLoading, warning } }) => ({ isLoading, warning }),
|
||||||
|
dispatch => ({
|
||||||
|
importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args)),
|
||||||
|
hideWarning: () => dispatch(hideWarning()),
|
||||||
|
})
|
||||||
|
)(ImportAccountScreen)
|
109
mascara/src/app/first-time/import-seed-phrase-screen.js
Normal file
109
mascara/src/app/first-time/import-seed-phrase-screen.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import LoadingScreen from './loading-screen'
|
||||||
|
import {createNewVaultAndRestore, hideWarning, displayWarning} from '../../../../ui/app/actions'
|
||||||
|
|
||||||
|
class ImportSeedPhraseScreen extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
warning: PropTypes.string,
|
||||||
|
back: PropTypes.func.isRequired,
|
||||||
|
next: PropTypes.func.isRequired,
|
||||||
|
createNewVaultAndRestore: PropTypes.func.isRequired,
|
||||||
|
hideWarning: PropTypes.func.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
displayWarning: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
seedPhrase: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = () => {
|
||||||
|
const { password, seedPhrase, confirmPassword } = this.state
|
||||||
|
const { createNewVaultAndRestore, next, displayWarning } = this.props
|
||||||
|
|
||||||
|
if (seedPhrase.split(' ').length !== 12) {
|
||||||
|
this.warning = 'Seed Phrases are 12 words long'
|
||||||
|
displayWarning(this.warning)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 8) {
|
||||||
|
this.warning = 'Passwords require a mimimum length of 8'
|
||||||
|
displayWarning(this.warning)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
this.warning = 'Confirmed password does not match'
|
||||||
|
displayWarning(this.warning)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.warning = null
|
||||||
|
createNewVaultAndRestore(password, seedPhrase)
|
||||||
|
.then(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return this.props.isLoading
|
||||||
|
? <LoadingScreen loadingMessage="Creating your new account" />
|
||||||
|
: (
|
||||||
|
<div className="import-account">
|
||||||
|
<a
|
||||||
|
className="import-account__back-button"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.back()
|
||||||
|
}}
|
||||||
|
href="#"
|
||||||
|
>
|
||||||
|
{`< Back`}
|
||||||
|
</a>
|
||||||
|
<div className="import-account__title">
|
||||||
|
Import an Account with Seed Phrase
|
||||||
|
</div>
|
||||||
|
<div className="import-account__selector-label">
|
||||||
|
Enter your secret twelve word phrase here to restore your vault.
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
className="import-account__secret-phrase"
|
||||||
|
onChange={e => this.setState({seedPhrase: e.target.value})}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="error"
|
||||||
|
>
|
||||||
|
{this.props.warning}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
className="first-time-flow__input"
|
||||||
|
type="password"
|
||||||
|
placeholder="New Password (min 8 characters)"
|
||||||
|
onChange={e => this.setState({password: e.target.value})}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="first-time-flow__input create-password__confirm-input"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
onChange={e => this.setState({confirmPassword: e.target.value})}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
onClick={this.onClick}
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ appState: { isLoading, warning } }) => ({ isLoading, warning }),
|
||||||
|
dispatch => ({
|
||||||
|
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
|
||||||
|
displayWarning: (warning) => dispatch(displayWarning(warning)),
|
||||||
|
hideWarning: () => dispatch(hideWarning()),
|
||||||
|
})
|
||||||
|
)(ImportSeedPhraseScreen)
|
750
mascara/src/app/first-time/index.css
Normal file
750
mascara/src/app/first-time/index.css
Normal file
File diff suppressed because one or more lines are too long
142
mascara/src/app/first-time/index.js
Normal file
142
mascara/src/app/first-time/index.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import CreatePasswordScreen from './create-password-screen'
|
||||||
|
import UniqueImageScreen from './unique-image-screen'
|
||||||
|
import NoticeScreen from './notice-screen'
|
||||||
|
import BackupPhraseScreen from './backup-phrase-screen'
|
||||||
|
import ImportAccountScreen from './import-account-screen'
|
||||||
|
import ImportSeedPhraseScreen from './import-seed-phrase-screen'
|
||||||
|
import {onboardingBuyEthView} from '../../../../ui/app/actions'
|
||||||
|
|
||||||
|
class FirstTimeFlow extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
isInitialized: PropTypes.bool,
|
||||||
|
seedWords: PropTypes.string,
|
||||||
|
address: PropTypes.string,
|
||||||
|
noActiveNotices: PropTypes.bool,
|
||||||
|
goToBuyEtherView: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
isInitialized: false,
|
||||||
|
seedWords: '',
|
||||||
|
noActiveNotices: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static SCREEN_TYPE = {
|
||||||
|
CREATE_PASSWORD: 'create_password',
|
||||||
|
IMPORT_ACCOUNT: 'import_account',
|
||||||
|
IMPORT_SEED_PHRASE: 'import_seed_phrase',
|
||||||
|
UNIQUE_IMAGE: 'unique_image',
|
||||||
|
NOTICE: 'notice',
|
||||||
|
BACK_UP_PHRASE: 'back_up_phrase',
|
||||||
|
CONFIRM_BACK_UP_PHRASE: 'confirm_back_up_phrase',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
screenType: this.getScreenType(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setScreenType (screenType) {
|
||||||
|
this.setState({ screenType })
|
||||||
|
}
|
||||||
|
|
||||||
|
getScreenType () {
|
||||||
|
const {
|
||||||
|
isInitialized,
|
||||||
|
seedWords,
|
||||||
|
noActiveNotices,
|
||||||
|
} = this.props
|
||||||
|
const {SCREEN_TYPE} = FirstTimeFlow
|
||||||
|
|
||||||
|
// return SCREEN_TYPE.NOTICE
|
||||||
|
|
||||||
|
if (!isInitialized) {
|
||||||
|
return SCREEN_TYPE.CREATE_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noActiveNotices) {
|
||||||
|
return SCREEN_TYPE.NOTICE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seedWords) {
|
||||||
|
return SCREEN_TYPE.BACK_UP_PHRASE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderScreen () {
|
||||||
|
const {SCREEN_TYPE} = FirstTimeFlow
|
||||||
|
const {goToBuyEtherView, address} = this.props
|
||||||
|
|
||||||
|
switch (this.state.screenType) {
|
||||||
|
case SCREEN_TYPE.CREATE_PASSWORD:
|
||||||
|
return (
|
||||||
|
<CreatePasswordScreen
|
||||||
|
next={() => this.setScreenType(SCREEN_TYPE.UNIQUE_IMAGE)}
|
||||||
|
goToImportAccount={() => this.setScreenType(SCREEN_TYPE.IMPORT_ACCOUNT)}
|
||||||
|
goToImportWithSeedPhrase={() => this.setScreenType(SCREEN_TYPE.IMPORT_SEED_PHRASE)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case SCREEN_TYPE.IMPORT_ACCOUNT:
|
||||||
|
return (
|
||||||
|
<ImportAccountScreen
|
||||||
|
back={() => this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)}
|
||||||
|
next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case SCREEN_TYPE.IMPORT_SEED_PHRASE:
|
||||||
|
return (
|
||||||
|
<ImportSeedPhraseScreen
|
||||||
|
back={() => this.setScreenType(SCREEN_TYPE.CREATE_PASSWORD)}
|
||||||
|
next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case SCREEN_TYPE.UNIQUE_IMAGE:
|
||||||
|
return (
|
||||||
|
<UniqueImageScreen
|
||||||
|
next={() => this.setScreenType(SCREEN_TYPE.NOTICE)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case SCREEN_TYPE.NOTICE:
|
||||||
|
return (
|
||||||
|
<NoticeScreen
|
||||||
|
next={() => this.setScreenType(SCREEN_TYPE.BACK_UP_PHRASE)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case SCREEN_TYPE.BACK_UP_PHRASE:
|
||||||
|
return (
|
||||||
|
<BackupPhraseScreen
|
||||||
|
next={() => goToBuyEtherView(address)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return <noscript />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className="first-time-flow">
|
||||||
|
{this.renderScreen()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ metamask: { isInitialized, seedWords, noActiveNotices, selectedAddress } }) => ({
|
||||||
|
isInitialized,
|
||||||
|
seedWords,
|
||||||
|
noActiveNotices,
|
||||||
|
address: selectedAddress,
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
goToBuyEtherView: address => dispatch(onboardingBuyEthView(address)),
|
||||||
|
})
|
||||||
|
)(FirstTimeFlow)
|
||||||
|
|
11
mascara/src/app/first-time/loading-screen.js
Normal file
11
mascara/src/app/first-time/loading-screen.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import Spinner from './spinner'
|
||||||
|
|
||||||
|
export default function LoadingScreen({ className = '', loadingMessage }) {
|
||||||
|
return (
|
||||||
|
<div className={`${className} loading-screen`}>
|
||||||
|
<Spinner color="#1B344D" />
|
||||||
|
<div className="loading-screen__message">{loadingMessage}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
92
mascara/src/app/first-time/notice-screen.js
Normal file
92
mascara/src/app/first-time/notice-screen.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import Markdown from 'react-markdown'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import debounce from 'lodash.debounce'
|
||||||
|
import {markNoticeRead} from '../../../../ui/app/actions'
|
||||||
|
import Identicon from '../../../../ui/app/components/identicon'
|
||||||
|
import Breadcrumbs from './breadcrumbs'
|
||||||
|
|
||||||
|
class NoticeScreen extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
address: PropTypes.string.isRequired,
|
||||||
|
lastUnreadNotice: PropTypes.shape({
|
||||||
|
title: PropTypes.string,
|
||||||
|
date: PropTypes.string,
|
||||||
|
body: PropTypes.string
|
||||||
|
}),
|
||||||
|
next: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
lastUnreadNotice: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
atBottom: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.onScroll()
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptTerms = () => {
|
||||||
|
const { markNoticeRead, lastUnreadNotice, next } = this.props;
|
||||||
|
const defer = markNoticeRead(lastUnreadNotice)
|
||||||
|
.then(() => this.setState({ atBottom: false }))
|
||||||
|
|
||||||
|
if ((/terms/gi).test(lastUnreadNotice.title)) {
|
||||||
|
defer.then(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll = debounce(() => {
|
||||||
|
if (this.state.atBottom) return
|
||||||
|
|
||||||
|
const target = document.querySelector('.tou__body')
|
||||||
|
const {scrollTop, offsetHeight, scrollHeight} = target;
|
||||||
|
const atBottom = scrollTop + offsetHeight >= scrollHeight;
|
||||||
|
|
||||||
|
this.setState({atBottom: atBottom})
|
||||||
|
}, 25)
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
address,
|
||||||
|
lastUnreadNotice: { title, body }
|
||||||
|
} = this.props;
|
||||||
|
const { atBottom } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="tou"
|
||||||
|
onScroll={this.onScroll}
|
||||||
|
>
|
||||||
|
<Identicon address={address} diameter={70} />
|
||||||
|
<div className="tou__title">{title}</div>
|
||||||
|
<Markdown
|
||||||
|
className="tou__body"
|
||||||
|
source={body}
|
||||||
|
skipHtml
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
onClick={atBottom && this.acceptTerms}
|
||||||
|
disabled={!atBottom}
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
<Breadcrumbs total={3} currentIndex={2} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ metamask: { selectedAddress, lastUnreadNotice } }) => ({
|
||||||
|
lastUnreadNotice,
|
||||||
|
address: selectedAddress
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
markNoticeRead: notice => dispatch(markNoticeRead(notice))
|
||||||
|
})
|
||||||
|
)(NoticeScreen)
|
70
mascara/src/app/first-time/spinner.js
Normal file
70
mascara/src/app/first-time/spinner.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Spinner({ className = '', color = "#000000" }) {
|
||||||
|
return (
|
||||||
|
<div className={`spinner ${className}`}>
|
||||||
|
<svg className="lds-spinner" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style={{background: 'none'}}>
|
||||||
|
<g transform="rotate(0 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(30 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(60 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.75s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(90 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(120 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(150 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(180 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(210 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(240 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.25s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(270 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(300 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(330 50 50)">
|
||||||
|
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
||||||
|
<animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" />
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
39
mascara/src/app/first-time/unique-image-screen.js
Normal file
39
mascara/src/app/first-time/unique-image-screen.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import Identicon from '../../../../ui/app/components/identicon'
|
||||||
|
import Breadcrumbs from './breadcrumbs'
|
||||||
|
|
||||||
|
class UniqueImageScreen extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
address: PropTypes.string,
|
||||||
|
next: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className="unique-image">
|
||||||
|
<Identicon address={this.props.address} diameter={70} />
|
||||||
|
<div className="unique-image__title">Your unique account image</div>
|
||||||
|
<div className="unique-image__body-text">
|
||||||
|
This image was programmatically generated for you by your new account number.
|
||||||
|
</div>
|
||||||
|
<div className="unique-image__body-text">
|
||||||
|
You’ll see this image everytime you need to confirm a transaction.
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="first-time-flow__button"
|
||||||
|
onClick={this.props.next}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
<Breadcrumbs total={3} currentIndex={1} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ metamask: { selectedAddress } }) => ({
|
||||||
|
address: selectedAddress,
|
||||||
|
})
|
||||||
|
)(UniqueImageScreen)
|
217
mascara/src/app/shapeshift-form/index.js
Normal file
217
mascara/src/app/shapeshift-form/index.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import {qrcode} from 'qrcode-npm'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import {shapeShiftSubview, pairUpdate, buyWithShapeShift} from '../../../../ui/app/actions'
|
||||||
|
import {isValidAddress} from '../../../../ui/app/util'
|
||||||
|
|
||||||
|
export class ShapeShiftForm extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
selectedAddress: PropTypes.string.isRequired,
|
||||||
|
btnClass: PropTypes.string.isRequired,
|
||||||
|
tokenExchangeRates: PropTypes.object.isRequired,
|
||||||
|
coinOptions: PropTypes.object.isRequired,
|
||||||
|
shapeShiftSubview: PropTypes.func.isRequired,
|
||||||
|
pairUpdate: PropTypes.func.isRequired,
|
||||||
|
buyWithShapeShift: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
depositCoin: 'btc',
|
||||||
|
refundAddress: '',
|
||||||
|
showQrCode: false,
|
||||||
|
depositAddress: '',
|
||||||
|
errorMessage: '',
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.props.shapeShiftSubview()
|
||||||
|
}
|
||||||
|
|
||||||
|
onCoinChange = e => {
|
||||||
|
const coin = e.target.value
|
||||||
|
this.setState({
|
||||||
|
depositCoin: coin,
|
||||||
|
errorMessage: '',
|
||||||
|
})
|
||||||
|
this.props.pairUpdate(coin)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBuyWithShapeShift = () => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: true,
|
||||||
|
showQrCode: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
buyWithShapeShift,
|
||||||
|
selectedAddress: withdrawal,
|
||||||
|
} = this.props
|
||||||
|
const {
|
||||||
|
refundAddress: returnAddress,
|
||||||
|
depositCoin,
|
||||||
|
} = this.state
|
||||||
|
const pair = `${depositCoin}_eth`
|
||||||
|
const data = {
|
||||||
|
withdrawal,
|
||||||
|
pair,
|
||||||
|
returnAddress,
|
||||||
|
// Public api key
|
||||||
|
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidAddress(withdrawal)) {
|
||||||
|
buyWithShapeShift(data)
|
||||||
|
.then(d => this.setState({
|
||||||
|
showQrCode: true,
|
||||||
|
depositAddress: d.deposit,
|
||||||
|
isLoading: false,
|
||||||
|
}))
|
||||||
|
.catch(() => this.setState({
|
||||||
|
showQrCode: false,
|
||||||
|
errorMessage: 'Invalid Request',
|
||||||
|
isLoading: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMetadata (label, value) {
|
||||||
|
return (
|
||||||
|
<div className='shapeshift-form__metadata-wrapper'>
|
||||||
|
<div className='shapeshift-form__metadata-label'>
|
||||||
|
{label}:
|
||||||
|
</div>
|
||||||
|
<div className='shapeshift-form__metadata-value'>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMarketInfo () {
|
||||||
|
const { depositCoin } = this.state
|
||||||
|
const coinPair = `${depositCoin}_eth`
|
||||||
|
const { tokenExchangeRates } = this.props
|
||||||
|
const {
|
||||||
|
limit,
|
||||||
|
rate,
|
||||||
|
minimum,
|
||||||
|
} = tokenExchangeRates[coinPair] || {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='shapeshift-form__metadata'>
|
||||||
|
{this.renderMetadata('Status', limit ? 'Available' : 'Unavailable')}
|
||||||
|
{this.renderMetadata('Limit', limit)}
|
||||||
|
{this.renderMetadata('Exchange Rate', rate)}
|
||||||
|
{this.renderMetadata('Minimum', minimum)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderQrCode () {
|
||||||
|
const { depositAddress, isLoading } = this.state
|
||||||
|
const qrImage = qrcode(4, 'M')
|
||||||
|
qrImage.addData(depositAddress)
|
||||||
|
qrImage.make()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='shapeshift-form'>
|
||||||
|
<div className='shapeshift-form__deposit-instruction'>
|
||||||
|
Deposit your BTC to the address bellow:
|
||||||
|
</div>
|
||||||
|
<div className='shapeshift-form__qr-code'>
|
||||||
|
{isLoading
|
||||||
|
? <img src='images/loading.svg' style={{ width: '60px' }} />
|
||||||
|
: <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{this.renderMarketInfo()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { coinOptions, btnClass } = this.props
|
||||||
|
const { depositCoin, errorMessage, showQrCode } = this.state
|
||||||
|
const coinPair = `${depositCoin}_eth`
|
||||||
|
const { tokenExchangeRates } = this.props
|
||||||
|
const token = tokenExchangeRates[coinPair]
|
||||||
|
|
||||||
|
return showQrCode ? this.renderQrCode() : (
|
||||||
|
<div>
|
||||||
|
<div className='shapeshift-form'>
|
||||||
|
<div className='shapeshift-form__selectors'>
|
||||||
|
<div className='shapeshift-form__selector'>
|
||||||
|
<div className='shapeshift-form__selector-label'>
|
||||||
|
Deposit
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
className='shapeshift-form__selector-input'
|
||||||
|
value={this.state.depositCoin}
|
||||||
|
onChange={this.onCoinChange}
|
||||||
|
>
|
||||||
|
{Object.entries(coinOptions).map(([coin]) => (
|
||||||
|
<option key={coin} value={coin.toLowerCase()}>
|
||||||
|
{coin}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='icon shapeshift-form__caret'
|
||||||
|
style={{ backgroundImage: 'url(images/caret-right.svg)'}}
|
||||||
|
/>
|
||||||
|
<div className='shapeshift-form__selector'>
|
||||||
|
<div className='shapeshift-form__selector-label'>
|
||||||
|
Receive
|
||||||
|
</div>
|
||||||
|
<div className='shapeshift-form__selector-input'>
|
||||||
|
ETH
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classnames('shapeshift-form__address-input-wrapper', {
|
||||||
|
'shapeshift-form__address-input-wrapper--error': errorMessage,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className='shapeshift-form__address-input-label'>
|
||||||
|
Your Refund Address
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='shapeshift-form__address-input'
|
||||||
|
onChange={e => this.setState({
|
||||||
|
refundAddress: e.target.value,
|
||||||
|
errorMessage: '',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<div className='shapeshift-form__address-input-error-message'>
|
||||||
|
{errorMessage}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.renderMarketInfo()}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={btnClass}
|
||||||
|
disabled={!token}
|
||||||
|
onClick={this.onBuyWithShapeShift}
|
||||||
|
>
|
||||||
|
Buy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
({ metamask: { coinOptions, tokenExchangeRates, selectedAddress } }) => ({
|
||||||
|
coinOptions, tokenExchangeRates, selectedAddress,
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
shapeShiftSubview: () => dispatch(shapeShiftSubview()),
|
||||||
|
pairUpdate: coin => dispatch(pairUpdate(coin)),
|
||||||
|
buyWithShapeShift: data => dispatch(buyWithShapeShift(data)),
|
||||||
|
})
|
||||||
|
)(ShapeShiftForm)
|
24
package.json
24
package.json
@ -13,7 +13,7 @@
|
|||||||
"dist": "npm run dist:clear && npm install && gulp dist",
|
"dist": "npm run dist:clear && npm install && gulp dist",
|
||||||
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
|
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
|
||||||
"test": "npm run lint && npm run test:coverage && npm run test:integration",
|
"test": "npm run lint && npm run test:coverage && npm run test:integration",
|
||||||
"test:unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"",
|
"test:unit": "METAMASK_ENV=test mocha --compilers js:babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
|
||||||
"test:single": "METAMASK_ENV=test mocha --require test/helper.js",
|
"test:single": "METAMASK_ENV=test mocha --require test/helper.js",
|
||||||
"test:integration": "npm run test:flat && npm run test:mascara",
|
"test:integration": "npm run test:flat && npm run test:mascara",
|
||||||
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
||||||
@ -29,6 +29,7 @@
|
|||||||
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
"test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js",
|
||||||
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
"test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js",
|
||||||
"lint": "gulp lint",
|
"lint": "gulp lint",
|
||||||
|
"lint:fix": "gulp lint:fix",
|
||||||
"disc": "gulp disc --debug",
|
"disc": "gulp disc --debug",
|
||||||
"announce": "node development/announcer.js",
|
"announce": "node development/announcer.js",
|
||||||
"generateNotice": "node notices/notice-generator.js",
|
"generateNotice": "node notices/notice-generator.js",
|
||||||
@ -56,6 +57,7 @@
|
|||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"bn.js": "^4.11.7",
|
"bn.js": "^4.11.7",
|
||||||
"browserify-derequire": "^0.9.4",
|
"browserify-derequire": "^0.9.4",
|
||||||
|
"classnames": "^2.2.5",
|
||||||
"client-sw-ready-event": "^3.3.0",
|
"client-sw-ready-event": "^3.3.0",
|
||||||
"clone": "^2.1.1",
|
"clone": "^2.1.1",
|
||||||
"copy-to-clipboard": "^3.0.8",
|
"copy-to-clipboard": "^3.0.8",
|
||||||
@ -66,20 +68,22 @@
|
|||||||
"dnode": "^1.2.2",
|
"dnode": "^1.2.2",
|
||||||
"end-of-stream": "^1.1.0",
|
"end-of-stream": "^1.1.0",
|
||||||
"ensnare": "^1.0.0",
|
"ensnare": "^1.0.0",
|
||||||
|
"eslint-plugin-react": "^7.4.0",
|
||||||
"eth-bin-to-ops": "^1.0.1",
|
"eth-bin-to-ops": "^1.0.1",
|
||||||
"eth-block-tracker": "^2.2.0",
|
"eth-block-tracker": "^2.2.0",
|
||||||
"eth-contract-metadata": "^1.1.4",
|
"eth-contract-metadata": "^1.1.4",
|
||||||
"eth-hd-keyring": "^1.2.1",
|
"eth-hd-keyring": "^1.2.1",
|
||||||
"eth-json-rpc-filters": "^1.2.2",
|
"eth-json-rpc-filters": "^1.2.4",
|
||||||
"eth-keyring-controller": "^2.1.0",
|
"eth-keyring-controller": "^2.1.2",
|
||||||
"eth-phishing-detect": "^1.1.4",
|
"eth-phishing-detect": "^1.1.4",
|
||||||
"eth-query": "^2.1.2",
|
"eth-query": "^2.1.2",
|
||||||
"eth-sig-util": "^1.4.0",
|
"eth-sig-util": "^1.4.0",
|
||||||
"eth-simple-keyring": "^1.1.1",
|
"eth-simple-keyring": "^1.2.0",
|
||||||
"eth-token-tracker": "^1.1.4",
|
"eth-token-tracker": "^1.1.4",
|
||||||
"ethereumjs-tx": "^1.3.0",
|
"ethereumjs-tx": "^1.3.0",
|
||||||
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
|
||||||
"ethereumjs-wallet": "^0.6.0",
|
"ethereumjs-wallet": "^0.6.0",
|
||||||
|
"etherscan-link": "^1.0.2",
|
||||||
"ethjs-contract": "^0.1.9",
|
"ethjs-contract": "^0.1.9",
|
||||||
"ethjs-ens": "^2.0.0",
|
"ethjs-ens": "^2.0.0",
|
||||||
"ethjs-query": "^0.2.9",
|
"ethjs-query": "^0.2.9",
|
||||||
@ -98,8 +102,11 @@
|
|||||||
"iframe-stream": "^3.0.0",
|
"iframe-stream": "^3.0.0",
|
||||||
"inject-css": "^0.1.1",
|
"inject-css": "^0.1.1",
|
||||||
"jazzicon": "^1.2.0",
|
"jazzicon": "^1.2.0",
|
||||||
"json-rpc-engine": "^3.2.0",
|
"json-rpc-engine": "3.2.0",
|
||||||
"json-rpc-middleware-stream": "^1.0.1",
|
"json-rpc-middleware-stream": "^1.0.1",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lodash.memoize": "^4.1.2",
|
||||||
|
"lodash.shuffle": "^4.2.0",
|
||||||
"loglevel": "^1.4.1",
|
"loglevel": "^1.4.1",
|
||||||
"metamascara": "^1.3.1",
|
"metamascara": "^1.3.1",
|
||||||
"metamask-logo": "^2.1.2",
|
"metamask-logo": "^2.1.2",
|
||||||
@ -126,14 +133,16 @@
|
|||||||
"react-markdown": "^2.3.0",
|
"react-markdown": "^2.3.0",
|
||||||
"react-redux": "^5.0.5",
|
"react-redux": "^5.0.5",
|
||||||
"react-select": "^1.0.0-rc.2",
|
"react-select": "^1.0.0-rc.2",
|
||||||
"react-simple-file-input": "^1.0.0",
|
"react-simple-file-input": "^2.0.0",
|
||||||
"react-tooltip-component": "^0.3.0",
|
"react-tooltip-component": "^0.3.0",
|
||||||
|
"react-trigger-change": "^1.0.2",
|
||||||
"readable-stream": "^2.3.3",
|
"readable-stream": "^2.3.3",
|
||||||
|
"recompose": "^0.25.0",
|
||||||
"redux": "^3.0.5",
|
"redux": "^3.0.5",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"request-promise": "^4.2.1",
|
"request-promise": "^4.2.1",
|
||||||
"sandwich-expando": "^1.0.5",
|
"sandwich-expando": "^1.1.3",
|
||||||
"semaphore": "^1.0.5",
|
"semaphore": "^1.0.5",
|
||||||
"sw-stream": "^2.0.0",
|
"sw-stream": "^2.0.0",
|
||||||
"textarea-caret": "^3.0.1",
|
"textarea-caret": "^3.0.1",
|
||||||
@ -151,6 +160,7 @@
|
|||||||
"babel-plugin-transform-async-to-generator": "^6.24.1",
|
"babel-plugin-transform-async-to-generator": "^6.24.1",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-polyfill": "^6.23.0",
|
"babel-polyfill": "^6.23.0",
|
||||||
|
"babel-preset-react": "^6.24.1",
|
||||||
"babel-preset-stage-0": "^6.24.1",
|
"babel-preset-stage-0": "^6.24.1",
|
||||||
"babel-register": "^6.7.2",
|
"babel-register": "^6.7.2",
|
||||||
"babelify": "^7.2.0",
|
"babelify": "^7.2.0",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const PASSWORD = 'password123'
|
const PASSWORD = 'password123'
|
||||||
|
const runMascaraFirstTimeTest = require('./mascara-first-time')
|
||||||
|
|
||||||
QUnit.module('first time usage')
|
QUnit.module('first time usage')
|
||||||
|
|
||||||
@ -11,9 +12,9 @@ QUnit.test('render init screen', (assert) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function runFirstTimeUsageTest(assert, done) {
|
async function runFirstTimeUsageTest(assert, done) {
|
||||||
let waitTime = 0
|
if (window.METAMASK_PLATFORM_TYPE === 'mascara') {
|
||||||
if (window.METAMASK_PLATFORM_TYPE === 'mascara') waitTime = 4000
|
return runMascaraFirstTimeTest(assert, done)
|
||||||
await timeout(waitTime)
|
}
|
||||||
|
|
||||||
const app = $('#app-content')
|
const app = $('#app-content')
|
||||||
|
|
||||||
|
167
test/integration/lib/mascara-first-time.js
Normal file
167
test/integration/lib/mascara-first-time.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
const PASSWORD = 'password123'
|
||||||
|
const reactTriggerChange = require('react-trigger-change')
|
||||||
|
|
||||||
|
async function runFirstTimeUsageTest (assert, done) {
|
||||||
|
await timeout(4000)
|
||||||
|
|
||||||
|
const app = $('#app-content')
|
||||||
|
|
||||||
|
// recurse notices
|
||||||
|
while (true) {
|
||||||
|
const button = app.find('button')
|
||||||
|
if (button.html() === 'Accept') {
|
||||||
|
// still notices to accept
|
||||||
|
const termsPage = app.find('.markdown')[0]
|
||||||
|
termsPage.scrollTop = termsPage.scrollHeight
|
||||||
|
await timeout()
|
||||||
|
console.log('Clearing notice')
|
||||||
|
button.click()
|
||||||
|
await timeout()
|
||||||
|
} else {
|
||||||
|
// exit loop
|
||||||
|
console.log('No more notices...')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await timeout()
|
||||||
|
|
||||||
|
// Scroll through terms
|
||||||
|
const title = app.find('.create-password__title').text()
|
||||||
|
assert.equal(title, 'Create Password', 'create password screen')
|
||||||
|
|
||||||
|
// enter password
|
||||||
|
const pwBox = app.find('.first-time-flow__input')[0]
|
||||||
|
const confBox = app.find('.first-time-flow__input')[1]
|
||||||
|
pwBox.value = PASSWORD
|
||||||
|
confBox.value = PASSWORD
|
||||||
|
reactTriggerChange(pwBox)
|
||||||
|
reactTriggerChange(confBox)
|
||||||
|
|
||||||
|
|
||||||
|
await timeout()
|
||||||
|
|
||||||
|
// Create Password
|
||||||
|
const createButton = app.find('button.first-time-flow__button')[0]
|
||||||
|
createButton.click()
|
||||||
|
|
||||||
|
await timeout(3000)
|
||||||
|
|
||||||
|
const created = app.find('.unique-image__title')[0]
|
||||||
|
assert.equal(created.textContent, 'Your unique account image', 'unique image screen')
|
||||||
|
|
||||||
|
// Agree button
|
||||||
|
const button = app.find('button')[0]
|
||||||
|
assert.ok(button, 'button present')
|
||||||
|
button.click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
// Privacy Screen
|
||||||
|
const detail = app.find('.tou__title')[0]
|
||||||
|
assert.equal(detail.textContent, 'Privacy Notice', 'privacy notice screen')
|
||||||
|
app.find('button').click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
|
||||||
|
// terms of service screen
|
||||||
|
const tou = app.find('.tou__title')[0]
|
||||||
|
assert.equal(tou.textContent, 'Terms of Use', 'terms of use screen')
|
||||||
|
app.find('.tou__body').scrollTop(100000)
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
app.find('.first-time-flow__button').click()
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
// secret backup phrase
|
||||||
|
const seedTitle = app.find('.backup-phrase__title')[0]
|
||||||
|
assert.equal(seedTitle.textContent, 'Secret Backup Phrase', 'seed phrase screen')
|
||||||
|
app.find('.backup-phrase__reveal-button').click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
const seedPhrase = app.find('.backup-phrase__secret-words').text().split(' ')
|
||||||
|
app.find('.first-time-flow__button').click()
|
||||||
|
|
||||||
|
const selectPhrase = text => {
|
||||||
|
const option = $('.backup-phrase__confirm-seed-option')
|
||||||
|
.filter((i, d) => d.textContent === text)[0]
|
||||||
|
|
||||||
|
$(option).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
seedPhrase.forEach(sp => selectPhrase(sp))
|
||||||
|
app.find('.first-time-flow__button').click()
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
// Deposit Ether Screen
|
||||||
|
const buyEthTitle = app.find('.buy-ether__title')[0]
|
||||||
|
assert.equal(buyEthTitle.textContent, 'Deposit Ether', 'deposit ether screen')
|
||||||
|
app.find('.buy-ether__do-it-later').click()
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
const sandwich = app.find('.sandwich-expando')[0]
|
||||||
|
sandwich.click()
|
||||||
|
|
||||||
|
await timeout()
|
||||||
|
|
||||||
|
const menu = app.find('.menu-droppo')[0]
|
||||||
|
const children = menu.children
|
||||||
|
const lock = children[children.length - 2]
|
||||||
|
assert.ok(lock, 'Lock menu item found')
|
||||||
|
lock.click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
const pwBox2 = app.find('#password-box')[0]
|
||||||
|
pwBox2.value = PASSWORD
|
||||||
|
|
||||||
|
const createButton2 = app.find('button.primary')[0]
|
||||||
|
createButton2.click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
const detail2 = app.find('.account-detail-section')[0]
|
||||||
|
assert.ok(detail2, 'Account detail section loaded again.')
|
||||||
|
|
||||||
|
await timeout()
|
||||||
|
|
||||||
|
// open account settings dropdown
|
||||||
|
const qrButton = app.find('.fa.fa-ellipsis-h')[0]
|
||||||
|
qrButton.click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
// qr code item
|
||||||
|
const qrButton2 = app.find('.dropdown-menu-item')[1]
|
||||||
|
qrButton2.click()
|
||||||
|
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
const qrHeader = app.find('.qr-header')[0]
|
||||||
|
const qrContainer = app.find('#qr-container')[0]
|
||||||
|
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
|
||||||
|
assert.ok(qrContainer, 'QR Container found')
|
||||||
|
|
||||||
|
await timeout()
|
||||||
|
|
||||||
|
const networkMenu = app.find('.network-indicator')[0]
|
||||||
|
networkMenu.click()
|
||||||
|
|
||||||
|
await timeout()
|
||||||
|
|
||||||
|
const networkMenu2 = app.find('.network-indicator')[0]
|
||||||
|
const children2 = networkMenu2.children
|
||||||
|
children2.length[3]
|
||||||
|
assert.ok(children2, 'All network options present')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = runFirstTimeUsageTest
|
||||||
|
|
||||||
|
function timeout (time) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(resolve, time || 1500)
|
||||||
|
})
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
var assert = require('assert')
|
|
||||||
var linkGen = require('../../ui/lib/account-link')
|
|
||||||
|
|
||||||
describe('account-link', function () {
|
|
||||||
it('adds ropsten prefix to ropsten test network', function () {
|
|
||||||
var result = linkGen('account', '3')
|
|
||||||
assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included')
|
|
||||||
assert.notEqual(result.indexOf('account'), -1, 'account included')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('adds kovan prefix to kovan test network', function () {
|
|
||||||
var result = linkGen('account', '42')
|
|
||||||
assert.notEqual(result.indexOf('kovan'), -1, 'kovan included')
|
|
||||||
assert.notEqual(result.indexOf('account'), -1, 'account included')
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,14 +0,0 @@
|
|||||||
var assert = require('assert')
|
|
||||||
var linkGen = require('../../ui/lib/explorer-link')
|
|
||||||
|
|
||||||
describe('explorer-link', function () {
|
|
||||||
it('adds ropsten prefix to ropsten test network', function () {
|
|
||||||
var result = linkGen('hash', '3')
|
|
||||||
assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten injected')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('adds kovan prefix to kovan test network', function () {
|
|
||||||
var result = linkGen('hash', '42')
|
|
||||||
assert.notEqual(result.indexOf('kovan'), -1, 'kovan injected')
|
|
||||||
})
|
|
||||||
})
|
|
@ -9,7 +9,7 @@ const etherBn = new BN(String(1e18))
|
|||||||
const ether = '0x' + etherBn.toString(16)
|
const ether = '0x' + etherBn.toString(16)
|
||||||
|
|
||||||
describe('PendingBalanceCalculator', function () {
|
describe('PendingBalanceCalculator', function () {
|
||||||
let balanceCalculator
|
let balanceCalculator, pendingTxs
|
||||||
|
|
||||||
describe('#calculateMaxCost(tx)', function () {
|
describe('#calculateMaxCost(tx)', function () {
|
||||||
it('returns a BN for a given tx value', function () {
|
it('returns a BN for a given tx value', function () {
|
||||||
|
@ -13,7 +13,8 @@ const otherNetworkId = 36
|
|||||||
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
|
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
|
||||||
|
|
||||||
describe('PendingTransactionTracker', function () {
|
describe('PendingTransactionTracker', function () {
|
||||||
let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, provider
|
let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub,
|
||||||
|
provider, txMeta3, txList, knownErrors
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
txMeta = {
|
txMeta = {
|
||||||
|
@ -121,6 +121,7 @@ AccountDetailScreen.prototype.render = function () {
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
padding: '5px 0px',
|
padding: '5px 0px',
|
||||||
|
lineHeight: '25px',
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
identity && identity.name,
|
identity && identity.name,
|
||||||
|
@ -133,9 +133,12 @@ var actions = {
|
|||||||
showLoadingIndication: showLoadingIndication,
|
showLoadingIndication: showLoadingIndication,
|
||||||
hideLoadingIndication: hideLoadingIndication,
|
hideLoadingIndication: hideLoadingIndication,
|
||||||
// buy Eth with coinbase
|
// buy Eth with coinbase
|
||||||
|
onboardingBuyEthView,
|
||||||
|
ONBOARDING_BUY_ETH_VIEW: 'ONBOARDING_BUY_ETH_VIEW',
|
||||||
BUY_ETH: 'BUY_ETH',
|
BUY_ETH: 'BUY_ETH',
|
||||||
buyEth: buyEth,
|
buyEth: buyEth,
|
||||||
buyEthView: buyEthView,
|
buyEthView: buyEthView,
|
||||||
|
buyWithShapeShift,
|
||||||
BUY_ETH_VIEW: 'BUY_ETH_VIEW',
|
BUY_ETH_VIEW: 'BUY_ETH_VIEW',
|
||||||
COINBASE_SUBVIEW: 'COINBASE_SUBVIEW',
|
COINBASE_SUBVIEW: 'COINBASE_SUBVIEW',
|
||||||
coinBaseSubview: coinBaseSubview,
|
coinBaseSubview: coinBaseSubview,
|
||||||
@ -215,14 +218,18 @@ function confirmSeedWords () {
|
|||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
log.debug(`background.clearSeedWordCache`)
|
log.debug(`background.clearSeedWordCache`)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
background.clearSeedWordCache((err, account) => {
|
background.clearSeedWordCache((err, account) => {
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
if (err) {
|
if (err) {
|
||||||
return dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('Seed word cache cleared. ' + account)
|
log.info('Seed word cache cleared. ' + account)
|
||||||
dispatch(actions.showAccountDetail(account))
|
dispatch(actions.showAccountsPage())
|
||||||
|
resolve(account)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,10 +238,20 @@ function createNewVaultAndRestore (password, seed) {
|
|||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
log.debug(`background.createNewVaultAndRestore`)
|
log.debug(`background.createNewVaultAndRestore`)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
background.createNewVaultAndRestore(password, seed, (err) => {
|
background.createNewVaultAndRestore(password, seed, (err) => {
|
||||||
|
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
if (err) return dispatch(actions.displayWarning(err.message))
|
|
||||||
|
if (err) {
|
||||||
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(actions.showAccountsPage())
|
dispatch(actions.showAccountsPage())
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,19 +260,26 @@ function createNewVaultAndKeychain (password) {
|
|||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
log.debug(`background.createNewVaultAndKeychain`)
|
log.debug(`background.createNewVaultAndKeychain`)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
background.createNewVaultAndKeychain(password, (err) => {
|
background.createNewVaultAndKeychain(password, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
}
|
}
|
||||||
log.debug(`background.placeSeedWords`)
|
log.debug(`background.placeSeedWords`)
|
||||||
background.placeSeedWords((err) => {
|
background.placeSeedWords((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
}
|
}
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
forceUpdateMetamaskState(dispatch)
|
forceUpdateMetamaskState(dispatch)
|
||||||
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,19 +323,26 @@ function importNewAccount (strategy, args) {
|
|||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
|
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
|
||||||
log.debug(`background.importAccountWithStrategy`)
|
log.debug(`background.importAccountWithStrategy`)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
background.importAccountWithStrategy(strategy, args, (err) => {
|
background.importAccountWithStrategy(strategy, args, (err) => {
|
||||||
if (err) return dispatch(actions.displayWarning(err.message))
|
if (err) {
|
||||||
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
log.debug(`background.getState`)
|
log.debug(`background.getState`)
|
||||||
background.getState((err, newState) => {
|
background.getState((err, newState) => {
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
if (err) {
|
if (err) {
|
||||||
return dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
}
|
}
|
||||||
dispatch(actions.updateMetamaskState(newState))
|
dispatch(actions.updateMetamaskState(newState))
|
||||||
dispatch({
|
dispatch({
|
||||||
type: actions.SHOW_ACCOUNT_DETAIL,
|
type: actions.SHOW_ACCOUNT_DETAIL,
|
||||||
value: newState.selectedAddress,
|
value: newState.selectedAddress,
|
||||||
})
|
})
|
||||||
|
resolve(newState)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -689,22 +720,24 @@ function goBackToInitView () {
|
|||||||
|
|
||||||
function markNoticeRead (notice) {
|
function markNoticeRead (notice) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch(this.showLoadingIndication())
|
dispatch(actions.showLoadingIndication())
|
||||||
log.debug(`background.markNoticeRead`)
|
log.debug(`background.markNoticeRead`)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
background.markNoticeRead(notice, (err, notice) => {
|
background.markNoticeRead(notice, (err, notice) => {
|
||||||
dispatch(this.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
if (err) {
|
if (err) {
|
||||||
return dispatch(actions.displayWarning(err))
|
dispatch(actions.displayWarning(err))
|
||||||
|
return reject(err)
|
||||||
}
|
}
|
||||||
if (notice) {
|
if (notice) {
|
||||||
return dispatch(actions.showNotice(notice))
|
dispatch(actions.showNotice(notice))
|
||||||
|
resolve()
|
||||||
} else {
|
} else {
|
||||||
dispatch(this.clearNotices())
|
dispatch(actions.clearNotices())
|
||||||
return {
|
resolve()
|
||||||
type: actions.SHOW_ACCOUNTS_PAGE,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,6 +916,13 @@ function buyEth (opts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onboardingBuyEthView (address) {
|
||||||
|
return {
|
||||||
|
type: actions.ONBOARDING_BUY_ETH_VIEW,
|
||||||
|
value: address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buyEthView (address) {
|
function buyEthView (address) {
|
||||||
return {
|
return {
|
||||||
type: actions.BUY_ETH_VIEW,
|
type: actions.BUY_ETH_VIEW,
|
||||||
@ -948,6 +988,18 @@ function coinShiftRquest (data, marketData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buyWithShapeShift (data) {
|
||||||
|
return dispatch => new Promise((resolve, reject) => {
|
||||||
|
shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
|
||||||
|
if (response.error) {
|
||||||
|
return reject(response.error)
|
||||||
|
}
|
||||||
|
background.createShapeShiftTx(response.deposit, response.depositType)
|
||||||
|
return resolve(response)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function showQrView (data, message) {
|
function showQrView (data, message) {
|
||||||
return {
|
return {
|
||||||
type: actions.SHOW_QR_VIEW,
|
type: actions.SHOW_QR_VIEW,
|
||||||
@ -981,9 +1033,14 @@ function shapeShiftRequest (query, options, cb) {
|
|||||||
options.method ? method = options.method : method = 'GET'
|
options.method ? method = options.method : method = 'GET'
|
||||||
|
|
||||||
var requestListner = function (request) {
|
var requestListner = function (request) {
|
||||||
|
try {
|
||||||
queryResponse = JSON.parse(this.responseText)
|
queryResponse = JSON.parse(this.responseText)
|
||||||
cb ? cb(queryResponse) : null
|
cb ? cb(queryResponse) : null
|
||||||
return queryResponse
|
return queryResponse
|
||||||
|
} catch (e) {
|
||||||
|
cb ? cb({error: e}) : null
|
||||||
|
return e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var shapShiftReq = new XMLHttpRequest()
|
var shapShiftReq = new XMLHttpRequest()
|
||||||
|
@ -73,7 +73,7 @@ AddTokenScreen.prototype.render = function () {
|
|||||||
}, [
|
}, [
|
||||||
h('a', {
|
h('a', {
|
||||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||||
href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
|
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
}, [
|
}, [
|
||||||
h('span', 'Token Contract Address '),
|
h('span', 'Token Contract Address '),
|
||||||
|
@ -3,6 +3,9 @@ const Component = require('react').Component
|
|||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const actions = require('./actions')
|
const actions = require('./actions')
|
||||||
|
// mascara
|
||||||
|
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
|
||||||
|
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
|
||||||
// init
|
// init
|
||||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||||
const NewKeyChainScreen = require('./new-keychain')
|
const NewKeyChainScreen = require('./new-keychain')
|
||||||
@ -43,6 +46,9 @@ function mapStateToProps (state) {
|
|||||||
accounts,
|
accounts,
|
||||||
address,
|
address,
|
||||||
keyrings,
|
keyrings,
|
||||||
|
isInitialized,
|
||||||
|
noActiveNotices,
|
||||||
|
seedWords,
|
||||||
} = state.metamask
|
} = state.metamask
|
||||||
const selected = address || Object.keys(accounts)[0]
|
const selected = address || Object.keys(accounts)[0]
|
||||||
|
|
||||||
@ -56,6 +62,8 @@ function mapStateToProps (state) {
|
|||||||
currentView: state.appState.currentView,
|
currentView: state.appState.currentView,
|
||||||
activeAddress: state.appState.activeAddress,
|
activeAddress: state.appState.activeAddress,
|
||||||
transForward: state.appState.transForward,
|
transForward: state.appState.transForward,
|
||||||
|
isMascara: state.metamask.isMascara,
|
||||||
|
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
||||||
seedWords: state.metamask.seedWords,
|
seedWords: state.metamask.seedWords,
|
||||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||||
@ -98,10 +106,7 @@ App.prototype.render = function () {
|
|||||||
this.renderNetworkDropdown(),
|
this.renderNetworkDropdown(),
|
||||||
this.renderDropdown(),
|
this.renderDropdown(),
|
||||||
|
|
||||||
h(Loading, {
|
this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
|
||||||
isLoading: isLoading || isLoadingNetwork,
|
|
||||||
loadingMessage: loadMessage,
|
|
||||||
}),
|
|
||||||
|
|
||||||
// panel content
|
// panel content
|
||||||
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
|
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
|
||||||
@ -123,6 +128,17 @@ App.prototype.renderAppBar = function () {
|
|||||||
const props = this.props
|
const props = this.props
|
||||||
const state = this.state || {}
|
const state = this.state || {}
|
||||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
||||||
|
const {isMascara, isOnboarding} = props
|
||||||
|
|
||||||
|
// Do not render header if user is in mascara onboarding
|
||||||
|
if (isMascara && isOnboarding) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not render header if user is in mascara buy ether
|
||||||
|
if (isMascara && props.currentView.name === 'buyEth') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
@ -388,6 +404,17 @@ App.prototype.renderDropdown = function () {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
|
||||||
|
const { isMascara } = this.props
|
||||||
|
|
||||||
|
return isMascara
|
||||||
|
? null
|
||||||
|
: h(Loading, {
|
||||||
|
isLoading: isLoading || isLoadingNetwork,
|
||||||
|
loadingMessage: loadMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||||
var props = this.props
|
var props = this.props
|
||||||
return (
|
return (
|
||||||
@ -410,6 +437,11 @@ App.prototype.renderBackButton = function (style, justArrow = false) {
|
|||||||
App.prototype.renderPrimary = function () {
|
App.prototype.renderPrimary = function () {
|
||||||
log.debug('rendering primary')
|
log.debug('rendering primary')
|
||||||
var props = this.props
|
var props = this.props
|
||||||
|
const {isMascara, isOnboarding} = props
|
||||||
|
|
||||||
|
if (isMascara && isOnboarding) {
|
||||||
|
return h(MascaraFirstTime)
|
||||||
|
}
|
||||||
|
|
||||||
// notices
|
// notices
|
||||||
if (!props.noActiveNotices) {
|
if (!props.noActiveNotices) {
|
||||||
@ -510,6 +542,10 @@ App.prototype.renderPrimary = function () {
|
|||||||
log.debug('rendering buy ether screen')
|
log.debug('rendering buy ether screen')
|
||||||
return h(BuyView, {key: 'buyEthView'})
|
return h(BuyView, {key: 'buyEthView'})
|
||||||
|
|
||||||
|
case 'onboardingBuyEth':
|
||||||
|
log.debug('rendering onboarding buy ether screen')
|
||||||
|
return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
|
||||||
|
|
||||||
case 'qr':
|
case 'qr':
|
||||||
log.debug('rendering show qr screen')
|
log.debug('rendering show qr screen')
|
||||||
return h('div', {
|
return h('div', {
|
||||||
|
@ -2,7 +2,7 @@ const Component = require('react').Component
|
|||||||
const PropTypes = require('react').PropTypes
|
const PropTypes = require('react').PropTypes
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const actions = require('../actions')
|
const actions = require('../actions')
|
||||||
const genAccountLink = require('../../lib/account-link.js')
|
const genAccountLink = require('etherscan-link').createAccountLink
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const Dropdown = require('./dropdown').Dropdown
|
const Dropdown = require('./dropdown').Dropdown
|
||||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||||
@ -161,8 +161,6 @@ class AccountDropdowns extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
renderAccountOptions () {
|
renderAccountOptions () {
|
||||||
const { actions } = this.props
|
const { actions } = this.props
|
||||||
const { optionsMenuActive } = this.state
|
const { optionsMenuActive } = this.state
|
||||||
@ -297,6 +295,11 @@ AccountDropdowns.propTypes = {
|
|||||||
identities: PropTypes.objectOf(PropTypes.object),
|
identities: PropTypes.objectOf(PropTypes.object),
|
||||||
selected: PropTypes.string,
|
selected: PropTypes.string,
|
||||||
keyrings: PropTypes.array,
|
keyrings: PropTypes.array,
|
||||||
|
actions: PropTypes.objectOf(PropTypes.func),
|
||||||
|
network: PropTypes.string,
|
||||||
|
style: PropTypes.object,
|
||||||
|
enableAccountOptions: PropTypes.bool,
|
||||||
|
enableAccountsSelector: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
|
@ -31,6 +31,8 @@ BnAsDecimalInput.prototype.render = function () {
|
|||||||
const suffix = props.suffix
|
const suffix = props.suffix
|
||||||
const style = props.style
|
const style = props.style
|
||||||
const valueString = value.toString(10)
|
const valueString = value.toString(10)
|
||||||
|
const newMin = min && this.downsize(min.toString(10), scale)
|
||||||
|
const newMax = max && this.downsize(max.toString(10), scale)
|
||||||
const newValue = this.downsize(valueString, scale)
|
const newValue = this.downsize(valueString, scale)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -47,8 +49,8 @@ BnAsDecimalInput.prototype.render = function () {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
step: 'any',
|
step: 'any',
|
||||||
required: true,
|
required: true,
|
||||||
min,
|
min: newMin,
|
||||||
max,
|
max: newMax,
|
||||||
style: extend({
|
style: extend({
|
||||||
display: 'block',
|
display: 'block',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
@ -128,15 +130,17 @@ BnAsDecimalInput.prototype.updateValidity = function (event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BnAsDecimalInput.prototype.constructWarning = function () {
|
BnAsDecimalInput.prototype.constructWarning = function () {
|
||||||
const { name, min, max } = this.props
|
const { name, min, max, scale, suffix } = this.props
|
||||||
|
const newMin = min && this.downsize(min.toString(10), scale)
|
||||||
|
const newMax = max && this.downsize(max.toString(10), scale)
|
||||||
let message = name ? name + ' ' : ''
|
let message = name ? name + ' ' : ''
|
||||||
|
|
||||||
if (min && max) {
|
if (min && max) {
|
||||||
message += `must be greater than or equal to ${min} and less than or equal to ${max}.`
|
message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.`
|
||||||
} else if (min) {
|
} else if (min) {
|
||||||
message += `must be greater than or equal to ${min}.`
|
message += `must be greater than or equal to ${newMin} ${suffix}.`
|
||||||
} else if (max) {
|
} else if (max) {
|
||||||
message += `must be less than or equal to ${max}.`
|
message += `must be less than or equal to ${newMax} ${suffix}.`
|
||||||
} else {
|
} else {
|
||||||
message += 'Invalid input.'
|
message += 'Invalid input.'
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,9 @@ Dropdown.propTypes = {
|
|||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
style: PropTypes.object.isRequired,
|
style: PropTypes.object.isRequired,
|
||||||
|
onClickOutside: PropTypes.func,
|
||||||
|
innerStyle: PropTypes.object,
|
||||||
|
useCssTransition: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
class DropdownMenuItem extends Component {
|
class DropdownMenuItem extends Component {
|
||||||
@ -86,6 +89,7 @@ DropdownMenuItem.propTypes = {
|
|||||||
closeMenu: PropTypes.func.isRequired,
|
closeMenu: PropTypes.func.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
style: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -48,6 +48,7 @@ EditableLabel.prototype.saveIfEnter = function (event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditableLabel.prototype.saveText = function () {
|
EditableLabel.prototype.saveText = function () {
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var container = findDOMNode(this)
|
var container = findDOMNode(this)
|
||||||
var text = container.querySelector('.editable-label input').value
|
var text = container.querySelector('.editable-label input').value
|
||||||
var truncatedText = text.substring(0, 20)
|
var truncatedText = text.substring(0, 20)
|
||||||
|
@ -6,7 +6,7 @@ const debounce = require('debounce')
|
|||||||
const copyToClipboard = require('copy-to-clipboard')
|
const copyToClipboard = require('copy-to-clipboard')
|
||||||
const ENS = require('ethjs-ens')
|
const ENS = require('ethjs-ens')
|
||||||
const networkMap = require('ethjs-ens/lib/network-map.json')
|
const networkMap = require('ethjs-ens/lib/network-map.json')
|
||||||
const ensRE = /.+\.eth$/
|
const ensRE = /.+\..+$/
|
||||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ IdenticonComponent.prototype.componentDidMount = function () {
|
|||||||
|
|
||||||
if (!address) return
|
if (!address) return
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var container = findDOMNode(this)
|
var container = findDOMNode(this)
|
||||||
|
|
||||||
var diameter = props.diameter || this.defaultDiameter
|
var diameter = props.diameter || this.defaultDiameter
|
||||||
@ -56,6 +57,7 @@ IdenticonComponent.prototype.componentDidUpdate = function () {
|
|||||||
|
|
||||||
if (!address) return
|
if (!address) return
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var container = findDOMNode(this)
|
var container = findDOMNode(this)
|
||||||
|
|
||||||
var children = container.children
|
var children = container.children
|
||||||
|
@ -19,7 +19,7 @@ MenuDroppoComponent.prototype.render = function () {
|
|||||||
|
|
||||||
this.manageListeners()
|
this.manageListeners()
|
||||||
|
|
||||||
let style = this.props.style || {}
|
const style = this.props.style || {}
|
||||||
if (!('position' in style)) {
|
if (!('position' in style)) {
|
||||||
style.position = 'fixed'
|
style.position = 'fixed'
|
||||||
}
|
}
|
||||||
@ -95,6 +95,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () {
|
|||||||
if (this && document.body) {
|
if (this && document.body) {
|
||||||
this.globalClickHandler = this.globalClickOccurred.bind(this)
|
this.globalClickHandler = this.globalClickOccurred.bind(this)
|
||||||
document.body.addEventListener('click', this.globalClickHandler)
|
document.body.addEventListener('click', this.globalClickHandler)
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var container = findDOMNode(this)
|
var container = findDOMNode(this)
|
||||||
this.container = container
|
this.container = container
|
||||||
}
|
}
|
||||||
@ -108,6 +109,7 @@ MenuDroppoComponent.prototype.componentWillUnmount = function () {
|
|||||||
|
|
||||||
MenuDroppoComponent.prototype.globalClickOccurred = function (event) {
|
MenuDroppoComponent.prototype.globalClickOccurred = function (event) {
|
||||||
const target = event.target
|
const target = event.target
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
const container = findDOMNode(this)
|
const container = findDOMNode(this)
|
||||||
|
|
||||||
if (target !== container &&
|
if (target !== container &&
|
||||||
|
@ -117,6 +117,7 @@ Notice.prototype.render = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notice.prototype.componentDidMount = function () {
|
Notice.prototype.componentDidMount = function () {
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var node = findDOMNode(this)
|
var node = findDOMNode(this)
|
||||||
linker.setupListener(node)
|
linker.setupListener(node)
|
||||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
||||||
@ -125,6 +126,7 @@ Notice.prototype.componentDidMount = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notice.prototype.componentWillUnmount = function () {
|
Notice.prototype.componentWillUnmount = function () {
|
||||||
|
// eslint-disable-next-line react/no-find-dom-node
|
||||||
var node = findDOMNode(this)
|
var node = findDOMNode(this)
|
||||||
linker.teardownListener(node)
|
linker.teardownListener(node)
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,9 @@ const addressSummary = util.addressSummary
|
|||||||
const nameForAddress = require('../../lib/contract-namer')
|
const nameForAddress = require('../../lib/contract-namer')
|
||||||
const BNInput = require('./bn-as-decimal-input')
|
const BNInput = require('./bn-as-decimal-input')
|
||||||
|
|
||||||
const MIN_GAS_PRICE_GWEI_BN = new BN(1)
|
// corresponds with 0.1 GWEI
|
||||||
const GWEI_FACTOR = new BN(1e9)
|
const MIN_GAS_PRICE_BN = new BN('100000000')
|
||||||
const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR)
|
const MIN_GAS_LIMIT_BN = new BN('21000')
|
||||||
const MIN_GAS_LIMIT_BN = new BN(21000)
|
|
||||||
|
|
||||||
module.exports = PendingTx
|
module.exports = PendingTx
|
||||||
inherits(PendingTx, Component)
|
inherits(PendingTx, Component)
|
||||||
@ -175,7 +174,7 @@ PendingTx.prototype.render = function () {
|
|||||||
precision: 0,
|
precision: 0,
|
||||||
scale: 0,
|
scale: 0,
|
||||||
// The hard lower limit for gas.
|
// The hard lower limit for gas.
|
||||||
min: MIN_GAS_LIMIT_BN.toString(10),
|
min: MIN_GAS_LIMIT_BN,
|
||||||
max: safeGasLimit,
|
max: safeGasLimit,
|
||||||
suffix: 'UNITS',
|
suffix: 'UNITS',
|
||||||
style: {
|
style: {
|
||||||
@ -200,7 +199,7 @@ PendingTx.prototype.render = function () {
|
|||||||
precision: 9,
|
precision: 9,
|
||||||
scale: 9,
|
scale: 9,
|
||||||
suffix: 'GWEI',
|
suffix: 'GWEI',
|
||||||
min: MIN_GAS_PRICE_GWEI_BN.toString(10),
|
min: MIN_GAS_PRICE_BN,
|
||||||
style: {
|
style: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
top: '5px',
|
top: '5px',
|
||||||
|
@ -130,9 +130,9 @@ ShapeshiftForm.prototype.renderMain = function () {
|
|||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
this.props.warning
|
this.props.warning ?
|
||||||
? this.props.warning
|
this.props.warning &&
|
||||||
&& h('span.error.flex-center', {
|
h('span.error.flex-center', {
|
||||||
style: {
|
style: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
width: '229px',
|
width: '229px',
|
||||||
|
@ -3,7 +3,7 @@ const Component = require('react').Component
|
|||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const connect = require('react-redux').connect
|
const connect = require('react-redux').connect
|
||||||
const vreme = new (require('vreme'))()
|
const vreme = new (require('vreme'))()
|
||||||
const explorerLink = require('../../lib/explorer-link')
|
const explorerLink = require('etherscan-link').createExplorerLink
|
||||||
const actions = require('../actions')
|
const actions = require('../actions')
|
||||||
const addressSummary = require('../util').addressSummary
|
const addressSummary = require('../util').addressSummary
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const inherits = require('util').inherits
|
|||||||
|
|
||||||
const EthBalance = require('./eth-balance')
|
const EthBalance = require('./eth-balance')
|
||||||
const addressSummary = require('../util').addressSummary
|
const addressSummary = require('../util').addressSummary
|
||||||
const explorerLink = require('../../lib/explorer-link')
|
const explorerLink = require('etherscan-link').createExplorerLink
|
||||||
const CopyButton = require('./copyButton')
|
const CopyButton = require('./copyButton')
|
||||||
const vreme = new (require('vreme'))()
|
const vreme = new (require('vreme'))()
|
||||||
const Tooltip = require('./tooltip')
|
const Tooltip = require('./tooltip')
|
||||||
|
@ -235,7 +235,8 @@ app sections
|
|||||||
|
|
||||||
/* unlock */
|
/* unlock */
|
||||||
.error {
|
.error {
|
||||||
color: #E20202;
|
color: #f7861c;
|
||||||
|
margin-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
|
@ -62,7 +62,8 @@ CreateVaultCompleteScreen.prototype.render = function () {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
h('button.primary', {
|
h('button.primary', {
|
||||||
onClick: () => this.confirmSeedWords(),
|
onClick: () => this.confirmSeedWords()
|
||||||
|
.then(account => this.showAccountDetail(account)),
|
||||||
style: {
|
style: {
|
||||||
margin: '24px',
|
margin: '24px',
|
||||||
fontSize: '0.9em',
|
fontSize: '0.9em',
|
||||||
@ -82,5 +83,9 @@ CreateVaultCompleteScreen.prototype.render = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
|
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
|
||||||
this.props.dispatch(actions.confirmSeedWords())
|
return this.props.dispatch(actions.confirmSeedWords())
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) {
|
||||||
|
return this.props.dispatch(actions.showAccountDetail(account))
|
||||||
}
|
}
|
||||||
|
@ -494,6 +494,16 @@ function reduceApp (state, action) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case actions.ONBOARDING_BUY_ETH_VIEW:
|
||||||
|
return extend(appState, {
|
||||||
|
transForward: true,
|
||||||
|
currentView: {
|
||||||
|
name: 'onboardingBuyEth',
|
||||||
|
context: appState.currentView.name,
|
||||||
|
},
|
||||||
|
identity: state.metamask.identities[action.value],
|
||||||
|
})
|
||||||
|
|
||||||
case actions.COINBASE_SUBVIEW:
|
case actions.COINBASE_SUBVIEW:
|
||||||
return extend(appState, {
|
return extend(appState, {
|
||||||
buyView: {
|
buyView: {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const actions = require('../actions')
|
const actions = require('../actions')
|
||||||
|
const MetamascaraPlatform = require('../../../app/scripts/platforms/window')
|
||||||
|
|
||||||
module.exports = reduceMetamask
|
module.exports = reduceMetamask
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ function reduceMetamask (state, action) {
|
|||||||
var metamaskState = extend({
|
var metamaskState = extend({
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
isUnlocked: false,
|
isUnlocked: false,
|
||||||
|
isMascara: window.platform instanceof MetamascaraPlatform,
|
||||||
rpcTarget: 'https://rawtestrpc.metamask.io/',
|
rpcTarget: 'https://rawtestrpc.metamask.io/',
|
||||||
identities: {},
|
identities: {},
|
||||||
unapprovedTxs: {},
|
unapprovedTxs: {},
|
||||||
@ -17,6 +19,8 @@ function reduceMetamask (state, action) {
|
|||||||
lastUnreadNotice: undefined,
|
lastUnreadNotice: undefined,
|
||||||
frequentRpcList: [],
|
frequentRpcList: [],
|
||||||
addressBook: [],
|
addressBook: [],
|
||||||
|
tokenExchangeRates: {},
|
||||||
|
coinOptions: {},
|
||||||
}, state.metamask)
|
}, state.metamask)
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -130,6 +134,25 @@ function reduceMetamask (state, action) {
|
|||||||
conversionDate: action.value.conversionDate,
|
conversionDate: action.value.conversionDate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case actions.PAIR_UPDATE:
|
||||||
|
const { value: { marketinfo: pairMarketInfo } } = action
|
||||||
|
return extend(metamaskState, {
|
||||||
|
tokenExchangeRates: {
|
||||||
|
...metamaskState.tokenExchangeRates,
|
||||||
|
[pairMarketInfo.pair]: pairMarketInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
case actions.SHAPESHIFT_SUBVIEW:
|
||||||
|
const { value: { marketinfo, coinOptions } } = action
|
||||||
|
return extend(metamaskState, {
|
||||||
|
tokenExchangeRates: {
|
||||||
|
...metamaskState.tokenExchangeRates,
|
||||||
|
[marketinfo.pair]: marketinfo,
|
||||||
|
},
|
||||||
|
coinOptions,
|
||||||
|
})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return metamaskState
|
return metamaskState
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ var cssFiles = {
|
|||||||
'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'),
|
'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'),
|
||||||
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
|
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
|
||||||
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
|
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
|
||||||
|
'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'),
|
||||||
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
||||||
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
module.exports = function (address, network) {
|
|
||||||
const net = parseInt(network)
|
|
||||||
let link
|
|
||||||
switch (net) {
|
|
||||||
case 1: // main net
|
|
||||||
link = `https://etherscan.io/address/${address}`
|
|
||||||
break
|
|
||||||
case 2: // morden test net
|
|
||||||
link = `https://morden.etherscan.io/address/${address}`
|
|
||||||
break
|
|
||||||
case 3: // ropsten test net
|
|
||||||
link = `https://ropsten.etherscan.io/address/${address}`
|
|
||||||
break
|
|
||||||
case 4: // rinkeby test net
|
|
||||||
link = `https://rinkeby.etherscan.io/address/${address}`
|
|
||||||
break
|
|
||||||
case 42: // kovan test net
|
|
||||||
link = `https://kovan.etherscan.io/address/${address}`
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
link = ''
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return link
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
const prefixForNetwork = require('./etherscan-prefix-for-network')
|
|
||||||
|
|
||||||
module.exports = function (hash, network) {
|
|
||||||
const prefix = prefixForNetwork(network)
|
|
||||||
return `http://${prefix}etherscan.io/tx/${hash}`
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user