diff options
Diffstat (limited to 'ui/app')
45 files changed, 1627 insertions, 6715 deletions
diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 514be7f25..7a78a360c 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -16,7 +16,6 @@ const ExportAccountView = require('./components/account-export') const ethUtil = require('ethereumjs-util') const EditableLabel = require('./components/editable-label') const Tooltip = require('./components/tooltip') -const BuyButtonSubview = require('./components/buy-button-subview') module.exports = connect(mapStateToProps)(AccountDetailScreen) function mapStateToProps (state) { @@ -30,6 +29,8 @@ function mapStateToProps (state) { unapprovedMsgs: valuesFor(state.metamask.unapprovedMsgs), shapeShiftTxList: state.metamask.shapeShiftTxList, transactions: state.metamask.selectedAddressTxList || [], + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -44,7 +45,7 @@ AccountDetailScreen.prototype.render = function () { var checksumAddress = selected && ethUtil.toChecksumAddress(selected) var identity = props.identities[selected] var account = props.accounts[selected] - const { network } = props + const { network, conversionRate, currentCurrency } = props return ( @@ -60,7 +61,7 @@ AccountDetailScreen.prototype.render = function () { // header - identicon + nav h('div', { style: { - marginTop: '20px', + paddingTop: '20px', display: 'flex', justifyContent: 'flex-start', alignItems: 'flex-start', @@ -183,6 +184,8 @@ AccountDetailScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, + conversionRate, + currentCurrency, style: { lineHeight: '7px', marginTop: '10px', @@ -238,19 +241,19 @@ AccountDetailScreen.prototype.subview = function () { case 'export': var state = extend({key: 'export'}, this.props) return h(ExportAccountView, state) - case 'buyForm': - return h(BuyButtonSubview, extend({key: 'buyForm'}, this.props)) default: return this.transactionList() } } AccountDetailScreen.prototype.transactionList = function () { - const {transactions, unapprovedMsgs, address, network, shapeShiftTxList } = this.props + 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) => { @@ -262,15 +265,3 @@ AccountDetailScreen.prototype.transactionList = function () { AccountDetailScreen.prototype.requestAccountExport = function () { this.props.dispatch(actions.requestExportAccount()) } - - -AccountDetailScreen.prototype.buyButtonDeligator = function () { - var props = this.props - var selected = props.address || Object.keys(props.accounts)[0] - - if (this.props.accountDetail.subview === 'buyForm') { - props.dispatch(actions.backToAccountDetail(props.address)) - } else { - props.dispatch(actions.buyEthView(selected)) - } -} diff --git a/ui/app/accounts/account-list-item.js b/ui/app/accounts/account-list-item.js index 2a3c13d05..10a0b6cc7 100644 --- a/ui/app/accounts/account-list-item.js +++ b/ui/app/accounts/account-list-item.js @@ -15,7 +15,8 @@ function AccountListItem () { } AccountListItem.prototype.render = function () { - const { identity, selectedAddress, accounts, onShowDetail } = this.props + const { identity, selectedAddress, accounts, onShowDetail, + conversionRate, currentCurrency } = this.props const checksumAddress = identity && identity.address && ethUtil.toChecksumAddress(identity.address) const isSelected = selectedAddress === identity.address @@ -52,6 +53,8 @@ AccountListItem.prototype.render = function () { }, checksumAddress), h(EthBalance, { value: account && account.balance, + currentCurrency, + conversionRate, style: { lineHeight: '7px', marginTop: '10px', diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index 96350852a..a0f0f9bdb 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -73,7 +73,7 @@ AccountImportSubview.prototype.render = function () { ) } -AccountImportSubview.prototype.renderImportView = function() { +AccountImportSubview.prototype.renderImportView = function () { const props = this.props const state = this.state || {} const { type } = state diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js index 1c2b331d4..5ed31ab0a 100644 --- a/ui/app/accounts/import/json.js +++ b/ui/app/accounts/import/json.js @@ -60,7 +60,7 @@ JsonImportSubview.prototype.render = function () { }, }, 'Import'), - error ? h('span.warning', error) : null, + error ? h('span.error', error) : null, ]) ) } @@ -95,4 +95,3 @@ JsonImportSubview.prototype.createNewKeychain = function () { this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) } - diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js index b139a0374..68ccee58e 100644 --- a/ui/app/accounts/import/private-key.js +++ b/ui/app/accounts/import/private-key.js @@ -48,7 +48,7 @@ PrivateKeyImportView.prototype.render = function () { }, }, 'Import'), - error ? h('span.warning', error) : null, + error ? h('span.error', error) : null, ]) ) } @@ -65,4 +65,3 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { const privateKey = input.value this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) } - diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index e236a4e85..ac2615cd7 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -11,7 +11,7 @@ module.exports = connect(mapStateToProps)(AccountsScreen) function mapStateToProps (state) { const pendingTxs = valuesFor(state.metamask.unapprovedTxs) - .filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network) + .filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network) const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) const pending = pendingTxs.concat(pendingMsgs) @@ -23,6 +23,8 @@ function mapStateToProps (state) { scrollToBottom: state.appState.scrollToBottom, pending, keyrings: state.metamask.keyrings, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, } } @@ -33,7 +35,7 @@ function AccountsScreen () { AccountsScreen.prototype.render = function () { const props = this.props - const { keyrings } = props + const { keyrings, conversionRate, currentCurrency } = props const identityList = valuesFor(props.identities) const unapprovedTxList = valuesFor(props.unapprovedTxs) @@ -81,6 +83,8 @@ AccountsScreen.prototype.render = function () { key: `acct-panel-${identity.address}`, identity, selectedAddress: this.props.selectedAddress, + conversionRate, + currentCurrency, accounts: this.props.accounts, onShowDetail: this.onShowDetail.bind(this), pending, @@ -97,7 +101,7 @@ AccountsScreen.prototype.render = function () { style: { display: 'flex', height: '40px', - paddint: '10px', + padding: '10px', justifyContent: 'center', alignItems: 'center', }, diff --git a/ui/app/actions.js b/ui/app/actions.js index b09021577..1a3557cb4 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1,3 +1,5 @@ +const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url') + var actions = { _setBackgroundConnection: _setBackgroundConnection, @@ -132,10 +134,6 @@ var actions = { buyEth: buyEth, buyEthView: buyEthView, BUY_ETH_VIEW: 'BUY_ETH_VIEW', - UPDATE_COINBASE_AMOUNT: 'UPDATE_COIBASE_AMOUNT', - updateCoinBaseAmount: updateCoinBaseAmount, - UPDATE_BUY_ADDRESS: 'UPDATE_BUY_ADDRESS', - updateBuyAddress: updateBuyAddress, COINBASE_SUBVIEW: 'COINBASE_SUBVIEW', coinBaseSubview: coinBaseSubview, SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW', @@ -269,11 +267,14 @@ function requestRevealSeed (password) { dispatch(actions.showLoadingIndication()) log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { - if (err) return dispatch(actions.displayWarning(err.message)) + if (err) { + return dispatch(actions.displayWarning(err.message)) + } log.debug(`background.placeSeedWords`) - background.placeSeedWords((err) => { + background.placeSeedWords((err, result) => { if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideLoadingIndication()) + dispatch(actions.showNewVaultSeed(result)) }) }) } @@ -296,10 +297,10 @@ function importNewAccount (strategy, args) { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) log.debug(`background.importAccountWithStrategy`) background.importAccountWithStrategy(strategy, args, (err) => { - dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) log.debug(`background.getState`) background.getState((err, newState) => { + dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -313,7 +314,7 @@ function importNewAccount (strategy, args) { } } -function navigateToNewAccountScreen() { +function navigateToNewAccountScreen () { return { type: this.NEW_ACCOUNT_SCREEN, } @@ -392,11 +393,10 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { - web3.eth.sendTransaction(txData, (err, data) => { + global.ethQuery.sendTransaction(txData, (err, data) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideWarning()) - dispatch(actions.goHome()) }) dispatch(this.showConfTxPage()) } @@ -421,6 +421,7 @@ function updateAndApproveTx (txData) { return (dispatch) => { log.debug(`actions calling background.updateAndApproveTx`) background.updateAndApproveTransaction(txData, (err) => { + dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.txError(err)) return console.error(err.message) @@ -664,7 +665,7 @@ function clearNotices () { } } -function markAccountsFound() { +function markAccountsFound () { log.debug(`background.markAccountsFound`) return callBackgroundThenUpdate(background.markAccountsFound) } @@ -698,7 +699,7 @@ function setRpcTarget (newRpc) { } } -// Calls the addressBookController to add a new address. +// Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname) { log.debug(`background.addToAddressBook`) return (dispatch) => { @@ -772,22 +773,30 @@ function requestExportAccount () { } } -function exportAccount (address) { +function exportAccount (password, address) { var self = this return function (dispatch) { dispatch(self.showLoadingIndication()) - log.debug(`background.exportAccount`) - background.exportAccount(address, function (err, result) { - dispatch(self.hideLoadingIndication()) - + log.debug(`background.submitPassword`) + background.submitPassword(password, function (err) { if (err) { - log.error(err) - return dispatch(self.displayWarning('Had a problem exporting the account.')) + log.error('Error in submiting password.') + dispatch(self.hideLoadingIndication()) + return dispatch(self.displayWarning('Incorrect Password.')) } + log.debug(`background.exportAccount`) + background.exportAccount(address, function (err, result) { + dispatch(self.hideLoadingIndication()) - dispatch(self.showPrivateKey(result)) + if (err) { + log.error(err) + return dispatch(self.displayWarning('Had a problem exporting the account.')) + } + + dispatch(self.showPrivateKey(result)) + }) }) } } @@ -822,10 +831,10 @@ function showSendPage () { } } -function buyEth (address, amount) { +function buyEth (opts) { return (dispatch) => { - log.debug(`background.buyEth`) - background.buyEth(address, amount) + const url = getBuyEthUrl(opts) + global.platform.openWindow({ url }) dispatch({ type: actions.BUY_ETH, }) @@ -839,20 +848,6 @@ function buyEthView (address) { } } -function updateCoinBaseAmount (value) { - return { - type: actions.UPDATE_COINBASE_AMOUNT, - value, - } -} - -function updateBuyAddress (value) { - return { - type: actions.UPDATE_BUY_ADDRESS, - value, - } -} - function coinBaseSubview () { return { type: actions.COINBASE_SUBVIEW, @@ -983,7 +978,7 @@ function callBackgroundThenUpdate (method, ...args) { } } -function forceUpdateMetamaskState(dispatch){ +function forceUpdateMetamaskState (dispatch) { log.debug(`background.getState`) background.getState((err, newState) => { if (err) { diff --git a/ui/app/app.js b/ui/app/app.js index 9c1ba8a3a..53dbc3354 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -249,13 +249,31 @@ App.prototype.renderNetworkDropdown = function () { h(DropMenuItem, { label: 'Ropsten Test Network', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setProviderType('testnet')), + 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)), @@ -323,7 +341,7 @@ App.prototype.renderDropdown = function () { }), h(DropMenuItem, { - label: 'Info', + label: 'Info/Help', closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }), action: () => this.props.dispatch(actions.showInfoPage()), icon: h('i.fa.fa-question.fa-lg'), @@ -543,5 +561,4 @@ App.prototype.renderCommonRpc = function (rpcList, provider) { }) } }) - } diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 6d8b099a5..888196c5d 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -4,14 +4,21 @@ 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 = ExportAccountView +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 () { console.log('EXPORT VIEW') console.dir(this.props) @@ -28,35 +35,58 @@ ExportAccountView.prototype.render = function () { if (notExporting) return h('div') if (exportRequested) { - var warning = `Exporting your private key is very dangerous, - and you should only do it if you know what you're doing.` - var confirmation = `If you're absolutely sure, type "I understand" below and - submit.` + var warning = `Export private keys at your own risk.` return ( - h('div', { - key: 'exporting', style: { - margin: '0 20px', + display: 'inline-block', + textAlign: 'center', }, - }, [ - h('p.error', warning), - h('p', confirmation), - h('input#exportAccount.sizing-input', { - onKeyPress: this.onExportKeyPress.bind(this), - style: { - position: 'relative', - top: '1.5px', + }, + [ + 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: () => {} }), - }, 'Submit'), - h('button', { - onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), - }, 'Cancel'), - ]) - + [ + 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('-')) + ), + ]) ) } @@ -89,15 +119,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { if (event.key !== 'Enter') return event.preventDefault() - var input = document.getElementById('exportAccount') - if (input.value === 'I understand') { - this.props.dispatch(actions.exportAccount(this.props.address)) - } else { - input.value = '' - input.placeholder = 'Please retype "I understand" exactly.' - } -} - -ExportAccountView.prototype.exportAccount = function (address) { - this.props.dispatch(actions.exportAccount(address)) + var input = document.getElementById('exportAccount').value + this.props.dispatch(actions.exportAccount(input, this.props.address)) } diff --git a/ui/app/components/account-info-link.js b/ui/app/components/account-info-link.js index 49c42e9ec..6526ab502 100644 --- a/ui/app/components/account-info-link.js +++ b/ui/app/components/account-info-link.js @@ -3,7 +3,6 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const Tooltip = require('./tooltip') const genAccountLink = require('../../lib/account-link') -const extension = require('../../../app/scripts/lib/extension') module.exports = AccountInfoLink @@ -35,7 +34,7 @@ AccountInfoLink.prototype.render = function () { style: { margin: '5px', }, - onClick () { extension.tabs.create({ url }) }, + onClick () { global.platform.openWindow({ url }) }, }), ]), ]) diff --git a/ui/app/components/binary-renderer.js b/ui/app/components/binary-renderer.js index a9d49b128..0b6a1f5c2 100644 --- a/ui/app/components/binary-renderer.js +++ b/ui/app/components/binary-renderer.js @@ -2,6 +2,7 @@ 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 @@ -12,20 +13,22 @@ function BinaryRenderer () { BinaryRenderer.prototype.render = function () { const props = this.props - const { value } = 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: { - width: '315px', - maxHeight: '210px', - resize: 'none', - border: 'none', - background: 'white', - padding: '3px', - }, + style: defaultStyle, defaultValue: text, }) ) diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js new file mode 100644 index 000000000..f3ace4720 --- /dev/null +++ b/ui/app/components/bn-as-decimal-input.js @@ -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 +} diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 3074bd7cd..87084f92d 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -5,14 +5,16 @@ const connect = require('react-redux').connect const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') -const extension = require('../../../app/scripts/lib/extension') const Loading = require('./loading') -const TabBar = require('./tab-bar') +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, @@ -32,7 +34,11 @@ BuyButtonSubview.prototype.render = function () { const isLoading = props.isSubLoading return ( - h('.buy-eth-section', [ + h('.buy-eth-section.flex-column', { + style: { + alignItems: 'center', + }, + }, [ // back button h('.flex-row', { style: { @@ -47,65 +53,87 @@ BuyButtonSubview.prototype.render = function () { left: '10px', }, }), - h('h2.page-subtitle', 'Buy Eth'), - ]), - - h(Loading, { isLoading }), - - h(TabBar, { - tabs: [ - { - content: [ - 'Coinbase', - h('a', { - onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), - }, [ - h('i.fa.fa-question-circle', { - style: { - margin: '0px 5px', - }, - }), - ]), - ], - key: 'coinbase', + h('h2.text-transform-uppercase.flex-center', { + style: { + width: '100vw', + background: 'rgb(235, 235, 235)', + color: 'rgb(174, 174, 174)', + paddingTop: '4px', + paddingBottom: '4px', }, - { - content: [ - 'Shapeshift', - h('a', { - href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', - onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), - }, [ - h('i.fa.fa-question-circle', { - style: { - margin: '0px 5px', - }, - }), - ]), - ], - key: 'shapeshift', + }, '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', }, - ], - defaultTab: 'coinbase', - tabSelected: (key) => { - switch (key) { - case 'coinbase': - props.dispatch(actions.coinBaseSubview()) - break - case 'shapeshift': - props.dispatch(actions.shapeShiftSubview(props.provider.type)) - break - } + 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 () { - if (this.props.network === '1') { + 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) { @@ -121,21 +149,34 @@ BuyButtonSubview.prototype.formVersionSubview = function () { h('h3.text-transform-uppercase', { style: { width: '225px', + marginBottom: '15px', }, }, 'In order to access this feature, please switch to the Main Network'), - (this.props.network === '3') ? h('h3.text-transform-uppercase', 'or:') : null, - (this.props.network === '3') ? h('button.text-transform-uppercase', { - onClick: () => this.props.dispatch(actions.buyEth()), + ((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', }, - }, 'Go To Test Faucet') : null, + }, '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) { - extension.tabs.create({ url }) + global.platform.openWindow({ url }) } BuyButtonSubview.prototype.backButtonContext = function () { @@ -145,3 +186,12 @@ BuyButtonSubview.prototype.backButtonContext = function () { 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)) + } +} diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index 40f5719bb..f44d86045 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -4,7 +4,6 @@ const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../actions') -const isValidAddress = require('../util').isValidAddress module.exports = connect(mapStateToProps)(CoinbaseForm) function mapStateToProps (state) { @@ -21,105 +20,36 @@ function CoinbaseForm () { CoinbaseForm.prototype.render = function () { var props = this.props - var amount = props.buyView.amount - var address = props.buyView.buyAddress return h('.flex-column', { style: { - // margin: '10px', + marginTop: '35px', padding: '25px', + width: '100%', }, }, [ - h('.flex-column', { - style: { - alignItems: 'flex-start', - }, - }, [ - h('.flex-row', [ - h('div', 'Address:'), - h('.ellip-address', address), - ]), - h('.flex-row', [ - h('div', 'Amount: $'), - h('.input-container', [ - h('input.buy-inputs', { - style: { - width: '3em', - boxSizing: 'border-box', - }, - defaultValue: amount, - onChange: this.handleAmount.bind(this), - }), - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'relative', - bottom: '5px', - right: '11px', - }, - }), - ]), - ]), - ]), - - h('.info-gray', { - style: { - fontSize: '10px', - fontFamily: 'Montserrat Light', - margin: '15px', - lineHeight: '13px', - }, - }, - `there is a USD$ 15 a day max and a USD$ 50 - dollar limit per the life time of an account without a - coinbase account. A fee of 3.75% will be aplied to debit/credit cards.`), - - !props.warning ? h('div', { - style: { - width: '340px', - height: '22px', - }, - }) : props.warning && h('span.error.flex-center', props.warning), - - h('.flex-row', { style: { justifyContent: 'space-around', margin: '33px', + marginTop: '0px', }, }, [ - h('button', { + h('button.btn-green', { onClick: this.toCoinbase.bind(this), }, 'Continue to Coinbase'), - h('button', { + h('button.btn-red', { onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), }, 'Cancel'), ]), ]) } -CoinbaseForm.prototype.handleAmount = function (event) { - this.props.dispatch(actions.updateCoinBaseAmount(event.target.value)) -} -CoinbaseForm.prototype.handleAddress = function (event) { - this.props.dispatch(actions.updateBuyAddress(event.target.value)) -} -CoinbaseForm.prototype.toCoinbase = function () { - var props = this.props - var amount = props.buyView.amount - var address = props.buyView.buyAddress - var message - if (isValidAddress(address) && isValidAmountforCoinBase(amount).valid) { - props.dispatch(actions.buyEth(address, props.buyView.amount)) - } else if (!isValidAmountforCoinBase(amount).valid) { - message = isValidAmountforCoinBase(amount).message - return props.dispatch(actions.displayWarning(message)) - } else { - message = 'Receiving address is invalid.' - return props.dispatch(actions.displayWarning(message)) - } +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 () { @@ -131,29 +61,3 @@ CoinbaseForm.prototype.renderLoading = function () { src: 'images/loading.svg', }) } - -function isValidAmountforCoinBase (amount) { - amount = parseFloat(amount) - if (amount) { - if (amount <= 15 && amount > 0) { - return { - valid: true, - } - } else if (amount > 15) { - return { - valid: false, - message: 'The amount can not be greater then $15', - } - } else { - return { - valid: false, - message: 'Can not buy amounts less then $0', - } - } - } else { - return { - valid: false, - message: 'The amount entered is not a number', - } - } -} diff --git a/ui/app/components/copyable.js b/ui/app/components/copyable.js new file mode 100644 index 000000000..a4f6f4bc6 --- /dev/null +++ b/ui/app/components/copyable.js @@ -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) +} diff --git a/ui/app/components/custom-radio-list.js b/ui/app/components/custom-radio-list.js new file mode 100644 index 000000000..a4c525396 --- /dev/null +++ b/ui/app/components/custom-radio-list.js @@ -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) + } + }) + ), + ]) + ) +} + diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 9f002234e..e42948209 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -42,7 +42,13 @@ DropMenuItem.prototype.activeNetworkRender = function () { if (providerType === 'mainnet') return h('.check', '✓') break case 'Ropsten Test Network': - if (provider.type === 'testnet') return h('.check', '✓') + 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', '✓') diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index facf29d97..43bb7ab22 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -5,11 +5,9 @@ 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 networkResolvers = { - '3': '112234455c3a32fd11230c42e7bccd4a84e02010', -} module.exports = EnsInput @@ -23,9 +21,10 @@ EnsInput.prototype.render = function () { const opts = extend(props, { list: 'addresses', onChange: () => { + this.setState({ ensResolution: '0x0000000000000000000000000000000000000000' }) const network = this.props.network - let resolverAddress = networkResolvers[network] - if (!resolverAddress) return + const networkHasEnsSupport = getNetworkEnsSupport(network) + if (!networkHasEnsSupport) return const recipient = document.querySelector('input[name="address"]').value if (recipient.match(ensRE) === null) { @@ -52,7 +51,7 @@ EnsInput.prototype.render = function () { [ // Corresponds to the addresses owned. Object.keys(props.identities).map((key) => { - let identity = props.identities[key] + const identity = props.identities[key] return h('option', { value: identity.address, label: identity.name, @@ -63,6 +62,7 @@ EnsInput.prototype.render = function () { return h('option', { value: identity.address, label: identity.name, + key: identity.address, }) }), ]), @@ -72,10 +72,10 @@ EnsInput.prototype.render = function () { EnsInput.prototype.componentDidMount = function () { const network = this.props.network - let resolverAddress = networkResolvers[network] + const networkHasEnsSupport = getNetworkEnsSupport(network) - if (resolverAddress) { - const provider = web3.currentProvider + if (networkHasEnsSupport) { + const provider = global.ethereumProvider this.ens = new ENS({ provider, network }) this.checkName = debounce(this.lookupEnsName.bind(this), 200) } @@ -96,12 +96,14 @@ EnsInput.prototype.lookupEnsName = function () { log.info(`ENS attempting to resolve name: ${recipient}`) this.ens.lookup(recipient.trim()) .then((address) => { + if (address === '0x0000000000000000000000000000000000000000') 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, }) } }) @@ -109,6 +111,7 @@ EnsInput.prototype.lookupEnsName = function () { log.error(reason) return this.setState({ loadingEns: false, + ensResolution: '0x0000000000000000000000000000000000000000', ensFailure: true, hoverText: reason.message, }) @@ -168,3 +171,8 @@ EnsInput.prototype.ensIconContents = function (recipient) { }) } } + +function getNetworkEnsSupport (network) { + return Boolean(networkMap[network]) +} + diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js index 57ca84564..4f538fd31 100644 --- a/ui/app/components/eth-balance.js +++ b/ui/app/components/eth-balance.js @@ -16,20 +16,19 @@ function EthBalanceComponent () { EthBalanceComponent.prototype.render = function () { var props = this.props let { value } = props - var style = props.style + const { style, width } = props 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, + style, }, [ h('div', { style: { display: 'inline', - width: width, + width, }, }, this.renderBalance(value)), ]) @@ -38,16 +37,17 @@ EthBalanceComponent.prototype.render = function () { } 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, props.shorten ? 1 : 3) + 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 (props.shorten) { + if (shorten) { balance = balanceObj.shortBalance } else { balance = balanceObj.balance @@ -73,7 +73,7 @@ EthBalanceComponent.prototype.renderBalance = function (value) { width: '100%', textAlign: 'right', }, - }, this.props.incoming ? `+${balance}` : balance), + }, incoming ? `+${balance}` : balance), h('div', { style: { color: ' #AEAEAE', @@ -83,7 +83,7 @@ EthBalanceComponent.prototype.renderBalance = function (value) { }, label), ]), - showFiat ? h(FiatValue, { value: props.value }) : null, + showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null, ])) ) } diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js index 298809b30..8a64a1cfc 100644 --- a/ui/app/components/fiat-value.js +++ b/ui/app/components/fiat-value.js @@ -1,17 +1,9 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const connect = require('react-redux').connect const formatBalance = require('../util').formatBalance -module.exports = connect(mapStateToProps)(FiatValue) - -function mapStateToProps (state) { - return { - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} +module.exports = FiatValue inherits(FiatValue, Component) function FiatValue () { @@ -20,23 +12,23 @@ function FiatValue () { 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 (props.conversionRate !== 0) { - fiatTooltipNumber = Number(splitBalance[0]) * props.conversionRate + if (conversionRate !== 0) { + fiatTooltipNumber = Number(splitBalance[0]) * conversionRate fiatDisplayNumber = fiatTooltipNumber.toFixed(2) } else { fiatDisplayNumber = 'N/A' fiatTooltipNumber = 'Unknown' } - var fiatSuffix = props.currentCurrency - - return fiatDisplay(fiatDisplayNumber, fiatSuffix) + return fiatDisplay(fiatDisplayNumber, currentCurrency) } function fiatDisplay (fiatDisplayNumber, fiatSuffix) { diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index c89ed0416..4a71e9585 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -9,6 +9,7 @@ module.exports = HexAsDecimalInput inherits(HexAsDecimalInput, Component) function HexAsDecimalInput () { + this.state = { invalid: null } Component.call(this) } @@ -23,51 +24,122 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange } = 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-row', { - style: { - alignItems: 'flex-end', - lineHeight: '13px', - fontFamily: 'Montserrat Light', - textRendering: 'geometricPrecision', - }, - }, [ - h('input.ether-balance.ether-balance-amount', { - type: 'number', - style: extend({ - display: 'block', - textAlign: 'right', - backgroundColor: 'transparent', - border: '1px solid #bdbdbd', - - }, style), - value: decimalValue, - onChange: (event) => { - const hexString = (event.target.value === '') ? '' : hexify(event.target.value) - onChange(hexString) + h('.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', }, - }), - h('div', { + }, [ + 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: { - color: ' #AEAEAE', - fontSize: '12px', - marginLeft: '5px', - marginRight: '6px', - width: '20px', + 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', }, - }, suffix), + }, 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(decimalString, 10) + const hexBN = new BN(parseInt(decimalString), 10) return '0x' + hexBN.toString('hex') } diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 6d4871d02..9de854b54 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -1,6 +1,7 @@ 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') @@ -40,8 +41,10 @@ IdenticonComponent.prototype.componentDidMount = function () { var container = findDOMNode(this) var diameter = props.diameter || this.defaultDiameter - var img = iconFactory.iconForAddress(address, diameter, false) - container.appendChild(img) + if (!isNode) { + var img = iconFactory.iconForAddress(address, diameter, false) + container.appendChild(img) + } } IdenticonComponent.prototype.componentDidUpdate = function () { @@ -58,6 +61,8 @@ IdenticonComponent.prototype.componentDidUpdate = function () { } var diameter = props.diameter || this.defaultDiameter - var img = iconFactory.iconForAddress(address, diameter, false) - container.appendChild(img) + if (!isNode) { + var img = iconFactory.iconForAddress(address, diameter, false) + container.appendChild(img) + } } diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 77805fd57..31a8fc17c 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -34,12 +34,18 @@ Network.prototype.render = function () { } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' iconName = 'ethereum-network' - } else if (providerName === 'testnet') { + } 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' @@ -70,6 +76,24 @@ Network.prototype.render = function () { }}, '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', { diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 23ded9d5d..d9f0067cd 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -92,6 +92,7 @@ Notice.prototype.render = function () { }, }, [ h(ReactMarkdown, { + className: 'notice-box', source: body, skipHtml: true, }), @@ -99,11 +100,14 @@ Notice.prototype.render = function () { h('button', { disabled, - onClick: onConfirm, + onClick: () => { + this.setState({disclaimerDisabled: true}) + onConfirm() + }, style: { marginTop: '18px', }, - }, 'Continue'), + }, 'Accept'), ]) ) } @@ -111,6 +115,9 @@ Notice.prototype.render = function () { 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 () { diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js index fa2c6416c..1050513f2 100644 --- a/ui/app/components/pending-personal-msg-details.js +++ b/ui/app/components/pending-personal-msg-details.js @@ -40,9 +40,18 @@ PendingMsgDetails.prototype.render = function () { }), // message data - h('div', [ + h('div', { + style: { + height: '260px', + }, + }, [ h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'), - h(BinaryRenderer, { value: data }), + h(BinaryRenderer, { + value: data, + style: { + height: '215px', + }, + }), ]), ]) diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js deleted file mode 100644 index e92ce575f..000000000 --- a/ui/app/components/pending-tx-details.js +++ /dev/null @@ -1,344 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const extend = require('xtend') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN - -const MiniAccountPanel = require('./mini-account-panel') -const EthBalance = require('./eth-balance') -const util = require('../util') -const addressSummary = util.addressSummary -const nameForAddress = require('../../lib/contract-namer') -const HexInput = require('./hex-as-decimal-input') - -module.exports = PendingTxDetails - -inherits(PendingTxDetails, Component) -function PendingTxDetails () { - Component.call(this) -} - -const PTXP = PendingTxDetails.prototype - -PTXP.render = function () { - var props = this.props - var state = this.state || {} - var txData = state.txMeta || props.txData - - var txParams = txData.txParams || {} - var address = txParams.from || props.selectedAddress - var identity = props.identities[address] || { address: address } - var account = props.accounts[address] - var balance = account ? account.balance : '0x0' - - const gas = (state.gas === undefined) ? txParams.gas : state.gas - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice - - var txFee = state.txFee || txData.txFee || '' - var maxCost = state.maxCost || txData.maxCost || '' - var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - - log.debug(`rendering gas: ${gas}, gasPrice: ${gasPrice}, txFee: ${txFee}, maxCost: ${maxCost}`) - - return ( - h('div', [ - - h('.flex-row.flex-center', { - style: { - maxWidth: '100%', - }, - }, [ - - h(MiniAccountPanel, { - imageSeed: address, - imageifyIdenticons: imageify, - picOrder: 'right', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, identity.name), - 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, - 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 }), - ]), - - // Gas Limit (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Limit'), - h('.cell.value', { - }, [ - h(HexInput, { - value: gas, - suffix: 'UNITS', - style: { - position: 'relative', - top: '5px', - }, - onChange: (newHex) => { - log.info(`Gas limit changed to ${newHex}`) - this.setState({ gas: newHex }) - }, - }), - ]), - ]), - - // Gas Price (customizable) - h('.cell.row', [ - h('.cell.label', 'Gas Price'), - h('.cell.value', { - }, [ - h(HexInput, { - value: gasPrice, - suffix: 'WEI', - style: { - position: 'relative', - top: '5px', - }, - onChange: (newHex) => { - log.info(`Gas price changed to: ${newHex}`) - this.setState({ gasPrice: newHex }) - }, - }), - ]), - ]), - - // Max Transaction Fee (calculated) - h('.cell.row', [ - h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFee.toString(16) }), - ]), - - 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), - 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 - - ]) - ) -} - -PTXP.miniAccountPanelForRecipient = function () { - var props = this.props - var txData = props.txData - var txParams = txData.txParams || {} - var isContractDeploy = !('to' in txParams) - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - - // If it's not a contract deploy, send to the account - if (!isContractDeploy) { - return h(MiniAccountPanel, { - imageSeed: txParams.to, - imageifyIdenticons: imageify, - picOrder: 'left', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, nameForAddress(txParams.to, props.identities)), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(txParams.to, 6, 4, false)), - ]) - } else { - return h(MiniAccountPanel, { - imageifyIdenticons: imageify, - picOrder: 'left', - }, [ - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, 'New Contract'), - - ]) - } -} - -PTXP.componentDidUpdate = function (prevProps, previousState) { - log.debug(`pending-tx-details componentDidUpdate`) - const state = this.state || {} - const prevState = previousState || {} - const { gas, gasPrice } = state - - // Only if gas or gasPrice changed: - if (!prevState || - (gas !== prevState.gas || - gasPrice !== prevState.gasPrice)) { - log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) - this.calculateGas() - } -} - -PTXP.calculateGas = function () { - const txMeta = this.gatherParams() - log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) - - var txParams = txMeta.txParams - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) - - const txFeeHex = '0x' + txFee.toString('hex') - const maxCostHex = '0x' + maxCost.toString('hex') - const gasPriceHex = '0x' + gasPrice.toString('hex') - - txMeta.txFee = txFeeHex - txMeta.maxCost = maxCostHex - txMeta.txParams.gasPrice = gasPriceHex - - this.setState({ - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - }) - - if (this.props.onTxChange) { - this.props.onTxChange(txMeta) - } -} - -PTXP.resetGasFields = function () { - log.debug(`pending-tx-details#resetGasFields`) - const txData = this.props.txData - this.setState({ - gas: txData.txParams.gas, - gasPrice: txData.gasPrice, - }) -} - -// After a customizable state value has been updated, -PTXP.gatherParams = function () { - log.debug(`pending-tx-details#gatherParams`) - const props = this.props - const state = this.state || {} - const txData = state.txData || props.txData - const txParams = txData.txParams - const gas = state.gas || txParams.gas - const gasPrice = state.gasPrice || txParams.gasPrice - const resultTx = extend(txParams, { - gas, - gasPrice, - }) - const resultTxMeta = extend(txData, { - txParams: resultTx, - }) - log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`) - return resultTxMeta -} - -PTXP.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) -} - -PTXP._notZeroOrEmptyString = function (obj) { - return obj !== '' && obj !== '0x0' -} - -function forwardCarrat () { - return ( - - h('img', { - src: 'images/forward-carrat.svg', - style: { - padding: '5px 6px 0px 10px', - height: '37px', - }, - }) - - ) -} diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2ab6f25a9..4b1a00eca 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -1,99 +1,478 @@ const Component = require('react').Component -const connect = require('react-redux').connect const h = require('react-hyperscript') const inherits = require('util').inherits -const PendingTxDetails = require('./pending-tx-details') -const extend = require('xtend') const actions = require('../actions') +const clone = require('clone') -module.exports = connect(mapStateToProps)(PendingTx) +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') -function mapStateToProps (state) { - return { - - } -} +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, + } } PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, {ref: 'details'}) - const txData = props.txData + 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: txData.id, + key: txMeta.id, }, [ - // tx info - h(PendingTxDetails, newProps), + h('form#pending-tx-form', { + onSubmit: this.onSubmit.bind(this), - h('style', ` - .conf-buttons button { - margin-left: 10px; - text-transform: uppercase; - } - `), + }, [ - txData.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, + // 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, - props.insufficientBalance ? - h('span.error', { + !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: { - marginLeft: 50, - fontSize: '0.9em', + display: 'flex', + justifyContent: 'flex-end', + margin: '14px 25px', }, - }, 'Insufficient balance for transaction') - : null, + }, [ + + + 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: 'ACCEPT', + style: { marginLeft: '10px' }, + disabled: insufficientBalance || !this.state.valid || !isValidAddress, + }), + + 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) - // send + cancel - h('.flex-row.flex-space-around.conf-buttons', { + // 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: { - display: 'flex', - justifyContent: 'flex-end', - margin: '14px 25px', + 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)), + ]), - props.insufficientBalance ? - h('button', { - onClick: props.buyEth, - }, 'Buy Ether') - : null, + ]) + } else { + return h(MiniAccountPanel, { + picOrder: 'left', + }, [ - h('button', { - onClick: () => { - this.refs.details.resetGasFields() - }, - }, 'Reset'), - - h('button.confirm.btn-green', { - disabled: props.insufficientBalance, - onClick: (txData, event) => { - if (this.refs.details.verifyGasParams()) { - props.sendTransaction(txData, event) - } else { - this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) - } - }, - }, 'Accept'), + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, 'New Contract'), - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), - ]), ]) + } +} + +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 }) + if (valid && this.verifyGasParams()) { + this.props.sendTransaction(txMeta, event) + } else { + this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) + } +} + +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', + }, + }) ) } diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 8c9686035..e0a720426 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -43,14 +43,18 @@ ShapeshiftForm.prototype.renderMain = function () { 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', { @@ -66,6 +70,7 @@ ShapeshiftForm.prototype.renderMain = function () { h('input#fromCoin.buy-inputs.ex-coins', { type: 'text', list: 'coinList', + autoFocus: true, dataset: { persistentFormId: 'input-coin', }, @@ -92,7 +97,6 @@ ShapeshiftForm.prototype.renderMain = function () { h('.icon-control', [ h('i.fa.fa-refresh.fa-4.orange', { style: { - position: 'relative', bottom: '5px', left: '5px', color: '#F7861C', @@ -121,8 +125,6 @@ ShapeshiftForm.prototype.renderMain = function () { }, }), ]), - - this.props.isSubLoading ? this.renderLoading() : null, h('.flex-column', { style: { alignItems: 'flex-start', @@ -138,17 +140,6 @@ ShapeshiftForm.prototype.renderMain = function () { this.props.warning) : this.renderInfo(), ]), - h('.flex-row', { - style: { - padding: '10px', - paddingBottom: '2px', - width: '100%', - }, - }, [ - h('div', 'Receiving address:'), - h('.ellip-address', this.props.buyView.buyAddress), - ]), - h(this.activeToggle('.input-container'), { style: { padding: '10px', @@ -156,6 +147,7 @@ ShapeshiftForm.prototype.renderMain = function () { width: '100%', }, }, [ + h('div', `${coin} Address:`), h('input#fromCoinAddress.buy-inputs', { @@ -166,8 +158,8 @@ ShapeshiftForm.prototype.renderMain = function () { }, style: { boxSizing: 'border-box', - width: '278px', - height: '20px', + width: '227px', + height: '30px', padding: ' 5px ', }, }), @@ -177,7 +169,7 @@ ShapeshiftForm.prototype.renderMain = function () { fontSize: '12px', color: '#F7861C', position: 'relative', - bottom: '5px', + bottom: '10px', right: '11px', }, }), @@ -190,6 +182,8 @@ ShapeshiftForm.prototype.renderMain = function () { onClick: this.shift.bind(this), style: { marginTop: '10px', + position: 'relative', + bottom: '40px', }, }, 'Submit'), @@ -266,8 +260,6 @@ ShapeshiftForm.prototype.renderInfo = function () { return h('span', { style: { - marginTop: '10px', - marginBottom: '15px', }, }, [ h('h3.flex-row.text-transform-uppercase', { @@ -286,10 +278,6 @@ ShapeshiftForm.prototype.renderInfo = function () { ]) } -ShapeshiftForm.prototype.handleAddress = function (event) { - this.props.dispatch(actions.updateBuyAddress(event.target.value)) -} - ShapeshiftForm.prototype.activeToggle = function (elementType) { if (!this.props.buyView.formView.response || this.props.warning) return elementType return `${elementType}.inactive` diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index e0243e247..32bfbeda4 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -4,19 +4,21 @@ const h = require('react-hyperscript') const connect = require('react-redux').connect const vreme = new (require('vreme')) const explorerLink = require('../../lib/explorer-link') -const extension = require('../../../app/scripts/lib/extension') const actions = require('../actions') const addressSummary = require('../util').addressSummary const CopyButton = require('./copyButton') -const EtherBalance = require('./eth-balance') +const EthBalance = require('./eth-balance') const Tooltip = require('./tooltip') module.exports = connect(mapStateToProps)(ShiftListItem) function mapStateToProps (state) { - return {} + return { + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + } } inherits(ShiftListItem, Component) @@ -65,6 +67,7 @@ function formatDate (date) { ShiftListItem.prototype.renderUtilComponents = function () { var props = this.props + const { conversionRate, currentCurrency } = props switch (props.response.status) { case 'no_deposits': @@ -95,8 +98,10 @@ ShiftListItem.prototype.renderUtilComponents = function () { h(CopyButton, { value: this.props.response.transaction, }), - h(EtherBalance, { + h(EthBalance, { value: `${props.response.outgoingCoin}`, + conversionRate, + currentCurrency, width: '55px', shorten: true, needsParse: false, @@ -172,9 +177,7 @@ ShiftListItem.prototype.renderInfo = function () { width: '200px', overflow: 'hidden', }, - onClick: () => extension.tabs.create({ - url, - }), + onClick: () => global.platform.openWindow({ url }), }, [ h('div', { style: { diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index ca2781451..431054340 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const Tooltip = require('./tooltip') const Identicon = require('./identicon') @@ -15,7 +16,7 @@ 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') + return h(!isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg') case 'rejected': return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { @@ -32,11 +33,16 @@ TransactionIcon.prototype.render = function () { }) case 'submitted': - return h('i.fa.fa-ellipsis-h', { - style: { - fontSize: '27px', - }, - }) + return h(Tooltip, { + title: 'Pending', + position: 'bottom', + }, [ + h('i.fa.fa-ellipsis-h', { + style: { + fontSize: '27px', + }, + }), + ]) } if (isMsg) { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 44d2dc587..dbda66a31 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -2,13 +2,13 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const EtherBalance = require('./eth-balance') +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 extension = require('../../../app/scripts/lib/extension') const Tooltip = require('./tooltip') +const numberToBN = require('number-to-bn') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -20,7 +20,7 @@ function TransactionListItem () { } TransactionListItem.prototype.render = function () { - const { transaction, network } = this.props + const { transaction, network, conversionRate, currentCurrency } = this.props if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -28,7 +28,7 @@ TransactionListItem.prototype.render = function () { let isLinkable = false const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 3 + isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 4 || numericNet === 42 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) @@ -40,6 +40,8 @@ TransactionListItem.prototype.render = function () { 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' : ''}`, { @@ -50,7 +52,7 @@ TransactionListItem.prototype.render = function () { event.stopPropagation() if (!transaction.hash || !isLinkable) return var url = explorerLink(transaction.hash, parseInt(network)) - extension.tabs.create({ url }) + global.platform.openWindow({ url }) }, style: { padding: '20px 0', @@ -63,13 +65,29 @@ TransactionListItem.prototype.render = function () { event.stopPropagation() if (!isTx || isPending) return var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}` - extension.tabs.create({ url }) + 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), @@ -79,8 +97,10 @@ TransactionListItem.prototype.render = function () { // 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(EtherBalance, { + isTx ? h(EthBalance, { value: txParams.value, + conversionRate, + currentCurrency, width: '55px', shorten: true, showFiat: false, @@ -135,7 +155,6 @@ function failIfFailed (transaction) { return h('span.error', ' (Rejected)') } if (transaction.err) { - return h(Tooltip, { title: transaction.err.message, position: 'bottom', @@ -143,5 +162,4 @@ function failIfFailed (transaction) { h('span.error', ' (Failed)'), ]) } - } diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 3ae953637..37a757309 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -13,7 +13,7 @@ function TransactionList () { } TransactionList.prototype.render = function () { - const { transactions, network, unapprovedMsgs } = this.props + const { transactions, network, unapprovedMsgs, conversionRate } = this.props var shapeShiftTxList if (network === '1') { @@ -69,6 +69,7 @@ TransactionList.prototype.render = function () { } return h(TransactionListItem, { transaction, i, network, key, + conversionRate, showTx: (txId) => { this.props.viewPendingTx(txId) }, diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 07985094c..747d3ce2b 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -7,8 +7,6 @@ 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 ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') @@ -29,6 +27,9 @@ function mapStateToProps (state) { warning: state.appState.warning, network: state.metamask.network, provider: state.metamask.provider, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + blockGasLimit: state.metamask.currentBlockGasLimit, } } @@ -39,15 +40,16 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props - const { network, provider, unapprovedTxs, - unapprovedMsgs, unapprovedPersonalMsgs } = props + const { network, provider, unapprovedTxs, currentCurrency, + unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - var index = props.index !== undefined && unconfTxList[index] ? props.index : 0 - var txData = unconfTxList[index] || {} + + 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 }) @@ -104,12 +106,12 @@ ConfirmTxScreen.prototype.render = function () { selectedAddress: props.selectedAddress, accounts: props.accounts, identities: props.identities, - insufficientBalance: this.checkBalanceAgainstTx(txData), - // State actions - onTxChange: this.onTxChange.bind(this), + conversionRate, + currentCurrency, + blockGasLimit, // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), - sendTransaction: this.sendTransaction.bind(this, txData), + sendTransaction: this.sendTransaction.bind(this), cancelTransaction: this.cancelTransaction.bind(this, txData), signMessage: this.signMessage.bind(this, txData), signPersonalMessage: this.signPersonalMessage.bind(this, txData), @@ -130,14 +132,12 @@ function currentTxView (opts) { 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) @@ -145,41 +145,19 @@ function currentTxView (opts) { } } -ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { - if (!txData.txParams) return false - var props = this.props - var address = txData.txParams.from || props.selectedAddress - var account = props.accounts[address] - var balance = account ? account.balance : '0x0' - var maxCost = new BN(txData.maxCost, 16) - - var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16) - return maxCost.gt(balanceBn) -} - ConfirmTxScreen.prototype.buyEth = function (address, event) { - event.stopPropagation() + event.preventDefault() this.props.dispatch(actions.buyEthView(address)) } -// Allows the detail view to update the gas calculations, -// for manual gas controls. -ConfirmTxScreen.prototype.onTxChange = function (txData) { - log.debug(`conf-tx onTxChange triggered with ${JSON.stringify(txData)}`) - this.setState({ txData }) -} - -// Must default to any local state txData, -// to allow manual override of gas calculations. ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { - event.stopPropagation() - const state = this.state || {} - const txMeta = state.txData - this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) + this.stopPropagation(event) + this.props.dispatch(actions.updateAndApproveTx(txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) + event.preventDefault() this.props.dispatch(actions.cancelTx(txData)) } @@ -187,32 +165,38 @@ ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams params.metamaskId = msgData.id - event.stopPropagation() + 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 - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.signPersonalMsg(params)) } ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { log.info('canceling message') - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelMsg(msgData)) } ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { log.info('canceling personal message') - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelPersonalMsg(msgData)) } ConfirmTxScreen.prototype.goHome = function (event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.goHome()) } diff --git a/ui/app/config.js b/ui/app/config.js index 3f0507f48..d7be26757 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -156,11 +156,21 @@ function currentProviderDisplay (metamaskState) { value = 'Main Ethereum Network' break - case 'testnet': + 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 diff --git a/ui/app/conversion.json b/ui/app/conversion.json index eeca164ce..155ffc4fc 100644 --- a/ui/app/conversion.json +++ b/ui/app/conversion.json @@ -1,5730 +1,207 @@ -{
- "rows":[
- {
- "code":"007",
- "name":"007",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"1337",
- "name":"1337",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"1CR",
- "name":"1CR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"256",
- "name":"256",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"2FLAV",
- "name":"2FLAV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"2GIVE",
- "name":"2GIVE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"32BIT",
- "name":"32BIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"404",
- "name":"404",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"611",
- "name":"611",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"888",
- "name":"888",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"8BIT",
- "name":"8Bit",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ACES",
- "name":"ACES",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ACID",
- "name":"ACID",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ACLR",
- "name":"ACLR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ACP",
- "name":"ACP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ADC",
- "name":"ADC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ADZ",
- "name":"Adzcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AEON",
- "name":"Aeon",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AGRS",
- "name":"Agoras Tokens",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AIB",
- "name":"AIB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ALC",
- "name":"ALC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ALTC",
- "name":"ALTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AM",
- "name":"AM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AMBER",
- "name":"AMBER",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AMS",
- "name":"AMS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ANAL",
- "name":"ANAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ANI",
- "name":"ANI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ANC",
- "name":"Anoncoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ANS",
- "name":"ANS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ANTI",
- "name":"AntiBitcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"APEX",
- "name":"APEX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"APC",
- "name":"Applecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"APT",
- "name":"APT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AR2",
- "name":"AR2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ARB",
- "name":"ARB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ARC",
- "name":"ARC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ARCH",
- "name":"ARCH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ARD",
- "name":"ARD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ARDR",
- "name":"ARDR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ABY",
- "name":"ArtByte",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ARTC",
- "name":"ARTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ASAFE",
- "name":"ASAFE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ADCN",
- "name":"Asiadigicoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ASN",
- "name":"ASN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ATEN",
- "name":"ATEN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ATOM",
- "name":"ATOM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ATX",
- "name":"ATX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"REP",
- "name":"Augur",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AUR",
- "name":"Auroracoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AUD",
- "name":"Australian Dollar",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"AV",
- "name":"AV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"B2",
- "name":"B2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"B3",
- "name":"B3",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BA",
- "name":"BA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BAC",
- "name":"BAC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BASH",
- "name":"BASH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTA",
- "name":"Bata",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BAY",
- "name":"BAY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BBCC",
- "name":"BBCC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BQC",
- "name":"BBQCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BEC",
- "name":"BEC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BEEP",
- "name":"BEEP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BELA",
- "name":"BellaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BERN",
- "name":"BERNcash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BHC",
- "name":"BHC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BILL",
- "name":"BILL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BILS",
- "name":"BILS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BIOS",
- "name":"BiosCrypto",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BIT",
- "name":"BIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BIT16",
- "name":"BIT16",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BITB",
- "name":"BitBean",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTC",
- "name":"Bitcoin",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"XBC",
- "name":"Bitcoin Plus",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTCD",
- "name":"BitcoinDark",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BCY",
- "name":"Bitcrystals",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BFX",
- "name":"Bitfinex Debt token",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTM",
- "name":"Bitmark",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BITON",
- "name":"BITON",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTQ",
- "name":"BitQuark",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BITS",
- "name":"BITS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BSD",
- "name":"BitSend",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTS",
- "name":"BitShares",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SWIFT",
- "name":"BitSwift",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BITZ",
- "name":"Bitz",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLK",
- "name":"Blackcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLC",
- "name":"Blakecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLEU",
- "name":"BLEU",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLITZ",
- "name":"Blitzcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLOCK",
- "name":"Blocknet",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLRY",
- "name":"BLRY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLU",
- "name":"BLU",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BLUS",
- "name":"BLUS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BNT",
- "name":"BNT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BOLI",
- "name":"Bolivarcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BBR",
- "name":"Boolberry",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BOOM",
- "name":"BOOM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BOST",
- "name":"BoostCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BOSS",
- "name":"BOSS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BPOK",
- "name":"BPOK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BRAIN",
- "name":"BRAIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BRC",
- "name":"BRC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BRDD",
- "name":"BRDD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BRIT",
- "name":"BRIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GBP",
- "name":"British Pound Sterling",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"BRK",
- "name":"BRK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BRX",
- "name":"BRX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BS",
- "name":"BS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BSC",
- "name":"BSC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BST",
- "name":"BST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTCHC",
- "name":"BTCHC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTCR",
- "name":"BTCR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTCS",
- "name":"BTCS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTD",
- "name":"BTD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTLC",
- "name":"BTLC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTTF",
- "name":"BTTF",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BTZ",
- "name":"BTZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BUCKS",
- "name":"BUCKS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BUN",
- "name":"BUN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BURST",
- "name":"Burst",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BUZZ",
- "name":"BUZZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BVC",
- "name":"BVC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BXT",
- "name":"BXT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BYC",
- "name":"Bytecent",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BCN",
- "name":"Bytecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CAB",
- "name":"Cabbage Unit",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CAGE",
- "name":"CAGE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CAID",
- "name":"CAID",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CAD",
- "name":"Canadian Dollar",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"CANN",
- "name":"CannabisCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CCN",
- "name":"Cannacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CPC",
- "name":"Capricoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CAPT",
- "name":"CAPT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DIEM",
- "name":"CarpeDiemCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CASH",
- "name":"CASH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CBD",
- "name":"CBD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CBIT",
- "name":"CBIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CCX",
- "name":"CCX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CD",
- "name":"CD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CDN",
- "name":"CDN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CF",
- "name":"CF",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CGA",
- "name":"CGA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CKC",
- "name":"Checkcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CHEMX",
- "name":"CHEMX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CHESS",
- "name":"CHESS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CHF",
- "name":"CHF",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"CNY",
- "name":"Chinese Yuan",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"CHOOF",
- "name":"CHOOF",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CJ",
- "name":"CJ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLAM",
- "name":"Clams",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLICK",
- "name":"CLICK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLINT",
- "name":"CLINT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLOAK",
- "name":"Cloakcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLR",
- "name":"CLR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLUB",
- "name":"CLUB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLUD",
- "name":"CLUD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CLV",
- "name":"CLV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CME",
- "name":"CME",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CMT",
- "name":"CMT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CNC",
- "name":"CNC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"COC",
- "name":"COC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"COXST",
- "name":"CoExistCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"COIN",
- "name":"COIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"C2",
- "name":"Coin2.1",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CV2",
- "name":"Colossuscoin2.0",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CON",
- "name":"CON",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XCP",
- "name":"Counterparty",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"COVAL",
- "name":"COVAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"COX",
- "name":"COX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRAB",
- "name":"CRAB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRC",
- "name":"CRC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRE",
- "name":"CRE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRBIT",
- "name":"Creditbit",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CREVA",
- "name":"CrevaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRNK",
- "name":"CRNK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRPC",
- "name":"CRPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRPS",
- "name":"CRPS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRT",
- "name":"CRT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRW",
- "name":"CRW",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRX",
- "name":"CRX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CRY",
- "name":"CRY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CBX",
- "name":"Crypto Bullion",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CESC",
- "name":"CryptoEscudo",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XCN",
- "name":"Cryptonite",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CSH",
- "name":"CSH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CST",
- "name":"CST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CTK",
- "name":"CTK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CTL",
- "name":"CTL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CTO",
- "name":"CTO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CURE",
- "name":"Curecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CYC",
- "name":"CYC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CYP",
- "name":"Cypher",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CZC",
- "name":"CZC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CZR",
- "name":"CZR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DGD",
- "name":"DarkGoldCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DNET",
- "name":"Darknet",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DAS",
- "name":"DAS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DASH",
- "name":"Dash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DTC",
- "name":"Datacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DB",
- "name":"DB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DBG",
- "name":"DBG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DBLK",
- "name":"DBLK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DBTC",
- "name":"DBTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DC",
- "name":"DC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DCK",
- "name":"DCK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DCRE",
- "name":"DCRE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DCT",
- "name":"DCT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DCYP",
- "name":"DCYP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DCR",
- "name":"Decred",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DES",
- "name":"Destiny",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DEUR",
- "name":"DEUR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DEM",
- "name":"Deutsche eMark",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DVC",
- "name":"Devcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DGMS",
- "name":"DGMS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DGORE",
- "name":"DGORE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DMD",
- "name":"Diamond",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DGB",
- "name":"Digibyte",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"CUBE",
- "name":"DigiCube",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DGC",
- "name":"Digitalcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XDN",
- "name":"DigitalNote",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DP",
- "name":"DigitalPrice",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DIME",
- "name":"Dimecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DISK",
- "name":"DISK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DKC",
- "name":"DKC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DLC",
- "name":"DLC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DLISK",
- "name":"DLISK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DMC",
- "name":"DMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NOTE",
- "name":"DNotes",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DOGE",
- "name":"Dogecoin",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"DOPE",
- "name":"DopeCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DOV",
- "name":"DOV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DOX",
- "name":"DOX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DPAY",
- "name":"DPAY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DRACO",
- "name":"DRACO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DRM8",
- "name":"DRM8",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DROP",
- "name":"DROP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DRZ",
- "name":"DRZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DSH",
- "name":"DSH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DTT",
- "name":"DTT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DBIC",
- "name":"DubaiCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DUO",
- "name":"DUO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DUST",
- "name":"DUST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EAGS",
- "name":"EAGS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EAC",
- "name":"Earthcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EBST",
- "name":"EBST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EC",
- "name":"EC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ECC",
- "name":"ECCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ECLI",
- "name":"ECLI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EDC",
- "name":"EDC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EDRC",
- "name":"EDRC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EDR",
- "name":"EDRCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EGG",
- "name":"EGG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EGO",
- "name":"EGO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EMC2",
- "name":"Einsteinium",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EL",
- "name":"EL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ELE",
- "name":"ELE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EFL",
- "name":"Electronic Gulden",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EMB",
- "name":"EMB",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"EME",
- "name":"EME",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"EMC",
- "name":"Emercoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EMIRG",
- "name":"EMIRG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EMP",
- "name":"EMP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EMPC",
- "name":"EMPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ENRG",
- "name":"Energycoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ENT",
- "name":"ENT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EPC",
- "name":"EPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EQM",
- "name":"EQM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EQUAL",
- "name":"EQUAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ERC",
- "name":"ERC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ERC3",
- "name":"ERC3",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ESB",
- "name":"ESB",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"ESC",
- "name":"ESC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ESP",
- "name":"ESP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ETCO",
- "name":"ETCO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ETH",
- "name":"Ethereum",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"ETC",
- "name":"Ethereum Classic",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ETHS",
- "name":"ETHS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EUC",
- "name":"EUC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EUR",
- "name":"Euro",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"EGC",
- "name":"EvergreenCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EVIL",
- "name":"EVIL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EXCL",
- "name":"EXCL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"EXP",
- "name":"Expanse",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FCT",
- "name":"Factom",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FAIR",
- "name":"Faircoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FC2",
- "name":"FC2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FCH",
- "name":"FCH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FCN",
- "name":"FCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FCP",
- "name":"FCP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FTC",
- "name":"Feathercoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TIPS",
- "name":"Fedoracoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FIND",
- "name":"FIND",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FIT",
- "name":"FIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FJC",
- "name":"FJC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FLO",
- "name":"Florincoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FLOZ",
- "name":"FLOZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FLT",
- "name":"FlutterCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FLY",
- "name":"Flycoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FLDC",
- "name":"FoldingCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FOREX",
- "name":"FOREX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FRK",
- "name":"Franko",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FRDC",
- "name":"FRDC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FRC",
- "name":"Freicoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FRN",
- "name":"FRN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FRWC",
- "name":"FRWC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FSN",
- "name":"FSN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FST",
- "name":"FST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FTP",
- "name":"FTP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FUEL",
- "name":"FUEL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FUN",
- "name":"FUN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FUTC",
- "name":"FUTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FUZZ",
- "name":"FUZZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"FX",
- "name":"FX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GAIA",
- "name":"GAIA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GAIN",
- "name":"GAIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GAKH",
- "name":"GAKH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GAM",
- "name":"GAM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GBT",
- "name":"GameBet Coin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GAME",
- "name":"GameCredits",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GAP",
- "name":"Gapcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GARY",
- "name":"GARY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GB",
- "name":"GB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GBC",
- "name":"GBC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GBIT",
- "name":"GBIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GBRC",
- "name":"GBRC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GCN",
- "name":"GCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GENE",
- "name":"GENE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GEO",
- "name":"GeoCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GEMZ",
- "name":"GetGems",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GHOST",
- "name":"GHOST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GHS",
- "name":"GHS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GLC",
- "name":"GLC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"BSTY",
- "name":"GlobalBoost-Y",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GMCX",
- "name":"GMCX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GML",
- "name":"GML",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GMX",
- "name":"GMX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GOAT",
- "name":"GOAT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GCR",
- "name":"GoCoineR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GLD",
- "name":"GoldCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GOON",
- "name":"GOON",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GOTX",
- "name":"GOTX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GP",
- "name":"GP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GPU",
- "name":"GPU",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRF",
- "name":"Graffiti",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRAM",
- "name":"GRAM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRT",
- "name":"Grantcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GREED",
- "name":"GREED",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRC",
- "name":"Gridcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRN",
- "name":"GRN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRS",
- "name":"Groestlcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GROW",
- "name":"GrowCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GRW",
- "name":"GRW",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GSY",
- "name":"GSY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GUA",
- "name":"GUA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NLG",
- "name":"Gulden",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GUM",
- "name":"GUM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GUN",
- "name":"GUN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"GYC",
- "name":"GYC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HALLO",
- "name":"HALLO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HAM",
- "name":"HAM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HBT",
- "name":"HBT",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"HCC",
- "name":"HCC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HEAT",
- "name":"HEAT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HMP",
- "name":"HempCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XHI",
- "name":"HiCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HILL",
- "name":"HILL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HODL",
- "name":"HOdlcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HKD",
- "name":"Hong Kong Dollar",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"HZ",
- "name":"Horizon",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HSP",
- "name":"HSP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HTC",
- "name":"HTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HTML5",
- "name":"HTMLCOIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HUC",
- "name":"HUC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HVCO",
- "name":"HVCO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HXX",
- "name":"HXX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HYPER",
- "name":"Hyper",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"HYP",
- "name":"HyperStake",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IBANK",
- "name":"IBANK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ICASH",
- "name":"iCash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ICN",
- "name":"iCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IFLT",
- "name":"IFLT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IMPS",
- "name":"IMPS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"INCP",
- "name":"INCP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IFC",
- "name":"Infinitecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"INFX",
- "name":"Influxcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IOC",
- "name":"IO Coin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ION",
- "name":"ION",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ISL",
- "name":"IslaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IVZ",
- "name":"IVZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"IXC",
- "name":"IXC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"JPY",
- "name":"Japanese Yen",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"JOBS",
- "name":"JOBS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"JPC",
- "name":"JPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"JBS",
- "name":"Jumbucks",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"JW",
- "name":"JW",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"JWL",
- "name":"JWL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KAT",
- "name":"KAT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KC",
- "name":"KC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KNC",
- "name":"KhanCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KLC",
- "name":"KLC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KOBO",
- "name":"KOBO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KORE",
- "name":"KoreCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KRAK",
- "name":"KRAK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KRB",
- "name":"KRB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KRC",
- "name":"KRC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KRYP",
- "name":"KRYP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KR",
- "name":"Krypton",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"KTK",
- "name":"KTK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LANA",
- "name":"LANA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LAZ",
- "name":"LAZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LBC",
- "name":"LBC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LC",
- "name":"LC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LEA",
- "name":"LeaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LEAF",
- "name":"LEAF",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LEO",
- "name":"LEO",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"LFC",
- "name":"LFC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LFO",
- "name":"LFO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LFTC",
- "name":"LFTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LGBTQ",
- "name":"LGBTQ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LIR",
- "name":"LIR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LSK",
- "name":"Lisk",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LTC",
- "name":"Litecoin",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"LTCR",
- "name":"Litecred",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LIV",
- "name":"LIV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LKC",
- "name":"LKC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LOC",
- "name":"LOC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LOOT",
- "name":"LOOT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LTBC",
- "name":"LTBcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LTH",
- "name":"LTH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LTS",
- "name":"LTS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LUCKY",
- "name":"LUCKY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LUN",
- "name":"LUN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LXC",
- "name":"LXC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MAD",
- "name":"MAD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XMG",
- "name":"Magi",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MAID",
- "name":"MaidSafeCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MXT",
- "name":"MarteXcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OMNI",
- "name":"Mastercoin (Omni)",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MTR",
- "name":"MasterTraderCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MAX",
- "name":"Maxcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MZC",
- "name":"Mazacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MBL",
- "name":"MBL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MCZ",
- "name":"MCZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MED",
- "name":"MediterraneanCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MEGA",
- "name":"MEGA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MEC",
- "name":"Megacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MEME",
- "name":"Memetic",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"METAL",
- "name":"METAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MG",
- "name":"MG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MND",
- "name":"MindCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MINT",
- "name":"Mintcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MIS",
- "name":"MIS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MMNXT",
- "name":"MMNXT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MMXVI",
- "name":"MMXVI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MNM",
- "name":"MNM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MOIN",
- "name":"MOIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MOJO",
- "name":"MojoCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MONA",
- "name":"MonaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XMR",
- "name":"Monero",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"MUE",
- "name":"MonetaryUnit",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MOON",
- "name":"Mooncoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MOOND",
- "name":"MOOND",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MPRO",
- "name":"MPRO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MRB",
- "name":"MRB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MUDRA",
- "name":"MUDRA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MYR",
- "name":"Myriadcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"N2O",
- "name":"N2O",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"N7",
- "name":"N7",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NMC",
- "name":"Namecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NAT",
- "name":"NAT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NAUT",
- "name":"Nautiluscoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NAV",
- "name":"NAV Coin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NBIT",
- "name":"NBIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NCS",
- "name":"NCS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NDOGE",
- "name":"NDOGE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XEM",
- "name":"NEM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NEOS",
- "name":"NeosCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NET",
- "name":"NetCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NEU",
- "name":"NeuCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NTRN",
- "name":"Neutron",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NEVA",
- "name":"NevaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NEWB",
- "name":"NEWB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NXS",
- "name":"Nexus",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NIC",
- "name":"NIC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NICE",
- "name":"NICE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NKC",
- "name":"NKC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NLC",
- "name":"NLC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NOBL",
- "name":"NobleCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NODES",
- "name":"NODES",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NVC",
- "name":"Novacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NRS",
- "name":"NRS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NTC",
- "name":"NTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NBT",
- "name":"NuBits",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NUKE",
- "name":"NUKE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NUM",
- "name":"NUM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NSR",
- "name":"NuShares",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NXE",
- "name":"NXE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NXT",
- "name":"NXT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NXTTY",
- "name":"Nxttycoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NYC",
- "name":"NYC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NZC",
- "name":"NZC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"NZD",
- "name":"NZD",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"OBS",
- "name":"OBS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OCOW",
- "name":"OCOW",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OK",
- "name":"OKCash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OLYMP",
- "name":"OLYMP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OMC",
- "name":"OMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ONE",
- "name":"ONE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OP",
- "name":"OP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OPAL",
- "name":"OPAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ORB",
- "name":"Orbitcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"OZC",
- "name":"OZC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PAC",
- "name":"PAC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PAL",
- "name":"PAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PND",
- "name":"Pandacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PARA",
- "name":"PARA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PAY",
- "name":"PAY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPY",
- "name":"Paycoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PBC",
- "name":"PBC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PCM",
- "name":"PCM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PCS",
- "name":"PCS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PDC",
- "name":"PDC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PEC",
- "name":"PEC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PPC",
- "name":"Peercoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PEN",
- "name":"PEN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PHR",
- "name":"PHR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PIN",
- "name":"PIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PC",
- "name":"Pinkcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PIO",
- "name":"PIO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PIZZA",
- "name":"PIZZA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PKB",
- "name":"PKB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PLN",
- "name":"PLN",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"PLNC",
- "name":"PLNC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PNK",
- "name":"PNK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"POKE",
- "name":"POKE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PONZ2",
- "name":"PONZ2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PONZI",
- "name":"PONZI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PEX",
- "name":"PosEx",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"POST",
- "name":"POST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"POT",
- "name":"Potcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PRE",
- "name":"PRE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PRES",
- "name":"PRES",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PXI",
- "name":"Prime-XI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PRIME",
- "name":"PrimeChain",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPM",
- "name":"Primecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PRM",
- "name":"PRM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PRT",
- "name":"PRT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PSB",
- "name":"PSB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PSP",
- "name":"PSP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PSY",
- "name":"PSY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PTC",
- "name":"PTC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PURE",
- "name":"PURE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PUTIN",
- "name":"PUTIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PWR",
- "name":"PWR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PX",
- "name":"PX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"PXL",
- "name":"PXL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QBC",
- "name":"QBC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QBK",
- "name":"QBK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QCN",
- "name":"QCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QORA",
- "name":"Qora",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QTZ",
- "name":"QTZ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QRK",
- "name":"Quark",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"QTL",
- "name":"Quatloo",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RADI",
- "name":"RADI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RADS",
- "name":"Radium",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XRA",
- "name":"RateCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RBIT",
- "name":"RBIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RCN",
- "name":"RCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RED",
- "name":"RED",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RDD",
- "name":"Reddcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"REE",
- "name":"REE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"REV",
- "name":"Revenu",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RICHX",
- "name":"RICHX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RIC",
- "name":"Riecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RBT",
- "name":"Rimbit",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"RIO",
- "name":"RIO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XRP",
- "name":"Ripple",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RISE",
- "name":"RISE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RMS",
- "name":"RMS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RONIN",
- "name":"RONIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ROYAL",
- "name":"ROYAL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RPC",
- "name":"RPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RRT",
- "name":"RRT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RBIES",
- "name":"Rubies",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RUBIT",
- "name":"RUBIT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RUR",
- "name":"Ruble",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"RBY",
- "name":"Rubycoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RUST",
- "name":"RUST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"RYCN",
- "name":"RYCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SEC",
- "name":"Safe Exchange Coin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SAK",
- "name":"SAK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SAR",
- "name":"SAR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SBD",
- "name":"SBD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCAN",
- "name":"SCAN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCB",
- "name":"SCB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCN",
- "name":"SCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCOT",
- "name":"Scotcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCRPT",
- "name":"SCRPT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCRT",
- "name":"SCRT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SCT",
- "name":"SCT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SRC",
- "name":"SecureCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SED",
- "name":"SED",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SXC",
- "name":"Sexcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SGD",
- "name":"SGD",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"SH",
- "name":"SH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SDC",
- "name":"ShadowCash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SHELL",
- "name":"SHELL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SHI",
- "name":"SHI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SHIFT",
- "name":"Shift",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SC",
- "name":"Siacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SIB",
- "name":"Siberian chervonets",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SIGU",
- "name":"SIGU",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SLFI",
- "name":"SLFI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SLING",
- "name":"Sling",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SLK",
- "name":"SLK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SLS",
- "name":"SLS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SMC",
- "name":"SMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SMLY",
- "name":"SmileyCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SNGLS",
- "name":"SNGLS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SNRG",
- "name":"SNRG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SOIL",
- "name":"SOILcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SLR",
- "name":"Solarcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SONG",
- "name":"SongCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SOON",
- "name":"SOON",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SP",
- "name":"SP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPACE",
- "name":"SPACE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPEX",
- "name":"SPEX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPHR",
- "name":"Sphere",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPKTR",
- "name":"SPKTR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPN",
- "name":"SPN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPORT",
- "name":"SPORT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPR",
- "name":"SpreadCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPT",
- "name":"SPT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SPX",
- "name":"SPX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SSC",
- "name":"SSC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STA",
- "name":"STA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STAR",
- "name":"STAR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"START",
- "name":"Startcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STE",
- "name":"STE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XST",
- "name":"Stealthcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STEEM",
- "name":"Steem",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XLM",
- "name":"Stellar",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STR",
- "name":"Stellar",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STEPS",
- "name":"Steps",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SLG",
- "name":"Sterlingcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STHR",
- "name":"STHR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STL",
- "name":"STL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STO",
- "name":"STO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SJCX",
- "name":"Storjcoin X",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STP",
- "name":"STP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STRAT",
- "name":"STRAT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STS",
- "name":"Stress",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"STV",
- "name":"STV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SUB",
- "name":"Subcriptio",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNITY",
- "name":"SuperNET",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SWEET",
- "name":"SWEET",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SWING",
- "name":"SWING",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SYNC",
- "name":"SYNC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"AMP",
- "name":"Synereo",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SYNX",
- "name":"SYNX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"SYS",
- "name":"Syscoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TAB",
- "name":"TAB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TAG",
- "name":"TagCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TAJ",
- "name":"TAJ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TAK",
- "name":"TAK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TAO",
- "name":"TAO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TBC",
- "name":"TBC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TC",
- "name":"TC",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"TCOIN",
- "name":"TCOIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TCR",
- "name":"TCR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TDFB",
- "name":"TDFB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TDY",
- "name":"TDY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TEAM",
- "name":"TEAM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TEC",
- "name":"TEC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TECH",
- "name":"TECH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TEK",
- "name":"TEKcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRC",
- "name":"Terracoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TESLA",
- "name":"TESLA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TES",
- "name":"TeslaCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TET",
- "name":"TET",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"THC",
- "name":"THC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"DAO",
- "name":"The DAO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TIA",
- "name":"TIA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TIX",
- "name":"Tickets",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XTC",
- "name":"TileCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TIT",
- "name":"Titcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TMC",
- "name":"TMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TNG",
- "name":"TNG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TODAY",
- "name":"TODAY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TOKEN",
- "name":"TOKEN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TP1",
- "name":"TP1",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TPC",
- "name":"TPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TPG",
- "name":"TPG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TX",
- "name":"Transfercoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRAP",
- "name":"TRAP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRICK",
- "name":"TRICK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRIG",
- "name":"TRIG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TROLL",
- "name":"TROLL",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRK",
- "name":"Truckcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRUMP",
- "name":"TrumpCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TRUST",
- "name":"TRUST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TSC",
- "name":"TSC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TWERK",
- "name":"TWERK",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TWIST",
- "name":"TWIST",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"TWO",
- "name":"TWO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UAE",
- "name":"UAE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UB",
- "name":"UB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UFO",
- "name":"UFO Coin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UIS",
- "name":"UIS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UAH",
- "name":"Ukrainian Hryvnia",
- "statuses":[
- "secondary"
- ]
- },
- {
- "code":"UTC",
- "name":"UltraCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNB",
- "name":"UNB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNC",
- "name":"UNC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNF",
- "name":"Unfed",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNIQ",
- "name":"UNIQ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNIT",
- "name":"Universal Currency",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"UNO",
- "name":"Unobtanium",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"URC",
- "name":"URC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"URO",
- "name":"Uro",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"USD",
- "name":"US Dollar",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"USDE",
- "name":"USDE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XVC",
- "name":"Vcash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VCN",
- "name":"VCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VCOIN",
- "name":"VCOIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VEC",
- "name":"VEC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VEG",
- "name":"VEG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XVG",
- "name":"Verge",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VRC",
- "name":"VeriCoin",
- "statuses":[
- "primary",
- "secondary"
- ]
- },
- {
- "code":"VTC",
- "name":"Vertcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VIA",
- "name":"Viacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VIP",
- "name":"VIP Tokens",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VIRAL",
- "name":"Viral",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VLT",
- "name":"VLT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VOOT",
- "name":"VootCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VOX",
- "name":"Voxels",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VOYA",
- "name":"VOYA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VPN",
- "name":"VPNCoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VRM",
- "name":"VRM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VRS",
- "name":"VRS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VTA",
- "name":"VTA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VTN",
- "name":"VTN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VTR",
- "name":"VTR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"VTY",
- "name":"VTY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WA",
- "name":"WA",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WAC",
- "name":"WAC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WARP",
- "name":"WARP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WASH",
- "name":"WASH",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WAV",
- "name":"WAV",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WAVES",
- "name":"WAVES",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WAY",
- "name":"WAY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WCN",
- "name":"WCN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WEX",
- "name":"WEX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WGC",
- "name":"WGC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XWC",
- "name":"Whitecoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WBB",
- "name":"Wild Beast Block",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WINE",
- "name":"WINE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WLC",
- "name":"WLC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WMC",
- "name":"WMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"LOG",
- "name":"Woodcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WDC",
- "name":"Worldcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"WRP",
- "name":"WRP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"X2",
- "name":"X2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"X2C",
- "name":"X2C",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XAB",
- "name":"XAB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XAUR",
- "name":"XAUR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XAU",
- "name":"Xaurum",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XBS",
- "name":"XBS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XBTS",
- "name":"XBTS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XBU",
- "name":"XBU",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XCO",
- "name":"XCO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XC",
- "name":"XCurrency",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XDB",
- "name":"XDB",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XDE2",
- "name":"XDE2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"MI",
- "name":"Xiaomicoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XID",
- "name":"XID",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XJO",
- "name":"XJO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XLTCG",
- "name":"XLTCG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XMINE",
- "name":"XMINE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XMS",
- "name":"XMS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XNG",
- "name":"XNG",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XODUS",
- "name":"XODUS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPC",
- "name":"XPC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPO",
- "name":"XPO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPOKE",
- "name":"XPOKE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPRO",
- "name":"XPRO",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XPTX",
- "name":"XPTX",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XQN",
- "name":"XQN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XRC",
- "name":"XRC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XSEED",
- "name":"XSEED",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XSY",
- "name":"XSY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XTP",
- "name":"XTP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XUP",
- "name":"XUP",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YAC",
- "name":"Yacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YAY",
- "name":"YAY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YBC",
- "name":"Ybcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YMC",
- "name":"YMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YOC",
- "name":"YOC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YOVI",
- "name":"YOVI",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"YUM",
- "name":"YUM",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZEC",
- "name":"Zcash",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZCL",
- "name":"Zcash classic",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZCC",
- "name":"ZCC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZCOIN",
- "name":"ZCOIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"XZC",
- "name":"Zcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZECD",
- "name":"ZECD",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZEIT",
- "name":"Zeitcoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZET2",
- "name":"ZET2",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZET",
- "name":"Zetacoin",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZRC",
- "name":"ZiftrCOIN",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZLQ",
- "name":"ZLQ",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZMC",
- "name":"ZMC",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZNE",
- "name":"ZNE",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZNY",
- "name":"ZNY",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZS",
- "name":"ZS",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZUR",
- "name":"ZUR",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZXT",
- "name":"ZXT",
- "statuses":[
- "primary"
- ]
- },
- {
- "code":"ZYD",
- "name":"ZYD",
- "statuses":[
- "primary"
- ]
- }
- ]
-}
\ No newline at end of file +{ + "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" + ] + } + ] +} diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 8c6ff29d3..808aafb4c 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -32,7 +32,7 @@ input:focus, textarea:focus { height: 500px; } -button { +button, input[type="submit"] { font-family: 'Montserrat Bold'; outline: none; cursor: pointer; @@ -46,17 +46,17 @@ button { box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); } -button.btn-green { +.btn-green, input[type="submit"].btn-green { background: rgba(106, 195, 96, 1); box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36); } -button.btn-red { +.btn-red { background: rgba(254, 35, 17, 1); box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); } -button[disabled] { +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); @@ -66,10 +66,10 @@ button.spaced { margin: 2px; } -button:not([disabled]):hover { +button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { transform: scale(1.1); } -button:not([disabled]):active { +button:not([disabled]):active, input[type="submit"]:not([disabled]):active { transform: scale(0.95); } @@ -148,7 +148,6 @@ h2.page-subtitle { } textarea.twelve-word-phrase { - margin-top: 20px; padding: 12px; width: 300px; height: 140px; @@ -266,8 +265,9 @@ app sections } .sizing-input{ - font-size: 1em; + font-size: 14px; height: 30px; + padding-left: 5px; } .editable-label{ display: flex; @@ -489,6 +489,47 @@ input.large-input { } /* 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; diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 99c6f1b9d..910a24ee2 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -188,7 +188,11 @@ hr.horizontal-line { .hollow-diamond { transform: rotate(45deg); - border: 1px solid #038789; + border: 3px solid #690496; +} + +.golden-square { + background: #EBB33F; } .pending-dot { diff --git a/ui/app/info.js b/ui/app/info.js index e79580be4..aa4503b62 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -3,7 +3,6 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') -const extension = require('../../app/scripts/lib/extension') module.exports = connect(mapStateToProps)(InfoScreen) @@ -17,13 +16,8 @@ function InfoScreen () { } InfoScreen.prototype.render = function () { - var state = this.props - var manifest - try { - manifest = extension.runtime.getManifest() - } catch (e) { - manifest = { version: '2.0.0' } - } + const state = this.props + const version = global.platform.getVersion() return ( h('.flex-column.flex-grow', [ @@ -53,12 +47,12 @@ InfoScreen.prototype.render = function () { style: { marginBottom: '10px', }, - }, `Version: ${manifest.version}`), + }, `Version: ${version}`), ]), h('div', { style: { - marginBottom: '10px', + marginBottom: '5px', }}, [ h('div', [ @@ -93,7 +87,7 @@ InfoScreen.prototype.render = function () { h('hr', { style: { - margin: '20px 0 ', + margin: '10px 0 ', width: '7em', }, }), @@ -103,6 +97,13 @@ InfoScreen.prototype.render = function () { paddingLeft: '30px', }}, [ + h('div.fa.fa-github', [ + h('a.info', { + href: 'https://github.com/MetaMask/faq', + target: '_blank', + onClick (event) { this.navigateTo(event.target.href) }, + }, 'Need Help? Read our FAQ!'), + ]), h('div', [ h('a', { href: 'https://metamask.io/', @@ -110,10 +111,12 @@ InfoScreen.prototype.render = function () { onClick (event) { this.navigateTo(event.target.href) }, }, [ h('img.icon-size', { - src: manifest.icons['128'], + src: 'images/icon-128.png', style: { - filter: 'grayscale(100%)', /* IE6-9 */ - WebkitFilter: 'grayscale(100%)', /* Microsoft Edge and Firefox 35+ */ + // IE6-9 + filter: 'grayscale(100%)', + // Microsoft Edge and Firefox 35+ + WebkitFilter: 'grayscale(100%)', }, }), h('div.info', 'Visit our web site'), @@ -139,17 +142,9 @@ InfoScreen.prototype.render = function () { h('a.info', { target: '_blank', style: { width: '85vw' }, - onClick () { extension.tabs.create({url: 'mailto:help@metamask.io?subject=Feedback'}) }, + onClick () { this.navigateTo('mailto:help@metamask.io?subject=Feedback') }, }, 'Email us!'), ]), - - h('div.fa.fa-github', [ - h('a.info', { - href: 'https://github.com/MetaMask/metamask-plugin/issues', - target: '_blank', - onClick (event) { this.navigateTo(event.target.href) }, - }, 'Start a thread on GitHub'), - ]), ]), ]), ]), @@ -158,5 +153,5 @@ InfoScreen.prototype.render = function () { } InfoScreen.prototype.navigateTo = function (url) { - extension.tabs.create({ url }) + global.platform.openWindow({ url }) } diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index 7272ebdbd..5230797ad 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -45,12 +45,17 @@ CreateVaultCompleteScreen.prototype.render = function () { 'Vault Created', ]), - h('span.error', { // Error for the right red + h('div', { style: { - padding: '12px 20px 0px 20px', + width: '360px', + height: '78px', + fontSize: '1em', + marginTop: '10px', textAlign: 'center', }, - }, 'These 12 words can restore all of your MetaMask accounts for this vault.\nSave them somewhere safe and secret.'), + }, [ + h('span.error', 'These 12 words can restore all of your MetaMask accounts for this vault.\nSave them somewhere safe and secret.'), + ]), h('textarea.twelve-word-phrase', { readOnly: true, diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 56ac461ea..4ccbec9fc 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -18,11 +18,8 @@ function mapStateToProps (state) { } } -RevealSeedConfirmation.prototype.confirmationPhrase = 'I understand' - RevealSeedConfirmation.prototype.render = function () { const props = this.props - const state = this.state return ( @@ -64,31 +61,13 @@ RevealSeedConfirmation.prototype.render = function () { }, }), - h(`h4${state && state.confirmationWrong ? '.error' : ''}`, { - style: { - marginTop: '12px', - }, - }, `Enter the phrase "${this.confirmationPhrase}" to proceed.`), - - // confirm confirmation - h('input.large-input.letter-spacey', { - type: 'text', - id: 'confirm-box', - placeholder: this.confirmationPhrase, - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: 16, - }, - }), - h('.flex-row.flex-space-between', { style: { marginTop: 30, width: '50%', }, }, [ -// cancel + // cancel h('button.primary', { onClick: this.goHome.bind(this), }, 'CANCEL'), @@ -134,15 +113,6 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) { } RevealSeedConfirmation.prototype.revealSeedWords = function () { - this.setState({ confirmationWrong: false }) - - const confirmBox = document.getElementById('confirm-box') - const confirmation = confirmBox.value - if (confirmation !== this.confirmationPhrase) { - confirmBox.value = '' - return this.setState({ confirmationWrong: true }) - } - var password = document.getElementById('password-box').value this.props.dispatch(actions.requestRevealSeed(password)) } diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 4d10e2b39..11efca529 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -42,6 +42,11 @@ function rootReducer (state, action) { } window.logState = function () { - var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, null, 2) + 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 } diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 7ea1e1d7c..deacad0a7 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -1,20 +1,20 @@ const extend = require('xtend') const actions = require('../actions') const txHelper = require('../../lib/tx-helper') -const notification = require('../../../app/scripts/lib/notifications') module.exports = reduceApp + function reduceApp (state, action) { log.debug('App Reducer got ' + action.type) // clone and defaults const selectedAddress = state.metamask.selectedAddress - let pendingTxs = hasPendingTxs(state) + const hasUnconfActions = checkUnconfActions(state) let name = 'accounts' if (selectedAddress) { name = 'accountDetail' } - if (pendingTxs) { + if (hasUnconfActions) { log.debug('pending txs detected, defaulting to conf-tx view.') name = 'confTx' } @@ -32,7 +32,9 @@ function reduceApp (state, action) { seedWords, } + // default state var appState = extend({ + shouldClose: false, menuOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { @@ -302,7 +304,7 @@ function reduceApp (state, action) { case actions.SHOW_CONF_MSG_PAGE: return extend(appState, { currentView: { - name: pendingTxs ? 'confTx' : 'account-detail', + name: hasUnconfActions ? 'confTx' : 'account-detail', context: 0, }, transForward: true, @@ -312,15 +314,11 @@ function reduceApp (state, action) { case actions.COMPLETED_TX: log.debug('reducing COMPLETED_TX for tx ' + action.value) - var { unapprovedTxs, unapprovedMsgs, - unapprovedPersonalMsgs, network } = state.metamask - - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - .filter(tx => tx.id !== action.value ) + const otherUnconfActions = getUnconfActionList(state) + .filter(tx => tx.id !== action.value) + const hasOtherUnconfActions = otherUnconfActions.length > 0 - pendingTxs = unconfTxList.length > 0 - - if (pendingTxs) { + if (hasOtherUnconfActions) { log.debug('reducer detected txs - rendering confTx view') return extend(appState, { transForward: false, @@ -332,9 +330,9 @@ function reduceApp (state, action) { }) } else { log.debug('attempting to close popup') - notification.closePopup() - return extend(appState, { + // indicate notification should close + shouldClose: true, transForward: false, warning: null, currentView: { @@ -426,6 +424,7 @@ function reduceApp (state, action) { case actions.DISPLAY_WARNING: return extend(appState, { warning: action.value, + isLoading: false, }) case actions.HIDE_WARNING: @@ -470,8 +469,9 @@ function reduceApp (state, action) { name: 'buyEth', context: appState.currentView.name, }, + identity: state.metamask.identities[action.value], buyView: { - subview: 'buyForm', + subview: 'Coinbase', amount: '15.00', buyAddress: action.value, formView: { @@ -481,36 +481,10 @@ function reduceApp (state, action) { }, }) - case actions.UPDATE_BUY_ADDRESS: - return extend(appState, { - buyView: { - subview: 'buyForm', - formView: { - coinbase: appState.buyView.formView.coinbase, - shapeshift: appState.buyView.formView.shapeshift, - }, - buyAddress: action.value, - amount: appState.buyView.amount, - }, - }) - - case actions.UPDATE_COINBASE_AMOUNT: - return extend(appState, { - buyView: { - subview: 'buyForm', - formView: { - coinbase: true, - shapeshift: false, - }, - buyAddress: appState.buyView.buyAddress, - amount: action.value, - }, - }) - case actions.COINBASE_SUBVIEW: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'Coinbase', formView: { coinbase: true, shapeshift: false, @@ -523,7 +497,7 @@ function reduceApp (state, action) { case actions.SHAPESHIFT_SUBVIEW: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'ShapeShift', formView: { coinbase: false, shapeshift: true, @@ -538,7 +512,7 @@ function reduceApp (state, action) { case actions.PAIR_UPDATE: return extend(appState, { buyView: { - subview: 'buyForm', + subview: 'ShapeShift', formView: { coinbase: false, shapeshift: true, @@ -579,25 +553,23 @@ function reduceApp (state, action) { } } -function hasPendingTxs (state) { - var { unapprovedTxs, unapprovedMsgs, +function checkUnconfActions (state) { + const unconfActionList = getUnconfActionList(state) + const hasUnconfActions = unconfActionList.length > 0 + return hasUnconfActions +} + +function getUnconfActionList (state) { + const { unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network } = state.metamask - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - var has = unconfTxList.length > 0 - return has + const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + return unconfActionList } function indexForPending (state, txId) { - var unapprovedTxs = state.metamask.unapprovedTxs - var unapprovedMsgs = state.metamask.unapprovedMsgs - var network = state.metamask.network - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) - let idx - unconfTxList.forEach((tx, i) => { - if (tx.id === txId) { - idx = i - } - }) - return idx + const unconfTxList = getUnconfActionList(state) + const match = unconfTxList.find((tx) => tx.id === txId) + const index = unconfTxList.indexOf(match) + return index } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 2b5151466..e0c416c2d 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -94,6 +94,7 @@ function reduceMetamask (state, action) { return extend(metamaskState, { isUnlocked: true, isInitialized: false, + seedWords: action.value, }) case actions.CLEAR_SEED_WORD_CACHE: diff --git a/ui/app/send.js b/ui/app/send.js index eb32d5e06..fd6994145 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -21,6 +21,8 @@ function mapStateToProps (state) { 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] @@ -40,13 +42,17 @@ function SendTransactionScreen () { SendTransactionScreen.prototype.render = function () { this.persistentFormParentId = 'send-tx-form' - var state = this.props - var address = state.address - var account = state.account - var identity = state.identity - var network = state.network - var identities = state.identities - var addressBook = state.addressBook + const props = this.props + const { + address, + account, + identity, + network, + identities, + addressBook, + conversionRate, + currentCurrency, + } = props return ( @@ -125,6 +131,8 @@ SendTransactionScreen.prototype.render = function () { h(EthBalance, { value: account && account.balance, + conversionRate, + currentCurrency, }), ]), @@ -147,7 +155,7 @@ SendTransactionScreen.prototype.render = function () { ]), // error message - state.error && h('span.error.flex-center', state.error), + props.error && h('span.error.flex-center', props.error), // 'to' field h('section.flex-row.flex-center', [ diff --git a/ui/app/util.js b/ui/app/util.js index 7a56bf6a0..ac3f42c6b 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -61,6 +61,7 @@ function miniAddressSummary (address) { function isValidAddress (address) { var prefixed = ethUtil.addHexPrefix(address) + if (address === '0x0000000000000000000000000000000000000000') return false return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } |