aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/_locales/en/messages.json3
-rw-r--r--app/scripts/background.js2
-rw-r--r--app/scripts/controllers/preferences.js68
-rw-r--r--app/scripts/metamask-controller.js3
-rw-r--r--old-ui/app/account-detail.js5
-rw-r--r--old-ui/app/add-suggested-token.js193
-rw-r--r--old-ui/app/app.js6
-rw-r--r--ui/app/actions.js38
-rw-r--r--ui/app/app.js3
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js118
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js25
-rw-r--r--ui/app/components/pages/confirm-add-suggested-token/index.js2
-rw-r--r--ui/app/components/pages/confirm-add-token/confirm-add-token.component.js4
-rw-r--r--ui/app/components/pages/confirm-add-token/confirm-add-token.container.js8
-rw-r--r--ui/app/components/pages/home.js10
-rw-r--r--ui/app/reducers/app.js9
-rw-r--r--ui/app/reducers/metamask.js1
-rw-r--r--ui/app/routes.js2
18 files changed, 490 insertions, 10 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 1b0183c92..c4c19762d 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -26,6 +26,9 @@
"addTokens": {
"message": "Add Tokens"
},
+ "addSuggestedTokens": {
+ "message": "Add Suggested Tokens"
+ },
"addAcquiredTokens": {
"message": "Add the tokens you've acquired using MetaMask"
},
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 7eb7b1255..c0b00730d 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -253,6 +253,7 @@ function setupController (initState, initLangCode) {
showUnconfirmedMessage: triggerUi,
unlockAccountMessage: triggerUi,
showUnapprovedTx: triggerUi,
+ showAddTokenUi: triggerUi,
// initial state
initState,
// initial locale code
@@ -446,3 +447,4 @@ extension.runtime.onInstalled.addListener(function (details) {
extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
}
})
+
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 707fd7de9..a42bb77b3 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -1,5 +1,6 @@
const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize
+const isValidAddress = require('ethereumjs-util').isValidAddress
const extend = require('xtend')
@@ -27,6 +28,7 @@ class PreferencesController {
currentAccountTab: 'history',
accountTokens: {},
tokens: [],
+ suggestedTokens: {},
useBlockie: false,
featureFlags: {},
currentLocale: opts.initLangCode,
@@ -37,6 +39,7 @@ class PreferencesController {
this.diagnostics = opts.diagnostics
this.network = opts.network
this.store = new ObservableStore(initState)
+ this.showAddTokenUi = opts.showAddTokenUi
this._subscribeProviderType()
}
// PUBLIC METHODS
@@ -51,6 +54,47 @@ class PreferencesController {
this.store.updateState({ useBlockie: val })
}
+ getSuggestedTokens () {
+ return this.store.getState().suggestedTokens
+ }
+
+ addSuggestedToken (tokenOpts) {
+ this._validateSuggestedTokenParams(tokenOpts)
+ const suggested = this.getSuggestedTokens()
+ const { rawAddress, symbol, decimals } = tokenOpts
+ const address = normalizeAddress(rawAddress)
+ const newEntry = { address, symbol, decimals }
+ suggested[address] = newEntry
+ this.store.updateState({ suggestedTokens: suggested })
+ }
+
+ /**
+ * RPC engine middleware for requesting new token added
+ *
+ * @param req
+ * @param res
+ * @param {Function} - next
+ * @param {Function} - end
+ */
+ requestAddToken (req, res, next, end) {
+ if (req.method === 'eth_watchToken') {
+ const [ rawAddress, symbol, decimals ] = req.params
+ this._validateSuggestedTokenParams({ rawAddress, symbol, decimals })
+ const tokenOpts = {
+ rawAddress,
+ decimals,
+ symbol,
+ }
+
+ this.addSuggestedToken(tokenOpts)
+ this.showAddTokenUi()
+ res.result = rawAddress
+ return end()
+ } else {
+ return next()
+ }
+ }
+
/**
* Getter for the `useBlockie` property
*
@@ -186,6 +230,13 @@ class PreferencesController {
return selected
}
+ removeSuggestedTokens () {
+ return new Promise((resolve, reject) => {
+ this.store.updateState({ suggestedTokens: {} })
+ resolve()
+ })
+ }
+
/**
* Setter for the `selectedAddress` property
*
@@ -387,6 +438,23 @@ class PreferencesController {
//
// PRIVATE METHODS
//
+
+ /**
+ * Validates that the passed options for suggested token have all required properties.
+ *
+ * @param {Object} opts The options object to validate
+ * @throws {string} Throw a custom error indicating that address, symbol and/or decimals
+ * doesn't fulfill requirements
+ *
+ */
+ _validateSuggestedTokenParams (opts) {
+ const { rawAddress, symbol, decimals } = opts
+ if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`)
+ if (!(symbol.length < 5)) throw new Error(`Invalid symbol ${symbol} more than four characters`)
+ const numDecimals = parseInt(decimals, 10)
+ if (isNaN(numDecimals) || numDecimals > 18 || numDecimals < 0) throw new Error(`Invalid decimals ${decimals}`)
+ if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`)
+
/**
* Subscription to network provider type.
*
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index db323e3fe..1464b16a6 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -87,6 +87,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
+ showAddTokenUi: opts.showAddTokenUi,
network: this.networkController,
})
@@ -392,6 +393,7 @@ module.exports = class MetamaskController extends EventEmitter {
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
addToken: nodeify(preferencesController.addToken, preferencesController),
removeToken: nodeify(preferencesController.removeToken, preferencesController),
+ removeSuggestedTokens: nodeify(preferencesController.removeSuggestedTokens, preferencesController),
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
@@ -1243,6 +1245,7 @@ module.exports = class MetamaskController extends EventEmitter {
engine.push(createOriginMiddleware({ origin }))
engine.push(createLoggerMiddleware({ origin }))
engine.push(filterMiddleware)
+ engine.push(this.preferencesController.requestAddToken.bind(this.preferencesController))
engine.push(createProviderMiddleware({ provider: this.provider }))
// setup connection
diff --git a/old-ui/app/account-detail.js b/old-ui/app/account-detail.js
index c67f0cf71..d240fc38e 100644
--- a/old-ui/app/account-detail.js
+++ b/old-ui/app/account-detail.js
@@ -32,6 +32,7 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
currentAccountTab: state.metamask.currentAccountTab,
tokens: state.metamask.tokens,
+ suggestedTokens: state.metamask.suggestedTokens,
computedBalances: state.metamask.computedBalances,
}
}
@@ -49,6 +50,10 @@ AccountDetailScreen.prototype.render = function () {
var account = props.accounts[selected]
const { network, conversionRate, currentCurrency } = props
+ if (Object.keys(props.suggestedTokens).length > 0) {
+ this.props.dispatch(actions.showAddSuggestedTokenPage())
+ }
+
return (
h('.account-detail-section.full-flex-height', [
diff --git a/old-ui/app/add-suggested-token.js b/old-ui/app/add-suggested-token.js
new file mode 100644
index 000000000..1a0fc0461
--- /dev/null
+++ b/old-ui/app/add-suggested-token.js
@@ -0,0 +1,193 @@
+const inherits = require('util').inherits
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const connect = require('react-redux').connect
+const actions = require('../../ui/app/actions')
+const Tooltip = require('./components/tooltip.js')
+const ethUtil = require('ethereumjs-util')
+
+module.exports = connect(mapStateToProps)(AddSuggestedTokenScreen)
+
+function mapStateToProps (state) {
+ return {
+ identities: state.metamask.identities,
+ suggestedTokens: state.metamask.suggestedTokens,
+ }
+}
+
+inherits(AddSuggestedTokenScreen, Component)
+function AddSuggestedTokenScreen () {
+ this.state = {
+ warning: null,
+ }
+ Component.call(this)
+}
+
+AddSuggestedTokenScreen.prototype.render = function () {
+ const state = this.state
+ const props = this.props
+ const { warning } = state
+ const key = Object.keys(props.suggestedTokens)[0]
+ const { address, symbol, decimals } = props.suggestedTokens[key]
+
+ return (
+ h('.flex-column.flex-grow', [
+
+ // subtitle and nav
+ h('.section-title.flex-row.flex-center', [
+ h('h2.page-subtitle', 'Add Suggested Token'),
+ ]),
+
+ h('.error', {
+ style: {
+ display: warning ? 'block' : 'none',
+ padding: '0 20px',
+ textAlign: 'center',
+ },
+ }, warning),
+
+ // conf view
+ h('.flex-column.flex-justify-center.flex-grow.select-none', [
+ h('.flex-space-around', {
+ style: {
+ padding: '20px',
+ },
+ }, [
+
+ h('div', [
+ h(Tooltip, {
+ position: 'top',
+ title: 'The contract of the actual token contract. Click for more info.',
+ }, [
+ h('a', {
+ style: { fontWeight: 'bold', paddingRight: '10px'},
+ href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address',
+ target: '_blank',
+ }, [
+ h('span', 'Token Contract Address '),
+ h('i.fa.fa-question-circle'),
+ ]),
+ ]),
+ ]),
+
+ h('section.flex-row.flex-center', [
+ h('p#token-address', {
+ name: 'address',
+ style: {
+ width: 'inherit',
+ flex: '1 0 auto',
+ height: '30px',
+ margin: '8px',
+ },
+ }, address),
+ ]),
+
+ h('div', [
+ h('span', {
+ style: { fontWeight: 'bold', paddingRight: '10px'},
+ }, 'Token Symbol'),
+ ]),
+
+ h('div', { style: {display: 'flex'} }, [
+ h('p#token_symbol', {
+ style: {
+ width: 'inherit',
+ flex: '1 0 auto',
+ height: '30px',
+ margin: '8px',
+ },
+ }, symbol),
+ ]),
+
+ h('div', [
+ h('span', {
+ style: { fontWeight: 'bold', paddingRight: '10px'},
+ }, 'Decimals of Precision'),
+ ]),
+
+ h('div', { style: {display: 'flex'} }, [
+ h('p#token_decimals', {
+ type: 'number',
+ style: {
+ width: 'inherit',
+ flex: '1 0 auto',
+ height: '30px',
+ margin: '8px',
+ },
+ }, decimals),
+ ]),
+
+ h('button', {
+ style: {
+ alignSelf: 'center',
+ margin: '8px',
+ },
+ onClick: (event) => {
+ this.props.dispatch(actions.removeSuggestedTokens())
+ },
+ }, 'Cancel'),
+
+ h('button', {
+ style: {
+ alignSelf: 'center',
+ margin: '8px',
+ },
+ onClick: (event) => {
+ const valid = this.validateInputs({ address, symbol, decimals })
+ if (!valid) return
+
+ this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals))
+ .then(() => {
+ this.props.dispatch(actions.removeSuggestedTokens())
+ })
+ },
+ }, 'Add'),
+ ]),
+ ]),
+ ])
+ )
+}
+
+AddSuggestedTokenScreen.prototype.componentWillMount = function () {
+ if (typeof global.ethereumProvider === 'undefined') return
+}
+
+AddSuggestedTokenScreen.prototype.validateInputs = function (opts) {
+ let msg = ''
+ const identitiesList = Object.keys(this.props.identities)
+ const { address, symbol, decimals } = opts
+ const standardAddress = ethUtil.addHexPrefix(address).toLowerCase()
+
+ const validAddress = ethUtil.isValidAddress(address)
+ if (!validAddress) {
+ msg += 'Address is invalid.'
+ }
+
+ const validDecimals = decimals >= 0 && decimals < 36
+ if (!validDecimals) {
+ msg += 'Decimals must be at least 0, and not over 36. '
+ }
+
+ const symbolLen = symbol.trim().length
+ const validSymbol = symbolLen > 0 && symbolLen < 10
+ if (!validSymbol) {
+ msg += 'Symbol must be between 0 and 10 characters.'
+ }
+
+ const ownAddress = identitiesList.includes(standardAddress)
+ if (ownAddress) {
+ msg = 'Personal address detected. Input the token contract address.'
+ }
+
+ const isValid = validAddress && validDecimals && !ownAddress
+
+ if (!isValid) {
+ this.setState({
+ warning: msg,
+ })
+ } else {
+ this.setState({ warning: null })
+ }
+
+ return isValid
+}
diff --git a/old-ui/app/app.js b/old-ui/app/app.js
index d3e9e823b..f60ee5beb 100644
--- a/old-ui/app/app.js
+++ b/old-ui/app/app.js
@@ -23,6 +23,7 @@ const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views
const ConfigScreen = require('./config')
const AddTokenScreen = require('./add-token')
+const AddSuggestedTokenScreen = require('./add-suggested-token')
const Import = require('./accounts/import')
const InfoScreen = require('./info')
const NewUiAnnouncement = require('./new-ui-annoucement')
@@ -74,6 +75,7 @@ function mapStateToProps (state) {
lostAccounts: state.metamask.lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
featureFlags,
+ suggestedTokens: state.metamask.suggestedTokens,
// state needed to get account dropdown temporarily rendering from app bar
identities,
@@ -236,6 +238,10 @@ App.prototype.renderPrimary = function () {
log.debug('rendering add-token screen from unlock screen.')
return h(AddTokenScreen, {key: 'add-token'})
+ case 'add-suggested-token':
+ log.debug('rendering add-token screen from unlock screen.')
+ return h(AddSuggestedTokenScreen, {key: 'add-suggested-token'})
+
case 'config':
log.debug('rendering config screen')
return h(ConfigScreen, {key: 'config'})
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 4f71d911b..5cc7dc2fa 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -221,11 +221,14 @@ var actions = {
SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE',
showConfigPage,
SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE',
+ SHOW_ADD_SUGGESTED_TOKEN_PAGE: 'SHOW_ADD_SUGGESTED_TOKEN_PAGE',
showAddTokenPage,
+ showAddSuggestedTokenPage,
addToken,
addTokens,
removeToken,
updateTokens,
+ removeSuggestedTokens,
UPDATE_TOKENS: 'UPDATE_TOKENS',
setRpcTarget: setRpcTarget,
setProviderType: setProviderType,
@@ -1559,6 +1562,13 @@ function showAddTokenPage (transitionForward = true) {
}
}
+function showAddSuggestedTokenPage (transitionForward = true) {
+ return {
+ type: actions.SHOW_ADD_SUGGESTED_TOKEN_PAGE,
+ value: transitionForward,
+ }
+}
+
function addToken (address, symbol, decimals) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@@ -1613,6 +1623,28 @@ function addTokens (tokens) {
}
}
+function removeSuggestedTokens () {
+ return (dispatch) => {
+ dispatch(actions.showLoadingIndication())
+ background.removeSuggestedTokens((err) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ }
+ dispatch(actions.clearPendingTokens())
+ if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
+ return global.platform.closeCurrentWindow()
+ }
+ })
+ }
+}
+
+function clearPendingTokens () {
+ return {
+ type: actions.CLEAR_PENDING_TOKENS,
+ }
+}
+
function updateTokens (newTokens) {
return {
type: actions.UPDATE_TOKENS,
@@ -2244,9 +2276,3 @@ function setPendingTokens (pendingTokens) {
payload: tokens,
}
}
-
-function clearPendingTokens () {
- return {
- type: actions.CLEAR_PENDING_TOKENS,
- }
-}
diff --git a/ui/app/app.js b/ui/app/app.js
index dbb6146d1..83c063c3f 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -26,6 +26,7 @@ const RestoreVaultPage = require('./components/pages/keychains/restore-vault').d
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const AddTokenPage = require('./components/pages/add-token')
const ConfirmAddTokenPage = require('./components/pages/confirm-add-token')
+const ConfirmAddSuggestedTokenPage = require('./components/pages/confirm-add-suggested-token')
const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice')
@@ -52,6 +53,7 @@ const {
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
CONFIRM_ADD_TOKEN_ROUTE,
+ CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
@@ -86,6 +88,7 @@ class App extends Component {
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen }),
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
h(Authenticated, { path: CONFIRM_ADD_TOKEN_ROUTE, exact, component: ConfirmAddTokenPage }),
+ h(Authenticated, { path: CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, exact, component: ConfirmAddSuggestedTokenPage }),
h(Authenticated, { path: NEW_ACCOUNT_ROUTE, component: CreateAccountPage }),
h(Authenticated, { path: DEFAULT_ROUTE, exact, component: Home }),
])
diff --git a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
new file mode 100644
index 000000000..2220ae685
--- /dev/null
+++ b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
@@ -0,0 +1,118 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import { DEFAULT_ROUTE } from '../../../routes'
+import Button from '../../button'
+import Identicon from '../../../components/identicon'
+import TokenBalance from '../confirm-add-token/token-balance'
+
+export default class ConfirmAddSuggestedToken extends Component {
+ static contextTypes = {
+ t: PropTypes.func,
+ }
+
+ static propTypes = {
+ history: PropTypes.object,
+ clearPendingTokens: PropTypes.func,
+ addTokens: PropTypes.func,
+ pendingTokens: PropTypes.object,
+ removeSuggestedTokens: PropTypes.func,
+ }
+
+ componentDidMount () {
+ const { pendingTokens = {}, history } = this.props
+
+ if (Object.keys(pendingTokens).length === 0) {
+ history.push(DEFAULT_ROUTE)
+ }
+ }
+
+ getTokenName (name, symbol) {
+ return typeof name === 'undefined'
+ ? symbol
+ : `${name} (${symbol})`
+ }
+
+ render () {
+ const { addTokens, clearPendingTokens, pendingTokens, removeSuggestedTokens } = this.props
+
+ return (
+ <div className="page-container">
+ <div className="page-container__header">
+ <div className="page-container__title">
+ { this.context.t('addSuggestedTokens') }
+ </div>
+ <div className="page-container__subtitle">
+ { this.context.t('likeToAddTokens') }
+ </div>
+ </div>
+ <div className="page-container__content">
+ <div className="confirm-add-token">
+ <div className="confirm-add-token__header">
+ <div className="confirm-add-token__token">
+ { this.context.t('token') }
+ </div>
+ <div className="confirm-add-token__balance">
+ { this.context.t('balance') }
+ </div>
+ </div>
+ <div className="confirm-add-token__token-list">
+ {
+ Object.entries(pendingTokens)
+ .map(([ address, token ]) => {
+ const { name, symbol } = token
+
+ return (
+ <div
+ className="confirm-add-token__token-list-item"
+ key={address}
+ >
+ <div className="confirm-add-token__token confirm-add-token__data">
+ <Identicon
+ className="confirm-add-token__token-icon"
+ diameter={48}
+ address={address}
+ />
+ <div className="confirm-add-token__name">
+ { this.getTokenName(name, symbol) }
+ </div>
+ </div>
+ <div className="confirm-add-token__balance">
+ <TokenBalance token={token} />
+ </div>
+ </div>
+ )
+ })
+ }
+ </div>
+ </div>
+ </div>
+ <div className="page-container__footer">
+ <Button
+ type="default"
+ large
+ className="page-container__footer-button"
+ onClick={() => {
+ removeSuggestedTokens()
+ }}
+ >
+ { this.context.t('cancel') }
+ </Button>
+ <Button
+ type="primary"
+ large
+ className="page-container__footer-button"
+ onClick={() => {
+ addTokens(pendingTokens)
+ .then(() => {
+ clearPendingTokens()
+ removeSuggestedTokens()
+ })
+ }}
+ >
+ { this.context.t('addTokens') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
new file mode 100644
index 000000000..938c363b4
--- /dev/null
+++ b/ui/app/components/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
@@ -0,0 +1,25 @@
+import { connect } from 'react-redux'
+import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component'
+
+const extend = require('xtend')
+
+const { addTokens, clearPendingTokens, removeSuggestedTokens } = require('../../../actions')
+
+const mapStateToProps = ({ metamask }) => {
+ const { pendingTokens, suggestedTokens } = metamask
+ const params = extend(pendingTokens, suggestedTokens)
+
+ return {
+ pendingTokens: params,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ addTokens: tokens => dispatch(addTokens(tokens)),
+ clearPendingTokens: () => dispatch(clearPendingTokens()),
+ removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ConfirmAddSuggestedToken)
diff --git a/ui/app/components/pages/confirm-add-suggested-token/index.js b/ui/app/components/pages/confirm-add-suggested-token/index.js
new file mode 100644
index 000000000..2ca56b43c
--- /dev/null
+++ b/ui/app/components/pages/confirm-add-suggested-token/index.js
@@ -0,0 +1,2 @@
+import ConfirmAddSuggestedToken from './confirm-add-suggested-token.container'
+module.exports = ConfirmAddSuggestedToken
diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
index 65d654b92..0f27ceb53 100644
--- a/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
+++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.component.js
@@ -90,7 +90,9 @@ export default class ConfirmAddToken extends Component {
type="default"
large
className="page-container__footer-button"
- onClick={() => history.push(ADD_TOKEN_ROUTE)}
+ onClick={() => {
+ history.push(ADD_TOKEN_ROUTE)
+ }}
>
{ this.context.t('back') }
</Button>
diff --git a/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js b/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js
index 0190024d9..500b406bb 100644
--- a/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js
+++ b/ui/app/components/pages/confirm-add-token/confirm-add-token.container.js
@@ -1,12 +1,16 @@
import { connect } from 'react-redux'
import ConfirmAddToken from './confirm-add-token.component'
+const extend = require('xtend')
+
const { addTokens, clearPendingTokens } = require('../../../actions')
const mapStateToProps = ({ metamask }) => {
- const { pendingTokens } = metamask
+ const { pendingTokens, suggestedTokens } = metamask
+ const params = extend(pendingTokens, suggestedTokens)
+
return {
- pendingTokens,
+ pendingTokens: params,
}
}
diff --git a/ui/app/components/pages/home.js b/ui/app/components/pages/home.js
index 5e3fdc9af..6ee083579 100644
--- a/ui/app/components/pages/home.js
+++ b/ui/app/components/pages/home.js
@@ -25,6 +25,7 @@ const {
RESTORE_VAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE,
+ CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
} = require('../../routes')
const { unconfirmedTransactionsCountSelector } = require('../../selectors/confirm-transaction')
@@ -33,9 +34,15 @@ class Home extends Component {
componentDidMount () {
const {
history,
+ suggestedTokens = {},
unconfirmedTransactionsCount = 0,
} = this.props
+ // suggested new tokens
+ if (Object.keys(suggestedTokens).length > 0) {
+ history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE)
+ }
+
// unapprovedTxs and unapproved messages
if (unconfirmedTransactionsCount > 0) {
history.push(CONFIRM_TRANSACTION_ROUTE)
@@ -165,6 +172,7 @@ Home.propTypes = {
isPopup: PropTypes.bool,
isMouseUser: PropTypes.bool,
t: PropTypes.func,
+ suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number,
}
@@ -226,7 +234,7 @@ function mapStateToProps (state) {
isRevealingSeedWords: state.metamask.isRevealingSeedWords,
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
-
+ suggestedTokens: state.metamask.suggestedTokens,
// state needed to get account dropdown temporarily rendering from app bar
selected,
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index 50d8bcba7..f76b73132 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -196,6 +196,15 @@ function reduceApp (state, action) {
transForward: action.value,
})
+ case actions.SHOW_ADD_SUGGESTED_TOKEN_PAGE:
+ return extend(appState, {
+ currentView: {
+ name: 'add-suggested-token',
+ context: appState.currentView.context,
+ },
+ transForward: action.value,
+ })
+
case actions.SHOW_IMPORT_PAGE:
return extend(appState, {
currentView: {
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 3f1d3394f..9e472bc6f 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -29,6 +29,7 @@ function reduceMetamask (state, action) {
tokenExchangeRates: {},
tokens: [],
pendingTokens: {},
+ suggestedTokens: {},
send: {
gasLimit: null,
gasPrice: null,
diff --git a/ui/app/routes.js b/ui/app/routes.js
index f6b2a7a55..76afed5db 100644
--- a/ui/app/routes.js
+++ b/ui/app/routes.js
@@ -7,6 +7,7 @@ const CONFIRM_SEED_ROUTE = '/confirm-seed'
const RESTORE_VAULT_ROUTE = '/restore-vault'
const ADD_TOKEN_ROUTE = '/add-token'
const CONFIRM_ADD_TOKEN_ROUTE = '/confirm-add-token'
+const CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE = '/confirm-add-suggested-token'
const NEW_ACCOUNT_ROUTE = '/new-account'
const IMPORT_ACCOUNT_ROUTE = '/new-account/import'
const CONNECT_HARDWARE_ROUTE = '/new-account/connect'
@@ -41,6 +42,7 @@ module.exports = {
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
CONFIRM_ADD_TOKEN_ROUTE,
+ CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
CONNECT_HARDWARE_ROUTE,