Get duplicate UI template working
|
@ -1,5 +1,5 @@
|
|||
const injectCss = require('inject-css')
|
||||
const MetaMaskUiCss = require('../../ui/css')
|
||||
const MetaMaskUiCss = require('../../ui/classic/css')
|
||||
const startPopup = require('./popup-core')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const isPopupOrNotification = require('./lib/is-popup-or-notification')
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const injectCss = require('inject-css')
|
||||
const startPopup = require('./responsive-core')
|
||||
const MetaMaskUiCss = require('../../ui/responsive/css')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const extension = require('extensionizer')
|
||||
const NotificationManager = require('./lib/notification-manager')
|
||||
const notificationManager = new NotificationManager()
|
||||
|
||||
// create platform global
|
||||
global.platform = new ExtensionPlatform()
|
||||
|
@ -20,9 +20,6 @@ const connectionStream = new PortStream(extensionPort)
|
|||
const container = document.getElementById('app-content')
|
||||
startPopup({ container, connectionStream }, (err, store) => {
|
||||
if (err) return displayCriticalError(err)
|
||||
store.subscribe(() => {
|
||||
const state = store.getState()
|
||||
})
|
||||
})
|
||||
|
||||
function displayCriticalError (err) {
|
||||
|
|
|
@ -174,6 +174,7 @@ const jsFiles = [
|
|||
'contentscript',
|
||||
'background',
|
||||
'popup',
|
||||
'responsive'
|
||||
]
|
||||
|
||||
// bundle tasks
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
|
||||
const getBuyEthUrl = require('../../../app/scripts/lib/buy-eth-url')
|
||||
|
||||
var actions = {
|
||||
_setBackgroundConnection: _setBackgroundConnection,
|
||||
|
|
|
@ -6,7 +6,7 @@ const clone = require('clone')
|
|||
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
|
||||
const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
|
||||
const util = require('../util')
|
||||
const MiniAccountPanel = require('./mini-account-panel')
|
||||
const Copyable = require('./copyable')
|
||||
|
|
|
@ -6,7 +6,7 @@ const connect = require('react-redux').connect
|
|||
const actions = require('./actions')
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const txHelper = require('../lib/tx-helper')
|
||||
const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
|
||||
const isPopupOrNotification = require('../../../app/scripts/lib/is-popup-or-notification')
|
||||
|
||||
const PendingTx = require('./components/pending-tx')
|
||||
const PendingMsg = require('./components/pending-msg')
|
||||
|
|
|
@ -9,8 +9,8 @@ var cssFiles = {
|
|||
'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'),
|
||||
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
|
||||
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
|
||||
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
||||
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
||||
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
||||
'react-css': fs.readFileSync(path.join(__dirname, '..', '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
||||
}
|
||||
|
||||
function bundleCss () {
|
||||
|
|
|
@ -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
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
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 CopyButton = require('./components/copyButton')
|
||||
const AccountInfoLink = require('./components/account-info-link')
|
||||
const actions = require('./actions')
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
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 Tooltip = require('./components/tooltip')
|
||||
const TabBar = require('./components/tab-bar')
|
||||
const TokenList = require('./components/token-list')
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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', [
|
||||
|
||||
// identicon, label, balance, etc
|
||||
h('.account-data-subsection', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// 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('flex-column', {
|
||||
style: {
|
||||
lineHeight: '10px',
|
||||
marginLeft: '15px',
|
||||
},
|
||||
}, [
|
||||
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('h2.font-medium.color-forest', {name: 'edit'}, identity && identity.name),
|
||||
]),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
width: '15em',
|
||||
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: '10px',
|
||||
marginBottom: '15px',
|
||||
color: '#AEAEAE',
|
||||
},
|
||||
}, checksumAddress),
|
||||
|
||||
// copy and export
|
||||
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(AccountInfoLink, { selected, network }),
|
||||
|
||||
h(CopyButton, {
|
||||
value: checksumAddress,
|
||||
}),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'QR Code',
|
||||
}, [
|
||||
h('i.fa.fa-qrcode.pointer.pop-hover', {
|
||||
onClick: () => props.dispatch(actions.showQrView(selected, identity ? identity.name : '')),
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
position: 'relative',
|
||||
color: 'rgb(247, 134, 28)',
|
||||
top: '5px',
|
||||
marginLeft: '3px',
|
||||
marginRight: '3px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'Export Private Key',
|
||||
}, [
|
||||
h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('img.cursor-pointer.color-orange', {
|
||||
src: 'images/key-32.png',
|
||||
onClick: () => this.requestAccountExport(selected),
|
||||
style: {
|
||||
height: '19px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
// 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('button', {
|
||||
onClick: () => props.dispatch(actions.buyEthView(selected)),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
marginRight: '8px',
|
||||
position: 'absolute',
|
||||
left: '219px',
|
||||
},
|
||||
}, 'BUY'),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.showSendPage()),
|
||||
style: {
|
||||
marginBottom: '20px',
|
||||
marginRight: '8px',
|
||||
},
|
||||
}, 'SEND'),
|
||||
|
||||
]),
|
||||
]),
|
||||
|
||||
// subview (tx history, pk export confirm, buy eth warning)
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
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', [
|
||||
|
||||
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))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
AccountDetailScreen.prototype.requestAccountExport = function () {
|
||||
this.props.dispatch(actions.requestExportAccount())
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
const EthBalance = require('../components/eth-balance')
|
||||
const CopyButton = require('../components/copyButton')
|
||||
const Identicon = require('../components/identicon')
|
||||
|
||||
module.exports = AccountListItem
|
||||
|
||||
inherits(AccountListItem, Component)
|
||||
function AccountListItem () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountListItem.prototype.render = function () {
|
||||
const { identity, selectedAddress, accounts, onShowDetail,
|
||||
conversionRate, currentCurrency } = this.props
|
||||
|
||||
const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address)
|
||||
const isSelected = selectedAddress === identity.address
|
||||
const account = accounts[identity.address]
|
||||
const selectedClass = isSelected ? '.selected' : ''
|
||||
|
||||
return (
|
||||
h(`.accounts-list-option.flex-row.flex-space-between.pointer.hover-white${selectedClass}`, {
|
||||
key: `account-panel-${identity.address}`,
|
||||
onClick: (event) => onShowDetail(identity.address, event),
|
||||
}, [
|
||||
|
||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
||||
this.pendingOrNot(),
|
||||
this.indicateIfLoose(),
|
||||
h(Identicon, {
|
||||
address: identity.address,
|
||||
imageify: true,
|
||||
}),
|
||||
]),
|
||||
|
||||
// account address, balance
|
||||
h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', {
|
||||
style: {
|
||||
width: '200px',
|
||||
},
|
||||
}, [
|
||||
h('span', identity.name),
|
||||
h('span.font-small', {
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
}, checksumAddress),
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
style: {
|
||||
lineHeight: '7px',
|
||||
marginTop: '10px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
// copy button
|
||||
h('.identity-copy.flex-column', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
h(CopyButton, {
|
||||
value: checksumAddress,
|
||||
}),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountListItem.prototype.indicateIfLoose = function () {
|
||||
try { // Sometimes keyrings aren't loaded yet:
|
||||
const type = this.props.keyring.type
|
||||
const isLoose = type !== 'HD Key Tree'
|
||||
return isLoose ? h('.keyring-label', 'LOOSE') : null
|
||||
} catch (e) { return }
|
||||
}
|
||||
|
||||
AccountListItem.prototype.pendingOrNot = function () {
|
||||
const pending = this.props.pending
|
||||
if (pending.length === 0) return null
|
||||
return h('.pending-dot', pending.length)
|
||||
}
|
|
@ -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('../../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) => {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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('../../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 ]))
|
||||
}
|
|
@ -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('../../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 ]))
|
||||
}
|
|
@ -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'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../actions')
|
||||
const valuesFor = require('../util').valuesFor
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
const AccountListItem = require('./account-list-item')
|
||||
|
||||
module.exports = connect(mapStateToProps)(AccountsScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const pendingTxs = valuesFor(state.metamask.unapprovedTxs)
|
||||
.filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network)
|
||||
const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs)
|
||||
const pending = pendingTxs.concat(pendingMsgs)
|
||||
|
||||
return {
|
||||
accounts: state.metamask.accounts,
|
||||
identities: state.metamask.identities,
|
||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||
selectedAddress: state.metamask.selectedAddress,
|
||||
scrollToBottom: state.appState.scrollToBottom,
|
||||
pending,
|
||||
keyrings: state.metamask.keyrings,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(AccountsScreen, Component)
|
||||
function AccountsScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { keyrings, conversionRate, currentCurrency } = props
|
||||
const identityList = valuesFor(props.identities)
|
||||
const unapprovedTxList = valuesFor(props.unapprovedTxs)
|
||||
|
||||
return (
|
||||
|
||||
h('.accounts-section.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.section-title.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: this.goHome.bind(this),
|
||||
}),
|
||||
h('h2.page-subtitle', 'Select Account'),
|
||||
]),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
|
||||
// identity selection
|
||||
h('section.identity-section', {
|
||||
style: {
|
||||
height: '418px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
},
|
||||
[
|
||||
identityList.map((identity) => {
|
||||
const pending = this.props.pending.filter((txOrMsg) => {
|
||||
if ('txParams' in txOrMsg) {
|
||||
return txOrMsg.txParams.from === identity.address
|
||||
} else if ('msgParams' in txOrMsg) {
|
||||
return txOrMsg.msgParams.from === identity.address
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const simpleAddress = identity.address.substring(2).toLowerCase()
|
||||
const keyring = keyrings.find((kr) => {
|
||||
return kr.accounts.includes(simpleAddress) ||
|
||||
kr.accounts.includes(identity.address)
|
||||
})
|
||||
|
||||
return h(AccountListItem, {
|
||||
key: `acct-panel-${identity.address}`,
|
||||
identity,
|
||||
selectedAddress: this.props.selectedAddress,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
accounts: this.props.accounts,
|
||||
onShowDetail: this.onShowDetail.bind(this),
|
||||
pending,
|
||||
keyring,
|
||||
})
|
||||
}),
|
||||
|
||||
h('hr.horizontal-line'),
|
||||
h('div.footer.hover-white.pointer', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: () => {
|
||||
this.addNewAccount()
|
||||
},
|
||||
style: {
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
padding: '10px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-plus.fa-lg', {key: ''}),
|
||||
]),
|
||||
h('hr.horizontal-line'),
|
||||
]),
|
||||
|
||||
unapprovedTxList.length ? (
|
||||
|
||||
h('.unconftx-link.flex-row.flex-center', {
|
||||
onClick: this.navigateToConfTx.bind(this),
|
||||
}, [
|
||||
h('span', 'Unconfirmed Txs'),
|
||||
h('i.fa.fa-arrow-right.fa-lg'),
|
||||
])
|
||||
|
||||
) : (
|
||||
null
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
// If a new account was revealed, scroll to the bottom
|
||||
AccountsScreen.prototype.componentDidUpdate = function () {
|
||||
const scrollToBottom = this.props.scrollToBottom
|
||||
|
||||
if (scrollToBottom) {
|
||||
var container = findDOMNode(this)
|
||||
var scrollable = container.querySelector('.identity-section')
|
||||
scrollable.scrollTop = scrollable.scrollHeight
|
||||
}
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.navigateToConfTx = function () {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showConfTxPage())
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.onShowDetail = function (address, event) {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showAccountDetail(address))
|
||||
}
|
||||
|
||||
AccountsScreen.prototype.addNewAccount = function () {
|
||||
this.props.dispatch(actions.addNewAccount(0))
|
||||
}
|
||||
|
||||
/* An optional view proposed in this design:
|
||||
* https://consensys.quip.com/zZVrAysM5znY
|
||||
AccountsScreen.prototype.addNewAccount = function () {
|
||||
this.props.dispatch(actions.navigateToNewAccountScreen())
|
||||
}
|
||||
*/
|
||||
|
||||
AccountsScreen.prototype.goHome = function () {
|
||||
this.props.dispatch(actions.goHome())
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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('span', {
|
||||
style: { fontWeight: 'bold', paddingRight: '10px'},
|
||||
}, 'Token Address'),
|
||||
]),
|
||||
|
||||
h('section.flex-row.flex-center', [
|
||||
h('input#token-address', {
|
||||
name: 'address',
|
||||
placeholder: 'Token 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 Sybmol'),
|
||||
]),
|
||||
|
||||
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 { address, symbol, decimals } = state
|
||||
|
||||
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 isValid = validAddress && validDecimals
|
||||
|
||||
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() })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,591 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('./actions')
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
// init
|
||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||
const NewKeyChainScreen = require('./new-keychain')
|
||||
// unlock
|
||||
const UnlockScreen = require('./unlock')
|
||||
// accounts
|
||||
const AccountsScreen = require('./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 MenuDroppo = require('menu-droppo')
|
||||
const DropMenuItem = require('./components/drop-menu-item')
|
||||
const NetworkIndicator = require('./components/network')
|
||||
const Tooltip = require('./components/tooltip')
|
||||
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')
|
||||
|
||||
module.exports = connect(mapStateToProps)(App)
|
||||
|
||||
inherits(App, Component)
|
||||
function App () { Component.call(this) }
|
||||
|
||||
function mapStateToProps (state) {
|
||||
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,
|
||||
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 || [],
|
||||
}
|
||||
}
|
||||
|
||||
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.flex-grow.full-height', {
|
||||
style: {
|
||||
// Windows was showing a vertical scroll bar:
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
},
|
||||
}, [
|
||||
|
||||
// app bar
|
||||
this.renderAppBar(),
|
||||
this.renderNetworkDropdown(),
|
||||
this.renderDropdown(),
|
||||
|
||||
h(Loading, {
|
||||
isLoading: isLoading || isLoadingNetwork,
|
||||
loadingMessage: loadMessage,
|
||||
}),
|
||||
|
||||
// panel content
|
||||
h('.app-primary.flex-grow' + (transForward ? '.from-right' : '.from-left'), {
|
||||
style: {
|
||||
height: '380px',
|
||||
width: '360px',
|
||||
},
|
||||
}, [
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
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
|
||||
|
||||
return (
|
||||
|
||||
h('div', [
|
||||
|
||||
h('.app-header.flex-row.flex-space-between', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
visibility: props.isUnlocked ? 'visible' : 'none',
|
||||
background: props.isUnlocked ? 'white' : 'none',
|
||||
height: '36px',
|
||||
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 })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
// metamask name
|
||||
props.isUnlocked && h('h1', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
left: '9px',
|
||||
},
|
||||
}, 'MetaMask'),
|
||||
|
||||
props.isUnlocked && h('div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
// small accounts nav
|
||||
props.isUnlocked && h(Tooltip, { title: 'Switch Accounts' }, [
|
||||
h('img.cursor-pointer.color-orange', {
|
||||
src: 'images/switch_acc.svg',
|
||||
style: {
|
||||
width: '23.5px',
|
||||
marginRight: '8px',
|
||||
},
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showAccountsPage())
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
// hamburger
|
||||
props.isUnlocked && h(SandwichExpando, {
|
||||
width: 16,
|
||||
barHeight: 2,
|
||||
padding: 0,
|
||||
isOpen: state.isMainMenuOpen,
|
||||
color: 'rgb(247,146,30)',
|
||||
onClick: (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.setState({ isMainMenuOpen: !state.isMainMenuOpen })
|
||||
},
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderNetworkDropdown = function () {
|
||||
const props = this.props
|
||||
const rpcList = props.frequentRpcList
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isNetworkMenuOpen
|
||||
|
||||
return h(MenuDroppo, {
|
||||
isOpen,
|
||||
onClickOutside: (event) => {
|
||||
this.setState({ isNetworkMenuOpen: !isOpen })
|
||||
},
|
||||
zIndex: 11,
|
||||
style: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
background: 'white',
|
||||
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
|
||||
},
|
||||
}, [ // DROP MENU ITEMS
|
||||
h('style', `
|
||||
.drop-menu-item:hover { background:rgb(235, 235, 235); }
|
||||
.drop-menu-item i { margin: 11px; }
|
||||
`),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Main Ethereum Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setProviderType('mainnet')),
|
||||
icon: h('.menu-icon.diamond'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Ropsten Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setProviderType('ropsten')),
|
||||
icon: h('.menu-icon.red-dot'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Kovan Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
|
||||
action: () => props.dispatch(actions.setProviderType('kovan')),
|
||||
icon: h('.menu-icon.hollow-diamond'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Rinkeby Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false}),
|
||||
action: () => props.dispatch(actions.setProviderType('rinkeby')),
|
||||
icon: h('.menu-icon.golden-square'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Localhost 8545',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: props.provider.rpcTarget,
|
||||
}),
|
||||
|
||||
this.renderCustomOption(props.provider),
|
||||
this.renderCommonRpc(rpcList, props.provider),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Custom RPC',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
}),
|
||||
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderDropdown = function () {
|
||||
const state = this.state || {}
|
||||
const isOpen = state.isMainMenuOpen
|
||||
|
||||
return h(MenuDroppo, {
|
||||
isOpen: isOpen,
|
||||
zIndex: 11,
|
||||
onClickOutside: (event) => {
|
||||
this.setState({ isMainMenuOpen: !isOpen })
|
||||
},
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: '36px',
|
||||
},
|
||||
innerStyle: {
|
||||
background: 'white',
|
||||
boxShadow: '1px 1px 2px rgba(0,0,0,0.1)',
|
||||
},
|
||||
}, [ // DROP MENU ITEMS
|
||||
h('style', `
|
||||
.drop-menu-item:hover { background:rgb(235, 235, 235); }
|
||||
.drop-menu-item i { margin: 11px; }
|
||||
`),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Settings',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showConfigPage()),
|
||||
icon: h('i.fa.fa-gear.fa-lg'),
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Import Account',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showImportPage()),
|
||||
icon: h('i.fa.fa-arrow-circle-o-up.fa-lg'),
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Lock',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.lockMetamask()),
|
||||
icon: h('i.fa.fa-lock.fa-lg'),
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Info/Help',
|
||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||
action: () => this.props.dispatch(actions.showInfoPage()),
|
||||
icon: h('i.fa.fa-question.fa-lg'),
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
App.prototype.renderBackButton = function (style, justArrow = false) {
|
||||
var props = this.props
|
||||
return (
|
||||
h('.flex-row', {
|
||||
key: 'leftArrow',
|
||||
style: style,
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, [
|
||||
h('i.fa.fa-arrow-left.cursor-pointer'),
|
||||
justArrow ? null : h('div.cursor-pointer', {
|
||||
style: {
|
||||
marginLeft: '3px',
|
||||
},
|
||||
onClick: () => props.dispatch(actions.goBackToInitView()),
|
||||
}, 'BACK'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
App.prototype.renderPrimary = function () {
|
||||
log.debug('rendering primary')
|
||||
var props = this.props
|
||||
|
||||
// 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 'accounts':
|
||||
log.debug('rendering accounts screen')
|
||||
return h(AccountsScreen, {key: 'accounts'})
|
||||
|
||||
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 'qr':
|
||||
log.debug('rendering show qr screen')
|
||||
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
|
||||
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(DropMenuItem, {
|
||||
label,
|
||||
key: rpcTarget,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: 'custom',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 { rpcTarget } = provider
|
||||
const props = this.props
|
||||
|
||||
return rpcList.map((rpc) => {
|
||||
if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
|
||||
return null
|
||||
} else {
|
||||
return h(DropMenuItem, {
|
||||
label: rpc,
|
||||
key: rpc,
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setRpcTarget(rpc)),
|
||||
icon: h('i.fa.fa-question-circle.fa-lg'),
|
||||
activeNetworkRender: rpc,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
const actions = require('../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 () {
|
||||
var state = this.props
|
||||
var accountDetail = state.accountDetail
|
||||
|
||||
if (!accountDetail) return h('div')
|
||||
var accountExport = accountDetail.accountExport
|
||||
|
||||
var notExporting = accountExport === 'none'
|
||||
var exportRequested = accountExport === 'requested'
|
||||
var accountExported = accountExport === 'completed'
|
||||
|
||||
if (notExporting) return h('div')
|
||||
|
||||
if (exportRequested) {
|
||||
var 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) {
|
||||
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',
|
||||
width: '100%',
|
||||
},
|
||||
onClick: function (event) {
|
||||
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
|
||||
},
|
||||
}, ethUtil.stripHexPrefix(accountDetail.privateKey)),
|
||||
h('button', {
|
||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
|
||||
}, 'Done'),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
ExportAccountView.prototype.onExportKeyPress = function (event) {
|
||||
if (event.key !== 'Enter') return
|
||||
event.preventDefault()
|
||||
|
||||
var input = document.getElementById('exportAccount').value
|
||||
this.props.dispatch(actions.exportAccount(input, this.props.address))
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const Tooltip = require('./tooltip')
|
||||
const genAccountLink = require('../../lib/account-link')
|
||||
|
||||
module.exports = AccountInfoLink
|
||||
|
||||
inherits(AccountInfoLink, Component)
|
||||
function AccountInfoLink () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountInfoLink.prototype.render = function () {
|
||||
const { selected, network } = this.props
|
||||
const title = 'View account on Etherscan'
|
||||
const url = genAccountLink(selected, network)
|
||||
|
||||
if (!url) {
|
||||
return null
|
||||
}
|
||||
|
||||
return h('.account-info-link', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h(Tooltip, {
|
||||
title,
|
||||
}, [
|
||||
h('i.fa.fa-info-circle.cursor-pointer.color-orange', {
|
||||
style: {
|
||||
margin: '5px',
|
||||
},
|
||||
onClick () { global.platform.openWindow({ url }) },
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
]))
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
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 newValue = this.downsize(valueString, scale, precision)
|
||||
|
||||
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,
|
||||
max,
|
||||
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 } = 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
|
||||
}
|
||||
|
||||
|
||||
BnAsDecimalInput.prototype.downsize = function (number, scale, precision) {
|
||||
// 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 decimals = (scale === precision) ? -1 : scale - precision
|
||||
return Number(number.slice(0, -scale) + '.' + number.slice(-scale, decimals))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../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')
|
||||
|
||||
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 () {
|
||||
const props = this.props
|
||||
const isLoading = props.isSubLoading
|
||||
|
||||
return (
|
||||
h('.buy-eth-section.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
// back button
|
||||
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'),
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: '57vh',
|
||||
left: '49vw',
|
||||
},
|
||||
}, [
|
||||
h(Loading, {isLoading}),
|
||||
]),
|
||||
h('div', {
|
||||
style: {
|
||||
width: '80%',
|
||||
},
|
||||
}, [
|
||||
h(AccountPanel, {
|
||||
showFullAddress: true,
|
||||
identity: props.identity,
|
||||
account: props.account,
|
||||
}),
|
||||
]),
|
||||
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',
|
||||
},
|
||||
}, 'Select Service'),
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
return h('div.flex-column', {
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
margin: '50px',
|
||||
},
|
||||
}, [
|
||||
h('h3.text-transform-uppercase', {
|
||||
style: {
|
||||
width: '225px',
|
||||
marginBottom: '15px',
|
||||
},
|
||||
}, 'In order to access this feature, please switch to the Main Network'),
|
||||
((network === '3') || (network === '4') || (network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null,
|
||||
(network === '3') ? h('button.text-transform-uppercase', {
|
||||
onClick: () => this.props.dispatch(actions.buyEth({ network })),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, 'Ropsten Test Faucet') : null,
|
||||
(network === '4') ? h('button.text-transform-uppercase', {
|
||||
onClick: () => this.props.dispatch(actions.buyEth({ network })),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, 'Rinkeby Test Faucet') : null,
|
||||
(network === '42') ? h('button.text-transform-uppercase', {
|
||||
onClick: () => this.props.dispatch(actions.buyEth({ network })),
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, 'Kovan Test Faucet') : null,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -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('../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',
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
|
||||
module.exports = DropMenuItem
|
||||
|
||||
inherits(DropMenuItem, Component)
|
||||
function DropMenuItem () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
DropMenuItem.prototype.render = function () {
|
||||
return h('li.drop-menu-item', {
|
||||
onClick: () => {
|
||||
this.props.closeMenu()
|
||||
this.props.action()
|
||||
},
|
||||
style: {
|
||||
listStyle: 'none',
|
||||
padding: '6px 16px 6px 5px',
|
||||
fontFamily: 'Montserrat Regular',
|
||||
color: 'rgb(125, 128, 130)',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
this.props.icon,
|
||||
this.props.label,
|
||||
this.activeNetworkRender(),
|
||||
])
|
||||
}
|
||||
|
||||
DropMenuItem.prototype.activeNetworkRender = function () {
|
||||
const activeNetwork = this.props.activeNetworkRender
|
||||
const { provider } = this.props
|
||||
const providerType = provider ? provider.type : null
|
||||
if (activeNetwork === undefined) return
|
||||
|
||||
switch (this.props.label) {
|
||||
case 'Main Ethereum Network':
|
||||
if (providerType === 'mainnet') return h('.check', '✓')
|
||||
break
|
||||
case 'Ropsten Test Network':
|
||||
if (providerType === 'ropsten') return h('.check', '✓')
|
||||
break
|
||||
case 'Kovan Test Network':
|
||||
if (providerType === 'kovan') return h('.check', '✓')
|
||||
break
|
||||
case 'Rinkeby Test Network':
|
||||
if (providerType === 'rinkeby') return h('.check', '✓')
|
||||
break
|
||||
case 'Localhost 8545':
|
||||
if (activeNetwork === 'http://localhost:8545') return h('.check', '✓')
|
||||
break
|
||||
default:
|
||||
if (activeNetwork === 'custom') return h('.check', '✓')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
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) => {
|
||||
this.setState({ isEditingLabel: true })
|
||||
},
|
||||
}, this.props.children)
|
||||
}
|
||||
}
|
||||
|
||||
EditableLabel.prototype.saveIfEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.saveText()
|
||||
}
|
||||
}
|
||||
|
||||
EditableLabel.prototype.saveText = function () {
|
||||
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 })
|
||||
}
|
|
@ -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 = /.+\.eth$/
|
||||
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])
|
||||
}
|
|
@ -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,
|
||||
]))
|
||||
)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
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 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, currentCurrency)
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
|
||||
|
||||
inherits(LoadingIndicator, Component)
|
||||
module.exports = LoadingIndicator
|
||||
|
||||
function LoadingIndicator () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
LoadingIndicator.prototype.render = function () {
|
||||
const { isLoading, loadingMessage } = this.props
|
||||
|
||||
return (
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'loader',
|
||||
transitionEnterTimeout: 150,
|
||||
transitionLeaveTimeout: 150,
|
||||
}, [
|
||||
|
||||
isLoading ? h('div', {
|
||||
style: {
|
||||
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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
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', {
|
||||
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-sort-desc'),
|
||||
])
|
||||
|
||||
} 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',
|
||||
}},
|
||||
'Ethereum Main Net'),
|
||||
])
|
||||
case 'ropsten-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.red-dot'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#ff6666',
|
||||
}},
|
||||
'Ropsten Test Net'),
|
||||
])
|
||||
case 'kovan-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.hollow-diamond'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#690496',
|
||||
}},
|
||||
'Kovan Test Net'),
|
||||
])
|
||||
case 'rinkeby-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.golden-square'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#e7a218',
|
||||
}},
|
||||
'Rinkeby Test Net'),
|
||||
])
|
||||
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'),
|
||||
])
|
||||
}
|
||||
})(),
|
||||
])
|
||||
)
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
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', [
|
||||
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 () {
|
||||
var node = findDOMNode(this)
|
||||
linker.setupListener(node)
|
||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
||||
this.setState({disclaimerDisabled: false})
|
||||
}
|
||||
}
|
||||
|
||||
Notice.prototype.componentWillUnmount = function () {
|
||||
var node = findDOMNode(this)
|
||||
linker.teardownListener(node)
|
||||
}
|
|
@ -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-row.flex-space-between', [
|
||||
h('label.font-small', 'MESSAGE'),
|
||||
h('span.font-small', msgParams.data),
|
||||
]),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
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,
|
||||
}, [
|
||||
|
||||
// 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 will be fixed in a future version.`),
|
||||
|
||||
// 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'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
|
@ -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',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
])
|
||||
)
|
||||
}
|
||||
|
|
@ -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'),
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const actions = require('../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')
|
||||
|
||||
const MIN_GAS_PRICE_GWEI_BN = new BN(2)
|
||||
const GWEI_FACTOR = new BN(1e9)
|
||||
const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR)
|
||||
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 || {}
|
||||
|
||||
// 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 safeGasLimit = this.bnMultiplyByFraction(gasLimit, 19, 20).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)
|
||||
|
||||
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.toString(10),
|
||||
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: MIN_GAS_PRICE_GWEI_BN.toString(10),
|
||||
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;
|
||||
}
|
||||
`),
|
||||
|
||||
txMeta.simulationFails ?
|
||||
h('.error', {
|
||||
style: {
|
||||
marginLeft: 50,
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Transaction Error. Exception thrown in contract code.')
|
||||
: null,
|
||||
|
||||
!isValidAddress ?
|
||||
h('.error', {
|
||||
style: {
|
||||
marginLeft: 50,
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
|
||||
: null,
|
||||
|
||||
insufficientBalance ?
|
||||
h('span.error', {
|
||||
style: {
|
||||
marginLeft: 50,
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'Insufficient balance for transaction')
|
||||
: null,
|
||||
|
||||
// send + cancel
|
||||
h('.flex-row.flex-space-around.conf-buttons', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
margin: '14px 25px',
|
||||
},
|
||||
}, [
|
||||
|
||||
|
||||
insufficientBalance ?
|
||||
h('button.btn-green', {
|
||||
onClick: props.buyEth,
|
||||
}, 'Buy Ether')
|
||||
: null,
|
||||
|
||||
h('button', {
|
||||
onClick: (event) => {
|
||||
this.resetGasFields()
|
||||
event.preventDefault()
|
||||
},
|
||||
}, 'Reset'),
|
||||
|
||||
// Accept Button
|
||||
h('input.confirm.btn-green', {
|
||||
type: 'submit',
|
||||
value: 'SUBMIT',
|
||||
style: { marginLeft: '10px' },
|
||||
disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting,
|
||||
}),
|
||||
|
||||
h('button.cancel.btn-red', {
|
||||
onClick: props.cancelTransaction,
|
||||
}, 'Reject'),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
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
|
||||
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
|
||||
}
|
|
@ -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})
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
const PersistentForm = require('../../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const connect = require('react-redux').connect
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const actions = require('../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 h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
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: {
|
||||
// marginTop: '10px',
|
||||
padding: '25px',
|
||||
paddingTop: '5px',
|
||||
width: '100%',
|
||||
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', [
|
||||
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: 'relative',
|
||||
bottom: '48px',
|
||||
left: '106px',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
|
||||
h('.icon-control', [
|
||||
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: 'relative',
|
||||
bottom: '26px',
|
||||
left: '10px',
|
||||
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: {
|
||||
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(),
|
||||
]),
|
||||
|
||||
h(this.activeToggle('.input-container'), {
|
||||
style: {
|
||||
padding: '10px',
|
||||
paddingTop: '0px',
|
||||
width: '100%',
|
||||
},
|
||||
}, [
|
||||
|
||||
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: 'relative',
|
||||
bottom: '10px',
|
||||
right: '11px',
|
||||
},
|
||||
}),
|
||||
h('.flex-row', {
|
||||
style: {
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
}, [
|
||||
h('button', {
|
||||
onClick: this.shift.bind(this),
|
||||
style: {
|
||||
marginTop: '10px',
|
||||
position: 'relative',
|
||||
bottom: '40px',
|
||||
},
|
||||
},
|
||||
'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',
|
||||
}),
|
||||
])
|
||||
}
|
|
@ -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('../../lib/explorer-link')
|
||||
const actions = require('../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 ''
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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',
|
||||
},
|
||||
}, tabs.map((tab) => {
|
||||
const { key, content } = tab
|
||||
return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
|
||||
onClick: () => {
|
||||
this.setState({ subview: key })
|
||||
tabSelected(key)
|
||||
},
|
||||
}, content)
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
|
@ -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}`
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
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')
|
||||
const normalizeAddress = require('eth-sig-util').normalize
|
||||
|
||||
const defaultTokens = []
|
||||
/*
|
||||
const contracts = require('eth-contract-metadata')
|
||||
for (const address in contracts) {
|
||||
const contract = contracts[address]
|
||||
if (contract.erc20) {
|
||||
contract.address = address
|
||||
defaultTokens.push(contract)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
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 this.message('There was a problem loading your token balances.')
|
||||
}
|
||||
|
||||
const tokenViews = tokens.map((tokenData) => {
|
||||
tokenData.network = network
|
||||
tokenData.userAddress = userAddress
|
||||
return h(TokenCell, tokenData)
|
||||
})
|
||||
|
||||
return h('div', [
|
||||
h('ol', {
|
||||
style: {
|
||||
height: '260px',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}, [
|
||||
h('style', `
|
||||
|
||||
li.token-cell {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
li.token-cell > h3 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
li.token-cell:hover {
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
`),
|
||||
...tokenViews,
|
||||
tokenViews.length ? null : this.message('No Tokens Found.'),
|
||||
]),
|
||||
this.addTokenButtonElement(),
|
||||
])
|
||||
}
|
||||
|
||||
TokenList.prototype.addTokenButtonElement = function () {
|
||||
return h('div', [
|
||||
h('div.footer.hover-white.pointer', {
|
||||
key: 'reveal-account-bar',
|
||||
onClick: () => {
|
||||
this.props.addToken()
|
||||
},
|
||||
style: {
|
||||
display: 'flex',
|
||||
height: '40px',
|
||||
padding: '10px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}, [
|
||||
h('i.fa.fa-plus.fa-lg'),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
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: uniqueMergeTokens(defaultTokens, 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()
|
||||
}
|
||||
|
||||
function uniqueMergeTokens (tokensA, tokensB) {
|
||||
const uniqueAddresses = []
|
||||
const result = []
|
||||
tokensA.concat(tokensB).forEach((token) => {
|
||||
const normal = normalizeAddress(token.address)
|
||||
if (!uniqueAddresses.includes(normal)) {
|
||||
uniqueAddresses.push(normal)
|
||||
result.push(token)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
|
@ -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: false,
|
||||
}, children)
|
||||
}
|
|
@ -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: 'bottom',
|
||||
}, [
|
||||
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',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
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('../../lib/explorer-link')
|
||||
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',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
||||
h('.pop-hover', {
|
||||
onClick: (event) => {
|
||||
event.stopPropagation()
|
||||
if (!isTx || isPending) return
|
||||
var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}`
|
||||
global.platform.openWindow({ url })
|
||||
},
|
||||
}, [
|
||||
h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
|
||||
]),
|
||||
]),
|
||||
|
||||
h(Tooltip, {
|
||||
title: 'Transaction Number',
|
||||
position: 'bottom',
|
||||
}, [
|
||||
h('span', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
cursor: 'normal',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '10px',
|
||||
},
|
||||
}, nonce),
|
||||
]),
|
||||
|
||||
h('.flex-column', {style: {width: '200px', 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,
|
||||
width: '55px',
|
||||
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,
|
||||
failIfFailed(transaction),
|
||||
])
|
||||
}
|
||||
|
||||
function formatDate (date) {
|
||||
return vreme.format(new Date(date), 'March 16 2014 14:30')
|
||||
}
|
||||
|
||||
function failIfFailed (transaction) {
|
||||
if (transaction.status === 'rejected') {
|
||||
return h('span.error', ' (Rejected)')
|
||||
}
|
||||
if (transaction.err) {
|
||||
return h(Tooltip, {
|
||||
title: transaction.err.message,
|
||||
position: 'bottom',
|
||||
}, [
|
||||
h('span.error', ' (Failed)'),
|
||||
])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
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', [
|
||||
|
||||
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: '300px',
|
||||
padding: '0 20px',
|
||||
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', {
|
||||
style: {
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
}, [
|
||||
'No transaction history.',
|
||||
]),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./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 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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(ConfirmTxScreen, Component)
|
||||
function ConfirmTxScreen () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
ConfirmTxScreen.prototype.render = function () {
|
||||
const props = this.props
|
||||
const { network, provider, unapprovedTxs, currentCurrency,
|
||||
unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props
|
||||
|
||||
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, 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 })
|
||||
|
||||
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),
|
||||
|
||||
h(ReactCSSTransitionGroup, {
|
||||
className: 'css-transition-group',
|
||||
transitionName: 'main',
|
||||
transitionEnterTimeout: 300,
|
||||
transitionLeaveTimeout: 300,
|
||||
}, [
|
||||
|
||||
currentTxView({
|
||||
// Properties
|
||||
txData: txData,
|
||||
key: txData.id,
|
||||
selectedAddress: props.selectedAddress,
|
||||
accounts: props.accounts,
|
||||
identities: props.identities,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
blockGasLimit,
|
||||
// Actions
|
||||
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
|
||||
sendTransaction: this.sendTransaction.bind(this),
|
||||
cancelTransaction: this.cancelTransaction.bind(this, txData),
|
||||
signMessage: this.signMessage.bind(this, txData),
|
||||
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
|
||||
cancelMessage: this.cancelMessage.bind(this, txData),
|
||||
cancelPersonalMessage: this.cancelPersonalMessage.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.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.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.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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
const currencies = require('./conversion.json').rows
|
||||
const validUrl = require('valid-url')
|
||||
const copyToClipboard = require('copy-to-clipboard')
|
||||
|
||||
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', [
|
||||
|
||||
// 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) {
|
||||
copyToClipboard(window.logState())
|
||||
},
|
||||
}, 'Copy 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,
|
||||
}, currencies.map((currency) => {
|
||||
return h('option', {key: currency.code, value: currency.code}, `${currency.code} - ${currency.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),
|
||||
])
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
{
|
||||
"rows": [
|
||||
{
|
||||
"code": "REP",
|
||||
"name": "Augur",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "BCN",
|
||||
"name": "Bytecoin",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "BTC",
|
||||
"name": "Bitcoin",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "BTS",
|
||||
"name": "BitShares",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "BLK",
|
||||
"name": "Blackcoin",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "GBP",
|
||||
"name": "British Pound Sterling",
|
||||
"statuses": [
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "CAD",
|
||||
"name": "Canadian Dollar",
|
||||
"statuses": [
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "CNY",
|
||||
"name": "Chinese Yuan",
|
||||
"statuses": [
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "DSH",
|
||||
"name": "Dashcoin",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "DOGE",
|
||||
"name": "Dogecoin",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "ETC",
|
||||
"name": "Ethereum Classic",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "EUR",
|
||||
"name": "Euro",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "GNO",
|
||||
"name": "GNO",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "GNT",
|
||||
"name": "GNT",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "JPY",
|
||||
"name": "Japanese Yen",
|
||||
"statuses": [
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "LTC",
|
||||
"name": "Litecoin",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "MAID",
|
||||
"name": "MaidSafeCoin",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "XEM",
|
||||
"name": "NEM",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "XLM",
|
||||
"name": "Stellar",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "XMR",
|
||||
"name": "Monero",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "XRP",
|
||||
"name": "Ripple",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "RUR",
|
||||
"name": "Ruble",
|
||||
"statuses": [
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "STEEM",
|
||||
"name": "Steem",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "STRAT",
|
||||
"name": "STRAT",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "UAH",
|
||||
"name": "Ukrainian Hryvnia",
|
||||
"statuses": [
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "USD",
|
||||
"name": "US Dollar",
|
||||
"statuses": [
|
||||
"primary",
|
||||
"secondary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "WAVES",
|
||||
"name": "WAVES",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "ZEC",
|
||||
"name": "Zcash",
|
||||
"statuses": [
|
||||
"primary"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,667 @@
|
|||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
overflow-x: hidden;
|
||||
min-width: 357px;
|
||||
width: 360px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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-primary {
|
||||
|
||||
}
|
||||
|
||||
.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: 7px;
|
||||
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: #E20202;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
||||
}
|
||||
.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);
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
/* 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);
|
||||
bottom: -47px;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ether-balance {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
display: inline-block;
|
||||
height: 9px;
|
||||
min-width: 9px;
|
||||
margin: 13px;
|
||||
}
|
||||
.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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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('../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,
|
||||
})
|
||||
}
|
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 380 KiB |
|
@ -0,0 +1,154 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./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', [
|
||||
|
||||
// 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-github', [
|
||||
h('a.info', {
|
||||
href: 'https://github.com/MetaMask/faq',
|
||||
target: '_blank',
|
||||
}, 'Need Help? Read our FAQ!'),
|
||||
]),
|
||||
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.fa.fa-slack', [
|
||||
h('a.info', {
|
||||
href: 'http://slack.metamask.io',
|
||||
target: '_blank',
|
||||
}, 'Join the conversation on Slack'),
|
||||
]),
|
||||
|
||||
h('div.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 })
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../actions')
|
||||
|
||||
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: {
|
||||
width: '360px',
|
||||
height: '78px',
|
||||
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(),
|
||||
style: {
|
||||
margin: '24px',
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
}, 'I\'ve copied it somewhere safe'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
|
||||
this.props.dispatch(actions.confirmSeedWords())
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
const inherits = require('util').inherits
|
||||
|
||||
const Component = require('react').Component
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../../actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(RevealSeedConfirmation)
|
||||
|
||||
inherits(RevealSeedConfirmation, Component)
|
||||
function RevealSeedConfirmation () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
RevealSeedConfirmation.prototype.render = function () {
|
||||
const props = this.props
|
||||
|
||||
return (
|
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
h('h3.flex-center.text-transform-uppercase', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginBottom: 24,
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
'Reveal Seed Words',
|
||||
]),
|
||||
|
||||
h('.div', {
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '20px',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'),
|
||||
|
||||
// confirmation
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'Enter your password to confirm',
|
||||
onKeyPress: this.checkConfirmation.bind(this),
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: '12px',
|
||||
},
|
||||
}),
|
||||
|
||||
h('.flex-row.flex-space-between', {
|
||||
style: {
|
||||
marginTop: 30,
|
||||
width: '50%',
|
||||
},
|
||||
}, [
|
||||
// cancel
|
||||
h('button.primary', {
|
||||
onClick: this.goHome.bind(this),
|
||||
}, 'CANCEL'),
|
||||
|
||||
// submit
|
||||
h('button.primary', {
|
||||
onClick: this.revealSeedWords.bind(this),
|
||||
}, 'OK'),
|
||||
|
||||
]),
|
||||
|
||||
(props.warning) && (
|
||||
h('span.error', {
|
||||
style: {
|
||||
margin: '20px',
|
||||
},
|
||||
}, props.warning.split('-'))
|
||||
),
|
||||
|
||||
props.inProgress && (
|
||||
h('span.in-progress-notification', 'Generating Seed...')
|
||||
),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
RevealSeedConfirmation.prototype.componentDidMount = function () {
|
||||
document.getElementById('password-box').focus()
|
||||
}
|
||||
|
||||
RevealSeedConfirmation.prototype.goHome = function () {
|
||||
this.props.dispatch(actions.showConfigPage(false))
|
||||
}
|
||||
|
||||
// create vault
|
||||
|
||||
RevealSeedConfirmation.prototype.checkConfirmation = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.revealSeedWords()
|
||||
}
|
||||
}
|
||||
|
||||
RevealSeedConfirmation.prototype.revealSeedWords = function () {
|
||||
var password = document.getElementById('password-box').value
|
||||
this.props.dispatch(actions.requestRevealSeed(password))
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
const inherits = require('util').inherits
|
||||
const PersistentForm = require('../../../lib/persistent-form')
|
||||
const connect = require('react-redux').connect
|
||||
const h = require('react-hyperscript')
|
||||
const actions = require('../../actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(RestoreVaultScreen)
|
||||
|
||||
inherits(RestoreVaultScreen, PersistentForm)
|
||||
function RestoreVaultScreen () {
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
forgottenPassword: state.appState.forgottenPassword,
|
||||
}
|
||||
}
|
||||
|
||||
RestoreVaultScreen.prototype.render = function () {
|
||||
var state = this.props
|
||||
this.persistentFormParentId = 'restore-vault-form'
|
||||
|
||||
return (
|
||||
|
||||
h('.initialize-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
h('h3.flex-center.text-transform-uppercase', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginBottom: 24,
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
'Restore Vault',
|
||||
]),
|
||||
|
||||
// wallet seed entry
|
||||
h('h3', 'Wallet Seed'),
|
||||
h('textarea.twelve-word-phrase.letter-spacey', {
|
||||
dataset: {
|
||||
persistentFormId: 'wallet-seed',
|
||||
},
|
||||
placeholder: 'Enter your secret twelve word phrase here to restore your vault.',
|
||||
}),
|
||||
|
||||
// password
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'New Password (min 8 chars)',
|
||||
dataset: {
|
||||
persistentFormId: 'password',
|
||||
},
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 12,
|
||||
},
|
||||
}),
|
||||
|
||||
// confirm password
|
||||
h('input.large-input.letter-spacey', {
|
||||
type: 'password',
|
||||
id: 'password-box-confirm',
|
||||
placeholder: 'Confirm Password',
|
||||
onKeyPress: this.createOnEnter.bind(this),
|
||||
dataset: {
|
||||
persistentFormId: 'password-confirmation',
|
||||
},
|
||||
style: {
|
||||
width: 260,
|
||||
marginTop: 16,
|
||||
},
|
||||
}),
|
||||
|
||||
(state.warning) && (
|
||||
h('span.error.in-progress-notification', state.warning)
|
||||
),
|
||||
|
||||
// submit
|
||||
|
||||
h('.flex-row.flex-space-between', {
|
||||
style: {
|
||||
marginTop: 30,
|
||||
width: '50%',
|
||||
},
|
||||
}, [
|
||||
|
||||
// cancel
|
||||
h('button.primary', {
|
||||
onClick: this.showInitializeMenu.bind(this),
|
||||
}, 'CANCEL'),
|
||||
|
||||
// submit
|
||||
h('button.primary', {
|
||||
onClick: this.createNewVaultAndRestore.bind(this),
|
||||
}, 'OK'),
|
||||
|
||||
]),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
RestoreVaultScreen.prototype.showInitializeMenu = function () {
|
||||
if (this.props.forgottenPassword) {
|
||||
this.props.dispatch(actions.backToUnlockView())
|
||||
} else {
|
||||
this.props.dispatch(actions.showInitializeMenu())
|
||||
}
|
||||
}
|
||||
|
||||
RestoreVaultScreen.prototype.createOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.createNewVaultAndRestore()
|
||||
}
|
||||
}
|
||||
|
||||
RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
|
||||
// check password
|
||||
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
|
||||
}
|
||||
// check seed
|
||||
var seedBox = document.querySelector('textarea.twelve-word-phrase')
|
||||
var seed = seedBox.value.trim()
|
||||
if (seed.split(' ').length !== 12) {
|
||||
this.warning = 'seed phrases are 12 words long'
|
||||
this.props.dispatch(actions.displayWarning(this.warning))
|
||||
return
|
||||
}
|
||||
// submit
|
||||
this.warning = null
|
||||
this.props.dispatch(actions.displayWarning(this.warning))
|
||||
this.props.dispatch(actions.createNewVaultAndRestore(password, seed))
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
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)(NewKeychain)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(NewKeychain, Component)
|
||||
function NewKeychain () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
NewKeychain.prototype.render = function () {
|
||||
// const props = this.props
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
background: 'blue',
|
||||
},
|
||||
}, [
|
||||
h('h1', `Here's a list!!!!`),
|
||||
])
|
||||
)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
const extend = require('xtend')
|
||||
|
||||
//
|
||||
// Sub-Reducers take in the complete state and return their sub-state
|
||||
//
|
||||
const reduceIdentities = require('./reducers/identities')
|
||||
const reduceMetamask = require('./reducers/metamask')
|
||||
const reduceApp = require('./reducers/app')
|
||||
|
||||
window.METAMASK_CACHED_LOG_STATE = null
|
||||
|
||||
module.exports = rootReducer
|
||||
|
||||
function rootReducer (state, action) {
|
||||
// clone
|
||||
state = extend(state)
|
||||
|
||||
if (action.type === 'GLOBAL_FORCE_UPDATE') {
|
||||
return action.value
|
||||
}
|
||||
|
||||
//
|
||||
// Identities
|
||||
//
|
||||
|
||||
state.identities = reduceIdentities(state, action)
|
||||
|
||||
//
|
||||
// MetaMask
|
||||
//
|
||||
|
||||
state.metamask = reduceMetamask(state, action)
|
||||
|
||||
//
|
||||
// AppState
|
||||
//
|
||||
|
||||
state.appState = reduceApp(state, action)
|
||||
|
||||
window.METAMASK_CACHED_LOG_STATE = state
|
||||
return state
|
||||
}
|
||||
|
||||
window.logState = function () {
|
||||
var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2)
|
||||
console.log(stateString)
|
||||
return stateString
|
||||
}
|
||||
|
||||
function removeSeedWords (key, value) {
|
||||
return key === 'seedWords' ? undefined : value
|
||||
}
|
|
@ -0,0 +1,585 @@
|
|||
const extend = require('xtend')
|
||||
const actions = require('../actions')
|
||||
const txHelper = require('../../lib/tx-helper')
|
||||
|
||||
module.exports = reduceApp
|
||||
|
||||
|
||||
function reduceApp (state, action) {
|
||||
log.debug('App Reducer got ' + action.type)
|
||||
// clone and defaults
|
||||
const selectedAddress = state.metamask.selectedAddress
|
||||
const hasUnconfActions = checkUnconfActions(state)
|
||||
let name = 'accounts'
|
||||
if (selectedAddress) {
|
||||
name = 'accountDetail'
|
||||
}
|
||||
if (hasUnconfActions) {
|
||||
log.debug('pending txs detected, defaulting to conf-tx view.')
|
||||
name = 'confTx'
|
||||
}
|
||||
|
||||
var defaultView = {
|
||||
name,
|
||||
detailView: null,
|
||||
context: selectedAddress,
|
||||
}
|
||||
|
||||
// confirm seed words
|
||||
var seedWords = state.metamask.seedWords
|
||||
var seedConfView = {
|
||||
name: 'createVaultComplete',
|
||||
seedWords,
|
||||
}
|
||||
|
||||
// default state
|
||||
var appState = extend({
|
||||
shouldClose: false,
|
||||
menuOpen: false,
|
||||
currentView: seedWords ? seedConfView : defaultView,
|
||||
accountDetail: {
|
||||
subview: 'transactions',
|
||||
},
|
||||
transForward: true, // Used to render transition direction
|
||||
isLoading: false, // Used to display loading indicator
|
||||
warning: null, // Used to display error text
|
||||
}, state.appState)
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
// transition methods
|
||||
|
||||
case actions.TRANSITION_FORWARD:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
})
|
||||
|
||||
case actions.TRANSITION_BACKWARD:
|
||||
return extend(appState, {
|
||||
transForward: false,
|
||||
})
|
||||
|
||||
// intialize
|
||||
|
||||
case actions.SHOW_CREATE_VAULT:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'createVault',
|
||||
},
|
||||
transForward: true,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.SHOW_RESTORE_VAULT:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'restoreVault',
|
||||
},
|
||||
transForward: true,
|
||||
forgottenPassword: true,
|
||||
})
|
||||
|
||||
case actions.FORGOT_PASSWORD:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'restoreVault',
|
||||
},
|
||||
transForward: false,
|
||||
forgottenPassword: true,
|
||||
})
|
||||
|
||||
case actions.SHOW_INIT_MENU:
|
||||
return extend(appState, {
|
||||
currentView: defaultView,
|
||||
transForward: false,
|
||||
})
|
||||
|
||||
case actions.SHOW_CONFIG_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'config',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: action.value,
|
||||
})
|
||||
|
||||
case actions.SHOW_ADD_TOKEN_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'add-token',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: action.value,
|
||||
})
|
||||
|
||||
case actions.SHOW_IMPORT_PAGE:
|
||||
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'import-menu',
|
||||
},
|
||||
transForward: true,
|
||||
})
|
||||
|
||||
case actions.SHOW_INFO_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'info',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: true,
|
||||
})
|
||||
|
||||
case actions.CREATE_NEW_VAULT_IN_PROGRESS:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'createVault',
|
||||
inProgress: true,
|
||||
},
|
||||
transForward: true,
|
||||
isLoading: true,
|
||||
})
|
||||
|
||||
case actions.SHOW_NEW_VAULT_SEED:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'createVaultComplete',
|
||||
seedWords: action.value,
|
||||
},
|
||||
transForward: true,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.NEW_ACCOUNT_SCREEN:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'new-account',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: true,
|
||||
})
|
||||
|
||||
case actions.SHOW_SEND_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'sendTransaction',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: true,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.SHOW_NEW_KEYCHAIN:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'newKeychain',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: true,
|
||||
})
|
||||
|
||||
// unlock
|
||||
|
||||
case actions.UNLOCK_METAMASK:
|
||||
return extend(appState, {
|
||||
forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
|
||||
detailView: {},
|
||||
transForward: true,
|
||||
isLoading: false,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.LOCK_METAMASK:
|
||||
return extend(appState, {
|
||||
currentView: defaultView,
|
||||
transForward: false,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.BACK_TO_INIT_MENU:
|
||||
return extend(appState, {
|
||||
warning: null,
|
||||
transForward: false,
|
||||
forgottenPassword: true,
|
||||
currentView: {
|
||||
name: 'InitMenu',
|
||||
},
|
||||
})
|
||||
|
||||
case actions.BACK_TO_UNLOCK_VIEW:
|
||||
return extend(appState, {
|
||||
warning: null,
|
||||
transForward: true,
|
||||
forgottenPassword: false,
|
||||
currentView: {
|
||||
name: 'UnlockScreen',
|
||||
},
|
||||
})
|
||||
// reveal seed words
|
||||
|
||||
case actions.REVEAL_SEED_CONFIRMATION:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'reveal-seed-conf',
|
||||
},
|
||||
transForward: true,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
// accounts
|
||||
|
||||
case actions.SET_SELECTED_ACCOUNT:
|
||||
return extend(appState, {
|
||||
activeAddress: action.value,
|
||||
})
|
||||
|
||||
case actions.GO_HOME:
|
||||
return extend(appState, {
|
||||
currentView: extend(appState.currentView, {
|
||||
name: 'accountDetail',
|
||||
}),
|
||||
accountDetail: {
|
||||
subview: 'transactions',
|
||||
accountExport: 'none',
|
||||
privateKey: '',
|
||||
},
|
||||
transForward: false,
|
||||
warning: null,
|
||||
})
|
||||
|
||||
case actions.SHOW_ACCOUNT_DETAIL:
|
||||
return extend(appState, {
|
||||
forgottenPassword: appState.forgottenPassword ? !appState.forgottenPassword : null,
|
||||
currentView: {
|
||||
name: 'accountDetail',
|
||||
context: action.value,
|
||||
},
|
||||
accountDetail: {
|
||||
subview: 'transactions',
|
||||
accountExport: 'none',
|
||||
privateKey: '',
|
||||
},
|
||||
transForward: false,
|
||||
})
|
||||
|
||||
case actions.BACK_TO_ACCOUNT_DETAIL:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'accountDetail',
|
||||
context: action.value,
|
||||
},
|
||||
accountDetail: {
|
||||
subview: 'transactions',
|
||||
accountExport: 'none',
|
||||
privateKey: '',
|
||||
},
|
||||
transForward: false,
|
||||
})
|
||||
|
||||
case actions.SHOW_ACCOUNTS_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: seedWords ? 'createVaultComplete' : 'accounts',
|
||||
seedWords,
|
||||
},
|
||||
transForward: true,
|
||||
isLoading: false,
|
||||
warning: null,
|
||||
scrollToBottom: false,
|
||||
forgottenPassword: false,
|
||||
})
|
||||
|
||||
case actions.SHOW_NOTICE:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.REVEAL_ACCOUNT:
|
||||
return extend(appState, {
|
||||
scrollToBottom: true,
|
||||
})
|
||||
|
||||
case actions.SHOW_CONF_TX_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'confTx',
|
||||
context: 0,
|
||||
},
|
||||
transForward: action.transForward,
|
||||
warning: null,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.SHOW_CONF_MSG_PAGE:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: hasUnconfActions ? 'confTx' : 'account-detail',
|
||||
context: 0,
|
||||
},
|
||||
transForward: true,
|
||||
warning: null,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.COMPLETED_TX:
|
||||
log.debug('reducing COMPLETED_TX for tx ' + action.value)
|
||||
const otherUnconfActions = getUnconfActionList(state)
|
||||
.filter(tx => tx.id !== action.value)
|
||||
const hasOtherUnconfActions = otherUnconfActions.length > 0
|
||||
|
||||
if (hasOtherUnconfActions) {
|
||||
log.debug('reducer detected txs - rendering confTx view')
|
||||
return extend(appState, {
|
||||
transForward: false,
|
||||
currentView: {
|
||||
name: 'confTx',
|
||||
context: 0,
|
||||
},
|
||||
warning: null,
|
||||
})
|
||||
} else {
|
||||
log.debug('attempting to close popup')
|
||||
return extend(appState, {
|
||||
// indicate notification should close
|
||||
shouldClose: true,
|
||||
transForward: false,
|
||||
warning: null,
|
||||
currentView: {
|
||||
name: 'accountDetail',
|
||||
context: state.metamask.selectedAddress,
|
||||
},
|
||||
accountDetail: {
|
||||
subview: 'transactions',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
case actions.NEXT_TX:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
currentView: {
|
||||
name: 'confTx',
|
||||
context: ++appState.currentView.context,
|
||||
warning: null,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.VIEW_PENDING_TX:
|
||||
const context = indexForPending(state, action.value)
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
currentView: {
|
||||
name: 'confTx',
|
||||
context,
|
||||
warning: null,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.PREVIOUS_TX:
|
||||
return extend(appState, {
|
||||
transForward: false,
|
||||
currentView: {
|
||||
name: 'confTx',
|
||||
context: --appState.currentView.context,
|
||||
warning: null,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.TRANSACTION_ERROR:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'confTx',
|
||||
errorMessage: 'There was a problem submitting this transaction.',
|
||||
},
|
||||
})
|
||||
|
||||
case actions.UNLOCK_FAILED:
|
||||
return extend(appState, {
|
||||
warning: action.value || 'Incorrect password. Try again.',
|
||||
})
|
||||
|
||||
case actions.SHOW_LOADING:
|
||||
return extend(appState, {
|
||||
isLoading: true,
|
||||
loadingMessage: action.value,
|
||||
})
|
||||
|
||||
case actions.HIDE_LOADING:
|
||||
return extend(appState, {
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.SHOW_SUB_LOADING_INDICATION:
|
||||
return extend(appState, {
|
||||
isSubLoading: true,
|
||||
})
|
||||
|
||||
case actions.HIDE_SUB_LOADING_INDICATION:
|
||||
return extend(appState, {
|
||||
isSubLoading: false,
|
||||
})
|
||||
case actions.CLEAR_SEED_WORD_CACHE:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
currentView: {},
|
||||
isLoading: false,
|
||||
accountDetail: {
|
||||
subview: 'transactions',
|
||||
accountExport: 'none',
|
||||
privateKey: '',
|
||||
},
|
||||
})
|
||||
|
||||
case actions.DISPLAY_WARNING:
|
||||
return extend(appState, {
|
||||
warning: action.value,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.HIDE_WARNING:
|
||||
return extend(appState, {
|
||||
warning: undefined,
|
||||
})
|
||||
|
||||
case actions.REQUEST_ACCOUNT_EXPORT:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
currentView: {
|
||||
name: 'accountDetail',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
accountDetail: {
|
||||
subview: 'export',
|
||||
accountExport: 'requested',
|
||||
},
|
||||
})
|
||||
|
||||
case actions.EXPORT_ACCOUNT:
|
||||
return extend(appState, {
|
||||
accountDetail: {
|
||||
subview: 'export',
|
||||
accountExport: 'completed',
|
||||
},
|
||||
})
|
||||
|
||||
case actions.SHOW_PRIVATE_KEY:
|
||||
return extend(appState, {
|
||||
accountDetail: {
|
||||
subview: 'export',
|
||||
accountExport: 'completed',
|
||||
privateKey: action.value,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.BUY_ETH_VIEW:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
currentView: {
|
||||
name: 'buyEth',
|
||||
context: appState.currentView.name,
|
||||
},
|
||||
identity: state.metamask.identities[action.value],
|
||||
buyView: {
|
||||
subview: 'Coinbase',
|
||||
amount: '15.00',
|
||||
buyAddress: action.value,
|
||||
formView: {
|
||||
coinbase: true,
|
||||
shapeshift: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
case actions.COINBASE_SUBVIEW:
|
||||
return extend(appState, {
|
||||
buyView: {
|
||||
subview: 'Coinbase',
|
||||
formView: {
|
||||
coinbase: true,
|
||||
shapeshift: false,
|
||||
},
|
||||
buyAddress: appState.buyView.buyAddress,
|
||||
amount: appState.buyView.amount,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.SHAPESHIFT_SUBVIEW:
|
||||
return extend(appState, {
|
||||
buyView: {
|
||||
subview: 'ShapeShift',
|
||||
formView: {
|
||||
coinbase: false,
|
||||
shapeshift: true,
|
||||
marketinfo: action.value.marketinfo,
|
||||
coinOptions: action.value.coinOptions,
|
||||
},
|
||||
buyAddress: appState.buyView.buyAddress,
|
||||
amount: appState.buyView.amount,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.PAIR_UPDATE:
|
||||
return extend(appState, {
|
||||
buyView: {
|
||||
subview: 'ShapeShift',
|
||||
formView: {
|
||||
coinbase: false,
|
||||
shapeshift: true,
|
||||
marketinfo: action.value.marketinfo,
|
||||
coinOptions: appState.buyView.formView.coinOptions,
|
||||
},
|
||||
buyAddress: appState.buyView.buyAddress,
|
||||
amount: appState.buyView.amount,
|
||||
warning: null,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.SHOW_QR:
|
||||
return extend(appState, {
|
||||
qrRequested: true,
|
||||
transForward: true,
|
||||
|
||||
Qr: {
|
||||
message: action.value.message,
|
||||
data: action.value.data,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.SHOW_QR_VIEW:
|
||||
return extend(appState, {
|
||||
currentView: {
|
||||
name: 'qr',
|
||||
context: appState.currentView.context,
|
||||
},
|
||||
transForward: true,
|
||||
Qr: {
|
||||
message: action.value.message,
|
||||
data: action.value.data,
|
||||
},
|
||||
})
|
||||
default:
|
||||
return appState
|
||||
}
|
||||
}
|
||||
|
||||
function checkUnconfActions (state) {
|
||||
const unconfActionList = getUnconfActionList(state)
|
||||
const hasUnconfActions = unconfActionList.length > 0
|
||||
return hasUnconfActions
|
||||
}
|
||||
|
||||
function getUnconfActionList (state) {
|
||||
const { unapprovedTxs, unapprovedMsgs,
|
||||
unapprovedPersonalMsgs, network } = state.metamask
|
||||
|
||||
const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network)
|
||||
return unconfActionList
|
||||
}
|
||||
|
||||
function indexForPending (state, txId) {
|
||||
const unconfTxList = getUnconfActionList(state)
|
||||
const match = unconfTxList.find((tx) => tx.id === txId)
|
||||
const index = unconfTxList.indexOf(match)
|
||||
return index
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
const extend = require('xtend')
|
||||
|
||||
module.exports = reduceIdentities
|
||||
|
||||
function reduceIdentities (state, action) {
|
||||
// clone + defaults
|
||||
var idState = extend({
|
||||
|
||||
}, state.identities)
|
||||
|
||||
switch (action.type) {
|
||||
default:
|
||||
return idState
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
const extend = require('xtend')
|
||||
const actions = require('../actions')
|
||||
|
||||
module.exports = reduceMetamask
|
||||
|
||||
function reduceMetamask (state, action) {
|
||||
let newState
|
||||
|
||||
// clone + defaults
|
||||
var metamaskState = extend({
|
||||
isInitialized: false,
|
||||
isUnlocked: false,
|
||||
rpcTarget: 'https://rawtestrpc.metamask.io/',
|
||||
identities: {},
|
||||
unapprovedTxs: {},
|
||||
noActiveNotices: true,
|
||||
lastUnreadNotice: undefined,
|
||||
frequentRpcList: [],
|
||||
addressBook: [],
|
||||
}, state.metamask)
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case actions.SHOW_ACCOUNTS_PAGE:
|
||||
newState = extend(metamaskState)
|
||||
delete newState.seedWords
|
||||
return newState
|
||||
|
||||
case actions.SHOW_NOTICE:
|
||||
return extend(metamaskState, {
|
||||
noActiveNotices: false,
|
||||
lastUnreadNotice: action.value,
|
||||
})
|
||||
|
||||
case actions.CLEAR_NOTICES:
|
||||
return extend(metamaskState, {
|
||||
noActiveNotices: true,
|
||||
})
|
||||
|
||||
case actions.UPDATE_METAMASK_STATE:
|
||||
return extend(metamaskState, action.value)
|
||||
|
||||
case actions.UNLOCK_METAMASK:
|
||||
return extend(metamaskState, {
|
||||
isUnlocked: true,
|
||||
isInitialized: true,
|
||||
selectedAddress: action.value,
|
||||
})
|
||||
|
||||
case actions.LOCK_METAMASK:
|
||||
return extend(metamaskState, {
|
||||
isUnlocked: false,
|
||||
})
|
||||
|
||||
case actions.SET_RPC_LIST:
|
||||
return extend(metamaskState, {
|
||||
frequentRpcList: action.value,
|
||||
})
|
||||
|
||||
case actions.SET_RPC_TARGET:
|
||||
return extend(metamaskState, {
|
||||
provider: {
|
||||
type: 'rpc',
|
||||
rpcTarget: action.value,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.SET_PROVIDER_TYPE:
|
||||
return extend(metamaskState, {
|
||||
provider: {
|
||||
type: action.value,
|
||||
},
|
||||
})
|
||||
|
||||
case actions.COMPLETED_TX:
|
||||
var stringId = String(action.id)
|
||||
newState = extend(metamaskState, {
|
||||
unapprovedTxs: {},
|
||||
unapprovedMsgs: {},
|
||||
})
|
||||
for (const id in metamaskState.unapprovedTxs) {
|
||||
if (id !== stringId) {
|
||||
newState.unapprovedTxs[id] = metamaskState.unapprovedTxs[id]
|
||||
}
|
||||
}
|
||||
for (const id in metamaskState.unapprovedMsgs) {
|
||||
if (id !== stringId) {
|
||||
newState.unapprovedMsgs[id] = metamaskState.unapprovedMsgs[id]
|
||||
}
|
||||
}
|
||||
return newState
|
||||
|
||||
case actions.SHOW_NEW_VAULT_SEED:
|
||||
return extend(metamaskState, {
|
||||
isUnlocked: true,
|
||||
isInitialized: false,
|
||||
seedWords: action.value,
|
||||
})
|
||||
|
||||
case actions.CLEAR_SEED_WORD_CACHE:
|
||||
newState = extend(metamaskState, {
|
||||
isUnlocked: true,
|
||||
isInitialized: true,
|
||||
selectedAddress: action.value,
|
||||
})
|
||||
delete newState.seedWords
|
||||
return newState
|
||||
|
||||
case actions.SHOW_ACCOUNT_DETAIL:
|
||||
newState = extend(metamaskState, {
|
||||
isUnlocked: true,
|
||||
isInitialized: true,
|
||||
selectedAddress: action.value,
|
||||
})
|
||||
delete newState.seedWords
|
||||
return newState
|
||||
|
||||
case actions.SAVE_ACCOUNT_LABEL:
|
||||
const account = action.value.account
|
||||
const name = action.value.label
|
||||
var id = {}
|
||||
id[account] = extend(metamaskState.identities[account], { name })
|
||||
var identities = extend(metamaskState.identities, id)
|
||||
return extend(metamaskState, { identities })
|
||||
|
||||
case actions.SET_CURRENT_FIAT:
|
||||
return extend(metamaskState, {
|
||||
currentCurrency: action.value.currentCurrency,
|
||||
conversionRate: action.value.conversionRate,
|
||||
conversionDate: action.value.conversionDate,
|
||||
})
|
||||
|
||||
default:
|
||||
return metamaskState
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const Provider = require('react-redux').Provider
|
||||
const h = require('react-hyperscript')
|
||||
const App = require('./app')
|
||||
|
||||
module.exports = Root
|
||||
|
||||
inherits(Root, Component)
|
||||
function Root () { Component.call(this) }
|
||||
|
||||
Root.prototype.render = function () {
|
||||
return (
|
||||
|
||||
h(Provider, {
|
||||
store: this.props.store,
|
||||
}, [
|
||||
h(App),
|
||||
])
|
||||
|
||||
)
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
const inherits = require('util').inherits
|
||||
const PersistentForm = require('../lib/persistent-form')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const Identicon = require('./components/identicon')
|
||||
const actions = require('./actions')
|
||||
const util = require('./util')
|
||||
const numericBalance = require('./util').numericBalance
|
||||
const addressSummary = require('./util').addressSummary
|
||||
const isHex = require('./util').isHex
|
||||
const EthBalance = require('./components/eth-balance')
|
||||
const EnsInput = require('./components/ens-input')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
module.exports = connect(mapStateToProps)(SendTransactionScreen)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
var result = {
|
||||
address: state.metamask.selectedAddress,
|
||||
accounts: state.metamask.accounts,
|
||||
identities: state.metamask.identities,
|
||||
warning: state.appState.warning,
|
||||
network: state.metamask.network,
|
||||
addressBook: state.metamask.addressBook,
|
||||
conversionRate: state.metamask.conversionRate,
|
||||
currentCurrency: state.metamask.currentCurrency,
|
||||
}
|
||||
|
||||
result.error = result.warning && result.warning.split('.')[0]
|
||||
|
||||
result.account = result.accounts[result.address]
|
||||
result.identity = result.identities[result.address]
|
||||
result.balance = result.account ? numericBalance(result.account.balance) : null
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
inherits(SendTransactionScreen, PersistentForm)
|
||||
function SendTransactionScreen () {
|
||||
PersistentForm.call(this)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.render = function () {
|
||||
this.persistentFormParentId = 'send-tx-form'
|
||||
|
||||
const props = this.props
|
||||
const {
|
||||
address,
|
||||
account,
|
||||
identity,
|
||||
network,
|
||||
identities,
|
||||
addressBook,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
} = props
|
||||
|
||||
return (
|
||||
|
||||
h('.send-screen.flex-column.flex-grow', [
|
||||
|
||||
//
|
||||
// Sender Profile
|
||||
//
|
||||
|
||||
h('.account-data-subsection.flex-row.flex-grow', {
|
||||
style: {
|
||||
margin: '0 20px',
|
||||
},
|
||||
}, [
|
||||
|
||||
// header - identicon + nav
|
||||
h('.flex-row.flex-space-between', {
|
||||
style: {
|
||||
marginTop: '15px',
|
||||
},
|
||||
}, [
|
||||
// back button
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
|
||||
onClick: this.back.bind(this),
|
||||
}),
|
||||
|
||||
// large identicon
|
||||
h('.identicon-wrapper.flex-column.flex-center.select-none', [
|
||||
h(Identicon, {
|
||||
diameter: 62,
|
||||
address: address,
|
||||
}),
|
||||
]),
|
||||
|
||||
// invisible place holder
|
||||
h('i.fa.fa-users.fa-lg.invisible', {
|
||||
style: {
|
||||
marginTop: '28px',
|
||||
},
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
// account label
|
||||
|
||||
h('.flex-column', {
|
||||
style: {
|
||||
marginTop: '10px',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
}, [
|
||||
h('h2.font-medium.color-forest.flex-center', {
|
||||
style: {
|
||||
paddingTop: '8px',
|
||||
marginBottom: '8px',
|
||||
},
|
||||
}, identity && identity.name),
|
||||
|
||||
// address and getter actions
|
||||
h('.flex-row.flex-center', {
|
||||
style: {
|
||||
marginBottom: '8px',
|
||||
},
|
||||
}, [
|
||||
|
||||
h('div', {
|
||||
style: {
|
||||
lineHeight: '16px',
|
||||
},
|
||||
}, addressSummary(address)),
|
||||
|
||||
]),
|
||||
|
||||
// balance
|
||||
h('.flex-row.flex-center', [
|
||||
|
||||
h(EthBalance, {
|
||||
value: account && account.balance,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
}),
|
||||
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
|
||||
//
|
||||
// Required Fields
|
||||
//
|
||||
|
||||
h('h3.flex-center.text-transform-uppercase', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginTop: '15px',
|
||||
marginBottom: '16px',
|
||||
},
|
||||
}, [
|
||||
'Send Transaction',
|
||||
]),
|
||||
|
||||
// error message
|
||||
props.error && h('span.error.flex-center', props.error),
|
||||
|
||||
// 'to' field
|
||||
h('section.flex-row.flex-center', [
|
||||
h(EnsInput, {
|
||||
name: 'address',
|
||||
placeholder: 'Recipient Address',
|
||||
onChange: this.recipientDidChange.bind(this),
|
||||
network,
|
||||
identities,
|
||||
addressBook,
|
||||
}),
|
||||
]),
|
||||
|
||||
// 'amount' and send button
|
||||
h('section.flex-row.flex-center', [
|
||||
|
||||
h('input.large-input', {
|
||||
name: 'amount',
|
||||
placeholder: 'Amount',
|
||||
type: 'number',
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
dataset: {
|
||||
persistentFormId: 'tx-amount',
|
||||
},
|
||||
}),
|
||||
|
||||
h('button.primary', {
|
||||
onClick: this.onSubmit.bind(this),
|
||||
style: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
}, 'Next'),
|
||||
|
||||
]),
|
||||
|
||||
//
|
||||
// Optional Fields
|
||||
//
|
||||
h('h3.flex-center.text-transform-uppercase', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginTop: '16px',
|
||||
marginBottom: '16px',
|
||||
},
|
||||
}, [
|
||||
'Transaction Data (optional)',
|
||||
]),
|
||||
|
||||
// 'data' field
|
||||
h('section.flex-column.flex-center', [
|
||||
h('input.large-input', {
|
||||
name: 'txData',
|
||||
placeholder: '0x01234',
|
||||
style: {
|
||||
width: '100%',
|
||||
resize: 'none',
|
||||
},
|
||||
dataset: {
|
||||
persistentFormId: 'tx-data',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.navigateToAccounts = function (event) {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showAccountsPage())
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.back = function () {
|
||||
var address = this.props.address
|
||||
this.props.dispatch(actions.backToAccountDetail(address))
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
|
||||
this.setState({
|
||||
recipient: recipient,
|
||||
nickname: nickname,
|
||||
})
|
||||
}
|
||||
|
||||
SendTransactionScreen.prototype.onSubmit = function () {
|
||||
const state = this.state || {}
|
||||
const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
|
||||
const nickname = state.nickname || ' '
|
||||
const input = document.querySelector('input[name="amount"]').value
|
||||
const value = util.normalizeEthStringToWei(input)
|
||||
const txData = document.querySelector('input[name="txData"]').value
|
||||
const balance = this.props.balance
|
||||
let message
|
||||
|
||||
if (value.gt(balance)) {
|
||||
message = 'Insufficient funds.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if (input < 0) {
|
||||
message = 'Can not send negative amounts of ETH.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if ((!util.isValidAddress(recipient) && !txData) || (!recipient && !txData)) {
|
||||
message = 'Recipient address is invalid.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
|
||||
message = 'Transaction data must be hex string.'
|
||||
return this.props.dispatch(actions.displayWarning(message))
|
||||
}
|
||||
|
||||
this.props.dispatch(actions.hideWarning())
|
||||
|
||||
this.props.dispatch(actions.addToAddressBook(recipient, nickname))
|
||||
|
||||
var txParams = {
|
||||
from: this.props.address,
|
||||
value: '0x' + value.toString(16),
|
||||
}
|
||||
|
||||
if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
|
||||
if (txData) txParams.data = txData
|
||||
|
||||
this.props.dispatch(actions.signTx(txParams))
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
|
||||
module.exports = connect(mapStateToProps)(AppSettingsPage)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(AppSettingsPage, Component)
|
||||
function AppSettingsPage () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AppSettingsPage.prototype.render = function () {
|
||||
return (
|
||||
|
||||
h('.account-detail-section.flex-column.flex-grow', [
|
||||
|
||||
// subtitle and nav
|
||||
h('.flex-row.flex-center', [
|
||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
|
||||
onClick: this.navigateToAccounts.bind(this),
|
||||
}),
|
||||
h('h2.page-subtitle', 'Settings'),
|
||||
]),
|
||||
|
||||
h('label', {
|
||||
htmlFor: 'settings-rpc-endpoint',
|
||||
}, 'RPC Endpoint:'),
|
||||
h('input', {
|
||||
type: 'url',
|
||||
id: 'settings-rpc-endpoint',
|
||||
onKeyPress: this.onKeyPress.bind(this),
|
||||
}),
|
||||
|
||||
])
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
AppSettingsPage.prototype.componentDidMount = function () {
|
||||
document.querySelector('input').focus()
|
||||
}
|
||||
|
||||
AppSettingsPage.prototype.onKeyPress = function (event) {
|
||||
// get submit event
|
||||
if (event.key === 'Enter') {
|
||||
// this.submitPassword(event)
|
||||
}
|
||||
}
|
||||
|
||||
AppSettingsPage.prototype.navigateToAccounts = function (event) {
|
||||
event.stopPropagation()
|
||||
this.props.dispatch(actions.showAccountsPage())
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
const createStore = require('redux').createStore
|
||||
const applyMiddleware = require('redux').applyMiddleware
|
||||
const thunkMiddleware = require('redux-thunk')
|
||||
const rootReducer = require('./reducers')
|
||||
const createLogger = require('redux-logger')
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
module.exports = configureStore
|
||||
|
||||
const loggerMiddleware = createLogger({
|
||||
predicate: () => global.METAMASK_DEBUG,
|
||||
})
|
||||
|
||||
const middlewares = [thunkMiddleware, loggerMiddleware]
|
||||
|
||||
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore)
|
||||
|
||||
function configureStore (initialState) {
|
||||
return createStoreWithMiddleware(rootReducer, initialState)
|
||||
}
|
|
@ -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)(COMPONENTNAME)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(COMPONENTNAME, Component)
|
||||
function COMPONENTNAME () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
COMPONENTNAME.prototype.render = function () {
|
||||
const props = this.props
|
||||
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
background: 'blue',
|
||||
},
|
||||
}, [
|
||||
`Hello, ${props.sender}`,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
const getCaretCoordinates = require('textarea-caret')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
|
||||
const Mascot = require('./components/mascot')
|
||||
|
||||
module.exports = connect(mapStateToProps)(UnlockScreen)
|
||||
|
||||
inherits(UnlockScreen, Component)
|
||||
function UnlockScreen () {
|
||||
Component.call(this)
|
||||
this.animationEventEmitter = new EventEmitter()
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
warning: state.appState.warning,
|
||||
}
|
||||
}
|
||||
|
||||
UnlockScreen.prototype.render = function () {
|
||||
const state = this.props
|
||||
const warning = state.warning
|
||||
return (
|
||||
h('.flex-column', [
|
||||
h('.unlock-screen.flex-column.flex-center.flex-grow', [
|
||||
|
||||
h(Mascot, {
|
||||
animationEventEmitter: this.animationEventEmitter,
|
||||
}),
|
||||
|
||||
h('h1', {
|
||||
style: {
|
||||
fontSize: '1.4em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#7F8082',
|
||||
},
|
||||
}, 'MetaMask'),
|
||||
|
||||
h('input.large-input', {
|
||||
type: 'password',
|
||||
id: 'password-box',
|
||||
placeholder: 'enter password',
|
||||
style: {
|
||||
|
||||
},
|
||||
onKeyPress: this.onKeyPress.bind(this),
|
||||
onInput: this.inputChanged.bind(this),
|
||||
}),
|
||||
|
||||
h('.error', {
|
||||
style: {
|
||||
display: warning ? 'block' : 'none',
|
||||
padding: '0 20px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
}, warning),
|
||||
|
||||
h('button.primary.cursor-pointer', {
|
||||
onClick: this.onSubmit.bind(this),
|
||||
style: {
|
||||
margin: 10,
|
||||
},
|
||||
}, 'Unlock'),
|
||||
]),
|
||||
|
||||
h('.flex-row.flex-center.flex-grow', [
|
||||
h('p.pointer', {
|
||||
onClick: () => this.props.dispatch(actions.forgotPassword()),
|
||||
style: {
|
||||
fontSize: '0.8em',
|
||||
color: 'rgb(247, 134, 28)',
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}, 'I forgot my password.'),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
UnlockScreen.prototype.componentDidMount = function () {
|
||||
document.getElementById('password-box').focus()
|
||||
}
|
||||
|
||||
UnlockScreen.prototype.onSubmit = function (event) {
|
||||
const input = document.getElementById('password-box')
|
||||
const password = input.value
|
||||
this.props.dispatch(actions.tryUnlockMetamask(password))
|
||||
}
|
||||
|
||||
UnlockScreen.prototype.onKeyPress = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.submitPassword(event)
|
||||
}
|
||||
}
|
||||
|
||||
UnlockScreen.prototype.submitPassword = function (event) {
|
||||
var element = event.target
|
||||
var password = element.value
|
||||
// reset input
|
||||
element.value = ''
|
||||
this.props.dispatch(actions.tryUnlockMetamask(password))
|
||||
}
|
||||
|
||||
UnlockScreen.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,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
const ethUtil = require('ethereumjs-util')
|
||||
|
||||
var valueTable = {
|
||||
wei: '1000000000000000000',
|
||||
kwei: '1000000000000000',
|
||||
mwei: '1000000000000',
|
||||
gwei: '1000000000',
|
||||
szabo: '1000000',
|
||||
finney: '1000',
|
||||
ether: '1',
|
||||
kether: '0.001',
|
||||
mether: '0.000001',
|
||||
gether: '0.000000001',
|
||||
tether: '0.000000000001',
|
||||
}
|
||||
var bnTable = {}
|
||||
for (var currency in valueTable) {
|
||||
bnTable[currency] = new ethUtil.BN(valueTable[currency], 10)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
valuesFor: valuesFor,
|
||||
addressSummary: addressSummary,
|
||||
miniAddressSummary: miniAddressSummary,
|
||||
isAllOneCase: isAllOneCase,
|
||||
isValidAddress: isValidAddress,
|
||||
numericBalance: numericBalance,
|
||||
parseBalance: parseBalance,
|
||||
formatBalance: formatBalance,
|
||||
generateBalanceObject: generateBalanceObject,
|
||||
dataSize: dataSize,
|
||||
readableDate: readableDate,
|
||||
normalizeToWei: normalizeToWei,
|
||||
normalizeEthStringToWei: normalizeEthStringToWei,
|
||||
normalizeNumberToWei: normalizeNumberToWei,
|
||||
valueTable: valueTable,
|
||||
bnTable: bnTable,
|
||||
isHex: isHex,
|
||||
}
|
||||
|
||||
function valuesFor (obj) {
|
||||
if (!obj) return []
|
||||
return Object.keys(obj)
|
||||
.map(function (key) { return obj[key] })
|
||||
}
|
||||
|
||||
function addressSummary (address, firstSegLength = 10, lastSegLength = 4, includeHex = true) {
|
||||
if (!address) return ''
|
||||
let checked = ethUtil.toChecksumAddress(address)
|
||||
if (!includeHex) {
|
||||
checked = ethUtil.stripHexPrefix(checked)
|
||||
}
|
||||
return checked ? checked.slice(0, firstSegLength) + '...' + checked.slice(checked.length - lastSegLength) : '...'
|
||||
}
|
||||
|
||||
function miniAddressSummary (address) {
|
||||
if (!address) return ''
|
||||
var checked = ethUtil.toChecksumAddress(address)
|
||||
return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...'
|
||||
}
|
||||
|
||||
function isValidAddress (address) {
|
||||
var prefixed = ethUtil.addHexPrefix(address)
|
||||
if (address === '0x0000000000000000000000000000000000000000') return false
|
||||
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
|
||||
}
|
||||
|
||||
function isAllOneCase (address) {
|
||||
if (!address) return true
|
||||
var lower = address.toLowerCase()
|
||||
var upper = address.toUpperCase()
|
||||
return address === lower || address === upper
|
||||
}
|
||||
|
||||
// Takes wei Hex, returns wei BN, even if input is null
|
||||
function numericBalance (balance) {
|
||||
if (!balance) return new ethUtil.BN(0, 16)
|
||||
var stripped = ethUtil.stripHexPrefix(balance)
|
||||
return new ethUtil.BN(stripped, 16)
|
||||
}
|
||||
|
||||
// Takes hex, returns [beforeDecimal, afterDecimal]
|
||||
function parseBalance (balance) {
|
||||
var beforeDecimal, afterDecimal
|
||||
const wei = numericBalance(balance)
|
||||
var weiString = wei.toString()
|
||||
const trailingZeros = /0+$/
|
||||
|
||||
beforeDecimal = weiString.length > 18 ? weiString.slice(0, weiString.length - 18) : '0'
|
||||
afterDecimal = ('000000000000000000' + wei).slice(-18).replace(trailingZeros, '')
|
||||
if (afterDecimal === '') { afterDecimal = '0' }
|
||||
return [beforeDecimal, afterDecimal]
|
||||
}
|
||||
|
||||
// Takes wei hex, returns an object with three properties.
|
||||
// Its "formatted" property is what we generally use to render values.
|
||||
function formatBalance (balance, decimalsToKeep, needsParse = true) {
|
||||
var parsed = needsParse ? parseBalance(balance) : balance.split('.')
|
||||
var beforeDecimal = parsed[0]
|
||||
var afterDecimal = parsed[1]
|
||||
var formatted = 'None'
|
||||
if (decimalsToKeep === undefined) {
|
||||
if (beforeDecimal === '0') {
|
||||
if (afterDecimal !== '0') {
|
||||
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
|
||||
if (sigFigs) { afterDecimal = sigFigs[0] }
|
||||
formatted = '0.' + afterDecimal + ' ETH'
|
||||
}
|
||||
} else {
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ' ETH'
|
||||
}
|
||||
} else {
|
||||
afterDecimal += Array(decimalsToKeep).join('0')
|
||||
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ' ETH'
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
||||
|
||||
function generateBalanceObject (formattedBalance, decimalsToKeep = 1) {
|
||||
var balance = formattedBalance.split(' ')[0]
|
||||
var label = formattedBalance.split(' ')[1]
|
||||
var beforeDecimal = balance.split('.')[0]
|
||||
var afterDecimal = balance.split('.')[1]
|
||||
var shortBalance = shortenBalance(balance, decimalsToKeep)
|
||||
|
||||
if (beforeDecimal === '0' && afterDecimal.substr(0, 5) === '00000') {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (afterDecimal == 0) {
|
||||
balance = '0'
|
||||
} else {
|
||||
balance = '<1.0e-5'
|
||||
}
|
||||
} else if (beforeDecimal !== '0') {
|
||||
balance = `${beforeDecimal}.${afterDecimal.slice(0, decimalsToKeep)}`
|
||||
}
|
||||
|
||||
return { balance, label, shortBalance }
|
||||
}
|
||||
|
||||
function shortenBalance (balance, decimalsToKeep = 1) {
|
||||
var truncatedValue
|
||||
var convertedBalance = parseFloat(balance)
|
||||
if (convertedBalance > 1000000) {
|
||||
truncatedValue = (balance / 1000000).toFixed(decimalsToKeep)
|
||||
return `${truncatedValue}m`
|
||||
} else if (convertedBalance > 1000) {
|
||||
truncatedValue = (balance / 1000).toFixed(decimalsToKeep)
|
||||
return `${truncatedValue}k`
|
||||
} else if (convertedBalance === 0) {
|
||||
return '0'
|
||||
} else if (convertedBalance < 0.001) {
|
||||
return '<0.001'
|
||||
} else if (convertedBalance < 1) {
|
||||
var stringBalance = convertedBalance.toString()
|
||||
if (stringBalance.split('.')[1].length > 3) {
|
||||
return convertedBalance.toFixed(3)
|
||||
} else {
|
||||
return stringBalance
|
||||
}
|
||||
} else {
|
||||
return convertedBalance.toFixed(decimalsToKeep)
|
||||
}
|
||||
}
|
||||
|
||||
function dataSize (data) {
|
||||
var size = data ? ethUtil.stripHexPrefix(data).length : 0
|
||||
return size + ' bytes'
|
||||
}
|
||||
|
||||
// Takes a BN and an ethereum currency name,
|
||||
// returns a BN in wei
|
||||
function normalizeToWei (amount, currency) {
|
||||
try {
|
||||
return amount.mul(bnTable.wei).div(bnTable[currency])
|
||||
} catch (e) {}
|
||||
return amount
|
||||
}
|
||||
|
||||
function normalizeEthStringToWei (str) {
|
||||
const parts = str.split('.')
|
||||
let eth = new ethUtil.BN(parts[0], 10).mul(bnTable.wei)
|
||||
if (parts[1]) {
|
||||
var decimal = parts[1]
|
||||
while (decimal.length < 18) {
|
||||
decimal += '0'
|
||||
}
|
||||
const decimalBN = new ethUtil.BN(decimal, 10)
|
||||
eth = eth.add(decimalBN)
|
||||
}
|
||||
return eth
|
||||
}
|
||||
|
||||
var multiple = new ethUtil.BN('10000', 10)
|
||||
function normalizeNumberToWei (n, currency) {
|
||||
var enlarged = n * 10000
|
||||
var amount = new ethUtil.BN(String(enlarged), 10)
|
||||
return normalizeToWei(amount, currency).div(multiple)
|
||||
}
|
||||
|
||||
function readableDate (ms) {
|
||||
var date = new Date(ms)
|
||||
var month = date.getMonth()
|
||||
var day = date.getDate()
|
||||
var year = date.getFullYear()
|
||||
var hours = date.getHours()
|
||||
var minutes = '0' + date.getMinutes()
|
||||
var seconds = '0' + date.getSeconds()
|
||||
|
||||
var dateStr = `${month}/${day}/${year}`
|
||||
var time = `${hours}:${minutes.substr(-2)}:${seconds.substr(-2)}`
|
||||
return `${dateStr} ${time}`
|
||||
}
|
||||
|
||||
function isHex (str) {
|
||||
return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/))
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = bundleCss
|
||||
|
||||
var cssFiles = {
|
||||
'fonts.css': fs.readFileSync(path.join(__dirname, '/app/css/fonts.css'), 'utf8'),
|
||||
'reset.css': fs.readFileSync(path.join(__dirname, '/app/css/reset.css'), 'utf8'),
|
||||
'lib.css': fs.readFileSync(path.join(__dirname, '/app/css/lib.css'), 'utf8'),
|
||||
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/index.css'), 'utf8'),
|
||||
'transitions.css': fs.readFileSync(path.join(__dirname, '/app/css/transitions.css'), 'utf8'),
|
||||
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
|
||||
'react-css': fs.readFileSync(path.join(__dirname, '..', '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
|
||||
}
|
||||
|
||||
function bundleCss () {
|
||||
var cssBundle = Object.keys(cssFiles).reduce(function (bundle, fileName) {
|
||||
var fileContent = cssFiles[fileName]
|
||||
var output = String()
|
||||
|
||||
output += '/*========== ' + fileName + ' ==========*/\n\n'
|
||||
output += fileContent
|
||||
output += '\n\n'
|
||||
|
||||
return bundle + output
|
||||
}, String())
|
||||
|
||||
return cssBundle
|
||||
}
|
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 244 KiB |
After Width: | Height: | Size: 215 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 247 KiB |