diff --git a/Cargo.lock b/Cargo.lock index f3109228a..cf6d3bb6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1773,7 +1773,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/paritytech/js-precompiled.git#b4927eeaa876ff4daffda80ec85382fbd95f313c" +source = "git+https://github.com/paritytech/js-precompiled.git#bceb0caa1eae7d78ad3386338a76b7ca92bf033a" dependencies = [ "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 286a89bc1..ed551e2f3 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -163,7 +163,7 @@ impl GasPriceCalibrator { let wei_per_usd: f32 = 1.0e18 / usd_per_eth; let gas_per_tx: f32 = 21000.0; let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx; - info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas))); + info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${:.2}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas))); set_price(U256::from(wei_per_gas as u64)); }); diff --git a/js/package.json b/js/package.json index 4424c9129..59f1ccc50 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "1.7.55", + "version": "1.7.57", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", diff --git a/js/src/api/api.js b/js/src/api/api.js index 2c102086f..526ecbf1b 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -59,7 +59,8 @@ export default class Api extends EventEmitter { } return null; - }); + }) + .catch(() => null); transport.addMiddleware(middleware); } diff --git a/js/src/contracts/tokenreg.js b/js/src/contracts/tokenreg.js index 22e3c834d..2bbf639bf 100644 --- a/js/src/contracts/tokenreg.js +++ b/js/src/contracts/tokenreg.js @@ -27,7 +27,7 @@ export default class TokenReg { } getInstance () { - return this.getContract().instance; + return this.getContract().then((contract) => contract.instance); } tokenCount () { diff --git a/js/src/modals/CreateAccount/GethCard/gethCard.js b/js/src/modals/CreateAccount/GethCard/gethCard.js index 17250bcb3..fb95b7515 100644 --- a/js/src/modals/CreateAccount/GethCard/gethCard.js +++ b/js/src/modals/CreateAccount/GethCard/gethCard.js @@ -16,8 +16,8 @@ import React, { Component, PropTypes } from 'react'; -import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png'; import { AccountCard } from '~/ui'; +import { ETH_TOKEN } from '~/util/tokens'; export default class GethCard extends Component { static propTypes = { @@ -36,14 +36,7 @@ export default class GethCard extends Component { name } } balance={ { - tokens: [ { - value: balance, - token: { - image: imagesEthereum, - native: true, - tag: 'ETH' - } - } ] + [ETH_TOKEN.id]: balance } } /> ); diff --git a/js/src/modals/DappPermissions/dappPermissions.js b/js/src/modals/DappPermissions/dappPermissions.js index 903e31fd9..81821cb8e 100644 --- a/js/src/modals/DappPermissions/dappPermissions.js +++ b/js/src/modals/DappPermissions/dappPermissions.js @@ -17,14 +17,12 @@ import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; import { AccountCard, Portal, SelectionList } from '~/ui'; @observer -class DappPermissions extends Component { +export default class DappPermissions extends Component { static propTypes = { - balances: PropTypes.object, permissionStore: PropTypes.object.isRequired }; @@ -66,27 +64,10 @@ class DappPermissions extends Component { } renderAccount = (account) => { - const { balances } = this.props; - const balance = balances[account.address]; - return ( ); } } - -function mapStateToProps (state) { - const { balances } = state.balances; - - return { - balances - }; -} - -export default connect( - mapStateToProps, - null -)(DappPermissions); diff --git a/js/src/modals/DappPermissions/dappPermissions.spec.js b/js/src/modals/DappPermissions/dappPermissions.spec.js index 717ec3b20..81af509b7 100644 --- a/js/src/modals/DappPermissions/dappPermissions.spec.js +++ b/js/src/modals/DappPermissions/dappPermissions.spec.js @@ -16,38 +16,15 @@ import { shallow } from 'enzyme'; import React from 'react'; -import sinon from 'sinon'; import DappPermissions from './'; let component; -let store; - -function createRedux () { - store = { - dispatch: sinon.stub(), - subscribe: sinon.stub(), - getState: () => { - return { - balances: { - balances: {} - } - }; - } - }; - - return store; -} function renderShallow (permissionStore = {}) { component = shallow( - , - { - context: { - store: createRedux() - } - } - ).find('DappPermissions').shallow(); + + ); return component; } diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 4086b08a3..7f5f9bdbc 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -47,7 +47,6 @@ export default class DetailsStep extends Component { abiError: PropTypes.string, amount: PropTypes.string, amountError: PropTypes.string, - balances: PropTypes.object, code: PropTypes.string, codeError: PropTypes.string, description: PropTypes.string, @@ -87,7 +86,6 @@ export default class DetailsStep extends Component { render () { const { accounts, - balances, readOnly, fromAddress, fromAddressError, @@ -141,7 +139,6 @@ export default class DetailsStep extends Component { . import BigNumber from 'bignumber.js'; -import { pick } from 'lodash'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -69,7 +68,6 @@ class DeployContract extends Component { static propTypes = { accounts: PropTypes.object.isRequired, abi: PropTypes.string, - balances: PropTypes.object, code: PropTypes.string, gasLimit: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, @@ -282,7 +280,7 @@ class DeployContract extends Component { } renderStep () { - const { accounts, readOnly, balances } = this.props; + const { accounts, readOnly } = this.props; const { step } = this.state; switch (step) { @@ -291,7 +289,6 @@ class DeployContract extends Component { { - const balances = pick(state.balances.balances, fromAddresses); - const { gasLimit } = state.nodeStatus; - - return { - accounts, - balances, - gasLimit - }; + return { + gasLimit }; } diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 44d2d1aef..913def0ee 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -34,7 +34,6 @@ export default class DetailsStep extends Component { accounts: PropTypes.object.isRequired, amount: PropTypes.string, amountError: PropTypes.string, - balances: PropTypes.object, contract: PropTypes.object.isRequired, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, @@ -51,13 +50,12 @@ export default class DetailsStep extends Component { } render () { - const { accounts, advancedOptions, amount, amountError, balances, fromAddress, fromAddressError, onAdvancedClick, onAmountChange, onFromAddressChange } = this.props; + const { accounts, advancedOptions, amount, amountError, fromAddress, fromAddressError, onAdvancedClick, onAmountChange, onFromAddressChange } = this.props; return (
. -import { pick } from 'lodash'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -58,7 +57,6 @@ class ExecuteContract extends Component { static propTypes = { accounts: PropTypes.object, - balances: PropTypes.object, contract: PropTypes.object.isRequired, fromAddress: PropTypes.string, gasLimit: PropTypes.object.isRequired, @@ -199,14 +197,16 @@ class ExecuteContract extends Component { } renderStep () { - const { onFromAddressChange } = this.props; + const { accounts, contract, fromAddress, onFromAddressChange } = this.props; const { step } = this.state; if (step === STEP_DETAILS) { return ( { - const balances = pick(state.balances.balances, fromAddresses); - const { gasLimit } = state.nodeStatus; - - return { gasLimit, balances }; - }; + return { gasLimit }; } export default connect( diff --git a/js/src/modals/Transfer/Details/details.js b/js/src/modals/Transfer/Details/details.js index c4bc6a2d8..9b5182cea 100644 --- a/js/src/modals/Transfer/Details/details.js +++ b/js/src/modals/Transfer/Details/details.js @@ -38,10 +38,9 @@ export default class Details extends Component { extras: PropTypes.bool, sender: PropTypes.string, senderError: PropTypes.string, - sendersBalances: PropTypes.object, recipient: PropTypes.string, recipientError: PropTypes.string, - tag: PropTypes.string, + token: PropTypes.object, total: PropTypes.string, totalError: PropTypes.string, value: PropTypes.string, @@ -57,13 +56,13 @@ export default class Details extends Component { }; render () { - const { all, extras, tag, total, totalError, value, valueError } = this.props; + const { all, extras, token, total, totalError, value, valueError } = this.props; const label = ( ); @@ -140,7 +139,7 @@ export default class Details extends Component { } renderFromAddress () { - const { sender, senderError, senders, sendersBalances } = this.props; + const { sender, senderError, senders } = this.props; if (!senders) { return null; @@ -165,7 +164,6 @@ export default class Details extends Component { } value={ sender } onChange={ this.onEditSender } - balances={ sendersBalances } /> ); @@ -198,19 +196,19 @@ export default class Details extends Component { } renderTokenSelect () { - const { balance, tag } = this.props; + const { balance, token } = this.props; return ( ); } - onChangeToken = (event, index, tag) => { - this.props.onChange('tag', tag); + onChangeToken = (event, index, tokenId) => { + this.props.onChange('token', tokenId); } onEditSender = (event, sender) => { diff --git a/js/src/modals/Transfer/Details/tokenSelect.js b/js/src/modals/Transfer/Details/tokenSelect.js index 47f42edaa..7f18e304b 100644 --- a/js/src/modals/Transfer/Details/tokenSelect.js +++ b/js/src/modals/Transfer/Details/tokenSelect.js @@ -16,6 +16,7 @@ import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; import { MenuItem } from 'material-ui'; import { isEqual } from 'lodash'; @@ -24,7 +25,7 @@ import TokenImage from '~/ui/TokenImage'; import styles from '../transfer.css'; -export default class TokenSelect extends Component { +class TokenSelect extends Component { static contextTypes = { api: PropTypes.object }; @@ -32,7 +33,8 @@ export default class TokenSelect extends Component { static propTypes = { onChange: PropTypes.func.isRequired, balance: PropTypes.object.isRequired, - tag: PropTypes.string.isRequired + tokens: PropTypes.object.isRequired, + value: PropTypes.string.isRequired }; componentWillMount () { @@ -40,8 +42,10 @@ export default class TokenSelect extends Component { } componentWillReceiveProps (nextProps) { - const prevTokens = this.props.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`); - const nextTokens = nextProps.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`); + const prevTokens = Object.keys(this.props.balance) + .map((tokenId) => `${tokenId}_${this.props.balance[tokenId].toNumber()}`); + const nextTokens = Object.keys(nextProps.balance) + .map((tokenId) => `${tokenId}_${nextProps.balance[tokenId].toNumber()}`); if (!isEqual(prevTokens, nextTokens)) { this.computeTokens(nextProps); @@ -50,23 +54,27 @@ export default class TokenSelect extends Component { computeTokens (props = this.props) { const { api } = this.context; - const { balance } = this.props; + const { balance, tokens } = this.props; - const items = balance.tokens - .filter((token, index) => !index || token.value.gt(0)) - .map((balance, index) => { - const token = balance.token; - const isEth = index === 0; + const items = Object.keys(balance) + .map((tokenId) => { + const token = tokens[tokenId]; + const tokenValue = balance[tokenId]; + const isEth = token.native; + + if (!isEth && tokenValue.eq(0)) { + return null; + } let value = 0; if (isEth) { - value = api.util.fromWei(balance.value).toFormat(3); + value = api.util.fromWei(tokenValue).toFormat(3); } else { - const format = balance.token.format || 1; + const format = token.format || 1; const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10)); - value = new BigNumber(balance.value).div(format).toFormat(decimals); + value = new BigNumber(tokenValue).div(format).toFormat(decimals); } const label = ( @@ -83,20 +91,21 @@ export default class TokenSelect extends Component { return ( { label } ); - }); + }) + .filter((node) => node); this.setState({ items }); } render () { - const { tag, onChange } = this.props; + const { onChange, value } = this.props; const { items } = this.state; return ( @@ -104,7 +113,7 @@ export default class TokenSelect extends Component { className={ styles.tokenSelect } label='type of token transfer' hint='type of token to transfer' - value={ tag } + value={ value } onChange={ onChange } > { items } @@ -112,3 +121,11 @@ export default class TokenSelect extends Component { ); } } + +function mapStateToProps (state) { + const { tokens } = state; + + return { tokens }; +} + +export default connect(mapStateToProps)(TokenSelect); diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index c36d5029b..a0f99503c 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -18,11 +18,12 @@ import { noop } from 'lodash'; import { observable, computed, action, transaction } from 'mobx'; import BigNumber from 'bignumber.js'; -import { wallet as walletAbi } from '~/contracts/abi'; +import { eip20 as tokenAbi, wallet as walletAbi } from '~/contracts/abi'; import { fromWei } from '~/api/util/wei'; import Contract from '~/api/contract'; import ERRORS from './errors'; import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants'; +import { ETH_TOKEN } from '~/util/tokens'; import GasPriceStore from '~/ui/GasPriceEditor/store'; import { getLogger, LOG_KEYS } from '~/config'; @@ -40,10 +41,10 @@ export const WALLET_WARNING_SPENT_TODAY_LIMIT = 'WALLET_WARNING_SPENT_TODAY_LIMI export default class TransferStore { @observable stage = 0; @observable extras = false; + @observable isEth = true; @observable valueAll = false; @observable sending = false; - @observable tag = 'ETH'; - @observable isEth = true; + @observable token = ETH_TOKEN; @observable data = ''; @observable dataError = null; @@ -69,6 +70,8 @@ export default class TransferStore { onClose = noop; senders = null; isWallet = false; + tokenContract = null; + tokens = {}; wallet = null; gasStore = null; @@ -76,14 +79,16 @@ export default class TransferStore { constructor (api, props) { this.api = api; - const { account, balance, gasLimit, onClose, senders, newError, sendersBalances } = props; + const { account, balance, gasLimit, onClose, senders, newError, sendersBalances, tokens } = props; this.account = account; this.balance = balance; this.isWallet = account && account.wallet; this.newError = newError; + this.tokens = tokens; this.gasStore = new GasPriceStore(api, { gasLimit }); + this.tokenContract = api.newContract(tokenAbi, ''); if (this.isWallet) { this.wallet = props.wallet; @@ -126,10 +131,6 @@ export default class TransferStore { } } - get token () { - return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token; - } - @action onNext = () => { this.stage += 1; } @@ -166,8 +167,8 @@ export default class TransferStore { case 'sender': return this._onUpdateSender(value); - case 'tag': - return this._onUpdateTag(value); + case 'token': + return this._onUpdateToken(value); case 'value': return this._onUpdateValue(value); @@ -244,10 +245,10 @@ export default class TransferStore { }); } - @action _onUpdateTag = (tag) => { + @action _onUpdateToken = (tokenId) => { transaction(() => { - this.tag = tag; - this.isEth = tag.toLowerCase().trim() === 'eth'; + this.token = { ...this.tokens[tokenId] }; + this.isEth = this.token.native; this.recalculateGas(); }); @@ -323,46 +324,15 @@ export default class TransferStore { return balance; } - getToken (tag = this.tag, forceSender = false) { - const balance = this.getBalance(forceSender); - - if (!balance) { - return null; - } - - const _tag = tag.toLowerCase(); - const token = balance.tokens.find((b) => b.token.tag.toLowerCase() === _tag); - - return token; - } - /** * Return the balance of the selected token * (in WEI for ETH, without formating for other tokens) */ - getTokenBalance (tag = this.tag, forceSender = false) { - const token = this.getToken(tag, forceSender); - - if (!token) { - return new BigNumber(0); - } - - const value = new BigNumber(token.value || 0); - - return value; + getTokenBalance (token = this.token, forceSender = false) { + return new BigNumber(this.balance[token.id] || 0); } - getTokenValue (tag = this.tag, value = this.value, inverse = false) { - const token = this.getToken(tag); - - if (!token) { - return new BigNumber(0); - } - - const format = token.token - ? new BigNumber(token.token.format || 1) - : new BigNumber(1); - + getTokenValue (token = this.token, value = this.value, inverse = false) { let _value; try { @@ -371,19 +341,11 @@ export default class TransferStore { _value = new BigNumber(0); } - if (token.token && token.token.tag.toLowerCase() === 'eth') { - if (inverse) { - return this.api.util.fromWei(_value); - } - - return this.api.util.toWei(_value); - } - if (inverse) { - return _value.div(format); + return _value.div(token.format); } - return _value.mul(format); + return _value.mul(token.format); } getValues (_gasTotal) { @@ -425,7 +387,7 @@ export default class TransferStore { } // Otherwise, substract the gas estimate - const availableEth = this.getTokenBalance('ETH'); + const availableEth = this.getTokenBalance(ETH_TOKEN); const totalEthValue = availableEth.gt(gasTotal) ? availableEth.minus(gasTotal) : new BigNumber(0); @@ -437,15 +399,7 @@ export default class TransferStore { } getFormattedTokenValue (tokenValue) { - const token = this.getToken(); - - if (!token) { - return new BigNumber(0); - } - - const tag = token.token && token.token.tag || ''; - - return this.getTokenValue(tag, tokenValue, true); + return this.getTokenValue(this.token, tokenValue, true); } @action recalculate = (redo = false) => { @@ -463,7 +417,7 @@ export default class TransferStore { const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0)); - const ethBalance = this.getTokenBalance('ETH', true); + const ethBalance = this.getTokenBalance(ETH_TOKEN, true); const tokenBalance = this.getTokenBalance(); const { eth, token } = this.getValues(gasTotal); @@ -545,7 +499,7 @@ export default class TransferStore { return this.wallet.instance.execute; } - return this.token.contract.instance.transfer; + return this.tokenContract.at(this.token.address).instance.transfer; } _getData (gas = false) { @@ -558,7 +512,7 @@ export default class TransferStore { const func = this._getTransferMethod(gas, true); const { options, values } = this._getTransferParams(gas, true); - return this.token.contract.getCallData(func, options, values); + return this.tokenContract.at(this.token.address).getCallData(func, options, values); } _getTransferParams (gas = false, forceToken = false) { @@ -587,7 +541,7 @@ export default class TransferStore { } if (isWallet && !forceToken) { - const to = isEth ? this.recipient : this.token.contract.address; + const to = isEth ? this.recipient : this.token.address; const value = isEth ? token : new BigNumber(0); const values = [ @@ -621,14 +575,7 @@ export default class TransferStore { } _validateDecimals (num) { - const { balance } = this; - - if (this.tag === 'ETH') { - return null; - } - - const token = balance.tokens.find((balance) => balance.token.tag === this.tag).token; - const s = new BigNumber(num).mul(token.format || 1).toFixed(); + const s = new BigNumber(num).mul(this.token.format || 1).toFixed(); if (s.indexOf('.') !== -1) { return ERRORS.invalidDecimals; diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 8488a2035..ab769ff02 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -45,12 +45,13 @@ class Transfer extends Component { newError: PropTypes.func.isRequired, gasLimit: PropTypes.object.isRequired, - senders: nullableProptype(PropTypes.object), - sendersBalances: nullableProptype(PropTypes.object), account: PropTypes.object, balance: PropTypes.object, - wallet: PropTypes.object, - onClose: PropTypes.func + onClose: PropTypes.func, + senders: nullableProptype(PropTypes.object), + sendersBalances: nullableProptype(PropTypes.object), + tokens: PropTypes.object, + wallet: PropTypes.object } store = new TransferStore(this.context.api, this.props); @@ -144,8 +145,8 @@ class Transfer extends Component { renderDetailsPage () { const { account, balance, senders } = this.props; - const { recipient, recipientError, sender, senderError, sendersBalances } = this.store; - const { valueAll, extras, tag, total, totalError, value, valueError } = this.store; + const { recipient, recipientError, sender, senderError } = this.store; + const { valueAll, extras, token, total, totalError, value, valueError } = this.store; return (
{ const { gasLimit } = state.nodeStatus; - const sendersBalances = senders ? pick(state.balances.balances, Object.keys(senders)) : null; + const { balances } = state; - return { gasLimit, wallet, senders, sendersBalances }; + const balance = balances[address]; + const sendersBalances = senders ? pick(balances, Object.keys(senders)) : null; + + return { balance, gasLimit, senders, sendersBalances, tokens, wallet }; }; } diff --git a/js/src/modals/VaultAccounts/vaultAccounts.js b/js/src/modals/VaultAccounts/vaultAccounts.js index 748969ffb..daf2d7c51 100644 --- a/js/src/modals/VaultAccounts/vaultAccounts.js +++ b/js/src/modals/VaultAccounts/vaultAccounts.js @@ -33,7 +33,6 @@ class VaultAccounts extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - balances: PropTypes.object.isRequired, newError: PropTypes.func.isRequired, personalAccountsInfo: PropTypes.func.isRequired, vaultStore: PropTypes.object.isRequired @@ -108,13 +107,9 @@ class VaultAccounts extends Component { } renderAccount = (account) => { - const { balances } = this.props; - const balance = balances[account.address]; - return ( ); } @@ -171,12 +166,10 @@ class VaultAccounts extends Component { } function mapStateToProps (state) { - const { balances } = state.balances; const { accounts } = state.personal; return { - accounts, - balances + accounts }; } diff --git a/js/src/redux/providers/balances.js b/js/src/redux/providers/balances.js index e438f6f1a..cc7df73dd 100644 --- a/js/src/redux/providers/balances.js +++ b/js/src/redux/providers/balances.js @@ -16,7 +16,8 @@ import { throttle } from 'lodash'; -import { loadTokens, setTokenReg, fetchBalances, fetchTokens, fetchTokensBalances } from './balancesActions'; +import { fetchBalances, fetchTokensBalances, queryTokensFilter } from './balancesActions'; +import { loadTokens, fetchTokens } from './tokensActions'; import { padRight } from '~/api/util/format'; import Contracts from '~/contracts'; @@ -193,6 +194,7 @@ export default class Balances { return console.warn('_subscribeBlockNumber', error); } + this._store.dispatch(queryTokensFilter()); return this.fetchAllBalances(); }) .then((blockNumberSID) => { @@ -276,7 +278,6 @@ export default class Balances { .then((tokenreg) => { this._tokenreg = tokenreg; - this._store.dispatch(setTokenReg(tokenreg)); this._store.dispatch(loadTokens(options)); return this.attachToTokens(tokenreg); diff --git a/js/src/redux/providers/balancesActions.js b/js/src/redux/providers/balancesActions.js index 2a357c916..73726e825 100644 --- a/js/src/redux/providers/balancesActions.js +++ b/js/src/redux/providers/balancesActions.js @@ -14,119 +14,19 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { range, uniq, isEqual } from 'lodash'; -import BigNumber from 'bignumber.js'; +import { uniq, isEqual } from 'lodash'; import { push } from 'react-router-redux'; -import { hashToImageUrl } from './imagesReducer'; -import { setAddressImage } from './imagesActions'; - -import * as ABIS from '~/contracts/abi'; import { notifyTransaction } from '~/util/notifications'; +import { ETH_TOKEN, fetchAccountsBalances } from '~/util/tokens'; import { LOG_KEYS, getLogger } from '~/config'; -import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png'; +import { sha3 } from '~/api/util/sha3'; + +const TRANSFER_SIGNATURE = sha3('Transfer(address,address,uint256)'); const log = getLogger(LOG_KEYS.Balances); -const ETH = { - name: 'Ethereum', - tag: 'ETH', - image: imagesEthereum, - native: true -}; - -function setBalances (_balances, skipNotifications = false) { - return (dispatch, getState) => { - const state = getState(); - - const currentTokens = Object.values(state.balances.tokens || {}); - const tokensAddresses = currentTokens - .map((token) => token.address) - .filter((address) => address); - - const accounts = state.personal.accounts; - const nextBalances = _balances; - const prevBalances = state.balances.balances; - const balances = { ...prevBalances }; - - Object.keys(nextBalances).forEach((address) => { - if (!balances[address]) { - balances[address] = Object.assign({}, nextBalances[address]); - return; - } - - const balance = Object.assign({}, balances[address]); - const { tokens, txCount = balance.txCount } = nextBalances[address]; - - const prevTokens = balance.tokens.slice(); - const nextTokens = []; - - const handleToken = (prevToken, nextToken) => { - // If the given token is not in the current tokens, skip - if (!nextToken && !prevToken) { - return false; - } - - // No updates - if (!nextToken) { - return nextTokens.push(prevToken); - } - - const { token, value } = nextToken; - - // If it's a new token, push it - if (!prevToken) { - return nextTokens.push({ - token, value - }); - } - - // Otherwise, update the value - const prevValue = prevToken.value; - - // If received a token/eth (old value < new value), notify - if (prevValue.lt(value) && accounts[address] && !skipNotifications) { - const account = accounts[address]; - const txValue = value.minus(prevValue); - - const redirectToAccount = () => { - const basePath = account.wallet - ? 'wallet' - : 'accounts'; - - const route = `/${basePath}/${account.address}`; - - dispatch(push(route)); - }; - - notifyTransaction(account, token, txValue, redirectToAccount); - } - - return nextTokens.push({ - ...prevToken, - value - }); - }; - - const prevEthToken = prevTokens.find((tok) => tok.token.native); - const nextEthToken = tokens.find((tok) => tok.token.native); - - handleToken(prevEthToken, nextEthToken); - - tokensAddresses - .forEach((address) => { - const prevToken = prevTokens.find((tok) => tok.token.address === address); - const nextToken = tokens.find((tok) => tok.token.address === address); - - handleToken(prevToken, nextToken); - }); - - balances[address] = { txCount: txCount || new BigNumber(0), tokens: nextTokens }; - }); - - dispatch(_setBalances(balances)); - }; -} +let tokensFilter = {}; function _setBalances (balances) { return { @@ -135,129 +35,88 @@ function _setBalances (balances) { }; } -export function setTokens (tokens) { - return { - type: 'setTokens', - tokens - }; -} - -export function setTokenReg (tokenreg) { - return { - type: 'setTokenReg', - tokenreg - }; -} - -export function setTokensFilter (tokensFilter) { - return { - type: 'setTokensFilter', - tokensFilter - }; -} - -export function setTokenImage (tokenAddress, image) { - return { - type: 'setTokenImage', - tokenAddress, image - }; -} - -export function loadTokens (options = {}) { - log.debug('loading tokens', Object.keys(options).length ? options : ''); - +/** + * @param {Object} _balances - In the shape: + * { + * [ who ]: { [ tokenId ]: BigNumber } // The balances of `who` + * } + * @param {Boolean} skipNotifications [description] + */ +function setBalances (updates, skipNotifications = false) { return (dispatch, getState) => { - const { tokenreg } = getState().balances; + const { tokens, balances } = getState(); - return tokenreg.instance.tokenCount - .call() - .then((numTokens) => { - const tokenIds = range(numTokens.toNumber()); + const prevBalances = balances; + const nextBalances = { ...prevBalances }; - dispatch(fetchTokens(tokenIds, options)); - }) - .catch((error) => { - console.warn('balances::loadTokens', error); - }); - }; -} + Object.keys(updates) + .forEach((who) => { + const accountUpdates = updates[who]; -export function fetchTokens (_tokenIds, options = {}) { - const tokenIds = uniq(_tokenIds || []); + Object.keys(accountUpdates) + .forEach((tokenId) => { + const token = tokens[tokenId]; + const prevTokenValue = (prevBalances[who] || {})[tokenId]; + const nextTokenValue = accountUpdates[tokenId]; - return (dispatch, getState) => { - const { api, images, balances } = getState(); - const { tokenreg } = balances; - - return Promise - .all(tokenIds.map((id) => fetchTokenInfo(tokenreg, id, api))) - // FIXME ; shouldn't have to filter out tokens... - .then((tokens) => tokens.filter((token) => token.tag && token.tag.toLowerCase() !== 'eth')) - .then((tokens) => { - // dispatch only the changed images - tokens - .forEach((token) => { - const { image, address } = token; - - if (images[address] === image) { - return; + if (prevTokenValue && prevTokenValue.lt(nextTokenValue)) { + dispatch(notifyBalanceChange(who, prevTokenValue, nextTokenValue, token)); } - dispatch(setTokenImage(address, image)); - dispatch(setAddressImage(address, image, true)); + // Add the token if it's native ETH or if it has a value + if (token.native || nextTokenValue.gt(0)) { + nextBalances[who] = { + ...(nextBalances[who] || {}), + [tokenId]: nextTokenValue + }; + } }); - - log.debug('fetched token', tokens); - dispatch(setTokens(tokens)); - dispatch(updateTokensFilter(null, null, options)); - }) - .catch((error) => { - console.warn('balances::fetchTokens', error); }); + + return dispatch(_setBalances(nextBalances)); }; } -export function fetchBalances (_addresses, skipNotifications = false) { +function notifyBalanceChange (who, fromValue, toValue, token) { return (dispatch, getState) => { - const { api, personal } = getState(); - const { visibleAccounts, accounts } = personal; + const account = getState().personal.accounts[who]; - const addresses = uniq(_addresses || visibleAccounts || []); + if (account) { + const txValue = toValue.minus(fromValue); - // With only a single account, more info will be displayed. - const fullFetch = addresses.length === 1; + const redirectToAccount = () => { + const basePath = account.wallet + ? 'wallet' + : 'accounts'; - // Add accounts addresses (for notifications, accounts selection, etc.) - const addressesToFetch = uniq(addresses.concat(Object.keys(accounts))); + const route = `/${basePath}/${account.address}`; - return Promise - .all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch))) - .then((accountsBalances) => { - const balances = {}; + dispatch(push(route)); + }; - addressesToFetch.forEach((addr, idx) => { - balances[addr] = accountsBalances[idx]; - }); - - dispatch(setBalances(balances, skipNotifications)); - }) - .catch((error) => { - console.warn('balances::fetchBalances', error); - }); + notifyTransaction(account, token, txValue, redirectToAccount); + } }; } +// TODO: fetch txCount when needed +export function fetchBalances (_addresses, skipNotifications = false) { + return fetchTokensBalances(_addresses, [ ETH_TOKEN ], skipNotifications); +} + export function updateTokensFilter (_addresses, _tokens, options = {}) { return (dispatch, getState) => { - const { api, balances, personal } = getState(); + const { api, personal, tokens } = getState(); const { visibleAccounts, accounts } = personal; - const { tokensFilter } = balances; const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts))); const addresses = uniq(_addresses || addressesToFetch || []).sort(); - const tokens = _tokens || Object.values(balances.tokens) || []; - const tokenAddresses = tokens.map((t) => t.address).sort(); + const tokensToUpdate = _tokens || Object.values(tokens); + const tokenAddresses = tokensToUpdate + .map((t) => t.address) + .filter((address) => address) + .sort(); if (tokensFilter.filterFromId || tokensFilter.filterToId) { // Has the tokens addresses changed (eg. a network change) @@ -287,23 +146,22 @@ export function updateTokensFilter (_addresses, _tokens, options = {}) { const promise = Promise.all(promises); - const TRANSFER_SIGNATURE = api.util.sha3('Transfer(address,address,uint256)'); const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ]; const topicsTo = [ TRANSFER_SIGNATURE, null, addresses ]; - const options = { + const filterOptions = { fromBlock: 0, toBlock: 'pending', address: tokenAddresses }; const optionsFrom = { - ...options, + ...filterOptions, topics: topicsFrom }; const optionsTo = { - ...options, + ...filterOptions, topics: topicsTo }; @@ -322,8 +180,8 @@ export function updateTokensFilter (_addresses, _tokens, options = {}) { const { skipNotifications } = options; - dispatch(setTokensFilter(nextTokensFilter)); - fetchTokensBalances(addresses, tokens, skipNotifications)(dispatch, getState); + tokensFilter = nextTokensFilter; + fetchTokensBalances(addresses, tokensToUpdate, skipNotifications)(dispatch, getState); }) .catch((error) => { console.warn('balances::updateTokensFilter', error); @@ -331,13 +189,14 @@ export function updateTokensFilter (_addresses, _tokens, options = {}) { }; } -export function queryTokensFilter (tokensFilter) { +export function queryTokensFilter () { return (dispatch, getState) => { - const { api, personal, balances } = getState(); + const { api, personal, tokens } = getState(); const { visibleAccounts, accounts } = personal; - const visibleAddresses = visibleAccounts.map((a) => a.toLowerCase()); - const addressesToFetch = uniq(visibleAddresses.concat(Object.keys(accounts))); + const allAddresses = visibleAccounts.concat(Object.keys(accounts)); + const addressesToFetch = uniq(allAddresses); + const lcAddresses = addressesToFetch.map((a) => a.toLowerCase()); Promise .all([ @@ -347,21 +206,28 @@ export function queryTokensFilter (tokensFilter) { .then(([ logsFrom, logsTo ]) => { const addresses = []; const tokenAddresses = []; + const logs = logsFrom.concat(logsTo); - logsFrom - .concat(logsTo) + if (logs.length > 0) { + log.debug('got tokens filter logs', logs); + } + + logs .forEach((log) => { const tokenAddress = log.address; const fromAddress = '0x' + log.topics[1].slice(-40); const toAddress = '0x' + log.topics[2].slice(-40); - if (addressesToFetch.includes(fromAddress)) { - addresses.push(fromAddress); + const fromAddressIndex = lcAddresses.indexOf(fromAddress); + const toAddressIndex = lcAddresses.indexOf(toAddress); + + if (fromAddressIndex > -1) { + addresses.push(addressesToFetch[fromAddressIndex]); } - if (addressesToFetch.includes(toAddress)) { - addresses.push(toAddress); + if (toAddressIndex > -1) { + addresses.push(addressesToFetch[toAddressIndex]); } tokenAddresses.push(tokenAddress); @@ -371,36 +237,35 @@ export function queryTokensFilter (tokensFilter) { return; } - const tokens = Object.values(balances.tokens) + const tokensToUpdate = Object.values(tokens) .filter((t) => tokenAddresses.includes(t.address)); - fetchTokensBalances(uniq(addresses), tokens)(dispatch, getState); + fetchTokensBalances(uniq(addresses), tokensToUpdate)(dispatch, getState); }); }; } export function fetchTokensBalances (_addresses = null, _tokens = null, skipNotifications = false) { return (dispatch, getState) => { - const { api, personal, balances } = getState(); + const { api, personal, tokens } = getState(); const { visibleAccounts, accounts } = personal; + const allTokens = Object.values(tokens); const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts))); const addresses = _addresses || addressesToFetch; - const tokens = _tokens || Object.values(balances.tokens); + const tokensToUpdate = _tokens || allTokens; if (addresses.length === 0) { return Promise.resolve(); } - return Promise - .all(addresses.map((addr) => fetchTokensBalance(addr, tokens, api))) - .then((tokensBalances) => { - const balances = {}; - - addresses.forEach((addr, idx) => { - balances[addr] = tokensBalances[idx]; - }); + const updates = addresses.reduce((updates, who) => { + updates[who] = tokensToUpdate.map((token) => token.id); + return updates; + }, {}); + return fetchAccountsBalances(api, allTokens, updates) + .then((balances) => { dispatch(setBalances(balances, skipNotifications)); }) .catch((error) => { @@ -408,78 +273,3 @@ export function fetchTokensBalances (_addresses = null, _tokens = null, skipNoti }); }; } - -function fetchAccount (address, api, full = false) { - const promises = [ api.eth.getBalance(address) ]; - - if (full) { - promises.push(api.eth.getTransactionCount(address)); - } - - return Promise - .all(promises) - .then(([ ethBalance, txCount ]) => { - const tokens = [ { token: ETH, value: ethBalance } ]; - const balance = { tokens }; - - if (full) { - balance.txCount = txCount; - } - - return balance; - }) - .catch((error) => { - console.warn('balances::fetchAccountBalance', `couldn't fetch balance for account #${address}`, error); - }); -} - -function fetchTokensBalance (address, _tokens, api) { - const tokensPromises = _tokens - .map((token) => { - return token.contract.instance.balanceOf.call({}, [ address ]); - }); - - return Promise - .all(tokensPromises) - .then((tokensBalance) => { - const tokens = _tokens - .map((token, index) => ({ - token, - value: tokensBalance[index] - })); - - const balance = { tokens }; - - return balance; - }) - .catch((error) => { - console.warn('balances::fetchTokensBalance', `couldn't fetch tokens balance for account #${address}`, error); - }); -} - -function fetchTokenInfo (tokenreg, tokenId, api, dispatch) { - return Promise - .all([ - tokenreg.instance.token.call({}, [tokenId]), - tokenreg.instance.meta.call({}, [tokenId, 'IMG']) - ]) - .then(([ tokenData, image ]) => { - const [ address, tag, format, name ] = tokenData; - const contract = api.newContract(ABIS.eip20, address); - - const token = { - format: format.toString(), - id: tokenId, - image: hashToImageUrl(image), - address, - tag, - name, - contract - }; - - return token; - }) - .catch((error) => { - console.warn('balances::fetchTokenInfo', `couldn't fetch token #${tokenId}`, error); - }); -} diff --git a/js/src/redux/providers/balancesReducer.js b/js/src/redux/providers/balancesReducer.js index 90bbd83d9..1b6f34391 100644 --- a/js/src/redux/providers/balancesReducer.js +++ b/js/src/redux/providers/balancesReducer.js @@ -16,72 +16,12 @@ import { handleActions } from 'redux-actions'; -const initialState = { - balances: {}, - tokens: {}, - tokenreg: null, - tokensFilter: {} -}; +const initialState = {}; export default handleActions({ setBalances (state, action) { const { balances } = action; - return Object.assign({}, state, { balances }); - }, - - setTokens (state, action) { - const { tokens } = action; - - if (Array.isArray(tokens)) { - const objTokens = tokens.reduce((_tokens, token) => { - _tokens[token.address] = token; - return _tokens; - }, {}); - - return Object.assign({}, state, { tokens: objTokens }); - } - - return Object.assign({}, state, { tokens }); - }, - - setTokenImage (state, action) { - const { tokenAddress, image } = action; - const { balances } = state; - const nextBalances = {}; - - Object.keys(balances).forEach((address) => { - const tokenIndex = balances[address].tokens.findIndex((t) => t.token.address === tokenAddress); - - if (tokenIndex === -1 || balances[address].tokens[tokenIndex].value.equals(0)) { - return; - } - - const tokens = [].concat(balances[address].tokens); - - tokens[tokenIndex].token = { - ...tokens[tokenIndex].token, - image - }; - - nextBalances[address] = { - ...balances[address], - tokens - }; - }); - - return Object.assign({}, state, { balance: { ...balances, nextBalances } }); - }, - - setTokenReg (state, action) { - const { tokenreg } = action; - - return Object.assign({}, state, { tokenreg }); - }, - - setTokensFilter (state, action) { - const { tokensFilter } = action; - - return Object.assign({}, state, { tokensFilter }); + return Object.assign({}, state, balances); } }, initialState); diff --git a/js/src/redux/providers/blockchainActions.js b/js/src/redux/providers/blockchainActions.js deleted file mode 100644 index 4c97f7ab3..000000000 --- a/js/src/redux/providers/blockchainActions.js +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -import Contracts from '~/contracts'; - -export function setBlock (blockNumber, block) { - return { - type: 'setBlock', - blockNumber, block - }; -} - -export function setTransaction (txHash, info) { - return { - type: 'setTransaction', - txHash, info - }; -} - -export function setBytecode (address, bytecode) { - return { - type: 'setBytecode', - address, bytecode - }; -} - -export function setMethod (signature, method) { - return { - type: 'setMethod', - signature, method - }; -} - -export function fetchBlock (blockNumber) { - return (dispatch, getState) => { - const { blocks } = getState().blockchain; - - if (blocks[blockNumber.toString()]) { - return; - } - - const { api } = getState(); - - api.eth - .getBlockByNumber(blockNumber) - .then(block => { - dispatch(setBlock(blockNumber, block)); - }) - .catch(e => { - console.error('blockchain::fetchBlock', e); - }); - }; -} - -export function fetchTransaction (txHash) { - return (dispatch, getState) => { - const { transactions } = getState().blockchain; - - if (transactions[txHash]) { - return; - } - - const { api } = getState(); - - api.eth - .getTransactionByHash(txHash) - .then(info => { - dispatch(setTransaction(txHash, info)); - }) - .catch(e => { - console.error('blockchain::fetchTransaction', e); - }); - }; -} - -export function fetchBytecode (address) { - return (dispatch, getState) => { - const { bytecodes } = getState().blockchain; - - if (bytecodes[address]) { - return; - } - - const { api } = getState(); - - api.eth - .getCode(address) - .then(code => { - dispatch(setBytecode(address, code)); - }) - .catch(e => { - console.error('blockchain::fetchBytecode', e); - }); - }; -} - -export function fetchMethod (signature) { - return (dispatch, getState) => { - const { methods } = getState().blockchain; - - if (methods[signature]) { - return; - } - - Contracts - .get() - .signatureReg.lookup(signature) - .then(method => { - dispatch(setMethod(signature, method)); - }) - .catch(e => { - console.error('blockchain::fetchMethod', e); - }); - }; -} diff --git a/js/src/redux/providers/blockchainReducer.js b/js/src/redux/providers/blockchainReducer.js deleted file mode 100644 index 04081457f..000000000 --- a/js/src/redux/providers/blockchainReducer.js +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -import { handleActions } from 'redux-actions'; - -const initialState = { - blocks: {}, - transactions: {}, - bytecodes: {}, - methods: {} -}; - -export default handleActions({ - setBlock (state, action) { - const { blockNumber, block } = action; - - const blocks = Object.assign({}, state.blocks, { - [blockNumber.toString()]: block - }); - - return Object.assign({}, state, { blocks }); - }, - - setTransaction (state, action) { - const { txHash, info } = action; - - const transactions = Object.assign({}, state.transactions, { - [txHash]: info - }); - - return Object.assign({}, state, { transactions }); - }, - - setBytecode (state, action) { - const { address, bytecode } = action; - - const bytecodes = Object.assign({}, state.bytecodes, { - [address]: bytecode - }); - - return Object.assign({}, state, { bytecodes }); - }, - - setMethod (state, action) { - const { signature, method } = action; - - const methods = Object.assign({}, state.methods, { - [signature]: method - }); - - return Object.assign({}, state, { methods }); - } -}, initialState); diff --git a/js/src/redux/providers/index.js b/js/src/redux/providers/index.js index 04231d6b3..967820cc7 100644 --- a/js/src/redux/providers/index.js +++ b/js/src/redux/providers/index.js @@ -21,7 +21,6 @@ export Status from './status'; export apiReducer from './apiReducer'; export balancesReducer from './balancesReducer'; -export blockchainReducer from './blockchainReducer'; export workerReducer from './workerReducer'; export imagesReducer from './imagesReducer'; export personalReducer from './personalReducer'; @@ -29,4 +28,5 @@ export requestsReducer from './requestsReducer'; export signerReducer from './signerReducer'; export snackbarReducer from './snackbarReducer'; export statusReducer from './statusReducer'; +export tokensReducer from './tokensReducer'; export walletReducer from './walletReducer'; diff --git a/js/src/redux/providers/personal.js b/js/src/redux/providers/personal.js index ffc331495..4c9d3e23a 100644 --- a/js/src/redux/providers/personal.js +++ b/js/src/redux/providers/personal.js @@ -35,6 +35,12 @@ export default class Personal { return; } + // Add the address to each accounts + Object.keys(accountsInfo) + .forEach((address) => { + accountsInfo[address].address = address; + }); + this._store.dispatch(personalAccountsInfo(accountsInfo)); }); } diff --git a/js/src/redux/providers/tokensActions.js b/js/src/redux/providers/tokensActions.js new file mode 100644 index 000000000..6f902882d --- /dev/null +++ b/js/src/redux/providers/tokensActions.js @@ -0,0 +1,88 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { uniq } from 'lodash'; + +import Contracts from '~/contracts'; +import { LOG_KEYS, getLogger } from '~/config'; +import { fetchTokenIds, fetchTokenInfo } from '~/util/tokens'; + +import { updateTokensFilter } from './balancesActions'; +import { setAddressImage } from './imagesActions'; + +const log = getLogger(LOG_KEYS.Balances); + +export function setTokens (tokens) { + return { + type: 'setTokens', + tokens + }; +} + +export function loadTokens (options = {}) { + log.debug('loading tokens', Object.keys(options).length ? options : ''); + + return (dispatch, getState) => { + const { tokenReg } = Contracts.get(); + + tokenReg.getInstance() + .then((tokenRegInstance) => { + return fetchTokenIds(tokenRegInstance); + }) + .then((tokenIndexes) => dispatch(fetchTokens(tokenIndexes, options))) + .catch((error) => { + console.warn('tokens::loadTokens', error); + }); + }; +} + +export function fetchTokens (_tokenIndexes, options = {}) { + const tokenIndexes = uniq(_tokenIndexes || []); + + return (dispatch, getState) => { + const { api, images } = getState(); + const { tokenReg } = Contracts.get(); + + return tokenReg.getInstance() + .then((tokenRegInstance) => { + const promises = tokenIndexes.map((id) => fetchTokenInfo(api, tokenRegInstance, id)); + + return Promise.all(promises); + }) + .then((results) => { + const tokens = results + .reduce((tokens, token) => { + const { id, image, address } = token; + + // dispatch only the changed images + if (images[address] !== image) { + dispatch(setAddressImage(address, image, true)); + } + + tokens[id] = token; + return tokens; + }, {}); + + log.debug('fetched token', tokens); + + dispatch(setTokens(tokens)); + dispatch(updateTokensFilter(null, null, options)); + }) + .catch((error) => { + console.warn('tokens::fetchTokens', error); + }); + }; +} diff --git a/js/src/redux/providers/tokensReducer.js b/js/src/redux/providers/tokensReducer.js new file mode 100644 index 000000000..e11fb41a3 --- /dev/null +++ b/js/src/redux/providers/tokensReducer.js @@ -0,0 +1,34 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { handleActions } from 'redux-actions'; + +import { ETH_TOKEN } from '~/util/tokens'; + +const initialState = { + [ ETH_TOKEN.id ]: ETH_TOKEN +}; + +export default handleActions({ + setTokens (state, action) { + const { tokens } = action; + + return { + ...state, + ...tokens + }; + } +}, initialState); diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js index ed1f7ef57..fb5384d6c 100644 --- a/js/src/redux/reducers.js +++ b/js/src/redux/reducers.js @@ -18,10 +18,10 @@ import { combineReducers } from 'redux'; import { routerReducer } from 'react-router-redux'; import { - apiReducer, balancesReducer, blockchainReducer, + apiReducer, balancesReducer, workerReducer, imagesReducer, personalReducer, requestsReducer, signerReducer, statusReducer as nodeStatusReducer, - snackbarReducer, walletReducer + snackbarReducer, tokensReducer, walletReducer } from './providers'; import certificationsReducer from './providers/certifications/reducer'; import registryReducer from './providers/registry/reducer'; @@ -40,7 +40,6 @@ export default function () { balances: balancesReducer, certifications: certificationsReducer, - blockchain: blockchainReducer, images: imagesReducer, nodeStatus: nodeStatusReducer, personal: personalReducer, @@ -48,6 +47,7 @@ export default function () { requests: requestsReducer, signer: signerReducer, snackbar: snackbarReducer, + tokens: tokensReducer, wallet: walletReducer, worker: workerReducer }); diff --git a/js/src/ui/AccountCard/accountCard.js b/js/src/ui/AccountCard/accountCard.js index 107fc39d2..da59f0e69 100644 --- a/js/src/ui/AccountCard/accountCard.js +++ b/js/src/ui/AccountCard/accountCard.js @@ -84,6 +84,7 @@ export default class AccountCard extends Component { . +import BigNumber from 'bignumber.js'; import { shallow } from 'enzyme'; import React from 'react'; import sinon from 'sinon'; +import { ETH_TOKEN } from '~/util/tokens'; + import AccountCard from './'; const TEST_ADDRESS = '0x1234567890123456789012345678901234567890'; @@ -27,6 +30,21 @@ let component; let onClick; let onFocus; +function reduxStore () { + const getState = () => ({ + balances: {}, + tokens: { + [ETH_TOKEN.id]: ETH_TOKEN + } + }); + + return { + getState, + dispatch: () => null, + subscribe: () => null + }; +} + function render (props = {}) { if (!props.account) { props.account = { @@ -39,6 +57,12 @@ function render (props = {}) { }; } + if (!props.balance) { + props.balance = { + [ETH_TOKEN.id]: new BigNumber(10) + }; + } + onClick = sinon.stub(); onFocus = sinon.stub(); @@ -67,7 +91,9 @@ describe('ui/AccountCard', () => { let balance; beforeEach(() => { - balance = component.find('Balance'); + balance = component.find('Connect(Balance)').shallow({ + context: { store: reduxStore() } + }); }); it('renders the balance', () => { diff --git a/js/src/ui/Balance/balance.js b/js/src/ui/Balance/balance.js index d00e1f01c..8bb552daa 100644 --- a/js/src/ui/Balance/balance.js +++ b/js/src/ui/Balance/balance.js @@ -17,18 +17,21 @@ import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; import TokenImage from '~/ui/TokenImage'; import styles from './balance.css'; -export default class Balance extends Component { +export class Balance extends Component { static contextTypes = { api: PropTypes.object }; static propTypes = { - balance: PropTypes.object, + balance: PropTypes.object.isRequired, + tokens: PropTypes.object.isRequired, + address: PropTypes.string, className: PropTypes.string, showOnlyEth: PropTypes.bool, showZeroValues: PropTypes.bool @@ -40,44 +43,38 @@ export default class Balance extends Component { }; render () { - const { api } = this.context; - const { balance, className, showOnlyEth } = this.props; + const { balance, className, showOnlyEth, tokens } = this.props; - if (!balance || !balance.tokens) { + if (Object.keys(balance).length === 0) { return null; } - let body = balance.tokens - .filter((balance) => { - const isEthToken = (balance.token.tag || '').toLowerCase() === 'eth'; - const hasBalance = new BigNumber(balance.value).gt(0); + let body = Object.keys(balance) + .map((tokenId) => { + const token = tokens[tokenId]; + const balanceValue = balance[tokenId]; - return hasBalance || isEthToken; - }) - .map((balance, index) => { - const isFullToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth'; - const token = balance.token; + const isEthToken = token.native; + const isFullToken = !showOnlyEth || isEthToken; + const hasBalance = balanceValue.gt(0); - let value; - - if (token.format) { - const bnf = new BigNumber(token.format); - - let decimals = 0; - - if (bnf.gte(1000)) { - decimals = 3; - } else if (bnf.gte(100)) { - decimals = 2; - } else if (bnf.gte(10)) { - decimals = 1; - } - - value = new BigNumber(balance.value).div(bnf).toFormat(decimals); - } else { - value = api.util.fromWei(balance.value).toFormat(3); + if (!hasBalance && !isEthToken) { + return null; } + const bnf = new BigNumber(token.format || 1); + let decimals = 0; + + if (bnf.gte(1000)) { + decimals = 3; + } else if (bnf.gte(100)) { + decimals = 2; + } else if (bnf.gte(10)) { + decimals = 1; + } + + const value = new BigNumber(balanceValue).div(bnf).toFormat(decimals); + const classNames = [styles.balance]; let details = null; @@ -104,13 +101,14 @@ export default class Balance extends Component { return (
{ details }
); - }); + }) + .filter((node) => node); if (!body.length) { body = ( @@ -140,3 +138,15 @@ export default class Balance extends Component { ); } } + +function mapStateToProps (state, props) { + const { balances, tokens } = state; + const { address } = props; + + return { + balance: balances[address] || props.balance || {}, + tokens + }; +} + +export default connect(mapStateToProps)(Balance); diff --git a/js/src/ui/Balance/balance.spec.js b/js/src/ui/Balance/balance.spec.js index ffc25243a..d5601a489 100644 --- a/js/src/ui/Balance/balance.spec.js +++ b/js/src/ui/Balance/balance.spec.js @@ -14,19 +14,24 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import BigNumber from 'bignumber.js'; import { shallow } from 'enzyme'; import React from 'react'; import apiutil from '~/api/util'; -import Balance from './'; +import { Balance } from './balance'; + +const TOKENS = { + 'eth': { tag: 'ETH' }, + 'gav': { tag: 'GAV', format: 1 }, + 'tst': { tag: 'TST', format: 1 } +}; const BALANCE = { - tokens: [ - { value: '122', token: { tag: 'ETH' } }, - { value: '345', token: { tag: 'GAV', format: 1 } }, - { value: '0', token: { tag: 'TST', format: 1 } } - ] + 'eth': new BigNumber(122), + 'gav': new BigNumber(345), + 'tst': new BigNumber(0) }; let api; @@ -46,6 +51,10 @@ function render (props = {}) { props.balance = BALANCE; } + if (!props.tokens) { + props.tokens = TOKENS; + } + const api = createApi(); component = shallow( diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index 3d66d2655..e01387406 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -32,6 +32,7 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); .hoverOverlay { background: $background; + display: none; left: 0; margin-top: -1.5em; margin-bottom: 3em; @@ -52,6 +53,7 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); .hoverOverlay { background: $backgroundHover; + display: block; /*transform: scale(1, 1);*/ opacity: 1; } diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index e7c210eba..c42442465 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -55,7 +55,6 @@ class AddressSelect extends Component { // Redux props accountsInfo: PropTypes.object, accounts: PropTypes.object, - balances: PropTypes.object, contacts: PropTypes.object, contracts: PropTypes.object, tokens: PropTypes.object, @@ -356,10 +355,9 @@ class AddressSelect extends Component { } renderAccountCard (_account) { - const { balances, accountsInfo } = this.props; + const { accountsInfo } = this.props; const { address, index = null } = _account; - const balance = balances[address]; const account = { ...accountsInfo[address], ..._account @@ -368,7 +366,6 @@ class AddressSelect extends Component { return ( address.toLowerCase() === lcValue); - const tokensAddress = Object.keys(tokens).find((address) => address.toLowerCase() === lcValue); + const accountInfo = Object.values(accountsInfo).find((account) => account.address.toLowerCase() === lcValue); + const token = Object.values(tokens).find((token) => token.address.toLowerCase() === lcValue); - const account = (accountsInfoAddress && accountsInfo[accountsInfoAddress]) || - (tokensAddress && tokens[tokensAddress]) || - null; + const account = accountInfo || token || null; return { account diff --git a/js/src/ui/IdentityName/identityName.js b/js/src/ui/IdentityName/identityName.js index 0e814d2db..b9a0ba045 100644 --- a/js/src/ui/IdentityName/identityName.js +++ b/js/src/ui/IdentityName/identityName.js @@ -34,21 +34,19 @@ const defaultNameNull = ( /> ); -class IdentityName extends Component { +export class IdentityName extends Component { static propTypes = { - accountsInfo: PropTypes.object, + account: PropTypes.object, address: PropTypes.string, className: PropTypes.string, empty: PropTypes.bool, name: PropTypes.string, shorten: PropTypes.bool, - tokens: PropTypes.object, unknown: PropTypes.bool } render () { - const { address, accountsInfo, className, empty, name, shorten, tokens, unknown } = this.props; - const account = accountsInfo[address] || tokens[address]; + const { account, address, className, empty, name, shorten, unknown } = this.props; if (!account && empty) { return null; @@ -76,13 +74,14 @@ class IdentityName extends Component { } } -function mapStateToProps (state) { - const { accountsInfo } = state.personal; - const { tokens } = state.balances; +function mapStateToProps (state, props) { + const { address } = props; + const { personal, tokens } = state; + + const account = personal.accountsInfo[address] || Object.values(tokens).find((token) => token.address === address); return { - accountsInfo, - tokens + account }; } diff --git a/js/src/ui/IdentityName/identityName.spec.js b/js/src/ui/IdentityName/identityName.spec.js index 07be20120..cfabfbefd 100644 --- a/js/src/ui/IdentityName/identityName.spec.js +++ b/js/src/ui/IdentityName/identityName.spec.js @@ -17,40 +17,19 @@ import { shallow } from 'enzyme'; import React from 'react'; -import sinon from 'sinon'; - -import IdentityName from './identityName'; +import { IdentityName } from './identityName'; const ADDR_A = '0x123456789abcdef0123456789A'; -const ADDR_B = '0x123456789abcdef0123456789B'; const ADDR_C = '0x123456789abcdef0123456789C'; const ADDR_NULL = '0x0000000000000000000000000000000000000000'; const NAME_JIMMY = 'Jimmy Test'; -const STORE = { - dispatch: sinon.stub(), - subscribe: sinon.stub(), - getState: () => { - return { - balances: { - tokens: {} - }, - personal: { - accountsInfo: { - [ADDR_A]: { name: 'testing' }, - [ADDR_B]: {} - } - } - }; - } -}; function render (props) { return shallow( - ).find('IdentityName').shallow(); + ); } describe('ui/IdentityName', () => { diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index 8203aa5fd..1d2810c54 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -653,10 +653,10 @@ class MethodDecoding extends Component { } function mapStateToProps (initState, initProps) { - const { tokens } = initState.balances; + const { tokens } = initState; const { transaction } = initProps; - const token = (tokens || {})[transaction.to]; + const token = Object.values(tokens).find((token) => token.address === transaction.to); return () => { return { token }; diff --git a/js/src/util/notifications.js b/js/src/util/notifications.js index b44ed481d..74732cac2 100644 --- a/js/src/util/notifications.js +++ b/js/src/util/notifications.js @@ -17,20 +17,12 @@ import Push from 'push.js'; import BigNumber from 'bignumber.js'; -import { fromWei } from '~/api/util/wei'; - -import ethereumIcon from '~/../assets/images/contracts/ethereum-black-64x64.png'; import unkownIcon from '~/../assets/images/contracts/unknown-64x64.png'; export function notifyTransaction (account, token, _value, onClick) { const name = account.name || account.address; - const value = token.tag.toLowerCase() === 'eth' - ? fromWei(_value) - : _value.div(new BigNumber(token.format || 1)); - - const icon = token.tag.toLowerCase() === 'eth' - ? ethereumIcon - : (token.image || unkownIcon); + const value = _value.div(new BigNumber(token.format || 1)); + const icon = token.image || unkownIcon; let _notification = null; diff --git a/js/src/util/tokens.js b/js/src/util/tokens.js new file mode 100644 index 000000000..63e6e9f02 --- /dev/null +++ b/js/src/util/tokens.js @@ -0,0 +1,133 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { range } from 'lodash'; +import BigNumber from 'bignumber.js'; + +import { hashToImageUrl } from '~/redux/util'; +import { sha3 } from '~/api/util/sha3'; +import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png'; + +const BALANCEOF_SIGNATURE = sha3('balanceOf(address)'); +const ADDRESS_PADDING = range(24).map(() => '0').join(''); + +export const ETH_TOKEN = { + address: '', + format: new BigNumber(10).pow(18), + id: sha3('eth_native_token').slice(0, 10), + image: imagesEthereum, + name: 'Ethereum', + native: true, + tag: 'ETH' +}; + +export function fetchTokenIds (tokenregInstance) { + return tokenregInstance.tokenCount + .call() + .then((numTokens) => { + const tokenIndexes = range(numTokens.toNumber()); + + return tokenIndexes; + }); +} + +export function fetchTokenInfo (api, tokenregInstace, tokenIndex) { + return Promise + .all([ + tokenregInstace.token.call({}, [tokenIndex]), + tokenregInstace.meta.call({}, [tokenIndex, 'IMG']) + ]) + .then(([ tokenData, image ]) => { + const [ address, tag, format, name ] = tokenData; + + const token = { + format: format.toString(), + index: tokenIndex, + image: hashToImageUrl(image), + id: sha3(address + tokenIndex).slice(0, 10), + address, + name, + tag + }; + + return token; + }); +} + +/** + * `updates` should be in the shape: + * { + * [ who ]: [ tokenId ] // Array of tokens to updates + * } + * + * Returns a Promise resolved witht the balances in the shape: + * { + * [ who ]: { [ tokenId ]: BigNumber } // The balances of `who` + * } + */ +export function fetchAccountsBalances (api, tokens, updates) { + const addresses = Object.keys(updates); + const promises = addresses + .map((who) => { + const tokensIds = updates[who]; + const tokensToUpdate = tokensIds.map((tokenId) => tokens.find((t) => t.id === tokenId)); + + return fetchAccountBalances(api, tokensToUpdate, who); + }); + + return Promise.all(promises) + .then((results) => { + return results.reduce((balances, accountBalances, index) => { + balances[addresses[index]] = accountBalances; + return balances; + }, {}); + }); +} + +/** + * Returns a Promise resolved with the balances in the shape: + * { + * [ tokenId ]: BigNumber // Token balance value + * } + */ +export function fetchAccountBalances (api, tokens, who) { + const calldata = '0x' + BALANCEOF_SIGNATURE.slice(2, 10) + ADDRESS_PADDING + who.slice(2); + const promises = tokens.map((token) => fetchTokenBalance(api, token, { who, calldata })); + + return Promise.all(promises) + .then((results) => { + return results.reduce((balances, value, index) => { + const token = tokens[index]; + + balances[token.id] = value; + return balances; + }, {}); + }); +} + +export function fetchTokenBalance (api, token, { who, calldata }) { + if (token.native) { + return api.eth.getBalance(who); + } + + return api.eth + .call({ data: calldata, to: token.address }) + .then((result) => { + const cleanResult = result.replace(/^0x/, ''); + + return new BigNumber(`0x${cleanResult || 0}`); + }); +} diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index d3c4a9c69..f02d5bd3a 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -22,9 +22,12 @@ import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, Id import styles from './header.css'; export default class Header extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + static propTypes = { account: PropTypes.object, - balance: PropTypes.object, children: PropTypes.node, className: PropTypes.string, disabled: PropTypes.bool, @@ -39,8 +42,49 @@ export default class Header extends Component { isContract: false }; + state = { + txCount: null + }; + + txCountSubId = null; + + componentWillMount () { + if (this.props.account && !this.props.isContract) { + this.subscribeTxCount(); + } + } + + componentWillUnmount () { + this.unsubscribeTxCount(); + } + + subscribeTxCount () { + const { api } = this.context; + + api + .subscribe('eth_blockNumber', (error) => { + if (error) { + return console.error(error); + } + + api.eth.getTransactionCount(this.props.account.address) + .then((txCount) => this.setState({ txCount })); + }) + .then((subscriptionId) => { + this.txCountSubId = subscriptionId; + }); + } + + unsubscribeTxCount () { + if (!this.txCountSubId) { + return; + } + + this.context.api.unsubscribe(this.txCountSubId); + } + render () { - const { account, balance, children, className, disabled, hideName } = this.props; + const { account, children, className, disabled, hideName } = this.props; if (!account) { return null; @@ -76,8 +120,7 @@ export default class Header extends Component { { this.renderTxCount() }
{ this.renderVault() } @@ -115,15 +158,10 @@ export default class Header extends Component { } renderTxCount () { - const { balance, isContract } = this.props; + const { isContract } = this.props; + const { txCount } = this.state; - if (!balance || isContract) { - return null; - } - - const { txCount } = balance; - - if (!txCount) { + if (!txCount || isContract) { return null; } diff --git a/js/src/views/Account/Header/header.spec.js b/js/src/views/Account/Header/header.spec.js index 4f56044f0..48c0b1a99 100644 --- a/js/src/views/Account/Header/header.spec.js +++ b/js/src/views/Account/Header/header.spec.js @@ -18,6 +18,8 @@ import BigNumber from 'bignumber.js'; import { shallow } from 'enzyme'; import React from 'react'; +import { ETH_TOKEN } from '~/util/tokens'; + import Header from './'; const ACCOUNT = { @@ -28,17 +30,44 @@ const ACCOUNT = { }, uuid: '0xabcdef' }; +const subscriptions = {}; let component; let instance; +const api = { + subscribe: (method, callback) => { + subscriptions[method] = (subscriptions[method] || []).concat(callback); + return Promise.resolve(0); + }, + eth: { + getTransactionCount: () => Promise.resolve(new BigNumber(1)) + } +}; + +function reduxStore () { + const getState = () => ({ + balances: {}, + tokens: { + [ETH_TOKEN.id]: ETH_TOKEN + } + }); + + return { + getState, + dispatch: () => null, + subscribe: () => null + }; +} + function render (props = {}) { if (props && !props.account) { props.account = ACCOUNT; } component = shallow( -
+
, + { context: { api } } ); instance = component.instance(); @@ -72,8 +101,9 @@ describe('views/Account/Header', () => { let balance; beforeEach(() => { - render({ balance: { balance: 'testing' } }); - balance = component.find('Balance'); + render(); + balance = component.find('Connect(Balance)') + .shallow({ context: { store: reduxStore() } }); }); it('renders', () => { @@ -81,11 +111,7 @@ describe('views/Account/Header', () => { }); it('passes the account', () => { - expect(balance.props().account).to.deep.equal(ACCOUNT); - }); - - it('passes the balance', () => { - + expect(balance.props().address).to.deep.equal(ACCOUNT.address); }); }); @@ -177,24 +203,33 @@ describe('views/Account/Header', () => { }); describe('renderTxCount', () => { - it('renders null when contract', () => { - render({ balance: { txCount: new BigNumber(1) }, isContract: true }); - expect(instance.renderTxCount()).to.be.null; - }); - - it('renders null when no balance', () => { - render({ balance: null, isContract: false }); - expect(instance.renderTxCount()).to.be.null; - }); - it('renders null when txCount is null', () => { - render({ balance: { txCount: null }, isContract: false }); + render(); expect(instance.renderTxCount()).to.be.null; }); + it('renders null when contract', () => { + render({ isContract: true }); + + subscriptions['eth_blockNumber'].forEach((callback) => { + callback(); + + setTimeout(() => { + expect(instance.renderTxCount()).to.be.null; + }); + }); + }); + it('renders the tx count', () => { - render({ balance: { txCount: new BigNumber(1) }, isContract: false }); - expect(instance.renderTxCount()).not.to.be.null; + render(); + + subscriptions['eth_blockNumber'].forEach((callback) => { + callback(); + + setTimeout(() => { + expect(instance.renderTxCount()).not.to.be.null; + }); + }); }); }); diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98f02afe3..6f38cdf72 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -46,8 +46,7 @@ class Account extends Component { fetchCertifications: PropTypes.func.isRequired, setVisibleAccounts: PropTypes.func.isRequired, - accounts: PropTypes.object, - balances: PropTypes.object, + account: PropTypes.object, certifications: PropTypes.object, netVersion: PropTypes.string.isRequired, params: PropTypes.object @@ -83,12 +82,9 @@ class Account extends Component { } render () { - const { accounts, balances } = this.props; + const { account } = this.props; const { address } = this.props.params; - const account = (accounts || {})[address]; - const balance = (balances || {})[address]; - if (!account) { return null; } @@ -102,17 +98,15 @@ class Account extends Component { { this.renderFaucetDialog() } { this.renderFundDialog() } { this.renderPasswordDialog(account) } - { this.renderTransferDialog(account, balance) } + { this.renderTransferDialog(account) } { this.renderVerificationDialog() } - { this.renderActionbar(account, balance) } + { this.renderActionbar(account) }
@@ -143,16 +137,14 @@ class Account extends Component { return certifications.length !== 0; } - renderActionbar (account, balance) { + renderActionbar (account) { const { certifications, netVersion } = this.props; const { address } = this.props.params; - const showTransferButton = !!(balance && balance.tokens); const isVerifiable = this.isMainnet(netVersion); const isFaucettable = this.isFaucettable(netVersion, certifications, address); const buttons = [