mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #2799 from MetaMask/NewUI-flat
Update UAT to version 4.0.5
This commit is contained in:
commit
409d1d30e9
@ -2,4 +2,5 @@ app/scripts/lib/extension-instance.js
|
||||
test/integration/bundle.js
|
||||
test/integration/jquery-3.1.0.min.js
|
||||
test/integration/helpers.js
|
||||
test/integration/lib/first-time.js
|
||||
test/integration/lib/first-time.js
|
||||
ui/lib/blockies.js
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,6 +6,8 @@ app/bower_components
|
||||
test/bower_components
|
||||
package
|
||||
|
||||
.idea
|
||||
|
||||
temp
|
||||
.tmp
|
||||
.sass-cache
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -2,6 +2,32 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- Fix bug that prevented updating custom token details.
|
||||
- No longer mark long-pending transactions as failed, since we now have button to retry with higher gas.
|
||||
|
||||
## 3.13.3 2017-12-14
|
||||
|
||||
- Show tokens that are held that have no balance.
|
||||
- Reduce load on Infura by using a new block polling endpoint.
|
||||
|
||||
## 3.13.2 2017-12-9
|
||||
|
||||
- Reduce new block polling interval to 8000 ms, to ease server load.
|
||||
|
||||
## 3.13.1 2017-12-7
|
||||
|
||||
- Allow Dapps to specify a transaction nonce, allowing dapps to propose resubmit and force-cancel transactions.
|
||||
|
||||
## 3.13.0 2017-12-7
|
||||
|
||||
- Allow resubmitting transactions that are taking long to complete.
|
||||
|
||||
## 3.12.1 2017-11-29
|
||||
|
||||
- Fix bug where a user could be shown two different seed phrases.
|
||||
- Detect when multiple web3 extensions are active, and provide useful error.
|
||||
- Adds notice about seed phrase backup.
|
||||
|
||||
## 3.12.0 2017-10-25
|
||||
|
||||
- Add support for alternative ENS TLDs (Ethereum Name Service Top-Level Domains).
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
FAQ:
|
||||
BEFORE SUBMITTING, please make sure your question hasn't been answered in our FAQ: https://github.com/MetaMask/faq
|
||||
Common questions such as "Where is my ether?" or "Where did my tokens go?" are answered in the FAQ.
|
||||
|
||||
BEFORE SUBMITTING, please make sure your question hasn't been answered in our support center: https://support.metamask.io
|
||||
Common questions such as "Where is my ether?" or "Where did my tokens go?" are answered there.
|
||||
|
||||
Bug Reports:
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# MetaMask Plugin
|
||||
# MetaMask Browser Extension
|
||||
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](http://waffle.io/MetaMask/metamask-extension)
|
||||
|
||||
|
||||
|
15
app/images/open.svg
Normal file
15
app/images/open.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>open</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Mobile-screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="MetaMascara-Mobile---structured" transform="translate(-329.000000, -93.000000)">
|
||||
<g id="open" transform="translate(330.000000, 94.000000)">
|
||||
<path d="M26,13 C26,20.1799 20.1799,26 13,26 C5.8201,26 0,20.1799 0,13 C0,5.8201 5.8201,0 13,0 C20.1799,0 26,5.8201 26,13 Z" id="Stroke-3" stroke="#4A4A4A"></path>
|
||||
<path d="M6,17 C6,17 7.78735344,10.8360387 13.7616996,10.8360387 L13.7616996,8 L19,12.3733433 L13.7616996,17 L13.7616996,14.1639613 C13.7616996,14.1639613 9.54083576,13.4629933 6,17" id="Fill-5" fill="#4A4A4A"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "4.0.4",
|
||||
"version": "4.0.5",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
|
@ -1,10 +1,11 @@
|
||||
const urlUtil = require('url')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const pipe = require('pump')
|
||||
const pump = require('pump')
|
||||
const log = require('loglevel')
|
||||
const extension = require('extensionizer')
|
||||
const LocalStorageStore = require('obs-store/lib/localStorage')
|
||||
const storeTransform = require('obs-store/lib/transform')
|
||||
const asStream = require('obs-store/lib/asStream')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const Migrator = require('./lib/migrator/')
|
||||
const migrations = require('./migrations/')
|
||||
@ -72,10 +73,10 @@ function setupController (initState) {
|
||||
global.metamaskController = controller
|
||||
|
||||
// setup state persistence
|
||||
pipe(
|
||||
controller.store,
|
||||
pump(
|
||||
asStream(controller.store),
|
||||
storeTransform(versionifyData),
|
||||
diskStore
|
||||
asStream(diskStore)
|
||||
)
|
||||
|
||||
function versionifyData (state) {
|
||||
|
@ -4,6 +4,15 @@ const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
||||
|
||||
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
|
||||
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
|
||||
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
|
||||
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
|
||||
|
||||
const DEFAULT_RPC = 'rinkeby'
|
||||
const OLD_UI_NETWORK_TYPE = 'network'
|
||||
const BETA_UI_NETWORK_TYPE = 'networkBeta'
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
module.exports = {
|
||||
@ -14,9 +23,22 @@ module.exports = {
|
||||
kovan: KOVAN_RPC_URL,
|
||||
rinkeby: RINKEBY_RPC_URL,
|
||||
},
|
||||
// Used for beta UI
|
||||
networkBeta: {
|
||||
localhost: LOCALHOST_RPC_URL,
|
||||
mainnet: MAINET_RPC_URL_BETA,
|
||||
ropsten: ROPSTEN_RPC_URL_BETA,
|
||||
kovan: KOVAN_RPC_URL_BETA,
|
||||
rinkeby: RINKEBY_RPC_URL_BETA,
|
||||
},
|
||||
networkNames: {
|
||||
3: 'Ropsten',
|
||||
4: 'Rinkeby',
|
||||
42: 'Kovan',
|
||||
},
|
||||
enums: {
|
||||
DEFAULT_RPC,
|
||||
OLD_UI_NETWORK_TYPE,
|
||||
BETA_UI_NETWORK_TYPE,
|
||||
},
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
}
|
||||
|
||||
function shouldInjectWeb3 () {
|
||||
return doctypeCheck() || suffixCheck()
|
||||
return doctypeCheck() && suffixCheck() && documentElementCheck()
|
||||
}
|
||||
|
||||
function doctypeCheck () {
|
||||
@ -104,7 +104,7 @@ function doctypeCheck () {
|
||||
if (doctype) {
|
||||
return doctype.name === 'html'
|
||||
} else {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +121,14 @@ function suffixCheck () {
|
||||
return true
|
||||
}
|
||||
|
||||
function documentElementCheck () {
|
||||
var documentElement = document.documentElement.nodeName
|
||||
if (documentElement) {
|
||||
return documentElement.toLowerCase() === 'html'
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function redirectToPhishingWarning () {
|
||||
console.log('MetaMask - redirecting to phishing warning')
|
||||
window.location.href = 'https://metamask.io/phishing.html'
|
||||
|
@ -1,18 +1,25 @@
|
||||
const assert = require('assert')
|
||||
const EventEmitter = require('events')
|
||||
const createMetamaskProvider = require('web3-provider-engine/zero.js')
|
||||
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ComposedStore = require('obs-store/lib/composed')
|
||||
const extend = require('xtend')
|
||||
const EthQuery = require('eth-query')
|
||||
const createEventEmitterProxy = require('../lib/events-proxy.js')
|
||||
const RPC_ADDRESS_LIST = require('../config.js').network
|
||||
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
|
||||
const networkConfig = require('../config.js')
|
||||
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
|
||||
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
|
||||
|
||||
module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
constructor (config) {
|
||||
super()
|
||||
|
||||
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
|
||||
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
|
||||
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
|
||||
|
||||
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
this.providerStore = new ObservableStore(config.provider)
|
||||
@ -22,10 +29,32 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
}
|
||||
|
||||
async setNetworkEndpoints (version) {
|
||||
if (version === this._networkEndpointVersion) {
|
||||
return
|
||||
}
|
||||
|
||||
this._networkEndpointVersion = version
|
||||
this._networkEndpoints = this.getNetworkEndpoints(version)
|
||||
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
|
||||
const { type } = this.getProviderConfig()
|
||||
|
||||
return this.setProviderType(type, true)
|
||||
}
|
||||
|
||||
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
|
||||
return networkConfig[version]
|
||||
}
|
||||
|
||||
initializeProvider (_providerParams) {
|
||||
this._baseProviderParams = _providerParams
|
||||
const rpcUrl = this.getCurrentRpcAddress()
|
||||
this._configureStandardProvider({ rpcUrl })
|
||||
const { type, rpcTarget } = this.providerStore.getState()
|
||||
// map rpcTarget to rpcUrl
|
||||
const opts = {
|
||||
type,
|
||||
rpcUrl: rpcTarget,
|
||||
}
|
||||
this._configureProvider(opts)
|
||||
this._proxy.on('block', this._logBlock.bind(this))
|
||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
||||
this.ethQuery = new EthQuery(this._proxy)
|
||||
@ -53,7 +82,7 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
lookupNetwork () {
|
||||
// Prevent firing when provider is not defined.
|
||||
if (!this.ethQuery || !this.ethQuery.sendAsync) {
|
||||
return
|
||||
return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery')
|
||||
}
|
||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||
if (err) return this.setNetworkState('loading')
|
||||
@ -76,14 +105,17 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
return this.getRpcAddressForType(provider.type)
|
||||
}
|
||||
|
||||
async setProviderType (type) {
|
||||
async setProviderType (type, forceUpdate = false) {
|
||||
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
|
||||
// skip if type already matches
|
||||
if (type === this.getProviderConfig().type) return
|
||||
if (type === this.getProviderConfig().type && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
const rpcTarget = this.getRpcAddressForType(type)
|
||||
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
|
||||
this.providerStore.updateState({ type, rpcTarget })
|
||||
this._switchNetwork({ rpcUrl: rpcTarget })
|
||||
this._switchNetwork({ type })
|
||||
}
|
||||
|
||||
getProviderConfig () {
|
||||
@ -91,22 +123,65 @@ module.exports = class NetworkController extends EventEmitter {
|
||||
}
|
||||
|
||||
getRpcAddressForType (type, provider = this.getProviderConfig()) {
|
||||
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type]
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
|
||||
if (this._networkEndpoints[type]) {
|
||||
return this._networkEndpoints[type]
|
||||
}
|
||||
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
|
||||
}
|
||||
|
||||
//
|
||||
// Private
|
||||
//
|
||||
|
||||
_switchNetwork (providerParams) {
|
||||
_switchNetwork (opts) {
|
||||
this.setNetworkState('loading')
|
||||
this._configureStandardProvider(providerParams)
|
||||
this._configureProvider(opts)
|
||||
this.emit('networkDidChange')
|
||||
}
|
||||
|
||||
_configureStandardProvider (_providerParams) {
|
||||
const providerParams = extend(this._baseProviderParams, _providerParams)
|
||||
_configureProvider (opts) {
|
||||
// type-based rpc endpoints
|
||||
const { type } = opts
|
||||
if (type) {
|
||||
// type-based infura rpc endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||
opts.rpcUrl = this.getRpcAddressForType(type)
|
||||
if (isInfura) {
|
||||
this._configureInfuraProvider(opts)
|
||||
// other type-based rpc endpoints
|
||||
} else {
|
||||
this._configureStandardProvider(opts)
|
||||
}
|
||||
// url-based rpc endpoints
|
||||
} else {
|
||||
this._configureStandardProvider(opts)
|
||||
}
|
||||
}
|
||||
|
||||
_configureInfuraProvider (opts) {
|
||||
log.info('_configureInfuraProvider', opts)
|
||||
const blockTrackerProvider = createInfuraProvider({
|
||||
network: opts.type,
|
||||
})
|
||||
const providerParams = extend(this._baseProviderParams, {
|
||||
rpcUrl: opts.rpcUrl,
|
||||
engineParams: {
|
||||
pollingInterval: 8000,
|
||||
blockTrackerProvider,
|
||||
},
|
||||
})
|
||||
const provider = createMetamaskProvider(providerParams)
|
||||
this._setProvider(provider)
|
||||
}
|
||||
|
||||
_configureStandardProvider ({ rpcUrl }) {
|
||||
const providerParams = extend(this._baseProviderParams, {
|
||||
rpcUrl,
|
||||
engineParams: {
|
||||
pollingInterval: 8000,
|
||||
},
|
||||
})
|
||||
const provider = createMetamaskProvider(providerParams)
|
||||
this._setProvider(provider)
|
||||
}
|
||||
|
@ -9,11 +9,21 @@ class PreferencesController {
|
||||
frequentRpcList: [],
|
||||
currentAccountTab: 'history',
|
||||
tokens: [],
|
||||
useBlockie: false,
|
||||
featureFlags: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
|
||||
setUseBlockie (val) {
|
||||
this.store.updateState({ useBlockie: val })
|
||||
}
|
||||
|
||||
getUseBlockie () {
|
||||
return this.store.getState().useBlockie
|
||||
}
|
||||
|
||||
setSelectedAddress (_address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const address = normalizeAddress(_address)
|
||||
@ -26,22 +36,24 @@ class PreferencesController {
|
||||
return this.store.getState().selectedAddress
|
||||
}
|
||||
|
||||
addToken (rawAddress, symbol, decimals) {
|
||||
async addToken (rawAddress, symbol, decimals) {
|
||||
const address = normalizeAddress(rawAddress)
|
||||
const newEntry = { address, symbol, decimals }
|
||||
|
||||
const tokens = this.store.getState().tokens
|
||||
const previousIndex = tokens.find((token, index) => {
|
||||
const previousEntry = tokens.find((token, index) => {
|
||||
return token.address === address
|
||||
})
|
||||
const previousIndex = tokens.indexOf(previousEntry)
|
||||
|
||||
if (previousIndex) {
|
||||
if (previousEntry) {
|
||||
tokens[previousIndex] = newEntry
|
||||
} else {
|
||||
tokens.push(newEntry)
|
||||
}
|
||||
|
||||
this.store.updateState({ tokens })
|
||||
|
||||
return Promise.resolve(tokens)
|
||||
}
|
||||
|
||||
@ -91,6 +103,22 @@ class PreferencesController {
|
||||
getFrequentRpcList () {
|
||||
return this.store.getState().frequentRpcList
|
||||
}
|
||||
|
||||
setFeatureFlag (feature, activated) {
|
||||
const currentFeatureFlags = this.store.getState().featureFlags
|
||||
const updatedFeatureFlags = {
|
||||
...currentFeatureFlags,
|
||||
[feature]: activated,
|
||||
}
|
||||
|
||||
this.store.updateState({ featureFlags: updatedFeatureFlags })
|
||||
|
||||
return Promise.resolve(updatedFeatureFlags)
|
||||
}
|
||||
|
||||
getFeatureFlags () {
|
||||
return this.store.getState().featureFlags
|
||||
}
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
44
app/scripts/controllers/recent-blocks.js
Normal file
44
app/scripts/controllers/recent-blocks.js
Normal file
@ -0,0 +1,44 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
|
||||
class RecentBlocksController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const { blockTracker } = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.historyLength = opts.historyLength || 40
|
||||
|
||||
const initState = extend({
|
||||
recentBlocks: [],
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.blockTracker.on('block', this.processBlock.bind(this))
|
||||
}
|
||||
|
||||
resetState () {
|
||||
this.store.updateState({
|
||||
recentBlocks: [],
|
||||
})
|
||||
}
|
||||
|
||||
processBlock (newBlock) {
|
||||
const block = extend(newBlock, {
|
||||
gasPrices: newBlock.transactions.map((tx) => {
|
||||
return tx.gasPrice
|
||||
}),
|
||||
})
|
||||
delete block.transactions
|
||||
|
||||
const state = this.store.getState()
|
||||
state.recentBlocks.push(block)
|
||||
|
||||
while (state.recentBlocks.length > this.historyLength) {
|
||||
state.recentBlocks.shift()
|
||||
}
|
||||
|
||||
this.store.updateState(state)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RecentBlocksController
|
@ -59,7 +59,6 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
this.pendingTxTracker = new PendingTransactionTracker({
|
||||
provider: this.provider,
|
||||
nonceTracker: this.nonceTracker,
|
||||
retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day.
|
||||
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||
@ -72,6 +71,12 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
})
|
||||
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
||||
this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
|
||||
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||
if (!txMeta.firstRetryBlockNumber) {
|
||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
|
||||
}
|
||||
})
|
||||
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
||||
if (!('retryCount' in txMeta)) txMeta.retryCount = 0
|
||||
txMeta.retryCount++
|
||||
@ -132,18 +137,20 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
|
||||
async newUnapprovedTransaction (txParams) {
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
const txMeta = await this.addUnapprovedTransaction(txParams)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
|
||||
this.emit('newUnapprovedTx', initialTxMeta)
|
||||
// listen for tx completion (success, fail)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => {
|
||||
switch (completedTx.status) {
|
||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
||||
switch (finishedTxMeta.status) {
|
||||
case 'submitted':
|
||||
return resolve(completedTx.hash)
|
||||
return resolve(finishedTxMeta.hash)
|
||||
case 'rejected':
|
||||
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
|
||||
case 'failed':
|
||||
return reject(new Error(finishedTxMeta.err.message))
|
||||
default:
|
||||
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`))
|
||||
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -171,6 +178,7 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
const txParams = txMeta.txParams
|
||||
// ensure value
|
||||
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
|
||||
txMeta.nonceSpecified = Boolean(txParams.nonce)
|
||||
const gasPrice = txParams.gasPrice || await this.query.gasPrice()
|
||||
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
|
||||
txParams.value = txParams.value || '0x0'
|
||||
@ -178,6 +186,17 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
return await this.txGasUtil.analyzeGasUsage(txMeta)
|
||||
}
|
||||
|
||||
async retryTransaction (txId) {
|
||||
this.txStateManager.setTxStatusUnapproved(txId)
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
txMeta.lastGasPrice = txMeta.txParams.gasPrice
|
||||
this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
|
||||
}
|
||||
|
||||
async updateTransaction (txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
||||
}
|
||||
|
||||
async updateAndApproveTransaction (txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
||||
await this.approveTransaction(txMeta.id)
|
||||
@ -194,7 +213,12 @@ module.exports = class TransactionController extends EventEmitter {
|
||||
// wait for a nonce
|
||||
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
|
||||
// add nonce to txParams
|
||||
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16))
|
||||
const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
|
||||
if (nonce > nonceLock.nextNonce) {
|
||||
const message = `Specified nonce may not be larger than account's next valid nonce.`
|
||||
throw new Error(message)
|
||||
}
|
||||
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
|
||||
// add nonce debugging information to txMeta
|
||||
txMeta.nonceDetails = nonceLock.nonceDetails
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
|
||||
|
@ -31,6 +31,13 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
// setup web3
|
||||
//
|
||||
|
||||
if (typeof window.web3 !== 'undefined') {
|
||||
throw new Error(`MetaMask detected another web3.
|
||||
MetaMask will not work reliably with another web3 extension.
|
||||
This usually happens if you have two MetaMasks installed,
|
||||
or MetaMask and another web3 extension. Please remove one
|
||||
and try again.`)
|
||||
}
|
||||
var web3 = new Web3(inpageProvider)
|
||||
web3.setProvider = function () {
|
||||
log.debug('MetaMask - overrode web3.setProvider')
|
||||
|
@ -117,8 +117,6 @@ class AccountTracker extends EventEmitter {
|
||||
const query = this._query
|
||||
async.parallel({
|
||||
balance: query.getBalance.bind(query, address),
|
||||
nonce: query.getTransactionCount.bind(query, address),
|
||||
code: query.getCode.bind(query, address),
|
||||
}, cb)
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ const RpcEngine = require('json-rpc-engine')
|
||||
const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware')
|
||||
const createStreamMiddleware = require('json-rpc-middleware-stream')
|
||||
const LocalStorageStore = require('obs-store')
|
||||
const asStream = require('obs-store/lib/asStream')
|
||||
const ObjectMultiplex = require('obj-multiplex')
|
||||
|
||||
module.exports = MetamaskInpageProvider
|
||||
@ -21,9 +22,10 @@ function MetamaskInpageProvider (connectionStream) {
|
||||
|
||||
// subscribe to metamask public config (one-way)
|
||||
self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })
|
||||
|
||||
pump(
|
||||
mux.createStream('publicConfig'),
|
||||
self.publicConfigStore,
|
||||
asStream(self.publicConfigStore),
|
||||
(err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err)
|
||||
)
|
||||
|
||||
|
@ -23,7 +23,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
this.query = new EthQuery(config.provider)
|
||||
this.nonceTracker = config.nonceTracker
|
||||
// default is one day
|
||||
this.retryTimePeriod = config.retryTimePeriod || 86400000
|
||||
this.getPendingTransactions = config.getPendingTransactions
|
||||
this.getCompletedTransactions = config.getCompletedTransactions
|
||||
this.publishTransaction = config.publishTransaction
|
||||
@ -65,11 +64,11 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
}
|
||||
|
||||
|
||||
resubmitPendingTxs () {
|
||||
resubmitPendingTxs (block) {
|
||||
const pending = this.getPendingTransactions()
|
||||
// only try resubmitting if their are transactions to resubmit
|
||||
if (!pending.length) return
|
||||
pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => {
|
||||
pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => {
|
||||
/*
|
||||
Dont marked as failed if the error is a "known" transaction warning
|
||||
"there is already a transaction with the same sender-nonce
|
||||
@ -101,13 +100,19 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
|
||||
}))
|
||||
}
|
||||
|
||||
async _resubmitTx (txMeta) {
|
||||
if (Date.now() > txMeta.time + this.retryTimePeriod) {
|
||||
const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1)
|
||||
const err = new Error(`Gave up submitting after ${hours} hours.`)
|
||||
return this.emit('tx:failed', txMeta.id, err)
|
||||
async _resubmitTx (txMeta, latestBlockNumber) {
|
||||
if (!txMeta.firstRetryBlockNumber) {
|
||||
this.emit('tx:block-update', txMeta, latestBlockNumber)
|
||||
}
|
||||
|
||||
const firstRetryBlockNumber = txMeta.firstRetryBlockNumber || latestBlockNumber
|
||||
const txBlockDistance = Number.parseInt(latestBlockNumber, 16) - Number.parseInt(firstRetryBlockNumber, 16)
|
||||
|
||||
const retryCount = txMeta.retryCount || 0
|
||||
|
||||
// Exponential backoff to limit retries at publishing
|
||||
if (txBlockDistance <= Math.pow(2, retryCount) - 1) return
|
||||
|
||||
// Only auto-submit already-signed txs:
|
||||
if (!('rawTx' in txMeta)) return
|
||||
|
||||
|
@ -22,7 +22,11 @@ module.exports = class txProvideUtil {
|
||||
try {
|
||||
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
|
||||
} catch (err) {
|
||||
if (err.message.includes('Transaction execution error.')) {
|
||||
const simulationFailed = (
|
||||
err.message.includes('Transaction execution error.') ||
|
||||
err.message.includes('gas required exceeds allowance or always failing transaction')
|
||||
)
|
||||
if ( simulationFailed ) {
|
||||
txMeta.simulationFails = true
|
||||
return txMeta
|
||||
}
|
||||
|
@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter {
|
||||
this._setTxStatus(txId, 'rejected')
|
||||
}
|
||||
|
||||
// should update the status of the tx to 'unapproved'.
|
||||
setTxStatusUnapproved (txId) {
|
||||
this._setTxStatus(txId, 'unapproved')
|
||||
}
|
||||
// should update the status of the tx to 'approved'.
|
||||
setTxStatusApproved (txId) {
|
||||
this._setTxStatus(txId, 'approved')
|
||||
@ -236,7 +240,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
|
||||
txMeta.status = status
|
||||
this.emit(`${txMeta.id}:${status}`, txId)
|
||||
this.emit(`tx:status-update`, txId, status)
|
||||
if (status === 'submitted' || status === 'rejected') {
|
||||
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||
}
|
||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||
|
@ -3,6 +3,7 @@ const extend = require('xtend')
|
||||
const pump = require('pump')
|
||||
const Dnode = require('dnode')
|
||||
const ObservableStore = require('obs-store')
|
||||
const asStream = require('obs-store/lib/asStream')
|
||||
const AccountTracker = require('./lib/account-tracker')
|
||||
const EthQuery = require('eth-query')
|
||||
const RpcEngine = require('json-rpc-engine')
|
||||
@ -22,6 +23,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
|
||||
const AddressBookController = require('./controllers/address-book')
|
||||
const InfuraController = require('./controllers/infura')
|
||||
const BlacklistController = require('./controllers/blacklist')
|
||||
const RecentBlocksController = require('./controllers/recent-blocks')
|
||||
const MessageManager = require('./lib/message-manager')
|
||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||
const TypedMessageManager = require('./lib/typed-message-manager')
|
||||
@ -31,6 +33,7 @@ const ConfigManager = require('./lib/config-manager')
|
||||
const nodeify = require('./lib/nodeify')
|
||||
const accountImporter = require('./account-import-strategies')
|
||||
const getBuyEthUrl = require('./lib/buy-eth-url')
|
||||
const Mutex = require('await-semaphore').Mutex
|
||||
const version = require('../manifest.json').version
|
||||
|
||||
module.exports = class MetamaskController extends EventEmitter {
|
||||
@ -38,10 +41,12 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
|
||||
|
||||
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
|
||||
|
||||
this.opts = opts
|
||||
const initState = opts.initState || {}
|
||||
this.recordFirstTimeInfo(initState)
|
||||
|
||||
// platform-specific api
|
||||
this.platform = opts.platform
|
||||
@ -49,6 +54,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// observable state store
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
// lock to ensure only one vault created at once
|
||||
this.createVaultMutex = new Mutex()
|
||||
|
||||
// network store
|
||||
this.networkController = new NetworkController(initState.NetworkController)
|
||||
|
||||
@ -84,6 +92,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.provider = this.initializeProvider()
|
||||
this.blockTracker = this.provider._blockTracker
|
||||
|
||||
this.recentBlocksController = new RecentBlocksController({
|
||||
blockTracker: this.blockTracker,
|
||||
})
|
||||
|
||||
// eth data query tools
|
||||
this.ethQuery = new EthQuery(this.provider)
|
||||
// account tracker watches balances, nonces, and any code at their address.
|
||||
@ -144,6 +156,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// notices
|
||||
this.noticeController = new NoticeController({
|
||||
initState: initState.NoticeController,
|
||||
version,
|
||||
firstVersion: initState.firstTimeInfo.version,
|
||||
})
|
||||
this.noticeController.updateNoticesList()
|
||||
// to be uncommented when retrieving notices from a remote server.
|
||||
@ -187,25 +201,30 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.blacklistController.store.subscribe((state) => {
|
||||
this.store.updateState({ BlacklistController: state })
|
||||
})
|
||||
this.recentBlocksController.store.subscribe((state) => {
|
||||
this.store.updateState({ RecentBlocks: state })
|
||||
})
|
||||
this.infuraController.store.subscribe((state) => {
|
||||
this.store.updateState({ InfuraController: state })
|
||||
})
|
||||
|
||||
// manual mem state subscriptions
|
||||
this.networkController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.accountTracker.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.txController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.balancesController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.addressBookController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.currencyController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this))
|
||||
this.infuraController.store.subscribe(this.sendUpdate.bind(this))
|
||||
const sendUpdate = this.sendUpdate.bind(this)
|
||||
this.networkController.store.subscribe(sendUpdate)
|
||||
this.accountTracker.store.subscribe(sendUpdate)
|
||||
this.txController.memStore.subscribe(sendUpdate)
|
||||
this.balancesController.store.subscribe(sendUpdate)
|
||||
this.messageManager.memStore.subscribe(sendUpdate)
|
||||
this.personalMessageManager.memStore.subscribe(sendUpdate)
|
||||
this.typedMessageManager.memStore.subscribe(sendUpdate)
|
||||
this.keyringController.memStore.subscribe(sendUpdate)
|
||||
this.preferencesController.store.subscribe(sendUpdate)
|
||||
this.recentBlocksController.store.subscribe(sendUpdate)
|
||||
this.addressBookController.store.subscribe(sendUpdate)
|
||||
this.currencyController.store.subscribe(sendUpdate)
|
||||
this.noticeController.memStore.subscribe(sendUpdate)
|
||||
this.shapeshiftController.store.subscribe(sendUpdate)
|
||||
this.infuraController.store.subscribe(sendUpdate)
|
||||
}
|
||||
|
||||
//
|
||||
@ -289,6 +308,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.currencyController.store.getState(),
|
||||
this.noticeController.memStore.getState(),
|
||||
this.infuraController.store.getState(),
|
||||
this.recentBlocksController.store.getState(),
|
||||
// config manager
|
||||
this.configManager.getConfig(),
|
||||
this.shapeshiftController.store.getState(),
|
||||
@ -315,6 +335,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// etc
|
||||
getState: (cb) => cb(null, this.getState()),
|
||||
setCurrentCurrency: this.setCurrentCurrency.bind(this),
|
||||
setUseBlockie: this.setUseBlockie.bind(this),
|
||||
markAccountsFound: this.markAccountsFound.bind(this),
|
||||
|
||||
// coinbase
|
||||
@ -332,6 +353,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
||||
|
||||
// network management
|
||||
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
|
||||
setProviderType: nodeify(networkController.setProviderType, networkController),
|
||||
setCustomRpc: nodeify(this.setCustomRpc, this),
|
||||
|
||||
@ -340,6 +362,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
addToken: nodeify(preferencesController.addToken, preferencesController),
|
||||
removeToken: nodeify(preferencesController.removeToken, preferencesController),
|
||||
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
|
||||
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
|
||||
|
||||
// AddressController
|
||||
setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
|
||||
@ -354,7 +377,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
// txController
|
||||
cancelTransaction: nodeify(txController.cancelTransaction, txController),
|
||||
updateTransaction: nodeify(txController.updateTransaction, txController),
|
||||
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
|
||||
retryTransaction: nodeify(this.retryTransaction, this),
|
||||
|
||||
// messageManager
|
||||
signMessage: nodeify(this.signMessage, this),
|
||||
@ -452,7 +477,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
setupPublicConfig (outStream) {
|
||||
pump(
|
||||
this.publicConfigStore,
|
||||
asStream(this.publicConfigStore),
|
||||
outStream,
|
||||
(err) => {
|
||||
if (err) log.error(err)
|
||||
@ -468,15 +493,34 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// Vault Management
|
||||
//
|
||||
|
||||
async createNewVaultAndKeychain (password, cb) {
|
||||
const vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||
this.selectFirstIdentity(vault)
|
||||
async createNewVaultAndKeychain (password) {
|
||||
const release = await this.createVaultMutex.acquire()
|
||||
let vault
|
||||
|
||||
try {
|
||||
const accounts = await this.keyringController.getAccounts()
|
||||
|
||||
if (accounts.length > 0) {
|
||||
vault = await this.keyringController.fullUpdate()
|
||||
|
||||
} else {
|
||||
vault = await this.keyringController.createNewVaultAndKeychain(password)
|
||||
this.selectFirstIdentity(vault)
|
||||
}
|
||||
release()
|
||||
} catch (err) {
|
||||
release()
|
||||
throw err
|
||||
}
|
||||
|
||||
return vault
|
||||
}
|
||||
|
||||
async createNewVaultAndRestore (password, seed, cb) {
|
||||
async createNewVaultAndRestore (password, seed) {
|
||||
const release = await this.createVaultMutex.acquire()
|
||||
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
|
||||
this.selectFirstIdentity(vault)
|
||||
release()
|
||||
return vault
|
||||
}
|
||||
|
||||
@ -546,6 +590,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
//
|
||||
// Identity Management
|
||||
//
|
||||
//
|
||||
|
||||
async retryTransaction (txId, cb) {
|
||||
await this.txController.retryTransaction(txId)
|
||||
const state = await this.getState()
|
||||
return state
|
||||
}
|
||||
|
||||
|
||||
newUnsignedMessage (msgParams, cb) {
|
||||
const msgId = this.messageManager.addUnapprovedMessage(msgParams)
|
||||
@ -774,4 +826,22 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return rpcTarget
|
||||
}
|
||||
|
||||
setUseBlockie (val, cb) {
|
||||
try {
|
||||
this.preferencesController.setUseBlockie(val)
|
||||
cb(null)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
recordFirstTimeInfo (initState) {
|
||||
if (!('firstTimeInfo' in initState)) {
|
||||
initState.firstTimeInfo = {
|
||||
version,
|
||||
date: Date.now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
41
app/scripts/migrations/020.js
Normal file
41
app/scripts/migrations/020.js
Normal file
@ -0,0 +1,41 @@
|
||||
const version = 20
|
||||
|
||||
/*
|
||||
|
||||
This migration ensures previous installations
|
||||
get a `firstTimeInfo` key on the metamask state,
|
||||
so that we can version notices in the future.
|
||||
|
||||
*/
|
||||
|
||||
const clone = require('clone')
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
const state = versionedData.data
|
||||
const newState = transformState(state)
|
||||
versionedData.data = newState
|
||||
} catch (err) {
|
||||
console.warn(`MetaMask Migration #${version}` + err.stack)
|
||||
}
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const newState = state
|
||||
if ('metamask' in newState &&
|
||||
!('firstTimeInfo' in newState.metamask)) {
|
||||
newState.metamask.firstTimeInfo = {
|
||||
version: '3.12.0',
|
||||
date: Date.now(),
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
@ -30,4 +30,5 @@ module.exports = [
|
||||
require('./017'),
|
||||
require('./018'),
|
||||
require('./019'),
|
||||
require('./020'),
|
||||
]
|
||||
|
@ -1,13 +1,17 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const semver = require('semver')
|
||||
const extend = require('xtend')
|
||||
const ObservableStore = require('obs-store')
|
||||
const hardCodedNotices = require('../../notices/notices.json')
|
||||
const uniqBy = require('lodash.uniqby')
|
||||
|
||||
module.exports = class NoticeController extends EventEmitter {
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.noticePoller = null
|
||||
this.firstVersion = opts.firstVersion
|
||||
this.version = opts.version
|
||||
const initState = extend({
|
||||
noticesList: [],
|
||||
}, opts.initState)
|
||||
@ -30,9 +34,9 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
return unreadNotices[unreadNotices.length - 1]
|
||||
}
|
||||
|
||||
setNoticesList (noticesList) {
|
||||
async setNoticesList (noticesList) {
|
||||
this.store.updateState({ noticesList })
|
||||
return Promise.resolve(true)
|
||||
return true
|
||||
}
|
||||
|
||||
markNoticeRead (noticeToMark, cb) {
|
||||
@ -50,12 +54,14 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
updateNoticesList () {
|
||||
return this._retrieveNoticeData().then((newNotices) => {
|
||||
var oldNotices = this.getNoticesList()
|
||||
var combinedNotices = this._mergeNotices(oldNotices, newNotices)
|
||||
return Promise.resolve(this.setNoticesList(combinedNotices))
|
||||
})
|
||||
async updateNoticesList () {
|
||||
const newNotices = await this._retrieveNoticeData()
|
||||
const oldNotices = this.getNoticesList()
|
||||
const combinedNotices = this._mergeNotices(oldNotices, newNotices)
|
||||
const filteredNotices = this._filterNotices(combinedNotices)
|
||||
const result = this.setNoticesList(filteredNotices)
|
||||
this._updateMemstore()
|
||||
return result
|
||||
}
|
||||
|
||||
startPolling () {
|
||||
@ -68,22 +74,30 @@ module.exports = class NoticeController extends EventEmitter {
|
||||
}
|
||||
|
||||
_mergeNotices (oldNotices, newNotices) {
|
||||
var noticeMap = this._mapNoticeIds(oldNotices)
|
||||
newNotices.forEach((notice) => {
|
||||
if (noticeMap.indexOf(notice.id) === -1) {
|
||||
oldNotices.push(notice)
|
||||
return uniqBy(oldNotices.concat(newNotices), 'id')
|
||||
}
|
||||
|
||||
_filterNotices(notices) {
|
||||
return notices.filter((newNotice) => {
|
||||
if ('version' in newNotice) {
|
||||
const satisfied = semver.satisfies(this.version, newNotice.version)
|
||||
return satisfied
|
||||
}
|
||||
if ('firstVersion' in newNotice) {
|
||||
const satisfied = semver.satisfies(this.firstVersion, newNotice.firstVersion)
|
||||
return satisfied
|
||||
}
|
||||
return true
|
||||
})
|
||||
return oldNotices
|
||||
}
|
||||
|
||||
_mapNoticeIds (notices) {
|
||||
return notices.map((notice) => notice.id)
|
||||
}
|
||||
|
||||
_retrieveNoticeData () {
|
||||
async _retrieveNoticeData () {
|
||||
// Placeholder for the API.
|
||||
return Promise.resolve(hardCodedNotices)
|
||||
return hardCodedNotices
|
||||
}
|
||||
|
||||
_updateMemstore () {
|
||||
|
@ -17,6 +17,11 @@ class ExtensionPlatform {
|
||||
return extension.runtime.getManifest().version
|
||||
}
|
||||
|
||||
openExtensionInBrowser () {
|
||||
const extensionURL = extension.runtime.getURL('home.html')
|
||||
this.openWindow({ url: extensionURL })
|
||||
}
|
||||
|
||||
getPlatformInfo (cb) {
|
||||
try {
|
||||
extension.runtime.getPlatformInfo((platform) => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const injectCss = require('inject-css')
|
||||
const MetaMaskUiCss = require('../../ui/css')
|
||||
const OldMetaMaskUiCss = require('../../old-ui/css')
|
||||
const NewMetaMaskUiCss = require('../../ui/css')
|
||||
const startPopup = require('./popup-core')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||
@ -11,10 +12,6 @@ const notificationManager = new NotificationManager()
|
||||
// create platform global
|
||||
global.platform = new ExtensionPlatform()
|
||||
|
||||
// inject css
|
||||
const css = MetaMaskUiCss()
|
||||
injectCss(css)
|
||||
|
||||
// identify window type (popup, notification)
|
||||
const windowType = isPopupOrNotification()
|
||||
global.METAMASK_UI_TYPE = windowType
|
||||
@ -28,8 +25,21 @@ const connectionStream = new PortStream(extensionPort)
|
||||
const container = document.getElementById('app-content')
|
||||
startPopup({ container, connectionStream }, (err, store) => {
|
||||
if (err) return displayCriticalError(err)
|
||||
|
||||
let betaUIState = store.getState().metamask.featureFlags.betaUI
|
||||
let css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
let deleteInjectedCss = injectCss(css)
|
||||
let newBetaUIState
|
||||
|
||||
store.subscribe(() => {
|
||||
const state = store.getState()
|
||||
newBetaUIState = state.metamask.featureFlags.betaUI
|
||||
if (newBetaUIState !== betaUIState) {
|
||||
deleteInjectedCss()
|
||||
betaUIState = newBetaUIState
|
||||
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
|
||||
deleteInjectedCss = injectCss(css)
|
||||
}
|
||||
if (state.appState.shouldClose) notificationManager.closePopup()
|
||||
})
|
||||
})
|
||||
|
@ -8,6 +8,7 @@
|
||||
"frequentRpcList": [],
|
||||
"unapprovedTxs": {},
|
||||
"currentCurrency": "USD",
|
||||
"featureFlags": {"betaUI": true},
|
||||
"conversionRate": 12.7527416,
|
||||
"conversionDate": 1487624341,
|
||||
"noActiveNotices": false,
|
||||
|
739
development/states/pending-tx.json
Normal file
739
development/states/pending-tx.json
Normal file
@ -0,0 +1,739 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"isMascara": false,
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"name": "Account 1"
|
||||
}
|
||||
},
|
||||
"unapprovedTxs": {},
|
||||
"noActiveNotices": true,
|
||||
"frequentRpcList": [
|
||||
"http://192.168.1.34:7545/"
|
||||
],
|
||||
"addressBook": [],
|
||||
"tokenExchangeRates": {},
|
||||
"coinOptions": {},
|
||||
"provider": {
|
||||
"type": "mainnet",
|
||||
"rpcTarget": "https://mainnet.infura.io/metamask"
|
||||
},
|
||||
"network": "1",
|
||||
"accounts": {
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||
"code": "0x",
|
||||
"balance": "0x1b3f641ed0c2f62",
|
||||
"nonce": "0x35",
|
||||
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
}
|
||||
},
|
||||
"currentBlockGasLimit": "0x66df83",
|
||||
"selectedAddressTxList": [
|
||||
{
|
||||
"id": 3516145537630216,
|
||||
"time": 1512615655535,
|
||||
"status": "submitted",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xc1b710800",
|
||||
"gas": "0x7b0c",
|
||||
"nonce": "0x35",
|
||||
"chainId": "0x1"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208",
|
||||
"history": [
|
||||
{
|
||||
"id": 3516145537630216,
|
||||
"time": 1512615655535,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xe6f7cec00",
|
||||
"gas": "0x7b0c"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/txParams/gasPrice",
|
||||
"value": "0xc1b710800",
|
||||
"note": "confTx: user approved transaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/nonce",
|
||||
"value": "0x35",
|
||||
"note": "transactions#approveTransaction"
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/nonceDetails",
|
||||
"value": {
|
||||
"params": {
|
||||
"highestLocalNonce": 53,
|
||||
"highestSuggested": 53,
|
||||
"nextNetworkNonce": 53
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 53,
|
||||
"details": {
|
||||
"startPoint": 53,
|
||||
"highest": 53
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 53,
|
||||
"details": {
|
||||
"baseCount": 53
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/chainId",
|
||||
"value": "0x1",
|
||||
"note": "txStateManager: setting status to signed"
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "signed"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/rawTx",
|
||||
"value": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95",
|
||||
"note": "transactions#publishTransaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/hash",
|
||||
"value": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353",
|
||||
"note": "transactions#setTxHash"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "submitted",
|
||||
"note": "txStateManager: setting status to submitted"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/firstRetryBlockNumber",
|
||||
"value": "0x478ab3",
|
||||
"note": "transactions/pending-tx-tracker#event: tx:block-update"
|
||||
}
|
||||
]
|
||||
],
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocalNonce": 53,
|
||||
"highestSuggested": 53,
|
||||
"nextNetworkNonce": 53
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 53,
|
||||
"details": {
|
||||
"startPoint": 53,
|
||||
"highest": 53
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 53,
|
||||
"details": {
|
||||
"baseCount": 53
|
||||
}
|
||||
}
|
||||
},
|
||||
"rawTx": "0xf86c35850c1b710800827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0f5142ba79a13ca7ec65548953017edafb217803244bbf9821d9ad077d89921e9a03afcb614169c90be9905d5b469d06984825c76675d3a535937cdb8f2ad1c0a95",
|
||||
"hash": "0x7ce19c0d128ca11293b44a4e6d3cc9063665c00ea8c8eb400f548e132c147353",
|
||||
"firstRetryBlockNumber": "0x478ab3"
|
||||
},
|
||||
{
|
||||
"id": 3516145537630211,
|
||||
"time": 1512613432658,
|
||||
"status": "confirmed",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xba43b7400",
|
||||
"gas": "0x7b0c",
|
||||
"nonce": "0x34",
|
||||
"chainId": "0x1"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208",
|
||||
"history": [
|
||||
{
|
||||
"id": 3516145537630211,
|
||||
"time": 1512613432658,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xdf8475800",
|
||||
"gas": "0x7b0c"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/txParams/gasPrice",
|
||||
"value": "0xba43b7400",
|
||||
"note": "confTx: user approved transaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/nonce",
|
||||
"value": "0x34",
|
||||
"note": "transactions#approveTransaction"
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/nonceDetails",
|
||||
"value": {
|
||||
"params": {
|
||||
"highestLocalNonce": 52,
|
||||
"highestSuggested": 52,
|
||||
"nextNetworkNonce": 52
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 52,
|
||||
"details": {
|
||||
"startPoint": 52,
|
||||
"highest": 52
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 52,
|
||||
"details": {
|
||||
"baseCount": 52
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/chainId",
|
||||
"value": "0x1",
|
||||
"note": "txStateManager: setting status to signed"
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "signed"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/rawTx",
|
||||
"value": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e",
|
||||
"note": "transactions#publishTransaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/hash",
|
||||
"value": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d",
|
||||
"note": "transactions#setTxHash"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "submitted",
|
||||
"note": "txStateManager: setting status to submitted"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/firstRetryBlockNumber",
|
||||
"value": "0x478a2c",
|
||||
"note": "transactions/pending-tx-tracker#event: tx:block-update"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "confirmed",
|
||||
"note": "txStateManager: setting status to confirmed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocalNonce": 52,
|
||||
"highestSuggested": 52,
|
||||
"nextNetworkNonce": 52
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 52,
|
||||
"details": {
|
||||
"startPoint": 52,
|
||||
"highest": 52
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 52,
|
||||
"details": {
|
||||
"baseCount": 52
|
||||
}
|
||||
}
|
||||
},
|
||||
"rawTx": "0xf86c34850ba43b7400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a073a4afdb8e8ad32b0cf9039af56c66baffd60d30e75cee5c1b783208824eafb8a0021ca6c1714a2c71281333ab77f776d3514348ab77967280fca8a5b4be44285e",
|
||||
"hash": "0x5c98409883fdfd3cd24058a83b91470da6c40ffae41a40eb90d7dee0b837d26d",
|
||||
"firstRetryBlockNumber": "0x478a2c"
|
||||
},
|
||||
{
|
||||
"id": 3516145537630210,
|
||||
"time": 1512612826136,
|
||||
"status": "confirmed",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xa7a358200",
|
||||
"gas": "0x7b0c",
|
||||
"nonce": "0x33",
|
||||
"chainId": "0x1"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208",
|
||||
"history": [
|
||||
{
|
||||
"id": 3516145537630210,
|
||||
"time": 1512612826136,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xba43b7400",
|
||||
"gas": "0x7b0c"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/txParams/gasPrice",
|
||||
"value": "0xa7a358200",
|
||||
"note": "confTx: user approved transaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/nonce",
|
||||
"value": "0x33",
|
||||
"note": "transactions#approveTransaction"
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/nonceDetails",
|
||||
"value": {
|
||||
"params": {
|
||||
"highestLocalNonce": 0,
|
||||
"highestSuggested": 51,
|
||||
"nextNetworkNonce": 51
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"startPoint": 51,
|
||||
"highest": 51
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"baseCount": 51
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/chainId",
|
||||
"value": "0x1",
|
||||
"note": "txStateManager: setting status to signed"
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "signed"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/rawTx",
|
||||
"value": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8",
|
||||
"note": "transactions#publishTransaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/hash",
|
||||
"value": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044",
|
||||
"note": "transactions#setTxHash"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "submitted",
|
||||
"note": "txStateManager: setting status to submitted"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/firstRetryBlockNumber",
|
||||
"value": "0x478a04",
|
||||
"note": "transactions/pending-tx-tracker#event: tx:block-update"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "confirmed",
|
||||
"note": "txStateManager: setting status to confirmed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocalNonce": 0,
|
||||
"highestSuggested": 51,
|
||||
"nextNetworkNonce": 51
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"startPoint": 51,
|
||||
"highest": 51
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"baseCount": 51
|
||||
}
|
||||
}
|
||||
},
|
||||
"rawTx": "0xf86c33850a7a358200827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008026a0021a8cd6c10208cc593e22af53637e5d127cee5cc6f9443a3e758a02afff1d7ca025f7420e974d3f2c668c165040987c72543a8e709bfea3528a62836a6ced9ce8",
|
||||
"hash": "0x289772800898bc9cd414530d8581c0da257a9055e4aaaa6d10d92d700bfbd044",
|
||||
"firstRetryBlockNumber": "0x478a04"
|
||||
},
|
||||
{
|
||||
"id": 3516145537630209,
|
||||
"time": 1512612809252,
|
||||
"status": "failed",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0x77359400",
|
||||
"gas": "0x7b0c",
|
||||
"nonce": "0x33",
|
||||
"chainId": "0x1"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208",
|
||||
"history": [
|
||||
{
|
||||
"id": 3516145537630209,
|
||||
"time": 1512612809252,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "1",
|
||||
"txParams": {
|
||||
"from": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"to": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"value": "0x16345785d8a0000",
|
||||
"gasPrice": "0xba43b7400",
|
||||
"gas": "0x7b0c"
|
||||
},
|
||||
"gasPriceSpecified": false,
|
||||
"gasLimitSpecified": false,
|
||||
"estimatedGas": "5208"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/txParams/gasPrice",
|
||||
"value": "0x77359400",
|
||||
"note": "confTx: user approved transaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/nonce",
|
||||
"value": "0x33",
|
||||
"note": "transactions#approveTransaction"
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/nonceDetails",
|
||||
"value": {
|
||||
"params": {
|
||||
"highestLocalNonce": 0,
|
||||
"highestSuggested": 51,
|
||||
"nextNetworkNonce": 51
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"startPoint": 51,
|
||||
"highest": 51
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"baseCount": 51
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/txParams/chainId",
|
||||
"value": "0x1",
|
||||
"note": "txStateManager: setting status to signed"
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "signed"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/rawTx",
|
||||
"value": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7",
|
||||
"note": "transactions#publishTransaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/err",
|
||||
"value": {
|
||||
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced",
|
||||
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "failed",
|
||||
"note": "txStateManager: setting status to failed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocalNonce": 0,
|
||||
"highestSuggested": 51,
|
||||
"nextNetworkNonce": 51
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"startPoint": 51,
|
||||
"highest": 51
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 51,
|
||||
"details": {
|
||||
"baseCount": 51
|
||||
}
|
||||
}
|
||||
},
|
||||
"rawTx": "0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7",
|
||||
"err": {
|
||||
"message": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced",
|
||||
"stack": "Error: [ethjs-rpc] rpc error with payload {\"id\":7801900228852,\"jsonrpc\":\"2.0\",\"params\":[\"0xf86b338477359400827b0c94fdea65c8e26263f6d9a1b5de9555d2931a33b82588016345785d8a00008025a098624a27ae79b2b1adc63b913850f266a920cb9d93e6588b8df9b8883eb1b323a00cc6fd855723a234f4f93b48caf7a7659366d09e5c5887f0a4c2e5fa68012cd7\"],\"method\":\"eth_sendRawTransaction\"} Error: transaction underpriced\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:60327:26\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88030:9\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16678:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16522:25)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16694:16\n at resultObj.id (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:88012:9)\n at chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16813:16\n at replenish (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16527:17)\n at iterateeCallback (chrome-extension://ebjbdknjcgcbchkagneicjfpneaghdhb/scripts/background.js:16512:17)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"unapprovedMsgs": {},
|
||||
"unapprovedMsgCount": 0,
|
||||
"unapprovedPersonalMsgs": {},
|
||||
"unapprovedPersonalMsgCount": 0,
|
||||
"unapprovedTypedMessages": {},
|
||||
"unapprovedTypedMessagesCount": 0,
|
||||
"keyringTypes": [
|
||||
"Simple Key Pair",
|
||||
"HD Key Tree"
|
||||
],
|
||||
"keyrings": [
|
||||
{
|
||||
"type": "HD Key Tree",
|
||||
"accounts": [
|
||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
]
|
||||
}
|
||||
],
|
||||
"computedBalances": {},
|
||||
"currentAccountTab": "history",
|
||||
"tokens": [
|
||||
{
|
||||
"address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef",
|
||||
"symbol": "BAT",
|
||||
"decimals": "18"
|
||||
}
|
||||
],
|
||||
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
|
||||
"currentCurrency": "usd",
|
||||
"conversionRate": 418.62,
|
||||
"conversionDate": 1512615622,
|
||||
"infuraNetworkStatus": {
|
||||
"mainnet": "ok",
|
||||
"ropsten": "ok",
|
||||
"kovan": "ok",
|
||||
"rinkeby": "ok"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": []
|
||||
},
|
||||
"appState": {
|
||||
"shouldClose": true,
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail",
|
||||
"context": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions",
|
||||
"accountExport": "none",
|
||||
"privateKey": ""
|
||||
},
|
||||
"transForward": false,
|
||||
"isLoading": false,
|
||||
"warning": null,
|
||||
"forgottenPassword": false,
|
||||
"scrollToBottom": false
|
||||
},
|
||||
"identities": {},
|
||||
"version": "3.12.1",
|
||||
"platform": {
|
||||
"arch": "x86-64",
|
||||
"nacl_arch": "x86-64",
|
||||
"os": "mac"
|
||||
},
|
||||
"browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
|
||||
}
|
@ -126,11 +126,17 @@ gulp.task('manifest:production', function() {
|
||||
'./dist/firefox/manifest.json',
|
||||
'./dist/chrome/manifest.json',
|
||||
'./dist/edge/manifest.json',
|
||||
'./dist/opera/manifest.json',
|
||||
],{base: './dist/'})
|
||||
|
||||
// Exclude chromereload script in production:
|
||||
.pipe(gulpif(!debug,jsoneditor(function(json) {
|
||||
json.background.scripts = ["scripts/background.js"]
|
||||
json.background.scripts = json.background.scripts.filter((script) => {
|
||||
return !script.includes('chromereload')
|
||||
})
|
||||
return json
|
||||
})))
|
||||
|
||||
.pipe(gulp.dest('./dist/', { overwrite: true }))
|
||||
})
|
||||
|
||||
|
@ -64,7 +64,7 @@ class NoticeScreen extends Component {
|
||||
<Identicon address={address} diameter={70} />
|
||||
<div className="tou__title">{title}</div>
|
||||
<Markdown
|
||||
className="tou__body"
|
||||
className="tou__body markdown"
|
||||
source={body}
|
||||
skipHtml
|
||||
/>
|
||||
|
11
notices/archive/notice_3.md
Normal file
11
notices/archive/notice_3.md
Normal file
@ -0,0 +1,11 @@
|
||||
Please take a moment to [back up your seed phrase again](https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up).
|
||||
|
||||
MetaMask has become aware of a previous issue where a very small number of users were shown the wrong seed phrase to back up. The only way to protect yourself from this issue, is to back up your seed phrase again now.
|
||||
|
||||
You can follow the guide at this link:
|
||||
|
||||
[https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up](https://support.metamask.io/kb/article/28-abbu-always-be-backed-up-how-to-make-sure-your-12-word-metamask-seed-phrase-is-backed-up)
|
||||
|
||||
We have fixed the known issue, but will be issuing ongoing bug bounties to help prevent this kind of problem in the future.
|
||||
|
||||
For more information on this issue, [see this blog post](https://medium.com/metamask/seed-phrase-issue-bounty-awarded-e1986e811021)
|
@ -1 +1 @@
|
||||
3
|
||||
4
|
File diff suppressed because one or more lines are too long
66
old-ui/.gitignore
vendored
Normal file
66
old-ui/.gitignore
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/osx,node
|
||||
|
||||
### OSX ###
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
289
old-ui/app/account-detail.js
Normal file
289
old-ui/app/account-detail.js
Normal file
@ -0,0 +1,289 @@
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const valuesFor = require('./util').valuesFor
|
||||
const Identicon = require('./components/identicon')
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const TransactionList = require('./components/transaction-list')
|
||||
const ExportAccountView = require('./components/account-export')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const EditableLabel = require('./components/editable-label')
|
||||
const TabBar = require('./components/tab-bar')
|
||||
const TokenList = require('./components/token-list')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountDetailScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
metamask: state.metamask,
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
address: state.metamask.selectedAddress,
|
||||
accountDetail: state.appState.accountDetail,
|
||||
network: state.metamask.network,
|
||||
unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs),
|
||||
shapeShiftTxList: state.metamask.shapeShiftTxList,
|
||||
transactions: state.metamask.selectedAddressTxList || [],
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
currentAccountTab: state.metamask.currentAccountTab,
|
||||
tokens: state.metamask.tokens,
|
||||
computedBalances: state.metamask.computedBalances,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountDetailScreen, Component)
|
||||
function AccountDetailScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.render = function () {
|
||||
var props = this.props
|
||||
var selected = props.address || Object.keys(props.accounts)[0]
|
||||
var checksumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
var identity = props.identities[selected]
|
||||
var account = props.accounts[selected]
|
||||
const { network, conversionRate, currentCurrency } = props
|
||||
|
||||
return (
|
||||
|
||||
h('.account-detail-section.full-flex-height', [
|
||||
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header - identicon + nav
|
||||
h('div', {
|
||||
style: {
|
||||
paddingTop: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
|
||||
// large identicon and addresses
|
||||
h('.identicon-wrapper.select-none', [
|
||||
h(Identicon, {
|
||||
diameter: 62,
|
||||
address: selected,
|
||||
}),
|
||||
]),
|
||||
h('div.flex-column', {
|
||||
style: {
|
||||
lineHeight: '10px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h(EditableLabel, {
|
||||
textValue: identity ? identity.name : '',
|
||||
state: {
|
||||
isEditingLabel: false,
|
||||
},
|
||||
saveText: (text) => {
|
||||
props.dispatch(actions.saveAccountLabel(selected, text))
|
||||
},
|
||||
}, [
|
||||
|
||||
// What is shown when not editing + edit text:
|
||||
h('label.editing-label', [h('.edit-text', 'edit')]),
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
'div.font-medium.color-forest',
|
||||
{
|
||||
name: 'edit',
|
||||
style: {
|
||||
},
|
||||
},
|
||||
[
|
||||
h('h2', {
|
||||
style: {
|
||||
maxWidth: '180px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
padding: '5px 0px',
|
||||
lineHeight: '25px',
|
||||
},
|
||||
}, [
|
||||
identity && identity.name,
|
||||
]),
|
||||
]
|
||||
),
|
||||
h(
|
||||
AccountDropdowns,
|
||||
{
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
selected,
|
||||
network,
|
||||
identities: props.identities,
|
||||
enableAccountOptions: true,
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'baseline',
|
||||
},
|
||||
}, [
|
||||
|
||||
// address
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
paddingTop: '3px',
|
||||
width: '5em',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
marginTop: '15px',
|
||||
marginBottom: '15px',
|
||||
color: '#AEAEAE',
|
||||
},
|
||||
}, checksumAddress),
|
||||
]),
|
||||
|
||||
// account ballence
|
||||
|
||||
]),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
marginTop: '10px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('div', {}, [
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: { marginRight: '10px' },
|
||||
}, 'BUY'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.showSendPage()),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
|
||||
]),
|
||||
]),
|
||||
|
||||
// subview (tx history, pk export confirm, buy eth warning)
|
||||
this.subview(),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.subview = function () {
|
||||
var subview
|
||||
try {
|
||||
subview = this.props.accountDetail.subview
|
||||
} catch (e) {
|
||||
subview = null
|
||||
}
|
||||
|
||||
switch (subview) {
|
||||
case 'transactions':
|
||||
return this.tabSections()
|
||||
case 'export':
|
||||
var state = extend({key: 'export'}, this.props)
|
||||
return h(ExportAccountView, state)
|
||||
default:
|
||||
return this.tabSections()
|
||||
}
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.tabSections = function () {
|
||||
const { currentAccountTab } = this.props
|
||||
|
||||
return h('section.tabSection.full-flex-height.grow-tenx', [
|
||||
|
||||
h(TabBar, {
|
||||
tabs: [
|
||||
{ content: 'Sent', key: 'history' },
|
||||
{ content: 'Tokens', key: 'tokens' },
|
||||
],
|
||||
defaultTab: currentAccountTab || 'history',
|
||||
tabSelected: (key) => {
|
||||
this.props.dispatch(actions.setCurrentAccountTab(key))
|
||||
},
|
||||
}),
|
||||
|
||||
this.tabSwitchView(),
|
||||
])
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.tabSwitchView = function () {
|
||||
const props = this.props
|
||||
const { address, network } = props
|
||||
const { currentAccountTab, tokens } = this.props
|
||||
|
||||
switch (currentAccountTab) {
|
||||
case 'tokens':
|
||||
return h(TokenList, {
|
||||
userAddress: address,
|
||||
network,
|
||||
tokens,
|
||||
addToken: () => this.props.dispatch(actions.showAddTokenPage()),
|
||||
})
|
||||
default:
|
||||
return this.transactionList()
|
||||
}
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.transactionList = function () {
|
||||
const {transactions, unapprovedMsgs, address,
|
||||
network, shapeShiftTxList, conversionRate } = this.props
|
||||
|
||||
return h(TransactionList, {
|
||||
transactions: transactions.sort((a, b) => b.time - a.time),
|
||||
network,
|
||||
unapprovedMsgs,
|
||||
conversionRate,
|
||||
address,
|
||||
shapeShiftTxList,
|
||||
viewPendingTx: (txId) => {
|
||||
this.props.dispatch(actions.viewPendingTx(txId))
|
||||
},
|
||||
})
|
||||
}
|
101
old-ui/app/accounts/import/index.js
Normal file
101
old-ui/app/accounts/import/index.js
Normal file
@ -0,0 +1,101 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
import Select from 'react-select'
|
||||
|
||||
// Subviews
|
||||
const JsonImportView = require('./json.js')
|
||||
const PrivateKeyImportView = require('./private-key.js')
|
||||
|
||||
const menuItems = [
|
||||
'Private Key',
|
||||
'JSON File',
|
||||
]
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountImportSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
menuItems,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountImportSubview, Component)
|
||||
function AccountImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { menuItems } = props
|
||||
const { type } = state
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Import Accounts'),
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
padding: '10px',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('h3', { style: { padding: '3px' } }, 'SELECT TYPE'),
|
||||
|
||||
h('style', `
|
||||
.has-value.Select--single > .Select-control .Select-value .Select-value-label, .Select-value-label {
|
||||
color: rgb(174,174,174);
|
||||
}
|
||||
`),
|
||||
|
||||
h(Select, {
|
||||
name: 'import-type-select',
|
||||
clearable: false,
|
||||
value: type || menuItems[0],
|
||||
options: menuItems.map((type) => {
|
||||
return {
|
||||
value: type,
|
||||
label: type,
|
||||
}
|
||||
}),
|
||||
onChange: (opt) => {
|
||||
props.dispatch(actions.showImportPage())
|
||||
this.setState({ type: opt.value })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
this.renderImportView(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.renderImportView = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { type } = state
|
||||
const { menuItems } = props
|
||||
const current = type || menuItems[0]
|
||||
|
||||
switch (current) {
|
||||
case 'Private Key':
|
||||
return h(PrivateKeyImportView)
|
||||
case 'JSON File':
|
||||
return h(JsonImportView)
|
||||
default:
|
||||
return h(JsonImportView)
|
||||
}
|
||||
}
|
100
old-ui/app/accounts/import/json.js
Normal file
100
old-ui/app/accounts/import/json.js
Normal file
@ -0,0 +1,100 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
const FileInput = require('react-simple-file-input').default
|
||||
|
||||
const HELP_LINK = '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'
|
||||
|
||||
module.exports = connect(mapStateToProps)(JsonImportSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
error: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(JsonImportSubview, Component)
|
||||
function JsonImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.render = function () {
|
||||
const { error } = this.props
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '5px 15px 0px 15px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('p', 'Used by a variety of different clients'),
|
||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'),
|
||||
|
||||
h(FileInput, {
|
||||
readAs: 'text',
|
||||
onLoad: this.onLoad.bind(this),
|
||||
style: {
|
||||
margin: '20px 0px 12px 20px',
|
||||
fontSize: '15px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
placeholder: 'Enter password',
|
||||
id: 'json-password-box',
|
||||
onKeyPress: this.createKeyringOnEnter.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
},
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: this.createNewKeychain.bind(this),
|
||||
style: {
|
||||
margin: 12,
|
||||
},
|
||||
}, 'Import'),
|
||||
|
||||
error ? h('span.error', error) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.onLoad = function (event, file) {
|
||||
this.setState({file: file, fileContents: event.target.result})
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
JsonImportSubview.prototype.createNewKeychain = function () {
|
||||
const state = this.state
|
||||
const { fileContents } = state
|
||||
|
||||
if (!fileContents) {
|
||||
const message = 'You must select a file to import.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
const passwordInput = document.getElementById('json-password-box')
|
||||
const password = passwordInput.value
|
||||
|
||||
if (!password) {
|
||||
const message = 'You must enter a password for the selected file.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ]))
|
||||
}
|
67
old-ui/app/accounts/import/private-key.js
Normal file
67
old-ui/app/accounts/import/private-key.js
Normal file
@ -0,0 +1,67 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(PrivateKeyImportView)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
error: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(PrivateKeyImportView, Component)
|
||||
function PrivateKeyImportView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.render = function () {
|
||||
const { error } = this.props
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '5px 15px 0px 15px',
|
||||
},
|
||||
}, [
|
||||
h('span', 'Paste your private key string here'),
|
||||
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'private-key-box',
|
||||
onKeyPress: this.createKeyringOnEnter.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
},
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: this.createNewKeychain.bind(this),
|
||||
style: {
|
||||
margin: 12,
|
||||
},
|
||||
}, 'Import'),
|
||||
|
||||
error ? h('span.error', error) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.createNewKeychain = function () {
|
||||
const input = document.getElementById('private-key-box')
|
||||
const privateKey = input.value
|
||||
this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ]))
|
||||
}
|
30
old-ui/app/accounts/import/seed.js
Normal file
30
old-ui/app/accounts/import/seed.js
Normal file
@ -0,0 +1,30 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
module.exports = connect(mapStateToProps)(SeedImportSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(SeedImportSubview, Component)
|
||||
function SeedImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
SeedImportSubview.prototype.render = function () {
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
`Paste your seed phrase here!`,
|
||||
h('textarea'),
|
||||
h('br'),
|
||||
h('button', 'Submit'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
238
old-ui/app/add-token.js
Normal file
238
old-ui/app/add-token.js
Normal file
@ -0,0 +1,238 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const Tooltip = require('./components/tooltip.js')
|
||||
|
||||
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const abi = require('human-standard-token-abi')
|
||||
const Eth = require('ethjs-query')
|
||||
const EthContract = require('ethjs-contract')
|
||||
|
||||
const emptyAddr = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
module.exports = connect(mapStateToProps)(AddTokenScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
identities: state.metamask.identities,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AddTokenScreen, Component)
|
||||
function AddTokenScreen () {
|
||||
this.state = {
|
||||
warning: null,
|
||||
address: null,
|
||||
symbol: 'TOKEN',
|
||||
decimals: 18,
|
||||
}
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.render = function () {
|
||||
const state = this.state
|
||||
const props = this.props
|
||||
const { warning, symbol, decimals } = state
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
props.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Add Token'),
|
||||
]),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
padding: '0 20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
// conf view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-space-around', {
|
||||
style: {
|
||||
padding: '20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div', [
|
||||
h(Tooltip, {
|
||||
position: 'top',
|
||||
title: 'The contract of the actual token contract. Click for more info.',
|
||||
}, [
|
||||
h('a', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
|
||||
target: '_blank',
|
||||
}, [
|
||||
h('span', 'Token Contract Address '),
|
||||
h('i.fa.fa-question-circle'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('section.flex-row.flex-center', [
|
||||
h('input#token-address', {
|
||||
name: 'address',
|
||||
placeholder: 'Token Contract Address',
|
||||
onChange: this.tokenAddressDidChange.bind(this),
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Token Symbol'),
|
||||
]),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#token_symbol', {
|
||||
placeholder: `Like "ETH"`,
|
||||
value: symbol,
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onChange: (event) => {
|
||||
var element = event.target
|
||||
var symbol = element.value
|
||||
this.setState({ symbol })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Decimals of Precision'),
|
||||
]),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#token_decimals', {
|
||||
value: decimals,
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 36,
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onChange: (event) => {
|
||||
var element = event.target
|
||||
var decimals = element.value.trim()
|
||||
this.setState({ decimals })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick: (event) => {
|
||||
const valid = this.validateInputs()
|
||||
if (!valid) return
|
||||
|
||||
const { address, symbol, decimals } = this.state
|
||||
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
|
||||
},
|
||||
}, 'Add'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.componentWillMount = function () {
|
||||
if (typeof global.ethereumProvider === 'undefined') return
|
||||
|
||||
this.eth = new Eth(global.ethereumProvider)
|
||||
this.contract = new EthContract(this.eth)
|
||||
this.TokenContract = this.contract(abi)
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.tokenAddressDidChange = function (event) {
|
||||
const el = event.target
|
||||
const address = el.value.trim()
|
||||
if (ethUtil.isValidAddress(address) && address !== emptyAddr) {
|
||||
this.setState({ address })
|
||||
this.attemptToAutoFillTokenParams(address)
|
||||
}
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.validateInputs = function () {
|
||||
let msg = ''
|
||||
const state = this.state
|
||||
const identitiesList = Object.keys(this.props.identities)
|
||||
const { address, symbol, decimals } = state
|
||||
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
|
||||
|
||||
const validAddress = ethUtil.isValidAddress(address)
|
||||
if (!validAddress) {
|
||||
msg += 'Address is invalid. '
|
||||
}
|
||||
|
||||
const validDecimals = decimals >= 0 && decimals < 36
|
||||
if (!validDecimals) {
|
||||
msg += 'Decimals must be at least 0, and not over 36. '
|
||||
}
|
||||
|
||||
const symbolLen = symbol.trim().length
|
||||
const validSymbol = symbolLen > 0 && symbolLen < 10
|
||||
if (!validSymbol) {
|
||||
msg += 'Symbol must be between 0 and 10 characters.'
|
||||
}
|
||||
|
||||
const ownAddress = identitiesList.includes(standardAddress)
|
||||
if (ownAddress) {
|
||||
msg = 'Personal address detected. Input the token contract address.'
|
||||
}
|
||||
|
||||
const isValid = validAddress && validDecimals && !ownAddress
|
||||
|
||||
if (!isValid) {
|
||||
this.setState({
|
||||
warning: msg,
|
||||
})
|
||||
} else {
|
||||
this.setState({ warning: null })
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) {
|
||||
const contract = this.TokenContract.at(address)
|
||||
|
||||
const results = await Promise.all([
|
||||
contract.symbol(),
|
||||
contract.decimals(),
|
||||
])
|
||||
|
||||
const [ symbol, decimals ] = results
|
||||
if (symbol && decimals) {
|
||||
console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals })
|
||||
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() })
|
||||
}
|
||||
}
|
684
old-ui/app/app.js
Normal file
684
old-ui/app/app.js
Normal file
@ -0,0 +1,684 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../ui/app/actions')
|
||||
// mascara
|
||||
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
|
||||
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
|
||||
// init
|
||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||
const NewKeyChainScreen = require('./new-keychain')
|
||||
// unlock
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
const NoticeScreen = require('./components/notice')
|
||||
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
|
||||
// other views
|
||||
const ConfigScreen = require('./config')
|
||||
const AddTokenScreen = require('./add-token')
|
||||
const Import = require('./accounts/import')
|
||||
const InfoScreen = require('./info')
|
||||
const Loading = require('./components/loading')
|
||||
const SandwichExpando = require('sandwich-expando')
|
||||
const Dropdown = require('./components/dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const BuyView = require('./components/buy-button-subview')
|
||||
const QrView = require('./components/qr-code')
|
||||
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
inherits(App, Component)
|
||||
function App () { Component.call(this) }
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const {
|
||||
identities,
|
||||
accounts,
|
||||
address,
|
||||
keyrings,
|
||||
isInitialized,
|
||||
noActiveNotices,
|
||||
seedWords,
|
||||
featureFlags,
|
||||
} = state.metamask
|
||||
const selected = address || Object.keys(accounts)[0]
|
||||
|
||||
return {
|
||||
// state from plugin
|
||||
isLoading: state.appState.isLoading,
|
||||
loadingMessage: state.appState.loadingMessage,
|
||||
noActiveNotices: state.metamask.noActiveNotices,
|
||||
isInitialized: state.metamask.isInitialized,
|
||||
isUnlocked: state.metamask.isUnlocked,
|
||||
currentView: state.appState.currentView,
|
||||
activeAddress: state.appState.activeAddress,
|
||||
transForward: state.appState.transForward,
|
||||
isMascara: state.metamask.isMascara,
|
||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
||||
seedWords: state.metamask.seedWords,
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||
menuOpen: state.appState.menuOpen,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||
lostAccounts: state.metamask.lostAccounts,
|
||||
frequentRpcList: state.metamask.frequentRpcList || [],
|
||||
featureFlags,
|
||||
|
||||
// state needed to get account dropdown temporarily rendering from app bar
|
||||
identities,
|
||||
selected,
|
||||
keyrings,
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.render = function () {
|
||||
var props = this.props
|
||||
const { isLoading, loadingMessage, transForward, network } = props
|
||||
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
|
||||
const loadMessage = loadingMessage || isLoadingNetwork ?
|
||||
`Connecting to ${this.getNetworkName()}` : null
|
||||
log.debug('Main ui render function')
|
||||
|
||||
return (
|
||||
h('.flex-column.full-height', {
|
||||
style: {
|
||||
// Windows was showing a vertical scroll bar:
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// app bar
|
||||
this.renderAppBar(),
|
||||
this.renderNetworkDropdown(),
|
||||
this.renderDropdown(),
|
||||
|
||||
this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
|
||||
|
||||
// panel content
|
||||
h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
this.renderPrimary(),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderAppBar = function () {
|
||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
||||
return null
|
||||
}
|
||||
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const isNetworkMenuOpen = state.isNetworkMenuOpen || false
|
||||
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 (
|
||||
|
||||
h('.full-width', {
|
||||
height: '38px',
|
||||
}, [
|
||||
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: props.isUnlocked ? 'visible' : 'none',
|
||||
background: props.isUnlocked ? 'white' : 'none',
|
||||
height: '38px',
|
||||
position: 'relative',
|
||||
zIndex: 12,
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div.left-menu-section', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// mini logo
|
||||
h('img', {
|
||||
height: 24,
|
||||
width: 24,
|
||||
src: '/images/icon-128.png',
|
||||
}),
|
||||
|
||||
h(NetworkIndicator, {
|
||||
network: this.props.network,
|
||||
provider: this.props.provider,
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
props.isUnlocked && h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
props.isUnlocked && h(AccountDropdowns, {
|
||||
style: {},
|
||||
enableAccountsSelector: true,
|
||||
identities: this.props.identities,
|
||||
selected: this.props.currentView.context,
|
||||
network: this.props.network,
|
||||
keyrings: this.props.keyrings,
|
||||
}, []),
|
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, {
|
||||
className: 'sandwich-expando',
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: state.isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
isMainMenuOpen: !state.isMainMenuOpen,
|
||||
})
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderNetworkDropdown = function () {
|
||||
const props = this.props
|
||||
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
|
||||
const rpcList = props.frequentRpcList
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = [
|
||||
classList.contains('menu-icon'),
|
||||
classList.contains('network-name'),
|
||||
classList.contains('network-indicator'),
|
||||
].filter(bool => bool).length === 0
|
||||
// classes from three constituent nodes of the toggle element
|
||||
|
||||
if (isNotToggleElement) {
|
||||
this.setState({ isNetworkMenuOpen: false })
|
||||
}
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '2px',
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '2px 16px 2px 0px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'main',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.diamond'),
|
||||
'Main Ethereum Network',
|
||||
providerType === 'mainnet' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'ropsten',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.red-dot'),
|
||||
'Ropsten Test Network',
|
||||
providerType === 'ropsten' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'kovan',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
'Kovan Test Network',
|
||||
providerType === 'kovan' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'rinkeby',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('.menu-icon.golden-square'),
|
||||
'Rinkeby Test Network',
|
||||
providerType === 'rinkeby' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: 'default',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => props.dispatch(actions.setProviderType('localhost')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Localhost 8545',
|
||||
activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
|
||||
onClick: () => this.props.dispatch(actions.showConfigPage()),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
'Custom RPC',
|
||||
activeNetwork === 'custom' ? h('.check', '✓') : null,
|
||||
]
|
||||
),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderDropdown = function () {
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isMainMenuOpen
|
||||
|
||||
return h(Dropdown, {
|
||||
useCssTransition: true,
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
const classList = event.target.classList
|
||||
const parentClassList = event.target.parentElement.classList
|
||||
|
||||
const isToggleElement = classList.contains('sandwich-expando') ||
|
||||
parentClassList.contains('sandwich-expando')
|
||||
|
||||
if (isOpen && !isToggleElement) {
|
||||
this.setState({ isMainMenuOpen: false })
|
||||
}
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '2px',
|
||||
top: '38px',
|
||||
},
|
||||
innerStyle: {},
|
||||
}, [
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showConfigPage()) },
|
||||
}, 'Settings'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.lockMetamask()) },
|
||||
}, 'Lock'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => { this.props.dispatch(actions.showInfoPage()) },
|
||||
}, 'Info/Help'),
|
||||
|
||||
h(DropdownMenuItem, {
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
onClick: () => {
|
||||
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
||||
},
|
||||
}, 'Try Beta!'),
|
||||
])
|
||||
}
|
||||
|
||||
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) {
|
||||
var props = this.props
|
||||
return (
|
||||
h('.flex-row', {
|
||||
key: 'leftArrow',
|
||||
style: style,
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.cursor-pointer'),
|
||||
justArrow ? null : h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginLeft: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, 'BACK'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderPrimary = function () {
|
||||
log.debug('rendering primary')
|
||||
var props = this.props
|
||||
const {isMascara, isOnboarding} = props
|
||||
|
||||
if (isMascara && isOnboarding) {
|
||||
return h(MascaraFirstTime)
|
||||
}
|
||||
|
||||
// notices
|
||||
if (!props.noActiveNotices) {
|
||||
log.debug('rendering notice screen for unread notices.')
|
||||
return h(NoticeScreen, {
|
||||
notice: props.lastUnreadNotice,
|
||||
key: 'NoticeScreen',
|
||||
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
||||
})
|
||||
} else if (props.lostAccounts && props.lostAccounts.length > 0) {
|
||||
log.debug('rendering notice screen for lost accounts view.')
|
||||
return h(NoticeScreen, {
|
||||
notice: generateLostAccountsNotice(props.lostAccounts),
|
||||
key: 'LostAccountsNotice',
|
||||
onConfirm: () => props.dispatch(actions.markAccountsFound()),
|
||||
})
|
||||
}
|
||||
|
||||
if (props.seedWords) {
|
||||
log.debug('rendering seed words')
|
||||
return h(HDCreateVaultComplete, {key: 'HDCreateVaultComplete'})
|
||||
}
|
||||
|
||||
// show initialize screen
|
||||
if (!props.isInitialized || props.forgottenPassword) {
|
||||
// show current view
|
||||
log.debug('rendering an initialize screen')
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'restoreVault':
|
||||
log.debug('rendering restore vault screen')
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
default:
|
||||
log.debug('rendering menu screen')
|
||||
return h(InitializeMenuScreen, {key: 'menuScreenInit'})
|
||||
}
|
||||
}
|
||||
|
||||
// show unlock screen
|
||||
if (!props.isUnlocked) {
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'restoreVault':
|
||||
log.debug('rendering restore vault screen')
|
||||
return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
|
||||
|
||||
case 'config':
|
||||
log.debug('rendering config screen from unlock screen.')
|
||||
return h(ConfigScreen, {key: 'config'})
|
||||
|
||||
default:
|
||||
log.debug('rendering locked screen')
|
||||
return h(UnlockScreen, {key: 'locked'})
|
||||
}
|
||||
}
|
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
case 'accountDetail':
|
||||
log.debug('rendering account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
|
||||
case 'sendTransaction':
|
||||
log.debug('rendering send tx screen')
|
||||
return h(SendTransactionScreen, {key: 'send-transaction'})
|
||||
|
||||
case 'newKeychain':
|
||||
log.debug('rendering new keychain screen')
|
||||
return h(NewKeyChainScreen, {key: 'new-keychain'})
|
||||
|
||||
case 'confTx':
|
||||
log.debug('rendering confirm tx screen')
|
||||
return h(ConfirmTxScreen, {key: 'confirm-tx'})
|
||||
|
||||
case 'add-token':
|
||||
log.debug('rendering add-token screen from unlock screen.')
|
||||
return h(AddTokenScreen, {key: 'add-token'})
|
||||
|
||||
case 'config':
|
||||
log.debug('rendering config screen')
|
||||
return h(ConfigScreen, {key: 'config'})
|
||||
|
||||
case 'import-menu':
|
||||
log.debug('rendering import screen')
|
||||
return h(Import, {key: 'import-menu'})
|
||||
|
||||
case 'reveal-seed-conf':
|
||||
log.debug('rendering reveal seed confirmation screen')
|
||||
return h(RevealSeedConfirmation, {key: 'reveal-seed-conf'})
|
||||
|
||||
case 'info':
|
||||
log.debug('rendering info screen')
|
||||
return h(InfoScreen, {key: 'info'})
|
||||
|
||||
case 'buyEth':
|
||||
log.debug('rendering buy ether screen')
|
||||
return h(BuyView, {key: 'buyEthView'})
|
||||
|
||||
case 'onboardingBuyEth':
|
||||
log.debug('rendering onboarding buy ether screen')
|
||||
return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
|
||||
|
||||
case 'qr':
|
||||
log.debug('rendering show qr screen')
|
||||
console.log(`QrView`, QrView);
|
||||
return h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
marginTop: '50px',
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '44px',
|
||||
width: '285px',
|
||||
},
|
||||
}, [
|
||||
h(QrView, {key: 'qr'}),
|
||||
]),
|
||||
])
|
||||
|
||||
default:
|
||||
log.debug('rendering default, account detail screen')
|
||||
return h(AccountDetailScreen, {key: 'account-detail'})
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.toggleMetamaskActive = function () {
|
||||
if (!this.props.isUnlocked) {
|
||||
// currently inactive: redirect to password box
|
||||
var passwordBox = document.querySelector('input[type=password]')
|
||||
if (!passwordBox) return
|
||||
passwordBox.focus()
|
||||
} else {
|
||||
// currently active: deactivate
|
||||
this.props.dispatch(actions.lockMetamask(false))
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.renderCustomOption = function (provider) {
|
||||
const { rpcTarget, type } = provider
|
||||
const props = this.props
|
||||
|
||||
if (type !== 'rpc') return null
|
||||
|
||||
// Concatenate long URLs
|
||||
let label = rpcTarget
|
||||
if (rpcTarget.length > 31) {
|
||||
label = label.substr(0, 34) + '...'
|
||||
}
|
||||
|
||||
switch (rpcTarget) {
|
||||
|
||||
case 'http://localhost:8545':
|
||||
return null
|
||||
|
||||
default:
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: rpcTarget,
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
label,
|
||||
h('.check', '✓'),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.prototype.getNetworkName = function () {
|
||||
const { provider } = this.props
|
||||
const providerName = provider.type
|
||||
|
||||
let name
|
||||
|
||||
if (providerName === 'mainnet') {
|
||||
name = 'Main Ethereum Network'
|
||||
} else if (providerName === 'ropsten') {
|
||||
name = 'Ropsten Test Network'
|
||||
} else if (providerName === 'kovan') {
|
||||
name = 'Kovan Test Network'
|
||||
} else if (providerName === 'rinkeby') {
|
||||
name = 'Rinkeby Test Network'
|
||||
} else {
|
||||
name = 'Unknown Private Network'
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
App.prototype.renderCommonRpc = function (rpcList, provider) {
|
||||
const props = this.props
|
||||
const rpcTarget = provider.rpcTarget
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
key: `common${rpc}`,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
},
|
||||
[
|
||||
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
|
||||
rpc,
|
||||
rpcTarget === rpc ? h('.check', '✓') : null,
|
||||
]
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
319
old-ui/app/components/account-dropdowns.js
Normal file
319
old-ui/app/components/account-dropdowns.js
Normal file
@ -0,0 +1,319 @@
|
||||
const Component = require('react').Component
|
||||
const PropTypes = require('react').PropTypes
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const genAccountLink = require('etherscan-link').createAccountLink
|
||||
const connect = require('react-redux').connect
|
||||
const Dropdown = require('./dropdown').Dropdown
|
||||
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
|
||||
const Identicon = require('./identicon')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
class AccountDropdowns extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: false,
|
||||
}
|
||||
this.accountSelectorToggleClassName = 'accounts-selector'
|
||||
this.optionsMenuToggleClassName = 'fa-ellipsis-h'
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
const { identities, selected, keyrings } = this.props
|
||||
|
||||
return Object.keys(identities).map((key, index) => {
|
||||
const identity = identities[key]
|
||||
const isSelected = identity.address === selected
|
||||
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(simpleAddress) ||
|
||||
kr.accounts.includes(identity.address)
|
||||
})
|
||||
|
||||
return h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
this.props.actions.showAccountDetail(identity.address)
|
||||
},
|
||||
style: {
|
||||
marginTop: index === 0 ? '5px' : '',
|
||||
fontSize: '24px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
address: identity.address,
|
||||
diameter: 32,
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
},
|
||||
),
|
||||
this.indicateIfLoose(keyring),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '20px',
|
||||
fontSize: '24px',
|
||||
maxWidth: '145px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}, identity.name || ''),
|
||||
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
|
||||
]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
indicateIfLoose (keyring) {
|
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = keyring.type
|
||||
const isLoose = type !== 'HD Key Tree'
|
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null
|
||||
} catch (e) { return }
|
||||
}
|
||||
|
||||
renderAccountSelector () {
|
||||
const { actions } = this.props
|
||||
const { accountSelectorActive } = this.state
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
|
||||
style: {
|
||||
marginLeft: '-238px',
|
||||
marginTop: '38px',
|
||||
minWidth: '180px',
|
||||
overflowY: 'auto',
|
||||
maxHeight: '300px',
|
||||
width: '300px',
|
||||
},
|
||||
innerStyle: {
|
||||
padding: '8px 25px',
|
||||
},
|
||||
isOpen: accountSelectorActive,
|
||||
onClickOutside: (event) => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.accountSelectorToggleClassName)
|
||||
if (accountSelectorActive && isNotToggleElement) {
|
||||
this.setState({ accountSelectorActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
...this.renderAccounts(),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.addNewAccount(),
|
||||
},
|
||||
[
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
diameter: 32,
|
||||
},
|
||||
),
|
||||
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, 'Create Account'),
|
||||
],
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => actions.showImportPage(),
|
||||
},
|
||||
[
|
||||
h(
|
||||
Identicon,
|
||||
{
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
diameter: 32,
|
||||
},
|
||||
),
|
||||
h('span', {
|
||||
style: {
|
||||
marginLeft: '20px',
|
||||
fontSize: '24px',
|
||||
marginBottom: '5px',
|
||||
},
|
||||
}, 'Import Account'),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
renderAccountOptions () {
|
||||
const { actions } = this.props
|
||||
const { optionsMenuActive } = this.state
|
||||
|
||||
return h(
|
||||
Dropdown,
|
||||
{
|
||||
style: {
|
||||
marginLeft: '-215px',
|
||||
minWidth: '180px',
|
||||
},
|
||||
isOpen: optionsMenuActive,
|
||||
onClickOutside: () => {
|
||||
const { classList } = event.target
|
||||
const isNotToggleElement = !classList.contains(this.optionsMenuToggleClassName)
|
||||
if (optionsMenuActive && isNotToggleElement) {
|
||||
this.setState({ optionsMenuActive: false })
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, network } = this.props
|
||||
const url = genAccountLink(selected, network)
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
},
|
||||
'View account on Etherscan',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected, identities } = this.props
|
||||
var identity = identities[selected]
|
||||
actions.showQrView(selected, identity ? identity.name : '')
|
||||
},
|
||||
},
|
||||
'Show QR Code',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
const { selected } = this.props
|
||||
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
|
||||
copyToClipboard(checkSumAddress)
|
||||
},
|
||||
},
|
||||
'Copy Address to clipboard',
|
||||
),
|
||||
h(
|
||||
DropdownMenuItem,
|
||||
{
|
||||
closeMenu: () => {},
|
||||
onClick: () => {
|
||||
actions.requestAccountExport()
|
||||
},
|
||||
},
|
||||
'Export Private Key',
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { style, enableAccountsSelector, enableAccountOptions } = this.props
|
||||
const { optionsMenuActive, accountSelectorActive } = this.state
|
||||
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
style: style,
|
||||
},
|
||||
[
|
||||
enableAccountsSelector && h(
|
||||
// 'i.fa.fa-angle-down',
|
||||
'div.cursor-pointer.color-orange.accounts-selector',
|
||||
{
|
||||
style: {
|
||||
// fontSize: '1.8em',
|
||||
background: 'url(images/switch_acc.svg) white center center no-repeat',
|
||||
height: '25px',
|
||||
width: '25px',
|
||||
transform: 'scale(0.75)',
|
||||
marginRight: '3px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: !accountSelectorActive,
|
||||
optionsMenuActive: false,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountSelector(),
|
||||
),
|
||||
enableAccountOptions && h(
|
||||
'i.fa.fa-ellipsis-h',
|
||||
{
|
||||
style: {
|
||||
fontSize: '1.8em',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.setState({
|
||||
accountSelectorActive: false,
|
||||
optionsMenuActive: !optionsMenuActive,
|
||||
})
|
||||
},
|
||||
},
|
||||
this.renderAccountOptions()
|
||||
),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AccountDropdowns.defaultProps = {
|
||||
enableAccountsSelector: false,
|
||||
enableAccountOptions: false,
|
||||
}
|
||||
|
||||
AccountDropdowns.propTypes = {
|
||||
identities: PropTypes.objectOf(PropTypes.object),
|
||||
selected: PropTypes.string,
|
||||
keyrings: PropTypes.array,
|
||||
actions: PropTypes.objectOf(PropTypes.func),
|
||||
network: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
enableAccountOptions: PropTypes.bool,
|
||||
enableAccountsSelector: PropTypes.bool,
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
actions: {
|
||||
showConfigPage: () => dispatch(actions.showConfigPage()),
|
||||
requestAccountExport: () => dispatch(actions.requestExportAccount()),
|
||||
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
|
||||
addNewAccount: () => dispatch(actions.addNewAccount()),
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns),
|
||||
}
|
132
old-ui/app/components/account-export.js
Normal file
132
old-ui/app/components/account-export.js
Normal file
@ -0,0 +1,132 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const exportAsFile = require('../util').exportAsFile
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
module.exports = connect(mapStateToProps)(ExportAccountView)
|
||||
|
||||
inherits(ExportAccountView, Component)
|
||||
function ExportAccountView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
ExportAccountView.prototype.render = function () {
|
||||
const state = this.props
|
||||
const accountDetail = state.accountDetail
|
||||
const nickname = state.identities[state.address].name
|
||||
|
||||
if (!accountDetail) return h('div')
|
||||
const accountExport = accountDetail.accountExport
|
||||
|
||||
const notExporting = accountExport === 'none'
|
||||
const exportRequested = accountExport === 'requested'
|
||||
const accountExported = accountExport === 'completed'
|
||||
|
||||
if (notExporting) return h('div')
|
||||
|
||||
if (exportRequested) {
|
||||
const warning = `Export private keys at your own risk.`
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('div', {
|
||||
key: 'exporting',
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
h('p.error', warning),
|
||||
h('input#exportAccount.sizing-input', {
|
||||
type: 'password',
|
||||
placeholder: 'confirm password',
|
||||
onKeyPress: this.onExportKeyPress.bind(this),
|
||||
style: {
|
||||
position: 'relative',
|
||||
top: '1.5px',
|
||||
marginBottom: '7px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
h('div', {
|
||||
key: 'buttons',
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('button', {
|
||||
onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }),
|
||||
style: {
|
||||
marginRight: '10px',
|
||||
},
|
||||
}, 'Submit'),
|
||||
h('button', {
|
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
|
||||
}, 'Cancel'),
|
||||
]),
|
||||
(this.props.warning) && (
|
||||
h('span.error', {
|
||||
style: {
|
||||
margin: '20px',
|
||||
},
|
||||
}, this.props.warning.split('-'))
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (accountExported) {
|
||||
const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
|
||||
|
||||
return h('div.privateKey', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
h('label', 'Your private key (click to copy):'),
|
||||
h('p.error.cursor-pointer', {
|
||||
style: {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
webkitUserSelect: 'text',
|
||||
maxWidth: '275px',
|
||||
},
|
||||
onClick: function (event) {
|
||||
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
|
||||
},
|
||||
}, plainKey),
|
||||
h('button', {
|
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
|
||||
}, 'Done'),
|
||||
h('button', {
|
||||
style: {
|
||||
marginLeft: '10px',
|
||||
},
|
||||
onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
|
||||
}, 'Save as File'),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
ExportAccountView.prototype.onExportKeyPress = function (event) {
|
||||
if (event.key !== 'Enter') return
|
||||
event.preventDefault()
|
||||
|
||||
const input = document.getElementById('exportAccount').value
|
||||
this.props.dispatch(actions.exportAccount(input, this.props.address))
|
||||
}
|
86
old-ui/app/components/account-panel.js
Normal file
86
old-ui/app/components/account-panel.js
Normal file
@ -0,0 +1,86 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const Identicon = require('./identicon')
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const addressSummary = require('../util').addressSummary
|
||||
|
||||
module.exports = AccountPanel
|
||||
|
||||
|
||||
inherits(AccountPanel, Component)
|
||||
function AccountPanel () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountPanel.prototype.render = function () {
|
||||
var state = this.props
|
||||
var identity = state.identity || {}
|
||||
var account = state.account || {}
|
||||
var isFauceting = state.isFauceting
|
||||
|
||||
var panelState = {
|
||||
key: `accountPanel${identity.address}`,
|
||||
identiconKey: identity.address,
|
||||
identiconLabel: identity.name || '',
|
||||
attributes: [
|
||||
{
|
||||
key: 'ADDRESS',
|
||||
value: addressSummary(identity.address),
|
||||
},
|
||||
balanceOrFaucetingIndication(account, isFauceting),
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
h('.identity-panel.flex-row.flex-space-between', {
|
||||
style: {
|
||||
flex: '1 0 auto',
|
||||
cursor: panelState.onClick ? 'pointer' : undefined,
|
||||
},
|
||||
onClick: panelState.onClick,
|
||||
}, [
|
||||
|
||||
// account identicon
|
||||
h('.identicon-wrapper.flex-column.select-none', [
|
||||
h(Identicon, {
|
||||
address: panelState.identiconKey,
|
||||
imageify: state.imageifyIdenticons,
|
||||
}),
|
||||
h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'),
|
||||
]),
|
||||
|
||||
// account address, balance
|
||||
h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
|
||||
panelState.attributes.map((attr) => {
|
||||
return h('.flex-row.flex-space-between', {
|
||||
key: '' + Math.round(Math.random() * 1000000),
|
||||
}, [
|
||||
h('label.font-small.no-select', attr.key),
|
||||
h('span.font-small', attr.value),
|
||||
])
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
function balanceOrFaucetingIndication (account, isFauceting) {
|
||||
// Temporarily deactivating isFauceting indication
|
||||
// because it shows fauceting for empty restored accounts.
|
||||
if (/* isFauceting*/ false) {
|
||||
return {
|
||||
key: 'Account is auto-funding.',
|
||||
value: 'Please wait.',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
key: 'BALANCE',
|
||||
value: formatBalance(account.balance),
|
||||
}
|
||||
}
|
||||
}
|
89
old-ui/app/components/balance.js
Normal file
89
old-ui/app/components/balance.js
Normal file
@ -0,0 +1,89 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
var style = props.style
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
var width = props.width
|
||||
|
||||
return (
|
||||
|
||||
h('.ether-balance.ether-balance-amount', {
|
||||
style: style,
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline',
|
||||
width: width,
|
||||
},
|
||||
}, this.renderBalance(value)),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var props = this.props
|
||||
if (value === 'None') return value
|
||||
if (value === '...') return value
|
||||
var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (props.shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
}
|
||||
|
||||
var label = balanceObj.label
|
||||
|
||||
return (
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
},
|
||||
}, this.props.incoming ? `+${balance}` : balance),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
|
||||
showFiat ? h(FiatValue, { value: props.value }) : null,
|
||||
]))
|
||||
)
|
||||
}
|
46
old-ui/app/components/binary-renderer.js
Normal file
46
old-ui/app/components/binary-renderer.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = BinaryRenderer
|
||||
|
||||
inherits(BinaryRenderer, Component)
|
||||
function BinaryRenderer () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
BinaryRenderer.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { value, style } = props
|
||||
const text = this.hexToText(value)
|
||||
|
||||
const defaultStyle = extend({
|
||||
width: '315px',
|
||||
maxHeight: '210px',
|
||||
resize: 'none',
|
||||
border: 'none',
|
||||
background: 'white',
|
||||
padding: '3px',
|
||||
}, style)
|
||||
|
||||
return (
|
||||
h('textarea.font-small', {
|
||||
readOnly: true,
|
||||
style: defaultStyle,
|
||||
defaultValue: text,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
BinaryRenderer.prototype.hexToText = function (hex) {
|
||||
try {
|
||||
const stripped = ethUtil.stripHexPrefix(hex)
|
||||
const buff = Buffer.from(stripped, 'hex')
|
||||
return buff.toString('utf8')
|
||||
} catch (e) {
|
||||
return hex
|
||||
}
|
||||
}
|
||||
|
181
old-ui/app/components/bn-as-decimal-input.js
Normal file
181
old-ui/app/components/bn-as-decimal-input.js
Normal file
@ -0,0 +1,181 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = BnAsDecimalInput
|
||||
|
||||
inherits(BnAsDecimalInput, Component)
|
||||
function BnAsDecimalInput () {
|
||||
this.state = { invalid: null }
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
/* Bn as Decimal Input
|
||||
*
|
||||
* A component for allowing easy, decimal editing
|
||||
* of a passed in bn string value.
|
||||
*
|
||||
* On change, calls back its `onChange` function parameter
|
||||
* and passes it an updated bn string.
|
||||
*/
|
||||
|
||||
BnAsDecimalInput.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
const { value, scale, precision, onChange, min, max } = props
|
||||
|
||||
const suffix = props.suffix
|
||||
const style = props.style
|
||||
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)
|
||||
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('input.hex-input', {
|
||||
type: 'number',
|
||||
step: 'any',
|
||||
required: true,
|
||||
min: newMin,
|
||||
max: newMax,
|
||||
style: extend({
|
||||
display: 'block',
|
||||
textAlign: 'right',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #bdbdbd',
|
||||
|
||||
}, style),
|
||||
value: newValue,
|
||||
onBlur: (event) => {
|
||||
this.updateValidity(event)
|
||||
},
|
||||
onChange: (event) => {
|
||||
this.updateValidity(event)
|
||||
const value = (event.target.value === '') ? '' : event.target.value
|
||||
|
||||
|
||||
const scaledNumber = this.upsize(value, scale, precision)
|
||||
const precisionBN = new BN(scaledNumber, 10)
|
||||
onChange(precisionBN, event.target.checkValidity())
|
||||
},
|
||||
onInvalid: (event) => {
|
||||
const msg = this.constructWarning()
|
||||
if (msg === state.invalid) {
|
||||
return
|
||||
}
|
||||
this.setState({ invalid: msg })
|
||||
event.preventDefault()
|
||||
return false
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '6px',
|
||||
width: '20px',
|
||||
},
|
||||
}, suffix),
|
||||
]),
|
||||
|
||||
state.invalid ? h('span.error', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '0px',
|
||||
textAlign: 'right',
|
||||
transform: 'translateY(26px)',
|
||||
padding: '3px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
zIndex: '1',
|
||||
textTransform: 'capitalize',
|
||||
border: '2px solid #E20202',
|
||||
},
|
||||
}, state.invalid) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.setValid = function (message) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.updateValidity = function (event) {
|
||||
const target = event.target
|
||||
const value = this.props.value
|
||||
const newValue = target.value
|
||||
|
||||
if (value === newValue) {
|
||||
return
|
||||
}
|
||||
|
||||
const valid = target.checkValidity()
|
||||
|
||||
if (valid) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.constructWarning = function () {
|
||||
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 + ' ' : ''
|
||||
|
||||
if (min && max) {
|
||||
message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.`
|
||||
} else if (min) {
|
||||
message += `must be greater than or equal to ${newMin} ${suffix}.`
|
||||
} else if (max) {
|
||||
message += `must be less than or equal to ${newMax} ${suffix}.`
|
||||
} else {
|
||||
message += 'Invalid input.'
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
|
||||
BnAsDecimalInput.prototype.downsize = function (number, scale) {
|
||||
// if there is no scaling, simply return the number
|
||||
if (scale === 0) {
|
||||
return Number(number)
|
||||
} else {
|
||||
// if the scale is the same as the precision, account for this edge case.
|
||||
var adjustedNumber = number
|
||||
while (adjustedNumber.length < scale) {
|
||||
adjustedNumber = '0' + adjustedNumber
|
||||
}
|
||||
return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale))
|
||||
}
|
||||
}
|
||||
|
||||
BnAsDecimalInput.prototype.upsize = function (number, scale, precision) {
|
||||
var stringArray = number.toString().split('.')
|
||||
var decimalLength = stringArray[1] ? stringArray[1].length : 0
|
||||
var newString = stringArray[0]
|
||||
|
||||
// If there is scaling and decimal parts exist, integrate them in.
|
||||
if ((scale !== 0) && (decimalLength !== 0)) {
|
||||
newString += stringArray[1].slice(0, precision)
|
||||
}
|
||||
|
||||
// Add 0s to account for the upscaling.
|
||||
for (var i = decimalLength; i < scale; i++) {
|
||||
newString += '0'
|
||||
}
|
||||
return newString
|
||||
}
|
262
old-ui/app/components/buy-button-subview.js
Normal file
262
old-ui/app/components/buy-button-subview.js
Normal file
@ -0,0 +1,262 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const CoinbaseForm = require('./coinbase-form')
|
||||
const ShapeshiftForm = require('./shapeshift-form')
|
||||
const Loading = require('./loading')
|
||||
const AccountPanel = require('./account-panel')
|
||||
const RadioList = require('./custom-radio-list')
|
||||
const networkNames = require('../../../app/scripts/config.js').networkNames
|
||||
|
||||
module.exports = connect(mapStateToProps)(BuyButtonSubview)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
identity: state.appState.identity,
|
||||
account: state.metamask.accounts[state.appState.buyView.buyAddress],
|
||||
warning: state.appState.warning,
|
||||
buyView: state.appState.buyView,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
context: state.appState.currentView.context,
|
||||
isSubLoading: state.appState.isSubLoading,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(BuyButtonSubview, Component)
|
||||
function BuyButtonSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.render = function () {
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
this.headerSubview(),
|
||||
this.primarySubview(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.headerSubview = function () {
|
||||
const props = this.props
|
||||
const isLoading = props.isSubLoading
|
||||
return (
|
||||
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header bar (back button, label)
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: this.backButtonContext.bind(this),
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '10px',
|
||||
},
|
||||
}),
|
||||
h('h2.text-transform-uppercase.flex-center', {
|
||||
style: {
|
||||
width: '100vw',
|
||||
background: 'rgb(235, 235, 235)',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
},
|
||||
}, 'Buy Eth'),
|
||||
]),
|
||||
|
||||
// loading indication
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '57vh',
|
||||
left: '49vw',
|
||||
},
|
||||
}, [
|
||||
h(Loading, { isLoading }),
|
||||
]),
|
||||
|
||||
// account panel
|
||||
h('div', {
|
||||
style: {
|
||||
width: '80%',
|
||||
},
|
||||
}, [
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: props.identity,
|
||||
account: props.account,
|
||||
}),
|
||||
]),
|
||||
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
h('h3.text-transform-uppercase.flex-center', {
|
||||
style: {
|
||||
paddingLeft: '15px',
|
||||
width: '100vw',
|
||||
background: 'rgb(235, 235, 235)',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
},
|
||||
}, 'Select Service'),
|
||||
]),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
BuyButtonSubview.prototype.primarySubview = function () {
|
||||
const props = this.props
|
||||
const network = props.network
|
||||
|
||||
switch (network) {
|
||||
case 'loading':
|
||||
return
|
||||
|
||||
case '1':
|
||||
return this.mainnetSubview()
|
||||
|
||||
// Ropsten, Rinkeby, Kovan
|
||||
case '3':
|
||||
case '4':
|
||||
case '42':
|
||||
const networkName = networkNames[network]
|
||||
const label = `${networkName} Test Faucet`
|
||||
return (
|
||||
h('div.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
margin: '20px 50px',
|
||||
},
|
||||
}, [
|
||||
h('button.text-transform-uppercase', {
|
||||
onClick: () => this.props.dispatch(actions.buyEth({ network })),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, label),
|
||||
// Kovan only: Dharma loans beta
|
||||
network === '42' ? (
|
||||
h('button.text-transform-uppercase', {
|
||||
onClick: () => this.navigateTo('https://borrow.dharma.io/'),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, 'Borrow With Dharma (Beta)')
|
||||
) : null,
|
||||
])
|
||||
)
|
||||
|
||||
default:
|
||||
return (
|
||||
h('h2.error', 'Unknown network ID')
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.mainnetSubview = function () {
|
||||
const props = this.props
|
||||
|
||||
return (
|
||||
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('.flex-row.selected-exchange', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
right: '35px',
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px',
|
||||
},
|
||||
}, [
|
||||
h(RadioList, {
|
||||
defaultFocus: props.buyView.subview,
|
||||
labels: [
|
||||
'Coinbase',
|
||||
'ShapeShift',
|
||||
],
|
||||
subtext: {
|
||||
'Coinbase': 'Crypto/FIAT (USA only)',
|
||||
'ShapeShift': 'Crypto',
|
||||
},
|
||||
onClick: this.radioHandler.bind(this),
|
||||
}),
|
||||
]),
|
||||
|
||||
h('h3.text-transform-uppercase', {
|
||||
style: {
|
||||
paddingLeft: '15px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
width: '100vw',
|
||||
background: 'rgb(235, 235, 235)',
|
||||
color: 'rgb(174, 174, 174)',
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
},
|
||||
}, props.buyView.subview),
|
||||
|
||||
this.formVersionSubview(),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.formVersionSubview = function () {
|
||||
const network = this.props.network
|
||||
if (network === '1') {
|
||||
if (this.props.buyView.formView.coinbase) {
|
||||
return h(CoinbaseForm, this.props)
|
||||
} else if (this.props.buyView.formView.shapeshift) {
|
||||
return h(ShapeshiftForm, this.props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.navigateTo = function (url) {
|
||||
global.platform.openWindow({ url })
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.backButtonContext = function () {
|
||||
if (this.props.context === 'confTx') {
|
||||
this.props.dispatch(actions.showConfTxPage(false))
|
||||
} else {
|
||||
console.log(`actions.goHome`, actions.goHome);
|
||||
this.props.dispatch(actions.goHome())
|
||||
}
|
||||
}
|
||||
|
||||
BuyButtonSubview.prototype.radioHandler = function (event) {
|
||||
switch (event.target.title) {
|
||||
case 'Coinbase':
|
||||
return this.props.dispatch(actions.coinBaseSubview())
|
||||
case 'ShapeShift':
|
||||
return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type))
|
||||
}
|
||||
}
|
63
old-ui/app/components/coinbase-form.js
Normal file
63
old-ui/app/components/coinbase-form.js
Normal file
@ -0,0 +1,63 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(CoinbaseForm)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(CoinbaseForm, Component)
|
||||
|
||||
function CoinbaseForm () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.render = function () {
|
||||
var props = this.props
|
||||
|
||||
return h('.flex-column', {
|
||||
style: {
|
||||
marginTop: '35px',
|
||||
padding: '25px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'space-around',
|
||||
margin: '33px',
|
||||
marginTop: '0px',
|
||||
},
|
||||
}, [
|
||||
h('button.btn-green', {
|
||||
onClick: this.toCoinbase.bind(this),
|
||||
}, 'Continue to Coinbase'),
|
||||
|
||||
h('button.btn-red', {
|
||||
onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)),
|
||||
}, 'Cancel'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.toCoinbase = function () {
|
||||
const props = this.props
|
||||
const address = props.buyView.buyAddress
|
||||
props.dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
|
||||
}
|
||||
|
||||
CoinbaseForm.prototype.renderLoading = function () {
|
||||
return h('img', {
|
||||
style: {
|
||||
width: '27px',
|
||||
marginRight: '-27px',
|
||||
},
|
||||
src: 'images/loading.svg',
|
||||
})
|
||||
}
|
59
old-ui/app/components/copyButton.js
Normal file
59
old-ui/app/components/copyButton.js
Normal file
@ -0,0 +1,59 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
module.exports = CopyButton
|
||||
|
||||
inherits(CopyButton, Component)
|
||||
function CopyButton () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
// As parameters, accepts:
|
||||
// "value", which is the value to copy (mandatory)
|
||||
// "title", which is the text to show on hover (optional, defaults to 'Copy')
|
||||
CopyButton.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
|
||||
const value = props.value
|
||||
const copied = state.copied
|
||||
|
||||
const message = copied ? 'Copied' : props.title || ' Copy '
|
||||
|
||||
return h('.copy-button', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(Tooltip, {
|
||||
title: message,
|
||||
}, [
|
||||
h('i.fa.fa-clipboard.cursor-pointer.color-orange', {
|
||||
style: {
|
||||
margin: '5px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
CopyButton.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
46
old-ui/app/components/copyable.js
Normal file
46
old-ui/app/components/copyable.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const Tooltip = require('./tooltip')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
module.exports = Copyable
|
||||
|
||||
inherits(Copyable, Component)
|
||||
function Copyable () {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
copied: false,
|
||||
}
|
||||
}
|
||||
|
||||
Copyable.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
const { value, children } = props
|
||||
const { copied } = state
|
||||
|
||||
return h(Tooltip, {
|
||||
title: copied ? 'Copied!' : 'Copy',
|
||||
position: 'bottom',
|
||||
}, h('span', {
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(value)
|
||||
this.debounceRestore()
|
||||
},
|
||||
}, children))
|
||||
}
|
||||
|
||||
Copyable.prototype.debounceRestore = function () {
|
||||
this.setState({ copied: true })
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({ copied: false })
|
||||
}, 850)
|
||||
}
|
60
old-ui/app/components/custom-radio-list.js
Normal file
60
old-ui/app/components/custom-radio-list.js
Normal file
@ -0,0 +1,60 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = RadioList
|
||||
|
||||
inherits(RadioList, Component)
|
||||
function RadioList () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
RadioList.prototype.render = function () {
|
||||
const props = this.props
|
||||
const activeClass = '.custom-radio-selected'
|
||||
const inactiveClass = '.custom-radio-inactive'
|
||||
const {
|
||||
labels,
|
||||
defaultFocus,
|
||||
} = props
|
||||
|
||||
|
||||
return (
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
},
|
||||
}, [
|
||||
h('.flex-column.custom-radios', {
|
||||
style: {
|
||||
marginRight: '5px',
|
||||
},
|
||||
},
|
||||
labels.map((lable, i) => {
|
||||
let isSelcted = (this.state !== null)
|
||||
isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable)
|
||||
return h(isSelcted ? activeClass : inactiveClass, {
|
||||
title: lable,
|
||||
onClick: (event) => {
|
||||
this.setState({selected: event.target.title})
|
||||
props.onClick(event)
|
||||
},
|
||||
})
|
||||
})
|
||||
),
|
||||
h('.text', {},
|
||||
labels.map((lable) => {
|
||||
if (props.subtext) {
|
||||
return h('.flex-row', {}, [
|
||||
h('.radio-titles', lable),
|
||||
h('.radio-titles-subtext', `- ${props.subtext[lable]}`),
|
||||
])
|
||||
} else {
|
||||
return h('.radio-titles', lable)
|
||||
}
|
||||
})
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
98
old-ui/app/components/dropdown.js
Normal file
98
old-ui/app/components/dropdown.js
Normal file
@ -0,0 +1,98 @@
|
||||
const Component = require('react').Component
|
||||
const PropTypes = require('react').PropTypes
|
||||
const h = require('react-hyperscript')
|
||||
const MenuDroppo = require('./menu-droppo')
|
||||
const extend = require('xtend')
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
class Dropdown extends Component {
|
||||
render () {
|
||||
const { isOpen, onClickOutside, style, innerStyle, children, useCssTransition } = this.props
|
||||
|
||||
const innerStyleDefaults = extend({
|
||||
borderRadius: '4px',
|
||||
padding: '8px 16px',
|
||||
background: 'rgba(0, 0, 0, 0.8)',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
|
||||
}, innerStyle)
|
||||
|
||||
return h(
|
||||
MenuDroppo,
|
||||
{
|
||||
useCssTransition,
|
||||
isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside,
|
||||
style,
|
||||
innerStyle: innerStyleDefaults,
|
||||
},
|
||||
[
|
||||
h(
|
||||
'style',
|
||||
`
|
||||
li.dropdown-menu-item:hover { color:rgb(225, 225, 225); }
|
||||
li.dropdown-menu-item { color: rgb(185, 185, 185); position: relative }
|
||||
`
|
||||
),
|
||||
...children,
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Dropdown.defaultProps = {
|
||||
isOpen: false,
|
||||
onClick: noop,
|
||||
useCssTransition: false,
|
||||
}
|
||||
|
||||
Dropdown.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
style: PropTypes.object.isRequired,
|
||||
onClickOutside: PropTypes.func,
|
||||
innerStyle: PropTypes.object,
|
||||
useCssTransition: PropTypes.bool,
|
||||
}
|
||||
|
||||
class DropdownMenuItem extends Component {
|
||||
render () {
|
||||
const { onClick, closeMenu, children, style } = this.props
|
||||
|
||||
return h(
|
||||
'li.dropdown-menu-item',
|
||||
{
|
||||
onClick: () => {
|
||||
onClick()
|
||||
closeMenu()
|
||||
},
|
||||
style: Object.assign({
|
||||
listStyle: 'none',
|
||||
padding: '8px 0px 8px 0px',
|
||||
fontSize: '18px',
|
||||
fontStyle: 'normal',
|
||||
fontFamily: 'Montserrat Regular',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
}, style),
|
||||
},
|
||||
children
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DropdownMenuItem.propTypes = {
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
style: PropTypes.object,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Dropdown,
|
||||
DropdownMenuItem,
|
||||
}
|
57
old-ui/app/components/editable-label.js
Normal file
57
old-ui/app/components/editable-label.js
Normal file
@ -0,0 +1,57 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
|
||||
module.exports = EditableLabel
|
||||
|
||||
inherits(EditableLabel, Component)
|
||||
function EditableLabel () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EditableLabel.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
if (state && state.isEditingLabel) {
|
||||
return h('div.editable-label', [
|
||||
h('input.sizing-input', {
|
||||
defaultValue: props.textValue,
|
||||
maxLength: '20',
|
||||
onKeyPress: (event) => {
|
||||
this.saveIfEnter(event)
|
||||
},
|
||||
}),
|
||||
h('button.editable-button', {
|
||||
onClick: () => this.saveText(),
|
||||
}, 'Save'),
|
||||
])
|
||||
} else {
|
||||
return h('div.name-label', {
|
||||
onClick: (event) => {
|
||||
const nameAttribute = event.target.getAttribute('name')
|
||||
// checks for class to handle smaller CTA above the account name
|
||||
const classAttribute = event.target.getAttribute('class')
|
||||
if (nameAttribute === 'edit' || classAttribute === 'edit-text') {
|
||||
this.setState({ isEditingLabel: true })
|
||||
}
|
||||
},
|
||||
}, this.props.children)
|
||||
}
|
||||
}
|
||||
|
||||
EditableLabel.prototype.saveIfEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.saveText()
|
||||
}
|
||||
}
|
||||
|
||||
EditableLabel.prototype.saveText = function () {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
var text = container.querySelector('.editable-label input').value
|
||||
var truncatedText = text.substring(0, 20)
|
||||
this.props.saveText(truncatedText)
|
||||
this.setState({ isEditingLabel: false, textLabel: truncatedText })
|
||||
}
|
170
old-ui/app/components/ens-input.js
Normal file
170
old-ui/app/components/ens-input.js
Normal file
@ -0,0 +1,170 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
const debounce = require('debounce')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const ENS = require('ethjs-ens')
|
||||
const networkMap = require('ethjs-ens/lib/network-map.json')
|
||||
const ensRE = /.+\..+$/
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
|
||||
module.exports = EnsInput
|
||||
|
||||
inherits(EnsInput, Component)
|
||||
function EnsInput () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EnsInput.prototype.render = function () {
|
||||
const props = this.props
|
||||
const opts = extend(props, {
|
||||
list: 'addresses',
|
||||
onChange: () => {
|
||||
const network = this.props.network
|
||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||
if (!networkHasEnsSupport) return
|
||||
|
||||
const recipient = document.querySelector('input[name="address"]').value
|
||||
if (recipient.match(ensRE) === null) {
|
||||
return this.setState({
|
||||
loadingEns: false,
|
||||
ensResolution: null,
|
||||
ensFailure: null,
|
||||
})
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loadingEns: true,
|
||||
})
|
||||
this.checkName()
|
||||
},
|
||||
})
|
||||
return h('div', {
|
||||
style: { width: '100%' },
|
||||
}, [
|
||||
h('input.large-input', opts),
|
||||
// The address book functionality.
|
||||
h('datalist#addresses',
|
||||
[
|
||||
// Corresponds to the addresses owned.
|
||||
Object.keys(props.identities).map((key) => {
|
||||
const identity = props.identities[key]
|
||||
return h('option', {
|
||||
value: identity.address,
|
||||
label: identity.name,
|
||||
key: identity.address,
|
||||
})
|
||||
}),
|
||||
// Corresponds to previously sent-to addresses.
|
||||
props.addressBook.map((identity) => {
|
||||
return h('option', {
|
||||
value: identity.address,
|
||||
label: identity.name,
|
||||
key: identity.address,
|
||||
})
|
||||
}),
|
||||
]),
|
||||
this.ensIcon(),
|
||||
])
|
||||
}
|
||||
|
||||
EnsInput.prototype.componentDidMount = function () {
|
||||
const network = this.props.network
|
||||
const networkHasEnsSupport = getNetworkEnsSupport(network)
|
||||
this.setState({ ensResolution: ZERO_ADDRESS })
|
||||
|
||||
if (networkHasEnsSupport) {
|
||||
const provider = global.ethereumProvider
|
||||
this.ens = new ENS({ provider, network })
|
||||
this.checkName = debounce(this.lookupEnsName.bind(this), 200)
|
||||
}
|
||||
}
|
||||
|
||||
EnsInput.prototype.lookupEnsName = function () {
|
||||
const recipient = document.querySelector('input[name="address"]').value
|
||||
const { ensResolution } = this.state
|
||||
|
||||
log.info(`ENS attempting to resolve name: ${recipient}`)
|
||||
this.ens.lookup(recipient.trim())
|
||||
.then((address) => {
|
||||
if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.')
|
||||
if (address !== ensResolution) {
|
||||
this.setState({
|
||||
loadingEns: false,
|
||||
ensResolution: address,
|
||||
nickname: recipient.trim(),
|
||||
hoverText: address + '\nClick to Copy',
|
||||
ensFailure: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(reason)
|
||||
return this.setState({
|
||||
loadingEns: false,
|
||||
ensResolution: ZERO_ADDRESS,
|
||||
ensFailure: true,
|
||||
hoverText: reason.message,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
|
||||
const state = this.state || {}
|
||||
const ensResolution = state.ensResolution
|
||||
// If an address is sent without a nickname, meaning not from ENS or from
|
||||
// the user's own accounts, a default of a one-space string is used.
|
||||
const nickname = state.nickname || ' '
|
||||
if (prevState && ensResolution && this.props.onChange &&
|
||||
ensResolution !== prevState.ensResolution) {
|
||||
this.props.onChange(ensResolution, nickname)
|
||||
}
|
||||
}
|
||||
|
||||
EnsInput.prototype.ensIcon = function (recipient) {
|
||||
const { hoverText } = this.state || {}
|
||||
return h('span', {
|
||||
title: hoverText,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
padding: '9px',
|
||||
transform: 'translatex(-40px)',
|
||||
},
|
||||
}, this.ensIconContents(recipient))
|
||||
}
|
||||
|
||||
EnsInput.prototype.ensIconContents = function (recipient) {
|
||||
const { loadingEns, ensFailure, ensResolution } = this.state || { ensResolution: ZERO_ADDRESS}
|
||||
|
||||
if (loadingEns) {
|
||||
return h('img', {
|
||||
src: 'images/loading.svg',
|
||||
style: {
|
||||
width: '30px',
|
||||
height: '30px',
|
||||
transform: 'translateY(-6px)',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (ensFailure) {
|
||||
return h('i.fa.fa-warning.fa-lg.warning')
|
||||
}
|
||||
|
||||
if (ensResolution && (ensResolution !== ZERO_ADDRESS)) {
|
||||
return h('i.fa.fa-check-circle.fa-lg.cursor-pointer', {
|
||||
style: { color: 'green' },
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
copyToClipboard(ensResolution)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkEnsSupport (network) {
|
||||
return Boolean(networkMap[network])
|
||||
}
|
89
old-ui/app/components/eth-balance.js
Normal file
89
old-ui/app/components/eth-balance.js
Normal file
@ -0,0 +1,89 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
const generateBalanceObject = require('../util').generateBalanceObject
|
||||
const Tooltip = require('./tooltip.js')
|
||||
const FiatValue = require('./fiat-value.js')
|
||||
|
||||
module.exports = EthBalanceComponent
|
||||
|
||||
inherits(EthBalanceComponent, Component)
|
||||
function EthBalanceComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
EthBalanceComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
let { value } = props
|
||||
const { style, width } = props
|
||||
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
|
||||
value = value ? formatBalance(value, 6, needsParse) : '...'
|
||||
|
||||
return (
|
||||
|
||||
h('.ether-balance.ether-balance-amount', {
|
||||
style,
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'inline',
|
||||
width,
|
||||
},
|
||||
}, this.renderBalance(value)),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
EthBalanceComponent.prototype.renderBalance = function (value) {
|
||||
var props = this.props
|
||||
const { conversionRate, shorten, incoming, currentCurrency } = props
|
||||
if (value === 'None') return value
|
||||
if (value === '...') return value
|
||||
var balanceObj = generateBalanceObject(value, shorten ? 1 : 3)
|
||||
var balance
|
||||
var splitBalance = value.split(' ')
|
||||
var ethNumber = splitBalance[0]
|
||||
var ethSuffix = splitBalance[1]
|
||||
const showFiat = 'showFiat' in props ? props.showFiat : true
|
||||
|
||||
if (shorten) {
|
||||
balance = balanceObj.shortBalance
|
||||
} else {
|
||||
balance = balanceObj.balance
|
||||
}
|
||||
|
||||
var label = balanceObj.label
|
||||
|
||||
return (
|
||||
h(Tooltip, {
|
||||
position: 'bottom',
|
||||
title: `${ethNumber} ${ethSuffix}`,
|
||||
}, h('div.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
},
|
||||
}, incoming ? `+${balance}` : balance),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}, label),
|
||||
]),
|
||||
|
||||
showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null,
|
||||
]))
|
||||
)
|
||||
}
|
64
old-ui/app/components/fiat-value.js
Normal file
64
old-ui/app/components/fiat-value.js
Normal file
@ -0,0 +1,64 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const formatBalance = require('../util').formatBalance
|
||||
|
||||
module.exports = FiatValue
|
||||
|
||||
inherits(FiatValue, Component)
|
||||
function FiatValue () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
FiatValue.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { conversionRate, currentCurrency } = props
|
||||
const renderedCurrency = currentCurrency || ''
|
||||
|
||||
const value = formatBalance(props.value, 6)
|
||||
|
||||
if (value === 'None') return value
|
||||
var fiatDisplayNumber, fiatTooltipNumber
|
||||
var splitBalance = value.split(' ')
|
||||
|
||||
if (conversionRate !== 0) {
|
||||
fiatTooltipNumber = Number(splitBalance[0]) * conversionRate
|
||||
fiatDisplayNumber = fiatTooltipNumber.toFixed(2)
|
||||
} else {
|
||||
fiatDisplayNumber = 'N/A'
|
||||
fiatTooltipNumber = 'Unknown'
|
||||
}
|
||||
|
||||
return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase())
|
||||
}
|
||||
|
||||
function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
|
||||
if (fiatDisplayNumber !== 'N/A') {
|
||||
return h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
textAlign: 'right',
|
||||
fontSize: '12px',
|
||||
color: '#333333',
|
||||
},
|
||||
}, fiatDisplayNumber),
|
||||
h('div', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
marginLeft: '5px',
|
||||
fontSize: '12px',
|
||||
},
|
||||
}, fiatSuffix),
|
||||
])
|
||||
} else {
|
||||
return h('div')
|
||||
}
|
||||
}
|
154
old-ui/app/components/hex-as-decimal-input.js
Normal file
154
old-ui/app/components/hex-as-decimal-input.js
Normal file
@ -0,0 +1,154 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = HexAsDecimalInput
|
||||
|
||||
inherits(HexAsDecimalInput, Component)
|
||||
function HexAsDecimalInput () {
|
||||
this.state = { invalid: null }
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
/* Hex as Decimal Input
|
||||
*
|
||||
* A component for allowing easy, decimal editing
|
||||
* of a passed in hex string value.
|
||||
*
|
||||
* On change, calls back its `onChange` function parameter
|
||||
* and passes it an updated hex string.
|
||||
*/
|
||||
|
||||
HexAsDecimalInput.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
|
||||
const { value, onChange, min, max } = props
|
||||
|
||||
const toEth = props.toEth
|
||||
const suffix = props.suffix
|
||||
const decimalValue = decimalize(value, toEth)
|
||||
const style = props.style
|
||||
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
alignItems: 'flex-end',
|
||||
lineHeight: '13px',
|
||||
fontFamily: 'Montserrat Light',
|
||||
textRendering: 'geometricPrecision',
|
||||
},
|
||||
}, [
|
||||
h('input.hex-input', {
|
||||
type: 'number',
|
||||
required: true,
|
||||
min: min,
|
||||
max: max,
|
||||
style: extend({
|
||||
display: 'block',
|
||||
textAlign: 'right',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #bdbdbd',
|
||||
|
||||
}, style),
|
||||
value: parseInt(decimalValue),
|
||||
onBlur: (event) => {
|
||||
this.updateValidity(event)
|
||||
},
|
||||
onChange: (event) => {
|
||||
this.updateValidity(event)
|
||||
const hexString = (event.target.value === '') ? '' : hexify(event.target.value)
|
||||
onChange(hexString)
|
||||
},
|
||||
onInvalid: (event) => {
|
||||
const msg = this.constructWarning()
|
||||
if (msg === state.invalid) {
|
||||
return
|
||||
}
|
||||
this.setState({ invalid: msg })
|
||||
event.preventDefault()
|
||||
return false
|
||||
},
|
||||
}),
|
||||
h('div', {
|
||||
style: {
|
||||
color: ' #AEAEAE',
|
||||
fontSize: '12px',
|
||||
marginLeft: '5px',
|
||||
marginRight: '6px',
|
||||
width: '20px',
|
||||
},
|
||||
}, suffix),
|
||||
]),
|
||||
|
||||
state.invalid ? h('span.error', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: '0px',
|
||||
textAlign: 'right',
|
||||
transform: 'translateY(26px)',
|
||||
padding: '3px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
zIndex: '1',
|
||||
textTransform: 'capitalize',
|
||||
border: '2px solid #E20202',
|
||||
},
|
||||
}, state.invalid) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
HexAsDecimalInput.prototype.setValid = function (message) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
|
||||
HexAsDecimalInput.prototype.updateValidity = function (event) {
|
||||
const target = event.target
|
||||
const value = this.props.value
|
||||
const newValue = target.value
|
||||
|
||||
if (value === newValue) {
|
||||
return
|
||||
}
|
||||
|
||||
const valid = target.checkValidity()
|
||||
if (valid) {
|
||||
this.setState({ invalid: null })
|
||||
}
|
||||
}
|
||||
|
||||
HexAsDecimalInput.prototype.constructWarning = function () {
|
||||
const { name, min, max } = this.props
|
||||
let message = name ? name + ' ' : ''
|
||||
|
||||
if (min && max) {
|
||||
message += `must be greater than or equal to ${min} and less than or equal to ${max}.`
|
||||
} else if (min) {
|
||||
message += `must be greater than or equal to ${min}.`
|
||||
} else if (max) {
|
||||
message += `must be less than or equal to ${max}.`
|
||||
} else {
|
||||
message += 'Invalid input.'
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
function hexify (decimalString) {
|
||||
const hexBN = new BN(parseInt(decimalString), 10)
|
||||
return '0x' + hexBN.toString('hex')
|
||||
}
|
||||
|
||||
function decimalize (input, toEth) {
|
||||
if (input === '') {
|
||||
return ''
|
||||
} else {
|
||||
const strippedInput = ethUtil.stripHexPrefix(input)
|
||||
const inputBN = new BN(strippedInput, 'hex')
|
||||
return inputBN.toString(10)
|
||||
}
|
||||
}
|
74
old-ui/app/components/identicon.js
Normal file
74
old-ui/app/components/identicon.js
Normal file
@ -0,0 +1,74 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const isNode = require('detect-node')
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
const jazzicon = require('jazzicon')
|
||||
const iconFactoryGen = require('../../lib/icon-factory')
|
||||
const iconFactory = iconFactoryGen(jazzicon)
|
||||
|
||||
module.exports = IdenticonComponent
|
||||
|
||||
inherits(IdenticonComponent, Component)
|
||||
function IdenticonComponent () {
|
||||
Component.call(this)
|
||||
|
||||
this.defaultDiameter = 46
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.render = function () {
|
||||
var props = this.props
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
return (
|
||||
h('div', {
|
||||
key: 'identicon-' + this.props.address,
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: diameter,
|
||||
width: diameter,
|
||||
borderRadius: diameter / 2,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidMount = function () {
|
||||
var props = this.props
|
||||
const { address } = props
|
||||
|
||||
if (!address) return
|
||||
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
if (!isNode) {
|
||||
var img = iconFactory.iconForAddress(address, diameter)
|
||||
container.appendChild(img)
|
||||
}
|
||||
}
|
||||
|
||||
IdenticonComponent.prototype.componentDidUpdate = function () {
|
||||
var props = this.props
|
||||
const { address } = props
|
||||
|
||||
if (!address) return
|
||||
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
|
||||
var children = container.children
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
container.removeChild(children[i])
|
||||
}
|
||||
|
||||
var diameter = props.diameter || this.defaultDiameter
|
||||
if (!isNode) {
|
||||
var img = iconFactory.iconForAddress(address, diameter)
|
||||
container.appendChild(img)
|
||||
}
|
||||
}
|
||||
|
45
old-ui/app/components/loading.js
Normal file
45
old-ui/app/components/loading.js
Normal file
@ -0,0 +1,45 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
|
||||
|
||||
inherits(LoadingIndicator, Component)
|
||||
module.exports = LoadingIndicator
|
||||
|
||||
function LoadingIndicator () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
LoadingIndicator.prototype.render = function () {
|
||||
const { isLoading, loadingMessage } = this.props
|
||||
|
||||
return (
|
||||
isLoading ? h('.full-flex-height', {
|
||||
style: {
|
||||
left: '0px',
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: 'images/loading.svg',
|
||||
}),
|
||||
|
||||
h('br'),
|
||||
|
||||
showMessageIfAny(loadingMessage),
|
||||
]) : null
|
||||
)
|
||||
}
|
||||
|
||||
function showMessageIfAny (loadingMessage) {
|
||||
if (!loadingMessage) return null
|
||||
return h('span', loadingMessage)
|
||||
}
|
59
old-ui/app/components/mascot.js
Normal file
59
old-ui/app/components/mascot.js
Normal file
@ -0,0 +1,59 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const metamaskLogo = require('metamask-logo')
|
||||
const debounce = require('debounce')
|
||||
|
||||
module.exports = Mascot
|
||||
|
||||
inherits(Mascot, Component)
|
||||
function Mascot () {
|
||||
Component.call(this)
|
||||
this.logo = metamaskLogo({
|
||||
followMouse: true,
|
||||
pxNotRatio: true,
|
||||
width: 200,
|
||||
height: 200,
|
||||
})
|
||||
|
||||
this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000)
|
||||
this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false)
|
||||
}
|
||||
|
||||
Mascot.prototype.render = function () {
|
||||
// this is a bit hacky
|
||||
// the event emitter is on `this.props`
|
||||
// and we dont get that until render
|
||||
this.handleAnimationEvents()
|
||||
|
||||
return h('#metamask-mascot-container', {
|
||||
style: { zIndex: 0 },
|
||||
})
|
||||
}
|
||||
|
||||
Mascot.prototype.componentDidMount = function () {
|
||||
var targetDivId = 'metamask-mascot-container'
|
||||
var container = document.getElementById(targetDivId)
|
||||
container.appendChild(this.logo.container)
|
||||
}
|
||||
|
||||
Mascot.prototype.componentWillUnmount = function () {
|
||||
this.animations = this.props.animationEventEmitter
|
||||
this.animations.removeAllListeners()
|
||||
this.logo.container.remove()
|
||||
this.logo.stopAnimation()
|
||||
}
|
||||
|
||||
Mascot.prototype.handleAnimationEvents = function () {
|
||||
// only setup listeners once
|
||||
if (this.animations) return
|
||||
this.animations = this.props.animationEventEmitter
|
||||
this.animations.on('point', this.lookAt.bind(this))
|
||||
this.animations.on('setFollowMouse', this.logo.setFollowMouse.bind(this.logo))
|
||||
}
|
||||
|
||||
Mascot.prototype.lookAt = function (target) {
|
||||
this.unfollowMouse()
|
||||
this.logo.lookAt(target)
|
||||
this.refollowMouse()
|
||||
}
|
132
old-ui/app/components/menu-droppo.js
Normal file
132
old-ui/app/components/menu-droppo.js
Normal file
@ -0,0 +1,132 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
|
||||
module.exports = MenuDroppoComponent
|
||||
|
||||
|
||||
inherits(MenuDroppoComponent, Component)
|
||||
function MenuDroppoComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.render = function () {
|
||||
const speed = this.props.speed || '300ms'
|
||||
const useCssTransition = this.props.useCssTransition
|
||||
const zIndex = ('zIndex' in this.props) ? this.props.zIndex : 0
|
||||
|
||||
this.manageListeners()
|
||||
|
||||
const style = this.props.style || {}
|
||||
if (!('position' in style)) {
|
||||
style.position = 'fixed'
|
||||
}
|
||||
style.zIndex = zIndex
|
||||
|
||||
return (
|
||||
h('.menu-droppo-container', {
|
||||
style,
|
||||
}, [
|
||||
h('style', `
|
||||
.menu-droppo-enter {
|
||||
transition: transform ${speed} ease-in-out;
|
||||
transform: translateY(-200%);
|
||||
}
|
||||
|
||||
.menu-droppo-enter.menu-droppo-enter-active {
|
||||
transition: transform ${speed} ease-in-out;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.menu-droppo-leave {
|
||||
transition: transform ${speed} ease-in-out;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.menu-droppo-leave.menu-droppo-leave-active {
|
||||
transition: transform ${speed} ease-in-out;
|
||||
transform: translateY(-200%);
|
||||
}
|
||||
`),
|
||||
|
||||
useCssTransition
|
||||
? h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'menu-droppo',
|
||||
transitionEnterTimeout: parseInt(speed),
|
||||
transitionLeaveTimeout: parseInt(speed),
|
||||
}, this.renderPrimary())
|
||||
: this.renderPrimary(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.renderPrimary = function () {
|
||||
const isOpen = this.props.isOpen
|
||||
if (!isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
const innerStyle = this.props.innerStyle || {}
|
||||
|
||||
return (
|
||||
h('.menu-droppo', {
|
||||
key: 'menu-droppo-drawer',
|
||||
style: innerStyle,
|
||||
},
|
||||
[ this.props.children ])
|
||||
)
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.manageListeners = function () {
|
||||
const isOpen = this.props.isOpen
|
||||
const onClickOutside = this.props.onClickOutside
|
||||
|
||||
if (isOpen) {
|
||||
this.outsideClickHandler = onClickOutside
|
||||
} else if (isOpen) {
|
||||
this.outsideClickHandler = null
|
||||
}
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.componentDidMount = function () {
|
||||
if (this && document.body) {
|
||||
this.globalClickHandler = this.globalClickOccurred.bind(this)
|
||||
document.body.addEventListener('click', this.globalClickHandler)
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var container = findDOMNode(this)
|
||||
this.container = container
|
||||
}
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.componentWillUnmount = function () {
|
||||
if (this && document.body) {
|
||||
document.body.removeEventListener('click', this.globalClickHandler)
|
||||
}
|
||||
}
|
||||
|
||||
MenuDroppoComponent.prototype.globalClickOccurred = function (event) {
|
||||
const target = event.target
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
const container = findDOMNode(this)
|
||||
|
||||
if (target !== container &&
|
||||
!isDescendant(this.container, event.target) &&
|
||||
this.outsideClickHandler) {
|
||||
this.outsideClickHandler(event)
|
||||
}
|
||||
}
|
||||
|
||||
function isDescendant (parent, child) {
|
||||
var node = child.parentNode
|
||||
while (node !== null) {
|
||||
if (node === parent) {
|
||||
return true
|
||||
}
|
||||
node = node.parentNode
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
74
old-ui/app/components/mini-account-panel.js
Normal file
74
old-ui/app/components/mini-account-panel.js
Normal file
@ -0,0 +1,74 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const Identicon = require('./identicon')
|
||||
|
||||
module.exports = AccountPanel
|
||||
|
||||
|
||||
inherits(AccountPanel, Component)
|
||||
function AccountPanel () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountPanel.prototype.render = function () {
|
||||
var props = this.props
|
||||
var picOrder = props.picOrder || 'left'
|
||||
const { imageSeed } = props
|
||||
|
||||
return (
|
||||
|
||||
h('.identity-panel.flex-row.flex-left', {
|
||||
style: {
|
||||
cursor: props.onClick ? 'pointer' : undefined,
|
||||
},
|
||||
onClick: props.onClick,
|
||||
}, [
|
||||
|
||||
this.genIcon(imageSeed, picOrder),
|
||||
|
||||
h('div.flex-column.flex-justify-center', {
|
||||
style: {
|
||||
lineHeight: '15px',
|
||||
order: 2,
|
||||
display: 'flex',
|
||||
alignItems: picOrder === 'left' ? 'flex-begin' : 'flex-end',
|
||||
},
|
||||
}, this.props.children),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountPanel.prototype.genIcon = function (seed, picOrder) {
|
||||
const props = this.props
|
||||
|
||||
// When there is no seed value, this is a contract creation.
|
||||
// We then show the contract icon.
|
||||
if (!seed) {
|
||||
return h('.identicon-wrapper.flex-column.select-none', {
|
||||
style: {
|
||||
order: picOrder === 'left' ? 1 : 3,
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-file-text-o.fa-lg', {
|
||||
style: {
|
||||
fontSize: '42px',
|
||||
transform: 'translate(0px, -16px)',
|
||||
},
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
// If there was a seed, we return an identicon for that address.
|
||||
return h('.identicon-wrapper.flex-column.select-none', {
|
||||
style: {
|
||||
order: picOrder === 'left' ? 1 : 3,
|
||||
},
|
||||
}, [
|
||||
h(Identicon, {
|
||||
address: seed,
|
||||
imageify: props.imageifyIdenticons,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
129
old-ui/app/components/network.js
Normal file
129
old-ui/app/components/network.js
Normal file
@ -0,0 +1,129 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = Network
|
||||
|
||||
inherits(Network, Component)
|
||||
|
||||
function Network () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
Network.prototype.render = function () {
|
||||
const props = this.props
|
||||
const networkNumber = props.network
|
||||
let providerName
|
||||
try {
|
||||
providerName = props.provider.type
|
||||
} catch (e) {
|
||||
providerName = null
|
||||
}
|
||||
let iconName, hoverText
|
||||
|
||||
if (networkNumber === 'loading') {
|
||||
return h('span.pointer', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
onClick: (event) => this.props.onClick(event),
|
||||
}, [
|
||||
h('img', {
|
||||
title: 'Attempting to connect to blockchain.',
|
||||
style: {
|
||||
width: '27px',
|
||||
},
|
||||
src: 'images/loading.svg',
|
||||
}),
|
||||
h('i.fa.fa-caret-down'),
|
||||
])
|
||||
} else if (providerName === 'mainnet') {
|
||||
hoverText = 'Main Ethereum Network'
|
||||
iconName = 'ethereum-network'
|
||||
} else if (providerName === 'ropsten') {
|
||||
hoverText = 'Ropsten Test Network'
|
||||
iconName = 'ropsten-test-network'
|
||||
} else if (parseInt(networkNumber) === 3) {
|
||||
hoverText = 'Ropsten Test Network'
|
||||
iconName = 'ropsten-test-network'
|
||||
} else if (providerName === 'kovan') {
|
||||
hoverText = 'Kovan Test Network'
|
||||
iconName = 'kovan-test-network'
|
||||
} else if (providerName === 'rinkeby') {
|
||||
hoverText = 'Rinkeby Test Network'
|
||||
iconName = 'rinkeby-test-network'
|
||||
} else {
|
||||
hoverText = 'Unknown Private Network'
|
||||
iconName = 'unknown-private-network'
|
||||
}
|
||||
|
||||
return (
|
||||
h('#network_component.pointer', {
|
||||
title: hoverText,
|
||||
onClick: (event) => this.props.onClick(event),
|
||||
}, [
|
||||
(function () {
|
||||
switch (iconName) {
|
||||
case 'ethereum-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.diamond'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#039396',
|
||||
}},
|
||||
'Main Network'),
|
||||
h('i.fa.fa-caret-down.fa-lg'),
|
||||
])
|
||||
case 'ropsten-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.red-dot'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#ff6666',
|
||||
}},
|
||||
'Ropsten Test Net'),
|
||||
h('i.fa.fa-caret-down.fa-lg'),
|
||||
])
|
||||
case 'kovan-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#690496',
|
||||
}},
|
||||
'Kovan Test Net'),
|
||||
h('i.fa.fa-caret-down.fa-lg'),
|
||||
])
|
||||
case 'rinkeby-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.golden-square'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#e7a218',
|
||||
}},
|
||||
'Rinkeby Test Net'),
|
||||
h('i.fa.fa-caret-down.fa-lg'),
|
||||
])
|
||||
default:
|
||||
return h('.network-indicator', [
|
||||
h('i.fa.fa-question-circle.fa-lg', {
|
||||
style: {
|
||||
margin: '10px',
|
||||
color: 'rgb(125, 128, 130)',
|
||||
},
|
||||
}),
|
||||
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#AEAEAE',
|
||||
}},
|
||||
'Private Network'),
|
||||
h('i.fa.fa-caret-down.fa-lg'),
|
||||
])
|
||||
}
|
||||
})(),
|
||||
])
|
||||
)
|
||||
}
|
132
old-ui/app/components/notice.js
Normal file
132
old-ui/app/components/notice.js
Normal file
@ -0,0 +1,132 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const ReactMarkdown = require('react-markdown')
|
||||
const linker = require('extension-link-enabler')
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
|
||||
module.exports = Notice
|
||||
|
||||
inherits(Notice, Component)
|
||||
function Notice () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
Notice.prototype.render = function () {
|
||||
const { notice, onConfirm } = this.props
|
||||
const { title, date, body } = notice
|
||||
const state = this.state || { disclaimerDisabled: true }
|
||||
const disabled = state.disclaimerDisabled
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-center.flex-grow', {
|
||||
style: {
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
h('h3.flex-center.text-transform-uppercase.terms-header', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
textAlign: 'center',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
title,
|
||||
]),
|
||||
|
||||
h('h5.flex-center.text-transform-uppercase.terms-header', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginBottom: 24,
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
textAlign: 'center',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
date,
|
||||
]),
|
||||
|
||||
h('style', `
|
||||
|
||||
.markdown {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.markdown h1, .markdown h2, .markdown h3 {
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markdown strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.markdown em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.markdown a {
|
||||
color: #df6b0e;
|
||||
}
|
||||
|
||||
`),
|
||||
|
||||
h('div.markdown', {
|
||||
onScroll: (e) => {
|
||||
var object = e.currentTarget
|
||||
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
|
||||
this.setState({disclaimerDisabled: false})
|
||||
}
|
||||
},
|
||||
style: {
|
||||
background: 'rgb(235, 235, 235)',
|
||||
height: '310px',
|
||||
padding: '6px',
|
||||
width: '90%',
|
||||
overflowY: 'scroll',
|
||||
scroll: 'auto',
|
||||
},
|
||||
}, [
|
||||
h(ReactMarkdown, {
|
||||
className: 'notice-box',
|
||||
source: body,
|
||||
skipHtml: true,
|
||||
}),
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
disabled,
|
||||
onClick: () => {
|
||||
this.setState({disclaimerDisabled: true})
|
||||
onConfirm()
|
||||
},
|
||||
style: {
|
||||
marginTop: '18px',
|
||||
},
|
||||
}, 'Accept'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
Notice.prototype.componentDidMount = function () {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var node = findDOMNode(this)
|
||||
linker.setupListener(node)
|
||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
||||
this.setState({disclaimerDisabled: false})
|
||||
}
|
||||
}
|
||||
|
||||
Notice.prototype.componentWillUnmount = function () {
|
||||
// eslint-disable-next-line react/no-find-dom-node
|
||||
var node = findDOMNode(this)
|
||||
linker.teardownListener(node)
|
||||
}
|
50
old-ui/app/components/pending-msg-details.js
Normal file
50
old-ui/app/components/pending-msg-details.js
Normal file
@ -0,0 +1,50 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const AccountPanel = require('./account-panel')
|
||||
|
||||
module.exports = PendingMsgDetails
|
||||
|
||||
inherits(PendingMsgDetails, Component)
|
||||
function PendingMsgDetails () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsgDetails.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
var msgParams = msgData.msgParams || {}
|
||||
var address = msgParams.from || state.selectedAddress
|
||||
var identity = state.identities[address] || { address: address }
|
||||
var account = state.accounts[address] || { address: address }
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
style: {
|
||||
margin: '10px 20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: identity,
|
||||
account: account,
|
||||
imageifyIdenticons: state.imageifyIdenticons,
|
||||
}),
|
||||
|
||||
// message data
|
||||
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-column.flex-space-between', [
|
||||
h('label.font-small', 'MESSAGE'),
|
||||
h('span.font-small', msgParams.data),
|
||||
]),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
70
old-ui/app/components/pending-msg.js
Normal file
70
old-ui/app/components/pending-msg.js
Normal file
@ -0,0 +1,70 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const PendingTxDetails = require('./pending-msg-details')
|
||||
|
||||
module.exports = PendingMsg
|
||||
|
||||
inherits(PendingMsg, Component)
|
||||
function PendingMsg () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsg.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
return (
|
||||
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
style: {
|
||||
maxWidth: '350px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header
|
||||
h('h3', {
|
||||
style: {
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, 'Sign Message'),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
margin: '10px',
|
||||
},
|
||||
}, [
|
||||
`Signing this message can have
|
||||
dangerous side effects. Only sign messages from
|
||||
sites you fully trust with your entire account.
|
||||
This dangerous method will be removed in a future version. `,
|
||||
h('a', {
|
||||
href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527',
|
||||
style: { color: 'rgb(247, 134, 28)' },
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527'
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
}, 'Read more here.'),
|
||||
]),
|
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state),
|
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [
|
||||
h('button', {
|
||||
onClick: state.cancelMessage,
|
||||
}, 'Cancel'),
|
||||
h('button', {
|
||||
onClick: state.signMessage,
|
||||
}, 'Sign'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
60
old-ui/app/components/pending-personal-msg-details.js
Normal file
60
old-ui/app/components/pending-personal-msg-details.js
Normal file
@ -0,0 +1,60 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const AccountPanel = require('./account-panel')
|
||||
const BinaryRenderer = require('./binary-renderer')
|
||||
|
||||
module.exports = PendingMsgDetails
|
||||
|
||||
inherits(PendingMsgDetails, Component)
|
||||
function PendingMsgDetails () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsgDetails.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
var msgParams = msgData.msgParams || {}
|
||||
var address = msgParams.from || state.selectedAddress
|
||||
var identity = state.identities[address] || { address: address }
|
||||
var account = state.accounts[address] || { address: address }
|
||||
|
||||
var { data } = msgParams
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
style: {
|
||||
margin: '10px 20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: identity,
|
||||
account: account,
|
||||
imageifyIdenticons: state.imageifyIdenticons,
|
||||
}),
|
||||
|
||||
// message data
|
||||
h('div', {
|
||||
style: {
|
||||
height: '260px',
|
||||
},
|
||||
}, [
|
||||
h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'),
|
||||
h(BinaryRenderer, {
|
||||
value: data,
|
||||
style: {
|
||||
height: '215px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
47
old-ui/app/components/pending-personal-msg.js
Normal file
47
old-ui/app/components/pending-personal-msg.js
Normal file
@ -0,0 +1,47 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const PendingTxDetails = require('./pending-personal-msg-details')
|
||||
|
||||
module.exports = PendingMsg
|
||||
|
||||
inherits(PendingMsg, Component)
|
||||
function PendingMsg () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsg.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
return (
|
||||
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
}, [
|
||||
|
||||
// header
|
||||
h('h3', {
|
||||
style: {
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, 'Sign Message'),
|
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state),
|
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [
|
||||
h('button', {
|
||||
onClick: state.cancelPersonalMessage,
|
||||
}, 'Cancel'),
|
||||
h('button', {
|
||||
onClick: state.signPersonalMessage,
|
||||
}, 'Sign'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
510
old-ui/app/components/pending-tx.js
Normal file
510
old-ui/app/components/pending-tx.js
Normal file
@ -0,0 +1,510 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const clone = require('clone')
|
||||
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
|
||||
const util = require('../util')
|
||||
const MiniAccountPanel = require('./mini-account-panel')
|
||||
const Copyable = require('./copyable')
|
||||
const EthBalance = require('./eth-balance')
|
||||
const addressSummary = util.addressSummary
|
||||
const nameForAddress = require('../../lib/contract-namer')
|
||||
const BNInput = require('./bn-as-decimal-input')
|
||||
|
||||
// corresponds with 0.1 GWEI
|
||||
const MIN_GAS_PRICE_BN = new BN('100000000')
|
||||
const MIN_GAS_LIMIT_BN = new BN('21000')
|
||||
|
||||
module.exports = PendingTx
|
||||
inherits(PendingTx, Component)
|
||||
function PendingTx () {
|
||||
Component.call(this)
|
||||
this.state = {
|
||||
valid: true,
|
||||
txData: null,
|
||||
submitting: false,
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { currentCurrency, blockGasLimit } = props
|
||||
|
||||
const conversionRate = props.conversionRate
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const txParams = txMeta.txParams || {}
|
||||
|
||||
// Allow retry txs
|
||||
const { lastGasPrice } = txMeta
|
||||
let forceGasMin
|
||||
if (lastGasPrice) {
|
||||
const stripped = ethUtil.stripHexPrefix(lastGasPrice)
|
||||
const lastGas = new BN(stripped, 16)
|
||||
const priceBump = lastGas.divn('10')
|
||||
forceGasMin = lastGas.add(priceBump)
|
||||
}
|
||||
|
||||
// Account Details
|
||||
const address = txParams.from || props.selectedAddress
|
||||
const identity = props.identities[address] || { address: address }
|
||||
const account = props.accounts[address]
|
||||
const balance = account ? account.balance : '0x0'
|
||||
|
||||
// recipient check
|
||||
const isValidAddress = !txParams.to || util.isValidAddress(txParams.to)
|
||||
|
||||
// Gas
|
||||
const gas = txParams.gas
|
||||
const gasBn = hexToBn(gas)
|
||||
const gasLimit = new BN(parseInt(blockGasLimit))
|
||||
const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
|
||||
const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
|
||||
const safeGasLimit = safeGasLimitBN.toString(10)
|
||||
|
||||
// Gas Price
|
||||
const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16)
|
||||
const gasPriceBn = hexToBn(gasPrice)
|
||||
|
||||
const txFeeBn = gasBn.mul(gasPriceBn)
|
||||
const valueBn = hexToBn(txParams.value)
|
||||
const maxCost = txFeeBn.add(valueBn)
|
||||
|
||||
const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
|
||||
|
||||
const balanceBn = hexToBn(balance)
|
||||
const insufficientBalance = balanceBn.lt(maxCost)
|
||||
const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
|
||||
const gasLimitSpecified = txMeta.gasLimitSpecified
|
||||
const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
|
||||
const showRejectAll = props.unconfTxListLength > 1
|
||||
|
||||
this.inputs = []
|
||||
|
||||
return (
|
||||
|
||||
h('div', {
|
||||
key: txMeta.id,
|
||||
}, [
|
||||
|
||||
h('form#pending-tx-form', {
|
||||
onSubmit: this.onSubmit.bind(this),
|
||||
|
||||
}, [
|
||||
|
||||
// tx info
|
||||
h('div', [
|
||||
|
||||
h('.flex-row.flex-center', {
|
||||
style: {
|
||||
maxWidth: '100%',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(MiniAccountPanel, {
|
||||
imageSeed: address,
|
||||
picOrder: 'right',
|
||||
}, [
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
|
||||
},
|
||||
}, identity.name),
|
||||
|
||||
h(Copyable, {
|
||||
value: ethUtil.toChecksumAddress(address),
|
||||
}, [
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
|
||||
},
|
||||
}, addressSummary(address, 6, 4, false)),
|
||||
]),
|
||||
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
|
||||
},
|
||||
}, [
|
||||
h(EthBalance, {
|
||||
value: balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
inline: true,
|
||||
labelColor: '#F7861C',
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
forwardCarrat(),
|
||||
|
||||
this.miniAccountPanelForRecipient(),
|
||||
]),
|
||||
|
||||
h('style', `
|
||||
.table-box {
|
||||
margin: 7px 0px 0px 0px;
|
||||
width: 100%;
|
||||
}
|
||||
.table-box .row {
|
||||
margin: 0px;
|
||||
background: rgb(236,236,236);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: Montserrat Light, sans-serif;
|
||||
font-size: 13px;
|
||||
padding: 5px 25px;
|
||||
}
|
||||
.table-box .row .value {
|
||||
font-family: Montserrat Regular;
|
||||
}
|
||||
`),
|
||||
|
||||
h('.table-box', [
|
||||
|
||||
// Ether Value
|
||||
// Currently not customizable, but easily modified
|
||||
// in the way that gas and gasLimit currently are.
|
||||
h('.row', [
|
||||
h('.cell.label', 'Amount'),
|
||||
h(EthBalance, { value: txParams.value, currentCurrency, conversionRate }),
|
||||
]),
|
||||
|
||||
// Gas Limit (customizable)
|
||||
h('.cell.row', [
|
||||
h('.cell.label', 'Gas Limit'),
|
||||
h('.cell.value', {
|
||||
}, [
|
||||
h(BNInput, {
|
||||
name: 'Gas Limit',
|
||||
value: gasBn,
|
||||
precision: 0,
|
||||
scale: 0,
|
||||
// The hard lower limit for gas.
|
||||
min: MIN_GAS_LIMIT_BN,
|
||||
max: safeGasLimit,
|
||||
suffix: 'UNITS',
|
||||
style: {
|
||||
position: 'relative',
|
||||
top: '5px',
|
||||
},
|
||||
onChange: this.gasLimitChanged.bind(this),
|
||||
|
||||
ref: (hexInput) => { this.inputs.push(hexInput) },
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
// Gas Price (customizable)
|
||||
h('.cell.row', [
|
||||
h('.cell.label', 'Gas Price'),
|
||||
h('.cell.value', {
|
||||
}, [
|
||||
h(BNInput, {
|
||||
name: 'Gas Price',
|
||||
value: gasPriceBn,
|
||||
precision: 9,
|
||||
scale: 9,
|
||||
suffix: 'GWEI',
|
||||
min: forceGasMin || MIN_GAS_PRICE_BN,
|
||||
style: {
|
||||
position: 'relative',
|
||||
top: '5px',
|
||||
},
|
||||
onChange: this.gasPriceChanged.bind(this),
|
||||
ref: (hexInput) => { this.inputs.push(hexInput) },
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
// Max Transaction Fee (calculated)
|
||||
h('.cell.row', [
|
||||
h('.cell.label', 'Max Transaction Fee'),
|
||||
h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }),
|
||||
]),
|
||||
|
||||
h('.cell.row', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Regular',
|
||||
background: 'white',
|
||||
padding: '10px 25px',
|
||||
},
|
||||
}, [
|
||||
h('.cell.label', 'Max Total'),
|
||||
h('.cell.value', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h(EthBalance, {
|
||||
value: maxCost.toString(16),
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
inline: true,
|
||||
labelColor: 'black',
|
||||
fontSize: '16px',
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
// Data size row:
|
||||
h('.cell.row', {
|
||||
style: {
|
||||
background: '#f7f7f7',
|
||||
paddingBottom: '0px',
|
||||
},
|
||||
}, [
|
||||
h('.cell.label'),
|
||||
h('.cell.value', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light',
|
||||
fontSize: '11px',
|
||||
},
|
||||
}, `Data included: ${dataLength} bytes`),
|
||||
]),
|
||||
]), // End of Table
|
||||
|
||||
]),
|
||||
|
||||
h('style', `
|
||||
.conf-buttons button {
|
||||
margin-left: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`),
|
||||
h('.cell.row', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, [
|
||||
txMeta.simulationFails ?
|
||||
h('.error', {
|
||||
style: {
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Transaction Error. Exception thrown in contract code.')
|
||||
: null,
|
||||
|
||||
!isValidAddress ?
|
||||
h('.error', {
|
||||
style: {
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
|
||||
: null,
|
||||
|
||||
insufficientBalance ?
|
||||
h('span.error', {
|
||||
style: {
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Insufficient balance for transaction')
|
||||
: null,
|
||||
|
||||
(dangerousGasLimit && !gasLimitSpecified) ?
|
||||
h('span.error', {
|
||||
style: {
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Gas limit set dangerously high. Approving this transaction is likely to fail.')
|
||||
: null,
|
||||
]),
|
||||
|
||||
|
||||
// send + cancel
|
||||
h('.flex-row.flex-space-around.conf-buttons', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
margin: '14px 25px',
|
||||
},
|
||||
}, [
|
||||
h('button', {
|
||||
onClick: (event) => {
|
||||
this.resetGasFields()
|
||||
event.preventDefault()
|
||||
},
|
||||
}, 'Reset'),
|
||||
|
||||
// Accept Button or Buy Button
|
||||
insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
|
||||
h('input.confirm.btn-green', {
|
||||
type: 'submit',
|
||||
value: 'SUBMIT',
|
||||
style: { marginLeft: '10px' },
|
||||
disabled: buyDisabled,
|
||||
}),
|
||||
|
||||
h('button.cancel.btn-red', {
|
||||
onClick: props.cancelTransaction,
|
||||
}, 'Reject'),
|
||||
]),
|
||||
showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
margin: '14px 25px',
|
||||
},
|
||||
}, [
|
||||
h('button.cancel.btn-red', {
|
||||
onClick: props.cancelAllTransactions,
|
||||
}, 'Reject All'),
|
||||
]) : null,
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype.miniAccountPanelForRecipient = function () {
|
||||
const props = this.props
|
||||
const txData = props.txData
|
||||
const txParams = txData.txParams || {}
|
||||
const isContractDeploy = !('to' in txParams)
|
||||
|
||||
// If it's not a contract deploy, send to the account
|
||||
if (!isContractDeploy) {
|
||||
return h(MiniAccountPanel, {
|
||||
imageSeed: txParams.to,
|
||||
picOrder: 'left',
|
||||
}, [
|
||||
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
|
||||
},
|
||||
}, nameForAddress(txParams.to, props.identities)),
|
||||
|
||||
h(Copyable, {
|
||||
value: ethUtil.toChecksumAddress(txParams.to),
|
||||
}, [
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light, Montserrat, sans-serif',
|
||||
},
|
||||
}, addressSummary(txParams.to, 6, 4, false)),
|
||||
]),
|
||||
|
||||
])
|
||||
} else {
|
||||
return h(MiniAccountPanel, {
|
||||
picOrder: 'left',
|
||||
}, [
|
||||
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
|
||||
},
|
||||
}, 'New Contract'),
|
||||
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.gasPriceChanged = function (newBN, valid) {
|
||||
log.info(`Gas price changed to: ${newBN.toString(10)}`)
|
||||
const txMeta = this.gatherTxMeta()
|
||||
txMeta.txParams.gasPrice = '0x' + newBN.toString('hex')
|
||||
this.setState({
|
||||
txData: clone(txMeta),
|
||||
valid,
|
||||
})
|
||||
}
|
||||
|
||||
PendingTx.prototype.gasLimitChanged = function (newBN, valid) {
|
||||
log.info(`Gas limit changed to ${newBN.toString(10)}`)
|
||||
const txMeta = this.gatherTxMeta()
|
||||
txMeta.txParams.gas = '0x' + newBN.toString('hex')
|
||||
this.setState({
|
||||
txData: clone(txMeta),
|
||||
valid,
|
||||
})
|
||||
}
|
||||
|
||||
PendingTx.prototype.resetGasFields = function () {
|
||||
log.debug(`pending-tx resetGasFields`)
|
||||
|
||||
this.inputs.forEach((hexInput) => {
|
||||
if (hexInput) {
|
||||
hexInput.setValid()
|
||||
}
|
||||
})
|
||||
|
||||
this.setState({
|
||||
txData: null,
|
||||
valid: true,
|
||||
})
|
||||
}
|
||||
|
||||
PendingTx.prototype.onSubmit = function (event) {
|
||||
event.preventDefault()
|
||||
const txMeta = this.gatherTxMeta()
|
||||
const valid = this.checkValidity()
|
||||
this.setState({ valid, submitting: true })
|
||||
if (valid && this.verifyGasParams()) {
|
||||
this.props.sendTransaction(txMeta, event)
|
||||
} else {
|
||||
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
|
||||
this.setState({ submitting: false })
|
||||
}
|
||||
}
|
||||
|
||||
PendingTx.prototype.checkValidity = function () {
|
||||
const form = this.getFormEl()
|
||||
const valid = form.checkValidity()
|
||||
return valid
|
||||
}
|
||||
|
||||
PendingTx.prototype.getFormEl = function () {
|
||||
const form = document.querySelector('form#pending-tx-form')
|
||||
// Stub out form for unit tests:
|
||||
if (!form) {
|
||||
return { checkValidity () { return true } }
|
||||
}
|
||||
return form
|
||||
}
|
||||
|
||||
// After a customizable state value has been updated,
|
||||
PendingTx.prototype.gatherTxMeta = function () {
|
||||
log.debug(`pending-tx gatherTxMeta`)
|
||||
const props = this.props
|
||||
const state = this.state
|
||||
const txData = clone(state.txData) || clone(props.txData)
|
||||
|
||||
log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
|
||||
return txData
|
||||
}
|
||||
|
||||
PendingTx.prototype.verifyGasParams = function () {
|
||||
// We call this in case the gas has not been modified at all
|
||||
if (!this.state) { return true }
|
||||
return (
|
||||
this._notZeroOrEmptyString(this.state.gas) &&
|
||||
this._notZeroOrEmptyString(this.state.gasPrice)
|
||||
)
|
||||
}
|
||||
|
||||
PendingTx.prototype._notZeroOrEmptyString = function (obj) {
|
||||
return obj !== '' && obj !== '0x0'
|
||||
}
|
||||
|
||||
PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) {
|
||||
const numBN = new BN(numerator)
|
||||
const denomBN = new BN(denominator)
|
||||
return targetBN.mul(numBN).div(denomBN)
|
||||
}
|
||||
|
||||
function forwardCarrat () {
|
||||
return (
|
||||
h('img', {
|
||||
src: 'images/forward-carrat.svg',
|
||||
style: {
|
||||
padding: '5px 6px 0px 10px',
|
||||
height: '37px',
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
59
old-ui/app/components/pending-typed-msg-details.js
Normal file
59
old-ui/app/components/pending-typed-msg-details.js
Normal file
@ -0,0 +1,59 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const AccountPanel = require('./account-panel')
|
||||
const TypedMessageRenderer = require('./typed-message-renderer')
|
||||
|
||||
module.exports = PendingMsgDetails
|
||||
|
||||
inherits(PendingMsgDetails, Component)
|
||||
function PendingMsgDetails () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsgDetails.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
var msgParams = msgData.msgParams || {}
|
||||
var address = msgParams.from || state.selectedAddress
|
||||
var identity = state.identities[address] || { address: address }
|
||||
var account = state.accounts[address] || { address: address }
|
||||
|
||||
var { data } = msgParams
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
style: {
|
||||
margin: '10px 20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// account that will sign
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: identity,
|
||||
account: account,
|
||||
imageifyIdenticons: state.imageifyIdenticons,
|
||||
}),
|
||||
|
||||
// message data
|
||||
h('div', {
|
||||
style: {
|
||||
height: '260px',
|
||||
},
|
||||
}, [
|
||||
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'),
|
||||
h(TypedMessageRenderer, {
|
||||
value: data,
|
||||
style: {
|
||||
height: '215px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
46
old-ui/app/components/pending-typed-msg.js
Normal file
46
old-ui/app/components/pending-typed-msg.js
Normal file
@ -0,0 +1,46 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const PendingTxDetails = require('./pending-typed-msg-details')
|
||||
|
||||
module.exports = PendingMsg
|
||||
|
||||
inherits(PendingMsg, Component)
|
||||
function PendingMsg () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PendingMsg.prototype.render = function () {
|
||||
var state = this.props
|
||||
var msgData = state.txData
|
||||
|
||||
return (
|
||||
|
||||
h('div', {
|
||||
key: msgData.id,
|
||||
}, [
|
||||
|
||||
// header
|
||||
h('h3', {
|
||||
style: {
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, 'Sign Message'),
|
||||
|
||||
// message details
|
||||
h(PendingTxDetails, state),
|
||||
|
||||
// sign + cancel
|
||||
h('.flex-row.flex-space-around', [
|
||||
h('button', {
|
||||
onClick: state.cancelTypedMessage,
|
||||
}, 'Cancel'),
|
||||
h('button', {
|
||||
onClick: state.signTypedMessage,
|
||||
}, 'Sign'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
80
old-ui/app/components/qr-code.js
Normal file
80
old-ui/app/components/qr-code.js
Normal file
@ -0,0 +1,80 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const qrCode = require('qrcode-npm').qrcode
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const isHexPrefixed = require('ethereumjs-util').isHexPrefixed
|
||||
const CopyButton = require('./copyButton')
|
||||
|
||||
module.exports = connect(mapStateToProps)(QrCodeView)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
Qr: state.appState.Qr,
|
||||
buyView: state.appState.buyView,
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(QrCodeView, Component)
|
||||
|
||||
function QrCodeView () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
QrCodeView.prototype.render = function () {
|
||||
const props = this.props
|
||||
const Qr = props.Qr
|
||||
console.log(`QrCodeView Qr`, Qr);
|
||||
const address = `${isHexPrefixed(Qr.data) ? 'ethereum:' : ''}${Qr.data}`
|
||||
const qrImage = qrCode(4, 'M')
|
||||
qrImage.addData(address)
|
||||
qrImage.make()
|
||||
return h('.main-container.flex-column', {
|
||||
key: 'qr',
|
||||
style: {
|
||||
justifyContent: 'center',
|
||||
paddingBottom: '45px',
|
||||
paddingLeft: '45px',
|
||||
paddingRight: '45px',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message),
|
||||
|
||||
this.props.warning ? this.props.warning && h('span.error.flex-center', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
width: '229px',
|
||||
height: '82px',
|
||||
},
|
||||
},
|
||||
this.props.warning) : null,
|
||||
|
||||
h('#qr-container.flex-column', {
|
||||
style: {
|
||||
marginTop: '25px',
|
||||
marginBottom: '15px',
|
||||
},
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: qrImage.createTableTag(4),
|
||||
},
|
||||
}),
|
||||
h('.flex-row', [
|
||||
h('h3.ellip-address', {
|
||||
style: {
|
||||
width: '247px',
|
||||
},
|
||||
}, Qr.data),
|
||||
h(CopyButton, {
|
||||
value: Qr.data,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
QrCodeView.prototype.renderMultiMessage = function () {
|
||||
var Qr = this.props.Qr
|
||||
var multiMessage = Qr.message.map((message) => h('.qr-message', message))
|
||||
return multiMessage
|
||||
}
|
58
old-ui/app/components/range-slider.js
Normal file
58
old-ui/app/components/range-slider.js
Normal file
@ -0,0 +1,58 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = RangeSlider
|
||||
|
||||
inherits(RangeSlider, Component)
|
||||
function RangeSlider () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
RangeSlider.prototype.render = function () {
|
||||
const state = this.state || {}
|
||||
const props = this.props
|
||||
const onInput = props.onInput || function () {}
|
||||
const name = props.name
|
||||
const {
|
||||
min = 0,
|
||||
max = 100,
|
||||
increment = 1,
|
||||
defaultValue = 50,
|
||||
mirrorInput = false,
|
||||
} = this.props.options
|
||||
const {container, input, range} = props.style
|
||||
|
||||
return (
|
||||
h('.flex-row', {
|
||||
style: container,
|
||||
}, [
|
||||
h('input', {
|
||||
type: 'range',
|
||||
name: name,
|
||||
min: min,
|
||||
max: max,
|
||||
step: increment,
|
||||
style: range,
|
||||
value: state.value || defaultValue,
|
||||
onChange: mirrorInput ? this.mirrorInputs.bind(this, event) : onInput,
|
||||
}),
|
||||
|
||||
// Mirrored input for range
|
||||
mirrorInput ? h('input.large-input', {
|
||||
type: 'number',
|
||||
name: `${name}Mirror`,
|
||||
min: min,
|
||||
max: max,
|
||||
value: state.value || defaultValue,
|
||||
step: increment,
|
||||
style: input,
|
||||
onChange: this.mirrorInputs.bind(this, event),
|
||||
}) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
RangeSlider.prototype.mirrorInputs = function (event) {
|
||||
this.setState({value: event.target.value})
|
||||
}
|
308
old-ui/app/components/shapeshift-form.js
Normal file
308
old-ui/app/components/shapeshift-form.js
Normal file
@ -0,0 +1,308 @@
|
||||
const PersistentForm = require('../../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const Qr = require('./qr-code')
|
||||
const isValidAddress = require('../util').isValidAddress
|
||||
module.exports = connect(mapStateToProps)(ShapeshiftForm)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
isSubLoading: state.appState.isSubLoading,
|
||||
qrRequested: state.appState.qrRequested,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ShapeshiftForm, PersistentForm)
|
||||
|
||||
function ShapeshiftForm () {
|
||||
PersistentForm.call(this)
|
||||
this.persistentFormParentId = 'shapeshift-buy-form'
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.render = function () {
|
||||
return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain()
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderMain = function () {
|
||||
const marketinfo = this.props.buyView.formView.marketinfo
|
||||
const coinOptions = this.props.buyView.formView.coinOptions
|
||||
var coin = marketinfo.pair.split('_')[0].toUpperCase()
|
||||
|
||||
return h('.flex-column', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
padding: '25px',
|
||||
paddingTop: '5px',
|
||||
width: '90%',
|
||||
minHeight: '215px',
|
||||
alignItems: 'center',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
}, [
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'baseline',
|
||||
height: '42px',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: coinOptions[coin].image,
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
style: {
|
||||
marginRight: '5px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('.input-container', {
|
||||
position: 'relative',
|
||||
}, [
|
||||
h('input#fromCoin.buy-inputs.ex-coins', {
|
||||
type: 'text',
|
||||
list: 'coinList',
|
||||
autoFocus: true,
|
||||
dataset: {
|
||||
persistentFormId: 'input-coin',
|
||||
},
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
onChange: this.handleLiveInput.bind(this),
|
||||
defaultValue: 'BTC',
|
||||
}),
|
||||
|
||||
this.renderCoinList(),
|
||||
|
||||
h('i.fa.fa-pencil-square-o.edit-text', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
color: '#F7861C',
|
||||
position: 'absolute',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('.icon-control', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
},
|
||||
}, [
|
||||
// Not visible on the screen, can't see it on master.
|
||||
|
||||
// h('i.fa.fa-refresh.fa-4.orange', {
|
||||
// style: {
|
||||
// bottom: '5px',
|
||||
// left: '5px',
|
||||
// color: '#F7861C',
|
||||
// },
|
||||
// onClick: this.updateCoin.bind(this),
|
||||
// }),
|
||||
h('i.fa.fa-chevron-right.fa-4.orange', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
bottom: '35%',
|
||||
left: '0%',
|
||||
color: '#F7861C',
|
||||
},
|
||||
onClick: this.updateCoin.bind(this),
|
||||
}),
|
||||
]),
|
||||
|
||||
h('#toCoin.ex-coins', marketinfo.pair.split('_')[1].toUpperCase()),
|
||||
|
||||
h('img', {
|
||||
src: coinOptions[marketinfo.pair.split('_')[1].toUpperCase()].image,
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
style: {
|
||||
marginLeft: '5px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
marginTop: '1%',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
this.props.warning ?
|
||||
this.props.warning &&
|
||||
h('span.error.flex-center', {
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
width: '229px',
|
||||
height: '82px',
|
||||
},
|
||||
}, this.props.warning)
|
||||
: this.renderInfo(),
|
||||
|
||||
this.renderRefundAddressForCoin(coin),
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) {
|
||||
return h(this.activeToggle('.input-container'), {
|
||||
style: {
|
||||
marginTop: '1%',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div', `${coin} Address:`),
|
||||
|
||||
h('input#fromCoinAddress.buy-inputs', {
|
||||
type: 'text',
|
||||
placeholder: `Your ${coin} Refund Address`,
|
||||
dataset: {
|
||||
persistentFormId: 'refund-address',
|
||||
|
||||
},
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
width: '227px',
|
||||
height: '30px',
|
||||
padding: ' 5px ',
|
||||
},
|
||||
}),
|
||||
|
||||
h('i.fa.fa-pencil-square-o.edit-text', {
|
||||
style: {
|
||||
fontSize: '12px',
|
||||
color: '#F7861C',
|
||||
position: 'absolute',
|
||||
},
|
||||
}),
|
||||
h('div.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
h('button', {
|
||||
onClick: this.shift.bind(this),
|
||||
style: {
|
||||
marginTop: '1%',
|
||||
},
|
||||
},
|
||||
'Submit'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.shift = function () {
|
||||
var props = this.props
|
||||
var withdrawal = this.props.buyView.buyAddress
|
||||
var returnAddress = document.getElementById('fromCoinAddress').value
|
||||
var pair = this.props.buyView.formView.marketinfo.pair
|
||||
var data = {
|
||||
'withdrawal': withdrawal,
|
||||
'pair': pair,
|
||||
'returnAddress': returnAddress,
|
||||
// Public api key
|
||||
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
|
||||
}
|
||||
var message = [
|
||||
`Deposit Limit: ${props.buyView.formView.marketinfo.limit}`,
|
||||
`Deposit Minimum:${props.buyView.formView.marketinfo.minimum}`,
|
||||
]
|
||||
if (isValidAddress(withdrawal)) {
|
||||
this.props.dispatch(actions.coinShiftRquest(data, message))
|
||||
}
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderCoinList = function () {
|
||||
var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => {
|
||||
return h('option', {
|
||||
value: item,
|
||||
}, item)
|
||||
})
|
||||
|
||||
return h('datalist#coinList', {
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
},
|
||||
}, list)
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.updateCoin = function (event) {
|
||||
event.preventDefault()
|
||||
const props = this.props
|
||||
var coinOptions = this.props.buyView.formView.coinOptions
|
||||
var coin = document.getElementById('fromCoin').value
|
||||
|
||||
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') {
|
||||
var message = 'Not a valid coin'
|
||||
return props.dispatch(actions.displayWarning(message))
|
||||
} else {
|
||||
return props.dispatch(actions.pairUpdate(coin))
|
||||
}
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.handleLiveInput = function () {
|
||||
const props = this.props
|
||||
var coinOptions = this.props.buyView.formView.coinOptions
|
||||
var coin = document.getElementById('fromCoin').value
|
||||
|
||||
if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') {
|
||||
return null
|
||||
} else {
|
||||
return props.dispatch(actions.pairUpdate(coin))
|
||||
}
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderInfo = function () {
|
||||
const marketinfo = this.props.buyView.formView.marketinfo
|
||||
const coinOptions = this.props.buyView.formView.coinOptions
|
||||
var coin = marketinfo.pair.split('_')[0].toUpperCase()
|
||||
|
||||
return h('span', {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
h('h3.flex-row.text-transform-uppercase', {
|
||||
style: {
|
||||
color: '#868686',
|
||||
paddingTop: '4px',
|
||||
justifyContent: 'space-around',
|
||||
textAlign: 'center',
|
||||
fontSize: '17px',
|
||||
},
|
||||
}, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`),
|
||||
h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]),
|
||||
h('.marketinfo', ['Exchange Rate: ', `${marketinfo.rate}`]),
|
||||
h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]),
|
||||
h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]),
|
||||
])
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.activeToggle = function (elementType) {
|
||||
if (!this.props.buyView.formView.response || this.props.warning) return elementType
|
||||
return `${elementType}.inactive`
|
||||
}
|
||||
|
||||
ShapeshiftForm.prototype.renderLoading = function () {
|
||||
return h('span', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: '70px',
|
||||
bottom: '194px',
|
||||
background: 'transparent',
|
||||
width: '229px',
|
||||
height: '82px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
style: {
|
||||
width: '60px',
|
||||
},
|
||||
src: 'images/loading.svg',
|
||||
}),
|
||||
])
|
||||
}
|
204
old-ui/app/components/shift-list-item.js
Normal file
204
old-ui/app/components/shift-list-item.js
Normal file
@ -0,0 +1,204 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const vreme = new (require('vreme'))()
|
||||
const explorerLink = require('etherscan-link').createExplorerLink
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const addressSummary = require('../util').addressSummary
|
||||
|
||||
const CopyButton = require('./copyButton')
|
||||
const EthBalance = require('./eth-balance')
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
|
||||
module.exports = connect(mapStateToProps)(ShiftListItem)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ShiftListItem, Component)
|
||||
|
||||
function ShiftListItem () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
ShiftListItem.prototype.render = function () {
|
||||
return (
|
||||
h('.transaction-list-item.flex-row', {
|
||||
style: {
|
||||
paddingTop: '20px',
|
||||
paddingBottom: '20px',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
width: '0px',
|
||||
position: 'relative',
|
||||
bottom: '19px',
|
||||
},
|
||||
}, [
|
||||
h('img', {
|
||||
src: 'https://info.shapeshift.io/sites/default/files/logo.png',
|
||||
style: {
|
||||
height: '35px',
|
||||
width: '132px',
|
||||
position: 'absolute',
|
||||
clip: 'rect(0px,23px,34px,0px)',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
this.renderInfo(),
|
||||
this.renderUtilComponents(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
function formatDate (date) {
|
||||
return vreme.format(new Date(date), 'March 16 2014 14:30')
|
||||
}
|
||||
|
||||
ShiftListItem.prototype.renderUtilComponents = function () {
|
||||
var props = this.props
|
||||
const { conversionRate, currentCurrency } = props
|
||||
|
||||
switch (props.response.status) {
|
||||
case 'no_deposits':
|
||||
return h('.flex-row', [
|
||||
h(CopyButton, {
|
||||
value: this.props.depositAddress,
|
||||
}),
|
||||
h(Tooltip, {
|
||||
title: 'QR Code',
|
||||
}, [
|
||||
h('i.fa.fa-qrcode.pointer.pop-hover', {
|
||||
onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)),
|
||||
style: {
|
||||
margin: '5px',
|
||||
marginLeft: '23px',
|
||||
marginRight: '12px',
|
||||
fontSize: '20px',
|
||||
color: '#F7861C',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
])
|
||||
case 'received':
|
||||
return h('.flex-row')
|
||||
|
||||
case 'complete':
|
||||
return h('.flex-row', [
|
||||
h(CopyButton, {
|
||||
value: this.props.response.transaction,
|
||||
}),
|
||||
h(EthBalance, {
|
||||
value: `${props.response.outgoingCoin}`,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
width: '55px',
|
||||
shorten: true,
|
||||
needsParse: false,
|
||||
incoming: true,
|
||||
style: {
|
||||
fontSize: '15px',
|
||||
color: '#01888C',
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
case 'failed':
|
||||
return ''
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
ShiftListItem.prototype.renderInfo = function () {
|
||||
var props = this.props
|
||||
switch (props.response.status) {
|
||||
case 'no_deposits':
|
||||
return h('.flex-column', {
|
||||
style: {
|
||||
width: '200px',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
width: '100%',
|
||||
},
|
||||
}, `${props.depositType} to ETH via ShapeShift`),
|
||||
h('div', 'No deposits received'),
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
width: '100%',
|
||||
},
|
||||
}, formatDate(props.time)),
|
||||
])
|
||||
case 'received':
|
||||
return h('.flex-column', {
|
||||
style: {
|
||||
width: '200px',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
width: '100%',
|
||||
},
|
||||
}, `${props.depositType} to ETH via ShapeShift`),
|
||||
h('div', 'Conversion in progress'),
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
width: '100%',
|
||||
},
|
||||
}, formatDate(props.time)),
|
||||
])
|
||||
case 'complete':
|
||||
var url = explorerLink(props.response.transaction, parseInt('1'))
|
||||
|
||||
return h('.flex-column.pointer', {
|
||||
style: {
|
||||
width: '200px',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
onClick: () => global.platform.openWindow({ url }),
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
width: '100%',
|
||||
},
|
||||
}, 'From ShapeShift'),
|
||||
h('div', formatDate(props.time)),
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
width: '100%',
|
||||
},
|
||||
}, addressSummary(props.response.transaction)),
|
||||
])
|
||||
|
||||
case 'failed':
|
||||
return h('span.error', '(Failed)')
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
37
old-ui/app/components/tab-bar.js
Normal file
37
old-ui/app/components/tab-bar.js
Normal file
@ -0,0 +1,37 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = TabBar
|
||||
|
||||
inherits(TabBar, Component)
|
||||
function TabBar () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TabBar.prototype.render = function () {
|
||||
const props = this.props
|
||||
const state = this.state || {}
|
||||
const { tabs = [], defaultTab, tabSelected } = props
|
||||
const { subview = defaultTab } = state
|
||||
|
||||
return (
|
||||
h('.flex-row.space-around.text-transform-uppercase', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
paddingTop: '4px',
|
||||
minHeight: '30px',
|
||||
},
|
||||
}, tabs.map((tab) => {
|
||||
const { key, content } = tab
|
||||
return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
|
||||
onClick: () => {
|
||||
this.setState({ subview: key })
|
||||
tabSelected(key)
|
||||
},
|
||||
}, content)
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
18
old-ui/app/components/template.js
Normal file
18
old-ui/app/components/template.js
Normal file
@ -0,0 +1,18 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = NewComponent
|
||||
|
||||
inherits(NewComponent, Component)
|
||||
function NewComponent () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
NewComponent.prototype.render = function () {
|
||||
const props = this.props
|
||||
|
||||
return (
|
||||
h('span', props.message)
|
||||
)
|
||||
}
|
72
old-ui/app/components/token-cell.js
Normal file
72
old-ui/app/components/token-cell.js
Normal file
@ -0,0 +1,72 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const Identicon = require('./identicon')
|
||||
const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
|
||||
|
||||
module.exports = TokenCell
|
||||
|
||||
inherits(TokenCell, Component)
|
||||
function TokenCell () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TokenCell.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { address, symbol, string, network, userAddress } = props
|
||||
|
||||
return (
|
||||
h('li.token-cell', {
|
||||
style: { cursor: network === '1' ? 'pointer' : 'default' },
|
||||
onClick: this.view.bind(this, address, userAddress, network),
|
||||
}, [
|
||||
|
||||
h(Identicon, {
|
||||
diameter: 50,
|
||||
address,
|
||||
network,
|
||||
}),
|
||||
|
||||
h('h3', `${string || 0} ${symbol}`),
|
||||
|
||||
h('span', { style: { flex: '1 0 auto' } }),
|
||||
|
||||
/*
|
||||
h('button', {
|
||||
onClick: this.send.bind(this, address),
|
||||
}, 'SEND'),
|
||||
*/
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
TokenCell.prototype.send = function (address, event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
const url = tokenFactoryFor(address)
|
||||
if (url) {
|
||||
navigateTo(url)
|
||||
}
|
||||
}
|
||||
|
||||
TokenCell.prototype.view = function (address, userAddress, network, event) {
|
||||
const url = etherscanLinkFor(address, userAddress, network)
|
||||
if (url) {
|
||||
navigateTo(url)
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo (url) {
|
||||
global.platform.openWindow({ url })
|
||||
}
|
||||
|
||||
function etherscanLinkFor (tokenAddress, address, network) {
|
||||
const prefix = prefixForNetwork(network)
|
||||
return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}`
|
||||
}
|
||||
|
||||
function tokenFactoryFor (tokenAddress) {
|
||||
return `https://tokenfactory.surge.sh/#/token/${tokenAddress}`
|
||||
}
|
||||
|
207
old-ui/app/components/token-list.js
Normal file
207
old-ui/app/components/token-list.js
Normal file
@ -0,0 +1,207 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const TokenTracker = require('eth-token-tracker')
|
||||
const TokenCell = require('./token-cell.js')
|
||||
|
||||
module.exports = TokenList
|
||||
|
||||
inherits(TokenList, Component)
|
||||
function TokenList () {
|
||||
this.state = {
|
||||
tokens: [],
|
||||
isLoading: true,
|
||||
network: null,
|
||||
}
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TokenList.prototype.render = function () {
|
||||
const state = this.state
|
||||
const { tokens, isLoading, error } = state
|
||||
const { userAddress, network } = this.props
|
||||
|
||||
if (isLoading) {
|
||||
return this.message('Loading')
|
||||
}
|
||||
|
||||
if (error) {
|
||||
log.error(error)
|
||||
return h('.hotFix', {
|
||||
style: {
|
||||
padding: '80px',
|
||||
},
|
||||
}, [
|
||||
'We had trouble loading your token balances. You can view them ',
|
||||
h('span.hotFix', {
|
||||
style: {
|
||||
color: 'rgba(247, 134, 28, 1)',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
onClick: () => {
|
||||
global.platform.openWindow({
|
||||
url: `https://ethplorer.io/address/${userAddress}`,
|
||||
})
|
||||
},
|
||||
}, 'here'),
|
||||
])
|
||||
}
|
||||
|
||||
const tokenViews = tokens.map((tokenData) => {
|
||||
tokenData.network = network
|
||||
tokenData.userAddress = userAddress
|
||||
return h(TokenCell, tokenData)
|
||||
})
|
||||
|
||||
return h('.full-flex-height', [
|
||||
this.renderTokenStatusBar(),
|
||||
|
||||
h('ol.full-flex-height.flex-column', {
|
||||
style: {
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}, [
|
||||
h('style', `
|
||||
|
||||
li.token-cell {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
li.token-cell > h3 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
li.token-cell:hover {
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
`),
|
||||
...tokenViews,
|
||||
h('.flex-grow'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
TokenList.prototype.renderTokenStatusBar = function () {
|
||||
const { tokens } = this.state
|
||||
|
||||
let msg
|
||||
if (tokens.length === 1) {
|
||||
msg = `You own 1 token`
|
||||
} else if (tokens.length > 1) {
|
||||
msg = `You own ${tokens.length} tokens`
|
||||
} else {
|
||||
msg = `No tokens found`
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minHeight: '70px',
|
||||
padding: '10px',
|
||||
},
|
||||
}, [
|
||||
h('span', msg),
|
||||
h('button', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
this.props.addToken()
|
||||
},
|
||||
style: {
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
padding: '10px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
'ADD TOKEN',
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
TokenList.prototype.message = function (body) {
|
||||
return h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
height: '250px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '30px',
|
||||
},
|
||||
}, body)
|
||||
}
|
||||
|
||||
TokenList.prototype.componentDidMount = function () {
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
|
||||
TokenList.prototype.createFreshTokenTracker = function () {
|
||||
if (this.tracker) {
|
||||
// Clean up old trackers when refreshing:
|
||||
this.tracker.stop()
|
||||
this.tracker.removeListener('update', this.balanceUpdater)
|
||||
this.tracker.removeListener('error', this.showError)
|
||||
}
|
||||
|
||||
if (!global.ethereumProvider) return
|
||||
const { userAddress } = this.props
|
||||
this.tracker = new TokenTracker({
|
||||
userAddress,
|
||||
provider: global.ethereumProvider,
|
||||
tokens: this.props.tokens,
|
||||
pollingInterval: 8000,
|
||||
})
|
||||
|
||||
|
||||
// Set up listener instances for cleaning up
|
||||
this.balanceUpdater = this.updateBalances.bind(this)
|
||||
this.showError = (error) => {
|
||||
this.setState({ error, isLoading: false })
|
||||
}
|
||||
this.tracker.on('update', this.balanceUpdater)
|
||||
this.tracker.on('error', this.showError)
|
||||
|
||||
this.tracker.updateBalances()
|
||||
.then(() => {
|
||||
this.updateBalances(this.tracker.serialize())
|
||||
})
|
||||
.catch((reason) => {
|
||||
log.error(`Problem updating balances`, reason)
|
||||
this.setState({ isLoading: false })
|
||||
})
|
||||
}
|
||||
|
||||
TokenList.prototype.componentWillUpdate = function (nextProps) {
|
||||
if (nextProps.network === 'loading') return
|
||||
const oldNet = this.props.network
|
||||
const newNet = nextProps.network
|
||||
|
||||
if (oldNet && newNet && newNet !== oldNet) {
|
||||
this.setState({ isLoading: true })
|
||||
this.createFreshTokenTracker()
|
||||
}
|
||||
}
|
||||
|
||||
TokenList.prototype.updateBalances = function (tokens) {
|
||||
const heldTokens = tokens.filter(token => {
|
||||
return token.balance !== '0' && token.string !== '0.000'
|
||||
})
|
||||
this.setState({ tokens: heldTokens, isLoading: false })
|
||||
}
|
||||
|
||||
TokenList.prototype.componentWillUnmount = function () {
|
||||
if (!this.tracker) return
|
||||
this.tracker.stop()
|
||||
}
|
||||
|
22
old-ui/app/components/tooltip.js
Normal file
22
old-ui/app/components/tooltip.js
Normal file
@ -0,0 +1,22 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ReactTooltip = require('react-tooltip-component')
|
||||
|
||||
module.exports = Tooltip
|
||||
|
||||
inherits(Tooltip, Component)
|
||||
function Tooltip () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
Tooltip.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { position, title, children } = props
|
||||
|
||||
return h(ReactTooltip, {
|
||||
position: position || 'left',
|
||||
title,
|
||||
fixed: true,
|
||||
}, children)
|
||||
}
|
68
old-ui/app/components/transaction-list-item-icon.js
Normal file
68
old-ui/app/components/transaction-list-item-icon.js
Normal file
@ -0,0 +1,68 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const Tooltip = require('./tooltip')
|
||||
|
||||
const Identicon = require('./identicon')
|
||||
|
||||
module.exports = TransactionIcon
|
||||
|
||||
inherits(TransactionIcon, Component)
|
||||
function TransactionIcon () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TransactionIcon.prototype.render = function () {
|
||||
const { transaction, txParams, isMsg } = this.props
|
||||
switch (transaction.status) {
|
||||
case 'unapproved':
|
||||
return h(!isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg')
|
||||
|
||||
case 'rejected':
|
||||
return h('i.fa.fa-exclamation-triangle.fa-lg.warning', {
|
||||
style: {
|
||||
width: '24px',
|
||||
},
|
||||
})
|
||||
|
||||
case 'failed':
|
||||
return h('i.fa.fa-exclamation-triangle.fa-lg.error', {
|
||||
style: {
|
||||
width: '24px',
|
||||
},
|
||||
})
|
||||
|
||||
case 'submitted':
|
||||
return h(Tooltip, {
|
||||
title: 'Pending',
|
||||
position: 'right',
|
||||
}, [
|
||||
h('i.fa.fa-ellipsis-h', {
|
||||
style: {
|
||||
fontSize: '27px',
|
||||
},
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
if (isMsg) {
|
||||
return h('i.fa.fa-certificate.fa-lg', {
|
||||
style: {
|
||||
width: '24px',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (txParams.to) {
|
||||
return h(Identicon, {
|
||||
diameter: 24,
|
||||
address: txParams.to || transaction.hash,
|
||||
})
|
||||
} else {
|
||||
return h('i.fa.fa-file-text-o.fa-lg', {
|
||||
style: {
|
||||
width: '24px',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
175
old-ui/app/components/transaction-list-item.js
Normal file
175
old-ui/app/components/transaction-list-item.js
Normal file
@ -0,0 +1,175 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const EthBalance = require('./eth-balance')
|
||||
const addressSummary = require('../util').addressSummary
|
||||
const explorerLink = require('etherscan-link').createExplorerLink
|
||||
const CopyButton = require('./copyButton')
|
||||
const vreme = new (require('vreme'))()
|
||||
const Tooltip = require('./tooltip')
|
||||
const numberToBN = require('number-to-bn')
|
||||
|
||||
const TransactionIcon = require('./transaction-list-item-icon')
|
||||
const ShiftListItem = require('./shift-list-item')
|
||||
module.exports = TransactionListItem
|
||||
|
||||
inherits(TransactionListItem, Component)
|
||||
function TransactionListItem () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TransactionListItem.prototype.render = function () {
|
||||
const { transaction, network, conversionRate, currentCurrency } = this.props
|
||||
if (transaction.key === 'shapeshift') {
|
||||
if (network === '1') return h(ShiftListItem, transaction)
|
||||
}
|
||||
var date = formatDate(transaction.time)
|
||||
|
||||
let isLinkable = false
|
||||
const numericNet = parseInt(network)
|
||||
isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 4 || numericNet === 42
|
||||
|
||||
var isMsg = ('msgParams' in transaction)
|
||||
var isTx = ('txParams' in transaction)
|
||||
var isPending = transaction.status === 'unapproved'
|
||||
let txParams
|
||||
if (isTx) {
|
||||
txParams = transaction.txParams
|
||||
} else if (isMsg) {
|
||||
txParams = transaction.msgParams
|
||||
}
|
||||
|
||||
const nonce = txParams.nonce ? numberToBN(txParams.nonce).toString(10) : ''
|
||||
|
||||
const isClickable = ('hash' in transaction && isLinkable) || isPending
|
||||
return (
|
||||
h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, {
|
||||
onClick: (event) => {
|
||||
if (isPending) {
|
||||
this.props.showTx(transaction.id)
|
||||
}
|
||||
event.stopPropagation()
|
||||
if (!transaction.hash || !isLinkable) return
|
||||
var url = explorerLink(transaction.hash, parseInt(network))
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
style: {
|
||||
padding: '20px 0',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
||||
h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
|
||||
]),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'Transaction Number',
|
||||
position: 'right',
|
||||
}, [
|
||||
h('span', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
cursor: 'normal',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, nonce),
|
||||
]),
|
||||
|
||||
h('.flex-column', {style: {width: '150px', overflow: 'hidden'}}, [
|
||||
domainField(txParams),
|
||||
h('div', date),
|
||||
recipientField(txParams, transaction, isTx, isMsg),
|
||||
]),
|
||||
|
||||
// Places a copy button if tx is successful, else places a placeholder empty div.
|
||||
transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}),
|
||||
|
||||
isTx ? h(EthBalance, {
|
||||
value: txParams.value,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
shorten: true,
|
||||
showFiat: false,
|
||||
style: {fontSize: '15px'},
|
||||
}) : h('.flex-column'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
function domainField (txParams) {
|
||||
return h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
txParams.origin,
|
||||
])
|
||||
}
|
||||
|
||||
function recipientField (txParams, transaction, isTx, isMsg) {
|
||||
let message
|
||||
|
||||
if (isMsg) {
|
||||
message = 'Signature Requested'
|
||||
} else if (txParams.to) {
|
||||
message = addressSummary(txParams.to)
|
||||
} else {
|
||||
message = 'Contract Published'
|
||||
}
|
||||
|
||||
return h('div', {
|
||||
style: {
|
||||
fontSize: 'x-small',
|
||||
color: '#ABA9AA',
|
||||
},
|
||||
}, [
|
||||
message,
|
||||
renderErrorOrWarning(transaction),
|
||||
])
|
||||
}
|
||||
|
||||
function formatDate (date) {
|
||||
return vreme.format(new Date(date), 'March 16 2014 14:30')
|
||||
}
|
||||
|
||||
function renderErrorOrWarning (transaction) {
|
||||
const { status, err, warning } = transaction
|
||||
|
||||
// show rejected
|
||||
if (status === 'rejected') {
|
||||
return h('span.error', ' (Rejected)')
|
||||
}
|
||||
|
||||
// show error
|
||||
if (err) {
|
||||
const message = err.message || ''
|
||||
return (
|
||||
h(Tooltip, {
|
||||
title: message,
|
||||
position: 'bottom',
|
||||
}, [
|
||||
h(`span.error`, ` (Failed)`),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
// show warning
|
||||
if (warning) {
|
||||
const message = warning.message
|
||||
return h(Tooltip, {
|
||||
title: message,
|
||||
position: 'bottom',
|
||||
}, [
|
||||
h(`span.warning`, ` (Warning)`),
|
||||
])
|
||||
}
|
||||
}
|
87
old-ui/app/components/transaction-list.js
Normal file
87
old-ui/app/components/transaction-list.js
Normal file
@ -0,0 +1,87 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
const TransactionListItem = require('./transaction-list-item')
|
||||
|
||||
module.exports = TransactionList
|
||||
|
||||
|
||||
inherits(TransactionList, Component)
|
||||
function TransactionList () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TransactionList.prototype.render = function () {
|
||||
const { transactions, network, unapprovedMsgs, conversionRate } = this.props
|
||||
|
||||
var shapeShiftTxList
|
||||
if (network === '1') {
|
||||
shapeShiftTxList = this.props.shapeShiftTxList
|
||||
}
|
||||
const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
|
||||
.sort((a, b) => b.time - a.time)
|
||||
|
||||
return (
|
||||
|
||||
h('section.transaction-list.full-flex-height', {
|
||||
style: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('style', `
|
||||
.transaction-list .transaction-list-item:not(:last-of-type) {
|
||||
border-bottom: 1px solid #D4D4D4;
|
||||
}
|
||||
.transaction-list .transaction-list-item .ether-balance-label {
|
||||
display: block !important;
|
||||
font-size: small;
|
||||
}
|
||||
`),
|
||||
|
||||
h('.tx-list', {
|
||||
style: {
|
||||
overflowY: 'auto',
|
||||
height: '100%',
|
||||
padding: '0 25px 0 15px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
txsToRender.length
|
||||
? txsToRender.map((transaction, i) => {
|
||||
let key
|
||||
switch (transaction.key) {
|
||||
case 'shapeshift':
|
||||
const { depositAddress, time } = transaction
|
||||
key = `shift-tx-${depositAddress}-${time}-${i}`
|
||||
break
|
||||
default:
|
||||
key = `tx-${transaction.id}-${i}`
|
||||
}
|
||||
return h(TransactionListItem, {
|
||||
transaction, i, network, key,
|
||||
conversionRate,
|
||||
showTx: (txId) => {
|
||||
this.props.viewPendingTx(txId)
|
||||
},
|
||||
})
|
||||
})
|
||||
: h('.flex-center.full-flex-height', {
|
||||
style: {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
h('p', {
|
||||
style: {
|
||||
marginTop: '50px',
|
||||
},
|
||||
}, 'No transaction history.'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
42
old-ui/app/components/typed-message-renderer.js
Normal file
42
old-ui/app/components/typed-message-renderer.js
Normal file
@ -0,0 +1,42 @@
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = TypedMessageRenderer
|
||||
|
||||
inherits(TypedMessageRenderer, Component)
|
||||
function TypedMessageRenderer () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
TypedMessageRenderer.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { value, style } = props
|
||||
const text = renderTypedData(value)
|
||||
|
||||
const defaultStyle = extend({
|
||||
width: '315px',
|
||||
maxHeight: '210px',
|
||||
resize: 'none',
|
||||
border: 'none',
|
||||
background: 'white',
|
||||
padding: '3px',
|
||||
overflow: 'scroll',
|
||||
}, style)
|
||||
|
||||
return (
|
||||
h('div.font-small', {
|
||||
style: defaultStyle,
|
||||
}, text)
|
||||
)
|
||||
}
|
||||
|
||||
function renderTypedData (values) {
|
||||
return values.map(function (value) {
|
||||
return h('div', {}, [
|
||||
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'),
|
||||
h('div', {}, value.value),
|
||||
])
|
||||
})
|
||||
}
|
235
old-ui/app/conf-tx.js
Normal file
235
old-ui/app/conf-tx.js
Normal file
@ -0,0 +1,235 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const txHelper = require('../lib/tx-helper')
|
||||
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
|
||||
|
||||
const PendingTx = require('./components/pending-tx')
|
||||
const PendingMsg = require('./components/pending-msg')
|
||||
const PendingPersonalMsg = require('./components/pending-personal-msg')
|
||||
const PendingTypedMsg = require('./components/pending-typed-msg')
|
||||
const Loading = require('./components/loading')
|
||||
|
||||
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
identities: state.metamask.identities,
|
||||
accounts: state.metamask.accounts,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
unapprovedMsgs: state.metamask.unapprovedMsgs,
|
||||
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
|
||||
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
|
||||
index: state.appState.currentView.context,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
provider: state.metamask.provider,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
blockGasLimit: state.metamask.currentBlockGasLimit,
|
||||
computedBalances: state.metamask.computedBalances,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ConfirmTxScreen, Component)
|
||||
function ConfirmTxScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { network, provider, unapprovedTxs, currentCurrency, computedBalances,
|
||||
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, conversionRate, blockGasLimit } = props
|
||||
|
||||
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
|
||||
|
||||
var txData = unconfTxList[props.index] || {}
|
||||
var txParams = txData.params || {}
|
||||
var isNotification = isPopupOrNotification() === 'notification'
|
||||
|
||||
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
|
||||
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
|
||||
|
||||
const unconfTxListLength = unconfTxList.length
|
||||
|
||||
return (
|
||||
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
!isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: this.goHome.bind(this),
|
||||
}) : null,
|
||||
h('h2.page-subtitle', 'Confirm Transaction'),
|
||||
isNotification ? h(NetworkIndicator, {
|
||||
network: network,
|
||||
provider: provider,
|
||||
}) : null,
|
||||
]),
|
||||
|
||||
h('h3', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
display: unconfTxList.length > 1 ? 'block' : 'none',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
style: {
|
||||
display: props.index === 0 ? 'none' : 'inline-block',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.previousTx()),
|
||||
}),
|
||||
` ${props.index + 1} of ${unconfTxList.length} `,
|
||||
h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', {
|
||||
style: {
|
||||
display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.nextTx()),
|
||||
}),
|
||||
]),
|
||||
|
||||
warningIfExists(props.warning),
|
||||
|
||||
currentTxView({
|
||||
// Properties
|
||||
txData: txData,
|
||||
key: txData.id,
|
||||
selectedAddress: props.selectedAddress,
|
||||
accounts: props.accounts,
|
||||
identities: props.identities,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
blockGasLimit,
|
||||
unconfTxListLength,
|
||||
computedBalances,
|
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||
sendTransaction: this.sendTransaction.bind(this),
|
||||
cancelTransaction: this.cancelTransaction.bind(this, txData),
|
||||
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
|
||||
signMessage: this.signMessage.bind(this, txData),
|
||||
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
|
||||
signTypedMessage: this.signTypedMessage.bind(this, txData),
|
||||
cancelMessage: this.cancelMessage.bind(this, txData),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
|
||||
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
|
||||
}),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
function currentTxView (opts) {
|
||||
log.info('rendering current tx view')
|
||||
const { txData } = opts
|
||||
const { txParams, msgParams, type } = txData
|
||||
|
||||
if (txParams) {
|
||||
log.debug('txParams detected, rendering pending tx')
|
||||
return h(PendingTx, opts)
|
||||
} else if (msgParams) {
|
||||
log.debug('msgParams detected, rendering pending msg')
|
||||
|
||||
if (type === 'eth_sign') {
|
||||
log.debug('rendering eth_sign message')
|
||||
return h(PendingMsg, opts)
|
||||
} else if (type === 'personal_sign') {
|
||||
log.debug('rendering personal_sign message')
|
||||
return h(PendingPersonalMsg, opts)
|
||||
} else if (type === 'eth_signTypedData') {
|
||||
log.debug('rendering eth_signTypedData message')
|
||||
return h(PendingTypedMsg, opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.buyEth = function (address, event) {
|
||||
event.preventDefault()
|
||||
this.props.dispatch(actions.buyEthView(address))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.updateAndApproveTx(txData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
|
||||
this.stopPropagation(event)
|
||||
event.preventDefault()
|
||||
this.props.dispatch(actions.cancelTx(txData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) {
|
||||
this.stopPropagation(event)
|
||||
event.preventDefault()
|
||||
this.props.dispatch(actions.cancelAllTx(unconfTxList))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
|
||||
log.info('conf-tx.js: signing message')
|
||||
var params = msgData.msgParams
|
||||
params.metamaskId = msgData.id
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.signMsg(params))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.stopPropagation = function (event) {
|
||||
if (event.stopPropagation) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) {
|
||||
log.info('conf-tx.js: signing personal message')
|
||||
var params = msgData.msgParams
|
||||
params.metamaskId = msgData.id
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.signPersonalMsg(params))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
|
||||
log.info('conf-tx.js: signing typed message')
|
||||
var params = msgData.msgParams
|
||||
params.metamaskId = msgData.id
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.signTypedMsg(params))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) {
|
||||
log.info('canceling message')
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.cancelMsg(msgData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) {
|
||||
log.info('canceling personal message')
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.cancelPersonalMsg(msgData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
|
||||
log.info('canceling typed message')
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.cancelTypedMsg(msgData))
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.goHome = function (event) {
|
||||
this.stopPropagation(event)
|
||||
this.props.dispatch(actions.goHome())
|
||||
}
|
||||
|
||||
function warningIfExists (warning) {
|
||||
if (warning &&
|
||||
// Do not display user rejections on this screen:
|
||||
warning.indexOf('User denied transaction signature') === -1) {
|
||||
return h('.error', {
|
||||
style: {
|
||||
margin: 'auto',
|
||||
},
|
||||
}, warning)
|
||||
}
|
||||
}
|
222
old-ui/app/config.js
Normal file
222
old-ui/app/config.js
Normal file
@ -0,0 +1,222 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
const infuraCurrencies = require('./infura-conversion.json').objects.sort((a, b) => {
|
||||
return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
|
||||
})
|
||||
const validUrl = require('valid-url')
|
||||
const exportAsFile = require('./util').exportAsFile
|
||||
const Modal = require('../../ui/app/components/modals/index').Modal
|
||||
|
||||
module.exports = connect(mapStateToProps)(ConfigScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
metamask: state.metamask,
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ConfigScreen, Component)
|
||||
function ConfigScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
ConfigScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
var metamaskState = state.metamask
|
||||
var warning = state.warning
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', [
|
||||
|
||||
h(Modal, {}, []),
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
state.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Settings'),
|
||||
]),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
padding: '0 20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
// conf view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-space-around', {
|
||||
style: {
|
||||
padding: '20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
currentProviderDisplay(metamaskState),
|
||||
|
||||
h('div', { style: {display: 'flex'} }, [
|
||||
h('input#new_rpc', {
|
||||
placeholder: 'New RPC URL',
|
||||
style: {
|
||||
width: 'inherit',
|
||||
flex: '1 0 auto',
|
||||
height: '30px',
|
||||
margin: '8px',
|
||||
},
|
||||
onKeyPress (event) {
|
||||
if (event.key === 'Enter') {
|
||||
var element = event.target
|
||||
var newRpc = element.value
|
||||
rpcValidation(newRpc, state)
|
||||
}
|
||||
},
|
||||
}),
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick (event) {
|
||||
event.preventDefault()
|
||||
var element = document.querySelector('input#new_rpc')
|
||||
var newRpc = element.value
|
||||
rpcValidation(newRpc, state)
|
||||
},
|
||||
}, 'Save'),
|
||||
]),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
currentConversionInformation(metamaskState, state),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '20px',
|
||||
},
|
||||
}, [
|
||||
h('p', {
|
||||
style: {
|
||||
fontFamily: 'Montserrat Light',
|
||||
fontSize: '13px',
|
||||
},
|
||||
}, `State logs contain your public account addresses and sent transactions.`),
|
||||
h('br'),
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick (event) {
|
||||
window.logStateString((err, result) => {
|
||||
if (err) {
|
||||
state.dispatch(actions.displayWarning('Error in retrieving state logs.'))
|
||||
} else {
|
||||
exportAsFile('MetaMask State Logs.json', result)
|
||||
}
|
||||
})
|
||||
},
|
||||
}, 'Download State Logs'),
|
||||
]),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '20px',
|
||||
},
|
||||
}, [
|
||||
h('button', {
|
||||
style: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
onClick (event) {
|
||||
event.preventDefault()
|
||||
state.dispatch(actions.revealSeedConfirmation())
|
||||
},
|
||||
}, 'Reveal Seed Words'),
|
||||
]),
|
||||
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
function rpcValidation (newRpc, state) {
|
||||
if (validUrl.isWebUri(newRpc)) {
|
||||
state.dispatch(actions.setRpcTarget(newRpc))
|
||||
} else {
|
||||
var appendedRpc = `http://${newRpc}`
|
||||
if (validUrl.isWebUri(appendedRpc)) {
|
||||
state.dispatch(actions.displayWarning('URIs require the appropriate HTTP/HTTPS prefix.'))
|
||||
} else {
|
||||
state.dispatch(actions.displayWarning('Invalid RPC URI'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function currentConversionInformation (metamaskState, state) {
|
||||
var currentCurrency = metamaskState.currentCurrency
|
||||
var conversionDate = metamaskState.conversionDate
|
||||
return h('div', [
|
||||
h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, 'Current Conversion'),
|
||||
h('span', {style: { fontWeight: 'bold', paddingRight: '10px', fontSize: '13px'}}, `Updated ${Date(conversionDate)}`),
|
||||
h('select#currentCurrency', {
|
||||
onChange (event) {
|
||||
event.preventDefault()
|
||||
var element = document.getElementById('currentCurrency')
|
||||
var newCurrency = element.value
|
||||
state.dispatch(actions.setCurrentCurrency(newCurrency))
|
||||
},
|
||||
defaultValue: currentCurrency,
|
||||
}, infuraCurrencies.map((currency) => {
|
||||
return h('option', {key: currency.quote.code, value: currency.quote.code}, `${currency.quote.code.toUpperCase()} - ${currency.quote.name}`)
|
||||
})
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
function currentProviderDisplay (metamaskState) {
|
||||
var provider = metamaskState.provider
|
||||
var title, value
|
||||
|
||||
switch (provider.type) {
|
||||
|
||||
case 'mainnet':
|
||||
title = 'Current Network'
|
||||
value = 'Main Ethereum Network'
|
||||
break
|
||||
|
||||
case 'ropsten':
|
||||
title = 'Current Network'
|
||||
value = 'Ropsten Test Network'
|
||||
break
|
||||
|
||||
case 'kovan':
|
||||
title = 'Current Network'
|
||||
value = 'Kovan Test Network'
|
||||
break
|
||||
|
||||
case 'rinkeby':
|
||||
title = 'Current Network'
|
||||
value = 'Rinkeby Test Network'
|
||||
break
|
||||
|
||||
default:
|
||||
title = 'Current RPC'
|
||||
value = metamaskState.provider.rpcTarget
|
||||
}
|
||||
|
||||
return h('div', [
|
||||
h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, title),
|
||||
h('span', value),
|
||||
])
|
||||
}
|
21
old-ui/app/css/debug.css
Normal file
21
old-ui/app/css/debug.css
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
debug / dev
|
||||
*/
|
||||
|
||||
#app-content {
|
||||
border: 2px solid green;
|
||||
}
|
||||
|
||||
#design-container {
|
||||
position: absolute;
|
||||
left: 360px;
|
||||
top: -42px;
|
||||
width: calc(100vw - 360px);
|
||||
height: 100vh;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#design-container img {
|
||||
width: 2000px;
|
||||
margin-right: 600px;
|
||||
}
|
36
old-ui/app/css/fonts.css
Normal file
36
old-ui/app/css/fonts.css
Normal file
@ -0,0 +1,36 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:300,500);
|
||||
@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css);
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat Regular';
|
||||
src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff');
|
||||
src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 'small';
|
||||
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat Bold';
|
||||
src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff');
|
||||
src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat Light';
|
||||
src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff');
|
||||
src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat UltraLight';
|
||||
src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff');
|
||||
src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
761
old-ui/app/css/index.css
Normal file
761
old-ui/app/css/index.css
Normal file
@ -0,0 +1,761 @@
|
||||
/*
|
||||
faint orange (textfield shades) #FAF6F0
|
||||
light orange (button shades): #F5C26D
|
||||
dark orange (text): #F5A623
|
||||
borders/font/any gray: #4A4A4A
|
||||
*/
|
||||
|
||||
/*
|
||||
application specific styles
|
||||
*/
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: 'Montserrat Regular', Arial;
|
||||
color: #4D4D4D;
|
||||
font-weight: 300;
|
||||
line-height: 1.4em;
|
||||
background: #F7F7F7;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.app-root {
|
||||
overflow: hidden;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.app-primary {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.full-size {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.full-flex-height {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button, input[type="submit"] {
|
||||
font-family: 'Montserrat Bold';
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
color: white;
|
||||
transform-origin: center center;
|
||||
transition: transform 50ms ease-in;
|
||||
/* default orange */
|
||||
background: rgba(247, 134, 28, 1);
|
||||
box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36);
|
||||
}
|
||||
|
||||
.btn-green, input[type="submit"].btn-green {
|
||||
background: rgba(106, 195, 96, 1);
|
||||
box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36);
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
background: rgba(254, 35, 17, 1);
|
||||
box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36);
|
||||
}
|
||||
|
||||
button[disabled], input[type="submit"][disabled] {
|
||||
cursor: not-allowed;
|
||||
background: rgba(197, 197, 197, 1);
|
||||
box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36);
|
||||
}
|
||||
|
||||
button.spaced {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.grow-on-hover:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a:hover{
|
||||
color: #df6b0e;
|
||||
}
|
||||
|
||||
/*
|
||||
app
|
||||
*/
|
||||
|
||||
.active {
|
||||
color: #909090;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
padding: 8px 12px;
|
||||
background: #F7861C;
|
||||
box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36);
|
||||
color: white;
|
||||
font-size: 1.1em;
|
||||
font-family: 'Montserrat Regular';
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
button.btn-thin {
|
||||
border: 1px solid;
|
||||
border-color: #4D4D4D;
|
||||
color: #4D4D4D;
|
||||
background: rgb(255, 174, 41);
|
||||
border-radius: 4px;
|
||||
min-width: 200px;
|
||||
margin: 12px 0;
|
||||
padding: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.app-header h1 {
|
||||
font-family: 'Montserrat Regular';
|
||||
text-transform: uppercase;
|
||||
color: #AEAEAE;
|
||||
}
|
||||
|
||||
h2.page-subtitle {
|
||||
font-family: 'Montserrat Regular';
|
||||
text-transform: uppercase;
|
||||
color: #AEAEAE;
|
||||
font-size: 1em;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
padding-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.identicon {
|
||||
height: 46px;
|
||||
width: 46px;
|
||||
background-size: cover;
|
||||
border-radius: 100%;
|
||||
border: 3px solid gray;
|
||||
}
|
||||
|
||||
textarea.twelve-word-phrase {
|
||||
padding: 12px;
|
||||
width: 300px;
|
||||
height: 140px;
|
||||
font-size: 16px;
|
||||
background: white;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.network-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.6em;
|
||||
|
||||
}
|
||||
|
||||
.network-name {
|
||||
width: 5.2em;
|
||||
line-height: 9px;
|
||||
text-rendering: geometricPrecision;
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-left: 12px;
|
||||
color: #F7861C;
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
/*
|
||||
app sections
|
||||
*/
|
||||
|
||||
/* initialize */
|
||||
|
||||
.initialize-screen hr {
|
||||
width: 60px;
|
||||
margin: 12px;
|
||||
border-color: #F7861C;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.initialize-screen label {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.initialize-screen button.create-vault {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.initialize-screen .warning {
|
||||
font-size: 14px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
/* unlock */
|
||||
.error {
|
||||
color: #f7861c;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #FFAE00;
|
||||
}
|
||||
|
||||
.lock {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.lock.locked {
|
||||
transform: scale(1.5);
|
||||
opacity: 0.0;
|
||||
transition: opacity 400ms ease-in, transform 400ms ease-in;
|
||||
}
|
||||
.lock.unlocked {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
transition: opacity 500ms ease-out, transform 500ms ease-out, background 200ms ease-in;
|
||||
}
|
||||
|
||||
.lock.locked .lock-top {
|
||||
transform: scaleX(1) translateX(0);
|
||||
transition: transform 250ms ease-in;
|
||||
}
|
||||
.lock.unlocked .lock-top {
|
||||
transform: scaleX(-1) translateX(-12px);
|
||||
transition: transform 250ms ease-in;
|
||||
}
|
||||
.lock.unlocked:hover {
|
||||
border-radius: 4px;
|
||||
background: #e5e5e5;
|
||||
border: 1px solid #b1b1b1;
|
||||
}
|
||||
.lock.unlocked:active {
|
||||
background: #c3c3c3;
|
||||
}
|
||||
|
||||
.section-title .fa-arrow-left {
|
||||
margin: -2px 8px 0px -8px;
|
||||
}
|
||||
|
||||
.unlock-screen #metamask-mascot-container {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.unlock-screen h1 {
|
||||
margin-top: -28px;
|
||||
margin-bottom: 42px;
|
||||
}
|
||||
|
||||
.unlock-screen input[type=password] {
|
||||
width: 260px;
|
||||
/*height: 36px;
|
||||
margin-bottom: 24px;
|
||||
padding: 8px;*/
|
||||
}
|
||||
|
||||
.sizing-input{
|
||||
font-size: 14px;
|
||||
height: 30px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.editable-label{
|
||||
display: flex;
|
||||
}
|
||||
/* Webkit */
|
||||
.unlock-screen input::-webkit-input-placeholder {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
/* Firefox 18- */
|
||||
.unlock-screen input:-moz-placeholder {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
/* Firefox 19+ */
|
||||
.unlock-screen input::-moz-placeholder {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
/* IE */
|
||||
.unlock-screen input:-ms-input-placeholder {
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
input.large-input, textarea.large-input {
|
||||
/*margin-bottom: 24px;*/
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
input.large-input {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.letter-spacey {
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* accounts */
|
||||
|
||||
.accounts-section {
|
||||
margin: 0 0px;
|
||||
}
|
||||
|
||||
.accounts-section .horizontal-line {
|
||||
margin: 0px 18px;
|
||||
}
|
||||
|
||||
.accounts-list-option {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.accounts-list-option .identicon-wrapper {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.unconftx-link {
|
||||
margin-top: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.unconftx-link .fa-arrow-right {
|
||||
margin: 0px -8px 0px 8px;
|
||||
}
|
||||
|
||||
/* identity panel */
|
||||
|
||||
.identity-panel {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.identity-panel .identicon-wrapper {
|
||||
margin: 4px;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.identity-panel .identicon-wrapper span {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.identity-panel .identity-data {
|
||||
margin: 8px 8px 8px 18px;
|
||||
}
|
||||
|
||||
.identity-panel i {
|
||||
margin-top: 32px;
|
||||
margin-right: 6px;
|
||||
color: #B9B9B9;
|
||||
}
|
||||
|
||||
.identity-panel .arrow-right {
|
||||
padding-left: 18px;
|
||||
width: 42px;
|
||||
min-width: 18px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.identity-copy.flex-column {
|
||||
flex: 0.25 0 auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* accounts screen */
|
||||
|
||||
.identity-section {
|
||||
|
||||
}
|
||||
|
||||
.identity-section .identity-panel {
|
||||
background: #E9E9E9;
|
||||
border-bottom: 1px solid #B1B1B1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.identity-section .identity-panel.selected {
|
||||
background: white;
|
||||
color: #F3C83E;
|
||||
}
|
||||
|
||||
.identity-section .identity-panel.selected .identicon {
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
.identity-section .accounts-list-option:hover,
|
||||
.identity-section .accounts-list-option.selected {
|
||||
background:white;
|
||||
}
|
||||
|
||||
/* account detail screen */
|
||||
|
||||
.account-detail-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: auto;
|
||||
flex-direction: inherit;
|
||||
|
||||
.name-label {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.grow-tenx {
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
.name-label{
|
||||
|
||||
}
|
||||
|
||||
.unapproved-tx-icon {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background: rgb(47, 174, 244);
|
||||
border-color: #AEAEAE;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.edit-text {
|
||||
height: 100%;
|
||||
visibility: hidden;
|
||||
}
|
||||
.editing-label {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-left: 50px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 11px;
|
||||
text-rendering: geometricPrecision;
|
||||
color: #F7861C;
|
||||
}
|
||||
.name-label:hover .edit-text {
|
||||
visibility: visible;
|
||||
}
|
||||
/* tx confirm */
|
||||
|
||||
.unconftx-section input[type=password] {
|
||||
height: 22px;
|
||||
padding: 2px;
|
||||
margin: 12px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #F3C83E;
|
||||
background: #FAF6F0;
|
||||
}
|
||||
|
||||
/* Send Screen */
|
||||
|
||||
.send-screen {
|
||||
|
||||
}
|
||||
|
||||
.send-screen section {
|
||||
margin: 8px 16px;
|
||||
}
|
||||
|
||||
.send-screen input {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Ether Balance Widget */
|
||||
|
||||
.ether-balance-amount {
|
||||
color: #F7861C;
|
||||
}
|
||||
|
||||
.ether-balance-label {
|
||||
color: #ABA9AA;
|
||||
}
|
||||
|
||||
/* Info screen */
|
||||
.info-gray{
|
||||
font-family: 'Montserrat Regular';
|
||||
text-transform: uppercase;
|
||||
color: #AEAEAE;
|
||||
}
|
||||
|
||||
.icon-size{
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.info{
|
||||
font-family: 'Montserrat Regular', Arial;
|
||||
padding-bottom: 10px;
|
||||
display: inline-block;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* buy eth warning screen */
|
||||
.custom-radios {
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.custom-radio-selected {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border: solid;
|
||||
border-style: double;
|
||||
border-radius: 15px;
|
||||
border-width: 5px;
|
||||
background: rgba(247, 134, 28, 1);
|
||||
border-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.custom-radio-inactive {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 24px;
|
||||
border-color: #AEAEAE;
|
||||
}
|
||||
|
||||
.radio-titles {
|
||||
color: rgba(247, 134, 28, 1);
|
||||
}
|
||||
|
||||
.radio-titles-subtext {
|
||||
|
||||
}
|
||||
|
||||
.selected-exchange {
|
||||
|
||||
}
|
||||
|
||||
.buy-radio {
|
||||
|
||||
}
|
||||
|
||||
.eth-warning{
|
||||
transition: opacity 400ms ease-in, transform 400ms ease-in;
|
||||
}
|
||||
|
||||
.buy-subview{
|
||||
transition: opacity 400ms ease-in, transform 400ms ease-in;
|
||||
}
|
||||
|
||||
.input-container:hover .edit-text{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.buy-inputs{
|
||||
font-family: 'Montserrat Light';
|
||||
font-size: 13px;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
border: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0.5px;
|
||||
border-radius: 2px;
|
||||
|
||||
}
|
||||
.input-container:hover .buy-inputs{
|
||||
box-sizing: inherit;
|
||||
border: solid;
|
||||
border-color: #F7861C;
|
||||
border-width: 0.5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.buy-inputs:focus{
|
||||
border: solid;
|
||||
border-color: #F7861C;
|
||||
border-width: 0.5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.activeForm {
|
||||
background: #F7F7F7;
|
||||
border: none;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
padding-bottom: 4px;
|
||||
|
||||
}
|
||||
|
||||
.inactiveForm {
|
||||
border: none;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.ex-coins {
|
||||
font-family: 'Montserrat Regular';
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 33px;
|
||||
width: 118px;
|
||||
height: 42px;
|
||||
padding: 1px;
|
||||
color: #4D4D4D;
|
||||
}
|
||||
|
||||
.marketinfo{
|
||||
font-family: 'Montserrat light';
|
||||
color: #AEAEAE;
|
||||
font-size: 15px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
#fromCoin::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#coinList {
|
||||
width: 400px;
|
||||
height: 500px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.icon-control .fa-refresh{
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.icon-control:hover .fa-refresh{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.icon-control:hover .fa-chevron-right{
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: #AEAEAE;
|
||||
}
|
||||
|
||||
.inactive button{
|
||||
background: #AEAEAE;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ellip-address {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 5em;
|
||||
font-size: 14px;
|
||||
font-family: "Montserrat Light";
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.qr-header {
|
||||
font-size: 25px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.qr-message {
|
||||
font-size: 12px;
|
||||
color: #F7861C;
|
||||
}
|
||||
|
||||
div.message-container > div:first-child {
|
||||
margin-top: 18px;
|
||||
font-size: 15px;
|
||||
color: #4D4D4D;
|
||||
}
|
||||
|
||||
.pop-hover:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
//Notification Modal
|
||||
|
||||
.notification-modal-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: 1px solid #dedede;
|
||||
box-shadow: 0 0 2px 2px #dedede;
|
||||
font-family: Roboto;
|
||||
}
|
||||
|
||||
.notification-modal-header {
|
||||
background: #f6f6f6;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 30px;
|
||||
font-size: 22px;
|
||||
color: #1b344d;
|
||||
height: 79px;
|
||||
}
|
||||
|
||||
.notification-modal-message {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.notification-modal-message {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 17px;
|
||||
color: #1b344d;
|
||||
}
|
||||
|
||||
.modal-close-x::after {
|
||||
content: '\00D7';
|
||||
font-size: 2em;
|
||||
color: #9b9b9b;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 17.5px;
|
||||
font-family: sans-serif;
|
||||
cursor: pointer;
|
||||
}
|
306
old-ui/app/css/lib.css
Normal file
306
old-ui/app/css/lib.css
Normal file
@ -0,0 +1,306 @@
|
||||
/* color */
|
||||
|
||||
.color-orange {
|
||||
color: #F7861C;
|
||||
}
|
||||
|
||||
.color-forest {
|
||||
color: #0A5448;
|
||||
}
|
||||
|
||||
/* lib */
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.flex-column-bottom {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.flex-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.flex-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.flex-fixed {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.flex-basis-auto {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-self-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.flex-self-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.flex-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.z-bump {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.select-none {
|
||||
cursor: inherit;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
transform-origin: center center;
|
||||
transition: transform 50ms ease-in-out;
|
||||
}
|
||||
.cursor-pointer:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.cursor-pointer:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.cursor-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.margin-bottom-sml {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-bottom-med {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.margin-right-left {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-transform-uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.font-small {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
hr.horizontal-line {
|
||||
display: block;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hover-white:hover {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.red-dot {
|
||||
background: #E91550;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.diamond {
|
||||
transform: rotate(45deg);
|
||||
background: #038789;
|
||||
}
|
||||
|
||||
.hollow-diamond {
|
||||
transform: rotate(45deg);
|
||||
border: 3px solid #690496;
|
||||
}
|
||||
|
||||
.golden-square {
|
||||
background: #EBB33F;
|
||||
}
|
||||
|
||||
.pending-dot {
|
||||
background: red;
|
||||
left: 14px;
|
||||
top: 14px;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.keyring-label {
|
||||
z-index: 1;
|
||||
font-size: 11px;
|
||||
background: rgba(255,0,0,0.8);
|
||||
color: white;
|
||||
bottom: 0px;
|
||||
left: -8px;
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ether-balance {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tabSection {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
min-width: 12px;
|
||||
margin: 13px;
|
||||
}
|
||||
|
||||
i.fa.fa-question-circle.fa-lg.menu-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ether-icon {
|
||||
background: rgb(0, 163, 68);
|
||||
border-radius: 20px;
|
||||
}
|
||||
.testnet-icon {
|
||||
background: #2465E1;
|
||||
}
|
||||
|
||||
.drop-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.one-line-concat {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.critical-error {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/*
|
||||
Hacky breakpoint fix for account + tab sections
|
||||
Resolves issue from @frankiebee in
|
||||
https://github.com/MetaMask/metamask-extension/pull/1835
|
||||
Please remove this when integrating new designs
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 575px) and (max-width: 800px) {
|
||||
.account-data-subsection {
|
||||
flex: 0 0 auto !important; // reset flex
|
||||
margin-left: 10px !important; // create additional horizontal space
|
||||
margin-right: 10px !important;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.tabSection {
|
||||
flex: 0 0 auto !important;
|
||||
margin-left: 10px !important;
|
||||
margin-right: 10px !important;
|
||||
min-width: 285px;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.name-label {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
5385
old-ui/app/css/output/index.css
Normal file
5385
old-ui/app/css/output/index.css
Normal file
File diff suppressed because one or more lines are too long
48
old-ui/app/css/reset.css
Normal file
48
old-ui/app/css/reset.css
Normal file
@ -0,0 +1,48 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
42
old-ui/app/css/transitions.css
Normal file
42
old-ui/app/css/transitions.css
Normal file
@ -0,0 +1,42 @@
|
||||
/* universal */
|
||||
.app-primary .main-enter {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* center position */
|
||||
.app-primary.from-right .main-enter-active,
|
||||
.app-primary.from-left .main-enter-active {
|
||||
overflow-x: hidden;
|
||||
transform: translateX(0px);
|
||||
transition: transform 300ms ease-in;
|
||||
}
|
||||
|
||||
/* exited positions */
|
||||
.app-primary.from-left .main-leave-active {
|
||||
transform: translateX(360px);
|
||||
transition: transform 300ms ease-in;
|
||||
}
|
||||
.app-primary.from-right .main-leave-active {
|
||||
transform: translateX(-360px);
|
||||
transition: transform 300ms ease-in;
|
||||
}
|
||||
|
||||
/* loader transitions */
|
||||
.loader-enter, .loader-leave-active {
|
||||
opacity: 0.0;
|
||||
transition: opacity 150 ease-in;
|
||||
}
|
||||
.loader-enter-active, .loader-leave {
|
||||
opacity: 1.0;
|
||||
transition: opacity 150 ease-in;
|
||||
}
|
||||
|
||||
/* entering positions */
|
||||
.app-primary.from-right .main-enter:not(.main-enter-active) {
|
||||
transform: translateX(360px);
|
||||
}
|
||||
.app-primary.from-left .main-enter:not(.main-enter-active) {
|
||||
transform: translateX(-360px);
|
||||
}
|
||||
|
179
old-ui/app/first-time/init-menu.js
Normal file
179
old-ui/app/first-time/init-menu.js
Normal file
@ -0,0 +1,179 @@
|
||||
const inherits = require('util').inherits
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const Mascot = require('../components/mascot')
|
||||
const actions = require('../../../ui/app/actions')
|
||||
const Tooltip = require('../components/tooltip')
|
||||
const getCaretCoordinates = require('textarea-caret')
|
||||
|
||||
module.exports = connect(mapStateToProps)(InitializeMenuScreen)
|
||||
|
||||
inherits(InitializeMenuScreen, Component)
|
||||
function InitializeMenuScreen () {
|
||||
Component.call(this)
|
||||
this.animationEventEmitter = new EventEmitter()
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
// state from plugin
|
||||
currentView: state.appState.currentView,
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
InitializeMenuScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
|
||||
switch (state.currentView.name) {
|
||||
|
||||
default:
|
||||
return this.renderMenu(state)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeMenuScreen.prototype.componentDidMount = function(){
|
||||
// document.getElementById('password-box').focus()
|
||||
// }
|
||||
|
||||
InitializeMenuScreen.prototype.renderMenu = function (state) {
|
||||
return (
|
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
h(Mascot, {
|
||||
animationEventEmitter: this.animationEventEmitter,
|
||||
}),
|
||||
|
||||
h('h1', {
|
||||
style: {
|
||||
fontSize: '1.3em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#7F8082',
|
||||
marginBottom: 10,
|
||||
},
|
||||
}, 'MetaMask'),
|
||||
|
||||
|
||||
h('div', [
|
||||
h('h3', {
|
||||
style: {
|
||||
fontSize: '0.8em',
|
||||
color: '#7F8082',
|
||||
display: 'inline',
|
||||
},
|
||||
}, 'Encrypt your new DEN'),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'Your DEN is your password-encrypted storage within MetaMask.',
|
||||
}, [
|
||||
h('i.fa.fa-question-circle.pointer', {
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
position: 'relative',
|
||||
color: 'rgb(247, 134, 28)',
|
||||
top: '2px',
|
||||
marginLeft: '4px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('span.in-progress-notification', state.warning),
|
||||
|
||||
// password
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'New Password (min 8 chars)',
|
||||
onInput: this.inputChanged.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
},
|
||||
}),
|
||||
|
||||
// confirm password
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'password-box-confirm',
|
||||
placeholder: 'Confirm Password',
|
||||
onKeyPress: this.createVaultOnEnter.bind(this),
|
||||
onInput: this.inputChanged.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 16,
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
h('button.primary', {
|
||||
onClick: this.createNewVaultAndKeychain.bind(this),
|
||||
style: {
|
||||
margin: 12,
|
||||
},
|
||||
}, 'Create'),
|
||||
|
||||
h('.flex-row.flex-center.flex-grow', [
|
||||
h('p.pointer', {
|
||||
onClick: this.showRestoreVault.bind(this),
|
||||
style: {
|
||||
fontSize: '0.8em',
|
||||
color: 'rgb(247, 134, 28)',
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}, 'Import Existing DEN'),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
InitializeMenuScreen.prototype.createVaultOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewVaultAndKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
InitializeMenuScreen.prototype.componentDidMount = function () {
|
||||
document.getElementById('password-box').focus()
|
||||
}
|
||||
|
||||
InitializeMenuScreen.prototype.showRestoreVault = function () {
|
||||
this.props.dispatch(actions.showRestoreVault())
|
||||
}
|
||||
|
||||
InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
|
||||
var passwordBox = document.getElementById('password-box')
|
||||
var password = passwordBox.value
|
||||
var passwordConfirmBox = document.getElementById('password-box-confirm')
|
||||
var passwordConfirm = passwordConfirmBox.value
|
||||
|
||||
if (password.length < 8) {
|
||||
this.warning = 'password not long enough'
|
||||
this.props.dispatch(actions.displayWarning(this.warning))
|
||||
return
|
||||
}
|
||||
if (password !== passwordConfirm) {
|
||||
this.warning = 'passwords don\'t match'
|
||||
this.props.dispatch(actions.displayWarning(this.warning))
|
||||
return
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.createNewVaultAndKeychain(password))
|
||||
}
|
||||
|
||||
InitializeMenuScreen.prototype.inputChanged = function (event) {
|
||||
// tell mascot to look at page action
|
||||
var element = event.target
|
||||
var boundingRect = element.getBoundingClientRect()
|
||||
var coordinates = getCaretCoordinates(element, element.selectionEnd)
|
||||
this.animationEventEmitter.emit('point', {
|
||||
x: boundingRect.left + coordinates.left - element.scrollLeft,
|
||||
y: boundingRect.top + coordinates.top - element.scrollTop,
|
||||
})
|
||||
}
|
BIN
old-ui/app/img/identicon-tardigrade.png
Normal file
BIN
old-ui/app/img/identicon-tardigrade.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
BIN
old-ui/app/img/identicon-walrus.png
Normal file
BIN
old-ui/app/img/identicon-walrus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 380 KiB |
155
old-ui/app/info.js
Normal file
155
old-ui/app/info.js
Normal file
@ -0,0 +1,155 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../ui/app/actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(InfoScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(InfoScreen, Component)
|
||||
function InfoScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
InfoScreen.prototype.render = function () {
|
||||
const state = this.props
|
||||
const version = global.platform.getVersion()
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-grow', {
|
||||
style: {
|
||||
maxWidth: '400px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: (event) => {
|
||||
state.dispatch(actions.goHome())
|
||||
},
|
||||
}),
|
||||
h('h2.page-subtitle', 'Info'),
|
||||
]),
|
||||
|
||||
// main view
|
||||
h('.flex-column.flex-justify-center.flex-grow.select-none', [
|
||||
h('.flex-space-around', {
|
||||
style: {
|
||||
padding: '20px',
|
||||
},
|
||||
}, [
|
||||
// current version number
|
||||
|
||||
h('.info.info-gray', [
|
||||
h('div', 'Metamask'),
|
||||
h('div', {
|
||||
style: {
|
||||
marginBottom: '10px',
|
||||
},
|
||||
}, `Version: ${version}`),
|
||||
]),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
marginBottom: '5px',
|
||||
}},
|
||||
[
|
||||
h('div', [
|
||||
h('a', {
|
||||
href: 'https://metamask.io/privacy.html',
|
||||
target: '_blank',
|
||||
onClick (event) { this.navigateTo(event.target.href) },
|
||||
}, [
|
||||
h('div.info', 'Privacy Policy'),
|
||||
]),
|
||||
]),
|
||||
h('div', [
|
||||
h('a', {
|
||||
href: 'https://metamask.io/terms.html',
|
||||
target: '_blank',
|
||||
onClick (event) { this.navigateTo(event.target.href) },
|
||||
}, [
|
||||
h('div.info', 'Terms of Use'),
|
||||
]),
|
||||
]),
|
||||
h('div', [
|
||||
h('a', {
|
||||
href: 'https://metamask.io/attributions.html',
|
||||
target: '_blank',
|
||||
onClick (event) { this.navigateTo(event.target.href) },
|
||||
}, [
|
||||
h('div.info', 'Attributions'),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
),
|
||||
|
||||
h('hr', {
|
||||
style: {
|
||||
margin: '10px 0 ',
|
||||
width: '7em',
|
||||
},
|
||||
}),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
paddingLeft: '30px',
|
||||
}},
|
||||
[
|
||||
h('div.fa.fa-support', [
|
||||
h('a.info', {
|
||||
href: 'https://support.metamask.io',
|
||||
target: '_blank',
|
||||
}, 'Visit our Support Center'),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('a', {
|
||||
href: 'https://metamask.io/',
|
||||
target: '_blank',
|
||||
}, [
|
||||
h('img.icon-size', {
|
||||
src: 'images/icon-128.png',
|
||||
style: {
|
||||
// IE6-9
|
||||
filter: 'grayscale(100%)',
|
||||
// Microsoft Edge and Firefox 35+
|
||||
WebkitFilter: 'grayscale(100%)',
|
||||
},
|
||||
}),
|
||||
h('div.info', 'Visit our web site'),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('div', [
|
||||
h('.fa.fa-twitter', [
|
||||
h('a.info', {
|
||||
href: 'https://twitter.com/metamask_io',
|
||||
target: '_blank',
|
||||
}, 'Follow us on Twitter'),
|
||||
]),
|
||||
]),
|
||||
|
||||
h('div.fa.fa-envelope', [
|
||||
h('a.info', {
|
||||
target: '_blank',
|
||||
style: { width: '85vw' },
|
||||
href: 'mailto:help@metamask.io?subject=Feedback',
|
||||
}, 'Email us!'),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
InfoScreen.prototype.navigateTo = function (url) {
|
||||
global.platform.openWindow({ url })
|
||||
}
|
||||
|
653
old-ui/app/infura-conversion.json
Normal file
653
old-ui/app/infura-conversion.json
Normal file
@ -0,0 +1,653 @@
|
||||
{
|
||||
"objects": [
|
||||
{
|
||||
"symbol": "ethaud",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "aud",
|
||||
"name": "Australian Dollar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethhkd",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "hkd",
|
||||
"name": "Hong Kong Dollar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethsgd",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "sgd",
|
||||
"name": "Singapore Dollar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethidr",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "idr",
|
||||
"name": "Indonesian Rupiah"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethphp",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "php",
|
||||
"name": "Philippine Peso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "eth1st",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "1st",
|
||||
"name": "FirstBlood"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethadt",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "adt",
|
||||
"name": "adToken"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethadx",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "adx",
|
||||
"name": "AdEx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethant",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "ant",
|
||||
"name": "Aragon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethbat",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "bat",
|
||||
"name": "Basic Attention Token"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethbnt",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "bnt",
|
||||
"name": "Bancor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethbtc",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "btc",
|
||||
"name": "Bitcoin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethcad",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "cad",
|
||||
"name": "Canadian Dollar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethcfi",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "cfi",
|
||||
"name": "Cofound.it"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethcrb",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "crb",
|
||||
"name": "CreditBit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethcvc",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "cvc",
|
||||
"name": "Civic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethdash",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "dash",
|
||||
"name": "Dash"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethdgd",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "dgd",
|
||||
"name": "DigixDAO"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethetc",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "etc",
|
||||
"name": "Ethereum Classic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "etheur",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "eur",
|
||||
"name": "Euro"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethfun",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "fun",
|
||||
"name": "FunFair"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethgbp",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "gbp",
|
||||
"name": "Pound Sterling"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethgno",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "gno",
|
||||
"name": "Gnosis"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethgnt",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "gnt",
|
||||
"name": "Golem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethgup",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "gup",
|
||||
"name": "Matchpool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethhmq",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "hmq",
|
||||
"name": "Humaniq"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethjpy",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "jpy",
|
||||
"name": "Japanese Yen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethlgd",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "lgd",
|
||||
"name": "Legends Room"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethlsk",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "lsk",
|
||||
"name": "Lisk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethltc",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "ltc",
|
||||
"name": "Litecoin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethlun",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "lun",
|
||||
"name": "Lunyr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethmco",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "mco",
|
||||
"name": "Monaco"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethmtl",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "mtl",
|
||||
"name": "Metal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethmyst",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "myst",
|
||||
"name": "Mysterium"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethnmr",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "nmr",
|
||||
"name": "Numeraire"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethomg",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "omg",
|
||||
"name": "OmiseGO"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethpay",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "pay",
|
||||
"name": "TenX"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethptoy",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "ptoy",
|
||||
"name": "Patientory"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethqrl",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "qrl",
|
||||
"name": "Quantum-Resistant Ledger"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethqtum",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "qtum",
|
||||
"name": "Qtum"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethrep",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "rep",
|
||||
"name": "Augur"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethrlc",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "rlc",
|
||||
"name": "iEx.ec"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethrub",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "rub",
|
||||
"name": "Russian Ruble"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethsc",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "sc",
|
||||
"name": "Siacoin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethsngls",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "sngls",
|
||||
"name": "SingularDTV"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethsnt",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "snt",
|
||||
"name": "Status"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethsteem",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "steem",
|
||||
"name": "Steem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethstorj",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "storj",
|
||||
"name": "Storj"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethtime",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "time",
|
||||
"name": "ChronoBank"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethtkn",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "tkn",
|
||||
"name": "TokenCard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethtrst",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "trst",
|
||||
"name": "WeTrust"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethuah",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "uah",
|
||||
"name": "Ukrainian Hryvnia"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethusd",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "usd",
|
||||
"name": "United States Dollar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethwings",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "wings",
|
||||
"name": "Wings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethxem",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "xem",
|
||||
"name": "NEM"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethxlm",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "xlm",
|
||||
"name": "Stellar Lumen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethxmr",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "xmr",
|
||||
"name": "Monero"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethxrp",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "xrp",
|
||||
"name": "Ripple"
|
||||
}
|
||||
},
|
||||
{
|
||||
"symbol": "ethzec",
|
||||
"base": {
|
||||
"code": "eth",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
"quote": {
|
||||
"code": "zec",
|
||||
"name": "Zcash"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
91
old-ui/app/keychains/hd/create-vault-complete.js
Normal file
91
old-ui/app/keychains/hd/create-vault-complete.js
Normal file
@ -0,0 +1,91 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../../ui/app/actions')
|
||||
const exportAsFile = require('../../util').exportAsFile
|
||||
|
||||
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
|
||||
|
||||
inherits(CreateVaultCompleteScreen, Component)
|
||||
function CreateVaultCompleteScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
seed: state.appState.currentView.seedWords,
|
||||
cachedSeed: state.metamask.seedWords,
|
||||
}
|
||||
}
|
||||
|
||||
CreateVaultCompleteScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
var seed = state.seed || state.cachedSeed || ''
|
||||
|
||||
return (
|
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
// // subtitle and nav
|
||||
// h('.section-title.flex-row.flex-center', [
|
||||
// h('h2.page-subtitle', 'Vault Created'),
|
||||
// ]),
|
||||
|
||||
h('h3.flex-center.text-transform-uppercase', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginTop: 36,
|
||||
marginBottom: 8,
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
'Vault Created',
|
||||
]),
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
fontSize: '1em',
|
||||
marginTop: '10px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, [
|
||||
h('span.error', 'These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret.'),
|
||||
]),
|
||||
|
||||
h('textarea.twelve-word-phrase', {
|
||||
readOnly: true,
|
||||
value: seed,
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: () => this.confirmSeedWords()
|
||||
.then(account => this.showAccountDetail(account)),
|
||||
style: {
|
||||
margin: '24px',
|
||||
fontSize: '0.9em',
|
||||
marginBottom: '10px',
|
||||
},
|
||||
}, 'I\'ve copied it somewhere safe'),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: () => exportAsFile(`MetaMask Seed Words`, seed),
|
||||
style: {
|
||||
margin: '10px',
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Save Seed Words As File'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
|
||||
return this.props.dispatch(actions.confirmSeedWords())
|
||||
}
|
||||
|
||||
CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) {
|
||||
return this.props.dispatch(actions.showAccountDetail(account))
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user