aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorfrankiebee <frankie.diamond@gmail.com>2017-09-27 07:24:43 +0800
committerfrankiebee <frankie.diamond@gmail.com>2017-09-27 07:24:43 +0800
commit8ab23c713db1f5d45abb0ba433450591b8759809 (patch)
treec192fcf5eec3c7b422f3d0a91a4a96037c7f2fa6 /ui
parent77a48fb0b16d7415377b02a3b7d383c9ef86fcb6 (diff)
parent6ca519e97c4c282023ab6b7788715ff8d7ec8189 (diff)
downloadtangerine-wallet-browser-8ab23c713db1f5d45abb0ba433450591b8759809.tar.gz
tangerine-wallet-browser-8ab23c713db1f5d45abb0ba433450591b8759809.tar.zst
tangerine-wallet-browser-8ab23c713db1f5d45abb0ba433450591b8759809.zip
Merge branch 'master' into transactionControllerRefractorPt3
Diffstat (limited to 'ui')
-rw-r--r--ui/app/account-detail.js5
-rw-r--r--ui/app/actions.js11
-rw-r--r--ui/app/add-token.js31
-rw-r--r--ui/app/app.js3
-rw-r--r--ui/app/components/account-dropdowns.js21
-rw-r--r--ui/app/components/account-export.js28
-rw-r--r--ui/app/components/dropdown.js2
-rw-r--r--ui/app/components/network.js11
-rw-r--r--ui/app/components/pending-tx.js107
-rw-r--r--ui/app/components/tooltip.js2
-rw-r--r--ui/app/components/transaction-list-item-icon.js2
-rw-r--r--ui/app/components/transaction-list-item.js2
-rw-r--r--ui/app/conf-tx.js15
-rw-r--r--ui/app/config.js7
-rw-r--r--ui/app/css/lib.css5
-rw-r--r--ui/app/info.js2
-rw-r--r--ui/app/keychains/hd/create-vault-complete.js10
-rw-r--r--ui/app/send.js5
-rw-r--r--ui/app/unlock.js2
-rw-r--r--ui/app/util.js23
20 files changed, 217 insertions, 77 deletions
diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js
index 02089ecd0..90724dc3f 100644
--- a/ui/app/account-detail.js
+++ b/ui/app/account-detail.js
@@ -32,6 +32,7 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
currentAccountTab: state.metamask.currentAccountTab,
tokens: state.metamask.tokens,
+ computedBalances: state.metamask.computedBalances,
}
}
@@ -45,7 +46,7 @@ AccountDetailScreen.prototype.render = function () {
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]
+ var account = props.computedBalances[selected]
const { network, conversionRate, currentCurrency } = props
return (
@@ -180,7 +181,7 @@ AccountDetailScreen.prototype.render = function () {
}, [
h(EthBalance, {
- value: account && account.balance,
+ value: account && account.ethBalance,
conversionRate,
currentCurrency,
style: {
diff --git a/ui/app/actions.js b/ui/app/actions.js
index eebe65ba2..e793e6a21 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -104,6 +104,7 @@ var actions = {
txError: txError,
nextTx: nextTx,
previousTx: previousTx,
+ cancelAllTx: cancelAllTx,
viewPendingTx: viewPendingTx,
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
// app messages
@@ -457,6 +458,16 @@ function cancelTx (txData) {
}
}
+function cancelAllTx (txsData) {
+ return (dispatch) => {
+ txsData.forEach((txData, i) => {
+ background.cancelTransaction(txData.id, () => {
+ dispatch(actions.completedTx(txData.id))
+ i === txsData.length - 1 ? dispatch(actions.goHome()) : null
+ })
+ })
+ }
+}
//
// initialize screen
//
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index 15ef7a852..18adc7eb5 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -3,6 +3,8 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
+const Tooltip = require('./components/tooltip.js')
+
const ethUtil = require('ethereumjs-util')
const abi = require('human-standard-token-abi')
@@ -15,6 +17,7 @@ module.exports = connect(mapStateToProps)(AddTokenScreen)
function mapStateToProps (state) {
return {
+ identities: state.metamask.identities,
}
}
@@ -64,15 +67,25 @@ AddTokenScreen.prototype.render = function () {
}, [
h('div', [
- h('span', {
- style: { fontWeight: 'bold', paddingRight: '10px'},
- }, 'Token Address'),
+ h(Tooltip, {
+ position: 'top',
+ title: 'The contract of the actual token contract. Click for more info.',
+ }, [
+ h('a', {
+ style: { fontWeight: 'bold', paddingRight: '10px'},
+ href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address',
+ target: '_blank',
+ }, [
+ h('span', 'Token Contract Address '),
+ h('i.fa.fa-question-circle'),
+ ]),
+ ]),
]),
h('section.flex-row.flex-center', [
h('input#token-address', {
name: 'address',
- placeholder: 'Token Address',
+ placeholder: 'Token Contract Address',
onChange: this.tokenAddressDidChange.bind(this),
style: {
width: 'inherit',
@@ -171,7 +184,9 @@ AddTokenScreen.prototype.tokenAddressDidChange = function (event) {
AddTokenScreen.prototype.validateInputs = function () {
let msg = ''
const state = this.state
+ const identitiesList = Object.keys(this.props.identities)
const { address, symbol, decimals } = state
+ const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
const validAddress = ethUtil.isValidAddress(address)
if (!validAddress) {
@@ -189,7 +204,12 @@ AddTokenScreen.prototype.validateInputs = function () {
msg += 'Symbol must be between 0 and 10 characters.'
}
- const isValid = validAddress && validDecimals
+ const ownAddress = identitiesList.includes(standardAddress)
+ if (ownAddress) {
+ msg = 'Personal address detected. Input the token contract address.'
+ }
+
+ const isValid = validAddress && validDecimals && !ownAddress
if (!isValid) {
this.setState({
@@ -216,4 +236,3 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address)
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() })
}
}
-
diff --git a/ui/app/app.js b/ui/app/app.js
index 1f3d5b0f8..ee800ea90 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -42,6 +42,7 @@ function mapStateToProps (state) {
identities,
accounts,
address,
+ keyrings,
} = state.metamask
const selected = address || Object.keys(accounts)[0]
@@ -69,6 +70,7 @@ function mapStateToProps (state) {
// state needed to get account dropdown temporarily rendering from app bar
identities,
selected,
+ keyrings,
}
}
@@ -187,6 +189,7 @@ App.prototype.renderAppBar = function () {
identities: this.props.identities,
selected: this.props.currentView.context,
network: this.props.network,
+ keyrings: this.props.keyrings,
}, []),
// hamburger
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
index 7c24e70bd..b087a40d4 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/account-dropdowns.js
@@ -22,12 +22,19 @@ class AccountDropdowns extends Component {
}
renderAccounts () {
- const { identities, selected } = this.props
+ const { identities, selected, keyrings } = this.props
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
const isSelected = identity.address === selected
+ const simpleAddress = identity.address.substring(2).toLowerCase()
+
+ const keyring = keyrings.find((kr) => {
+ return kr.accounts.includes(simpleAddress) ||
+ kr.accounts.includes(identity.address)
+ })
+
return h(
DropdownMenuItem,
{
@@ -51,6 +58,7 @@ class AccountDropdowns extends Component {
},
},
),
+ this.indicateIfLoose(keyring),
h('span', {
style: {
marginLeft: '20px',
@@ -67,6 +75,14 @@ class AccountDropdowns extends Component {
})
}
+ indicateIfLoose (keyring) {
+ try { // Sometimes keyrings aren't loaded yet:
+ const type = keyring.type
+ const isLoose = type !== 'HD Key Tree'
+ return isLoose ? h('.keyring-label', 'LOOSE') : null
+ } catch (e) { return }
+ }
+
renderAccountSelector () {
const { actions } = this.props
const { accountSelectorActive } = this.state
@@ -145,6 +161,8 @@ class AccountDropdowns extends Component {
)
}
+
+
renderAccountOptions () {
const { actions } = this.props
const { optionsMenuActive } = this.state
@@ -278,6 +296,7 @@ AccountDropdowns.defaultProps = {
AccountDropdowns.propTypes = {
identities: PropTypes.objectOf(PropTypes.object),
selected: PropTypes.string,
+ keyrings: PropTypes.array,
}
const mapDispatchToProps = (dispatch) => {
diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js
index 330f73805..32b103c86 100644
--- a/ui/app/components/account-export.js
+++ b/ui/app/components/account-export.js
@@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const exportAsFile = require('../util').exportAsFile
const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions')
const ethUtil = require('ethereumjs-util')
@@ -20,20 +21,21 @@ function mapStateToProps (state) {
}
ExportAccountView.prototype.render = function () {
- var state = this.props
- var accountDetail = state.accountDetail
+ const state = this.props
+ const accountDetail = state.accountDetail
+ const nickname = state.identities[state.address].name
if (!accountDetail) return h('div')
- var accountExport = accountDetail.accountExport
+ const accountExport = accountDetail.accountExport
- var notExporting = accountExport === 'none'
- var exportRequested = accountExport === 'requested'
- var accountExported = accountExport === 'completed'
+ const notExporting = accountExport === 'none'
+ const exportRequested = accountExport === 'requested'
+ const accountExported = accountExport === 'completed'
if (notExporting) return h('div')
if (exportRequested) {
- var warning = `Export private keys at your own risk.`
+ const warning = `Export private keys at your own risk.`
return (
h('div', {
style: {
@@ -89,6 +91,8 @@ ExportAccountView.prototype.render = function () {
}
if (accountExported) {
+ const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
+
return h('div.privateKey', {
style: {
margin: '0 20px',
@@ -105,10 +109,16 @@ ExportAccountView.prototype.render = function () {
onClick: function (event) {
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
},
- }, ethUtil.stripHexPrefix(accountDetail.privateKey)),
+ }, plainKey),
h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, 'Done'),
+ h('button', {
+ style: {
+ marginLeft: '10px',
+ },
+ onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
+ }, 'Save as File'),
])
}
}
@@ -117,6 +127,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) {
if (event.key !== 'Enter') return
event.preventDefault()
- var input = document.getElementById('exportAccount').value
+ const input = document.getElementById('exportAccount').value
this.props.dispatch(actions.exportAccount(input, this.props.address))
}
diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdown.js
index 34c7149ee..73710acc2 100644
--- a/ui/app/components/dropdown.js
+++ b/ui/app/components/dropdown.js
@@ -32,7 +32,7 @@ class Dropdown extends Component {
'style',
`
li.dropdown-menu-item:hover { color:rgb(225, 225, 225); }
- li.dropdown-menu-item { color: rgb(185, 185, 185); }
+ li.dropdown-menu-item { color: rgb(185, 185, 185); position: relative }
`
),
...children,
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index 698a0bbb9..0dbe37cdd 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -22,7 +22,7 @@ Network.prototype.render = function () {
let iconName, hoverText
if (networkNumber === 'loading') {
- return h('span', {
+ return h('span.pointer', {
style: {
display: 'flex',
alignItems: 'center',
@@ -37,7 +37,7 @@ Network.prototype.render = function () {
},
src: 'images/loading.svg',
}),
- h('i.fa.fa-sort-desc'),
+ h('i.fa.fa-caret-down'),
])
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
@@ -73,7 +73,8 @@ Network.prototype.render = function () {
style: {
color: '#039396',
}},
- 'Ethereum Main Net'),
+ 'Main Network'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
case 'ropsten-test-network':
return h('.network-indicator', [
@@ -83,6 +84,7 @@ Network.prototype.render = function () {
color: '#ff6666',
}},
'Ropsten Test Net'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
case 'kovan-test-network':
return h('.network-indicator', [
@@ -92,6 +94,7 @@ Network.prototype.render = function () {
color: '#690496',
}},
'Kovan Test Net'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
case 'rinkeby-test-network':
return h('.network-indicator', [
@@ -101,6 +104,7 @@ Network.prototype.render = function () {
color: '#e7a218',
}},
'Rinkeby Test Net'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
default:
return h('.network-indicator', [
@@ -116,6 +120,7 @@ Network.prototype.render = function () {
color: '#AEAEAE',
}},
'Private Network'),
+ h('i.fa.fa-caret-down.fa-lg'),
])
}
})(),
diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js
index 5324ccd64..6f8c19a3c 100644
--- a/ui/app/components/pending-tx.js
+++ b/ui/app/components/pending-tx.js
@@ -33,7 +33,7 @@ function PendingTx () {
PendingTx.prototype.render = function () {
const props = this.props
- const { currentCurrency, blockGasLimit } = props
+ const { currentCurrency, blockGasLimit, computedBalances } = props
const conversionRate = props.conversionRate
const txMeta = this.gatherTxMeta()
@@ -42,8 +42,8 @@ PendingTx.prototype.render = function () {
// 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'
+ const account = computedBalances[address]
+ const balance = account ? account.ethBalance : '0x0'
// recipient check
const isValidAddress = !txParams.to || util.isValidAddress(txParams.to)
@@ -52,7 +52,9 @@ PendingTx.prototype.render = function () {
const gas = txParams.gas
const gasBn = hexToBn(gas)
const gasLimit = new BN(parseInt(blockGasLimit))
- const safeGasLimit = this.bnMultiplyByFraction(gasLimit, 19, 20).toString(10)
+ const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
+ const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
+ const safeGasLimit = safeGasLimitBN.toString(10)
// Gas Price
const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16)
@@ -66,6 +68,10 @@ PendingTx.prototype.render = function () {
const balanceBn = hexToBn(balance)
const insufficientBalance = balanceBn.lt(maxCost)
+ const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
+ const gasLimitSpecified = txMeta.gasLimitSpecified
+ const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
+ const showRejectAll = props.unconfTxListLength > 1
this.inputs = []
@@ -261,33 +267,44 @@ PendingTx.prototype.render = function () {
text-transform: uppercase;
}
`),
+ h('.cell.row', {
+ style: {
+ textAlign: 'center',
+ },
+ }, [
+ txMeta.simulationFails ?
+ h('.error', {
+ style: {
+ fontSize: '0.9em',
+ },
+ }, 'Transaction Error. Exception thrown in contract code.')
+ : null,
- txMeta.simulationFails ?
- h('.error', {
- style: {
- marginLeft: 50,
- fontSize: '0.9em',
- },
- }, 'Transaction Error. Exception thrown in contract code.')
- : null,
+ !isValidAddress ?
+ h('.error', {
+ style: {
+ fontSize: '0.9em',
+ },
+ }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
+ : null,
- !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: {
+ fontSize: '0.9em',
+ },
+ }, 'Insufficient balance for transaction')
+ : null,
+
+ (dangerousGasLimit && !gasLimitSpecified) ?
+ h('span.error', {
+ style: {
+ fontSize: '0.9em',
+ },
+ }, 'Gas limit set dangerously high. Approving this transaction is likely to fail.')
+ : null,
+ ]),
- 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', {
@@ -297,14 +314,6 @@ PendingTx.prototype.render = function () {
margin: '14px 25px',
},
}, [
-
-
- insufficientBalance ?
- h('button.btn-green', {
- onClick: props.buyEth,
- }, 'Buy Ether')
- : null,
-
h('button', {
onClick: (event) => {
this.resetGasFields()
@@ -312,18 +321,30 @@ PendingTx.prototype.render = function () {
},
}, 'Reset'),
- // Accept Button
- h('input.confirm.btn-green', {
- type: 'submit',
- value: 'SUBMIT',
- style: { marginLeft: '10px' },
- disabled: insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting,
- }),
+ // Accept Button or Buy Button
+ insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
+ h('input.confirm.btn-green', {
+ type: 'submit',
+ value: 'SUBMIT',
+ style: { marginLeft: '10px' },
+ disabled: buyDisabled,
+ }),
h('button.cancel.btn-red', {
onClick: props.cancelTransaction,
}, 'Reject'),
]),
+ showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
+ style: {
+ display: 'flex',
+ justifyContent: 'flex-end',
+ margin: '14px 25px',
+ },
+ }, [
+ h('button.cancel.btn-red', {
+ onClick: props.cancelAllTransactions,
+ }, 'Reject All'),
+ ]) : null,
]),
])
)
diff --git a/ui/app/components/tooltip.js b/ui/app/components/tooltip.js
index edbc074bb..efab2c497 100644
--- a/ui/app/components/tooltip.js
+++ b/ui/app/components/tooltip.js
@@ -17,6 +17,6 @@ Tooltip.prototype.render = function () {
return h(ReactTooltip, {
position: position || 'left',
title,
- fixed: false,
+ fixed: true,
}, children)
}
diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js
index 431054340..f442b05af 100644
--- a/ui/app/components/transaction-list-item-icon.js
+++ b/ui/app/components/transaction-list-item-icon.js
@@ -35,7 +35,7 @@ TransactionIcon.prototype.render = function () {
case 'submitted':
return h(Tooltip, {
title: 'Pending',
- position: 'bottom',
+ position: 'right',
}, [
h('i.fa.fa-ellipsis-h', {
style: {
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index 5d5d0bcc5..0e5c0b5a3 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -65,7 +65,7 @@ TransactionListItem.prototype.render = function () {
h(Tooltip, {
title: 'Transaction Number',
- position: 'bottom',
+ position: 'right',
}, [
h('span', {
style: {
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 34727ff78..15fb9a59f 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -29,6 +29,7 @@ function mapStateToProps (state) {
conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency,
blockGasLimit: state.metamask.currentBlockGasLimit,
+ computedBalances: state.metamask.computedBalances,
}
}
@@ -39,7 +40,7 @@ function ConfirmTxScreen () {
ConfirmTxScreen.prototype.render = function () {
const props = this.props
- const { network, provider, unapprovedTxs, currentCurrency,
+ const { network, provider, unapprovedTxs, currentCurrency, computedBalances,
unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network)
@@ -48,10 +49,11 @@ ConfirmTxScreen.prototype.render = function () {
var txParams = txData.params || {}
var isNotification = isPopupOrNotification() === 'notification'
-
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
+ const unconfTxListLength = unconfTxList.length
+
return (
h('.flex-column.flex-grow', [
@@ -101,10 +103,13 @@ ConfirmTxScreen.prototype.render = function () {
conversionRate,
currentCurrency,
blockGasLimit,
+ unconfTxListLength,
+ computedBalances,
// Actions
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
sendTransaction: this.sendTransaction.bind(this),
cancelTransaction: this.cancelTransaction.bind(this, txData),
+ cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
@@ -151,6 +156,12 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
this.props.dispatch(actions.cancelTx(txData))
}
+ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) {
+ this.stopPropagation(event)
+ event.preventDefault()
+ this.props.dispatch(actions.cancelAllTx(unconfTxList))
+}
+
ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
log.info('conf-tx.js: signing message')
var params = msgData.msgParams
diff --git a/ui/app/config.js b/ui/app/config.js
index 62785c49b..d64088ccb 100644
--- a/ui/app/config.js
+++ b/ui/app/config.js
@@ -5,7 +5,8 @@ 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')
+const exportAsFile = require('./util').exportAsFile
+
module.exports = connect(mapStateToProps)(ConfigScreen)
@@ -110,9 +111,9 @@ ConfigScreen.prototype.render = function () {
alignSelf: 'center',
},
onClick (event) {
- copyToClipboard(window.logState())
+ exportAsFile('MetaMask State Logs', window.logState())
},
- }, 'Copy State Logs'),
+ }, 'Download State Logs'),
]),
h('hr.horizontal-line'),
diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css
index 81647d1c1..f3acbee76 100644
--- a/ui/app/css/lib.css
+++ b/ui/app/css/lib.css
@@ -215,12 +215,13 @@ hr.horizontal-line {
z-index: 1;
font-size: 11px;
background: rgba(255,0,0,0.8);
- bottom: -47px;
color: white;
+ bottom: 0px;
+ left: -8px;
border-radius: 10px;
height: 20px;
min-width: 20px;
- position: relative;
+ position: absolute;
display: flex;
align-items: center;
justify-content: center;
diff --git a/ui/app/info.js b/ui/app/info.js
index c69d83715..4c7d4cb4c 100644
--- a/ui/app/info.js
+++ b/ui/app/info.js
@@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () {
[
h('div.fa.fa-support', [
h('a.info', {
- href: 'https://support.metamask.com',
+ href: 'https://support.metamask.io',
target: '_blank',
}, 'Visit our Support Center'),
]),
diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js
index c32751fff..745990351 100644
--- a/ui/app/keychains/hd/create-vault-complete.js
+++ b/ui/app/keychains/hd/create-vault-complete.js
@@ -3,6 +3,7 @@ const Component = require('react').Component
const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('../../actions')
+const exportAsFile = require('../../util').exportAsFile
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
@@ -65,8 +66,17 @@ CreateVaultCompleteScreen.prototype.render = function () {
style: {
margin: '24px',
fontSize: '0.9em',
+ marginBottom: '10px',
},
}, 'I\'ve copied it somewhere safe'),
+
+ h('button.primary', {
+ onClick: () => exportAsFile(`MetaMask Seed Words`, seed),
+ style: {
+ margin: '10px',
+ fontSize: '0.9em',
+ },
+ }, 'Save Seed Words As File'),
])
)
}
diff --git a/ui/app/send.js b/ui/app/send.js
index a21a219eb..e59c1130e 100644
--- a/ui/app/send.js
+++ b/ui/app/send.js
@@ -262,6 +262,11 @@ SendTransactionScreen.prototype.onSubmit = function () {
return this.props.dispatch(actions.displayWarning(message))
}
+ if ((util.isInvalidChecksumAddress(recipient))) {
+ message = 'Recipient address checksum is invalid.'
+ 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))
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index 9bacd5124..4180791c4 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -80,7 +80,7 @@ UnlockScreen.prototype.render = function () {
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
- }, 'I forgot my password.'),
+ }, 'Restore from seed phrase'),
]),
])
)
diff --git a/ui/app/util.js b/ui/app/util.js
index ac3f42c6b..3f8b4dcc3 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -36,6 +36,8 @@ module.exports = {
valueTable: valueTable,
bnTable: bnTable,
isHex: isHex,
+ exportAsFile: exportAsFile,
+ isInvalidChecksumAddress,
}
function valuesFor (obj) {
@@ -65,6 +67,12 @@ function isValidAddress (address) {
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
}
+function isInvalidChecksumAddress (address) {
+ var prefixed = ethUtil.addHexPrefix(address)
+ if (address === '0x0000000000000000000000000000000000000000') return false
+ return !isAllOneCase(prefixed) && !ethUtil.isValidChecksumAddress(prefixed) && ethUtil.isValidAddress(prefixed)
+}
+
function isAllOneCase (address) {
if (!address) return true
var lower = address.toLowerCase()
@@ -215,3 +223,18 @@ function readableDate (ms) {
function isHex (str) {
return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/))
}
+
+function exportAsFile (filename, data) {
+ // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
+ const blob = new Blob([data], {type: 'text/csv'})
+ if (window.navigator.msSaveOrOpenBlob) {
+ window.navigator.msSaveBlob(blob, filename)
+ } else {
+ const elem = window.document.createElement('a')
+ elem.href = window.URL.createObjectURL(blob)
+ elem.download = filename
+ document.body.appendChild(elem)
+ elem.click()
+ document.body.removeChild(elem)
+ }
+}