Refactoring of Tokens & Balances (#5372)
* Remove ETH filter * Remove unused Blockchain reducer+actions * Simpler Token updates and fetching * Cleanup use of balances * Cleanup of balances * Cleanup of Balances * Linting * Update List Component * Separate tokens from balances * Refactoring balance fetchin and storing - Part I * Linting * Better ETH token description and use * Working Transfer with new logic * Add debugging * Querying the tokens filter on new block * Fixing the tests - PART I * Fix txCount
This commit is contained in:
parent
fc18299869
commit
37690cfde2
@ -59,7 +59,8 @@ export default class Api extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
transport.addMiddleware(middleware);
|
transport.addMiddleware(middleware);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export default class TokenReg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getInstance () {
|
getInstance () {
|
||||||
return this.getContract().instance;
|
return this.getContract().then((contract) => contract.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenCount () {
|
tokenCount () {
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
|
||||||
import { AccountCard } from '~/ui';
|
import { AccountCard } from '~/ui';
|
||||||
|
import { ETH_TOKEN } from '~/util/tokens';
|
||||||
|
|
||||||
export default class GethCard extends Component {
|
export default class GethCard extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -36,14 +36,7 @@ export default class GethCard extends Component {
|
|||||||
name
|
name
|
||||||
} }
|
} }
|
||||||
balance={ {
|
balance={ {
|
||||||
tokens: [ {
|
[ETH_TOKEN.id]: balance
|
||||||
value: balance,
|
|
||||||
token: {
|
|
||||||
image: imagesEthereum,
|
|
||||||
native: true,
|
|
||||||
tag: 'ETH'
|
|
||||||
}
|
|
||||||
} ]
|
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -17,14 +17,12 @@
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { AccountCard, Portal, SelectionList } from '~/ui';
|
import { AccountCard, Portal, SelectionList } from '~/ui';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class DappPermissions extends Component {
|
export default class DappPermissions extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
balances: PropTypes.object,
|
|
||||||
permissionStore: PropTypes.object.isRequired
|
permissionStore: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,27 +64,10 @@ class DappPermissions extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccount = (account) => {
|
renderAccount = (account) => {
|
||||||
const { balances } = this.props;
|
|
||||||
const balance = balances[account.address];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
|
||||||
const { balances } = state.balances;
|
|
||||||
|
|
||||||
return {
|
|
||||||
balances
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
null
|
|
||||||
)(DappPermissions);
|
|
||||||
|
@ -16,38 +16,15 @@
|
|||||||
|
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sinon from 'sinon';
|
|
||||||
|
|
||||||
import DappPermissions from './';
|
import DappPermissions from './';
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
let store;
|
|
||||||
|
|
||||||
function createRedux () {
|
|
||||||
store = {
|
|
||||||
dispatch: sinon.stub(),
|
|
||||||
subscribe: sinon.stub(),
|
|
||||||
getState: () => {
|
|
||||||
return {
|
|
||||||
balances: {
|
|
||||||
balances: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderShallow (permissionStore = {}) {
|
function renderShallow (permissionStore = {}) {
|
||||||
component = shallow(
|
component = shallow(
|
||||||
<DappPermissions permissionStore={ permissionStore } />,
|
<DappPermissions permissionStore={ permissionStore } />
|
||||||
{
|
);
|
||||||
context: {
|
|
||||||
store: createRedux()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).find('DappPermissions').shallow();
|
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@ export default class DetailsStep extends Component {
|
|||||||
abiError: PropTypes.string,
|
abiError: PropTypes.string,
|
||||||
amount: PropTypes.string,
|
amount: PropTypes.string,
|
||||||
amountError: PropTypes.string,
|
amountError: PropTypes.string,
|
||||||
balances: PropTypes.object,
|
|
||||||
code: PropTypes.string,
|
code: PropTypes.string,
|
||||||
codeError: PropTypes.string,
|
codeError: PropTypes.string,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
@ -87,7 +86,6 @@ export default class DetailsStep extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
accounts,
|
accounts,
|
||||||
balances,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
|
|
||||||
fromAddress, fromAddressError,
|
fromAddress, fromAddressError,
|
||||||
@ -141,7 +139,6 @@ export default class DetailsStep extends Component {
|
|||||||
|
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
balances={ balances }
|
|
||||||
error={ fromAddressError }
|
error={ fromAddressError }
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { pick } from 'lodash';
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
@ -69,7 +68,6 @@ class DeployContract extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
abi: PropTypes.string,
|
abi: PropTypes.string,
|
||||||
balances: PropTypes.object,
|
|
||||||
code: PropTypes.string,
|
code: PropTypes.string,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
@ -282,7 +280,7 @@ class DeployContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { accounts, readOnly, balances } = this.props;
|
const { accounts, readOnly } = this.props;
|
||||||
const { step } = this.state;
|
const { step } = this.state;
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
@ -291,7 +289,6 @@ class DeployContract extends Component {
|
|||||||
<DetailsStep
|
<DetailsStep
|
||||||
{ ...this.state }
|
{ ...this.state }
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
balances={ balances }
|
|
||||||
onAmountChange={ this.onAmountChange }
|
onAmountChange={ this.onAmountChange }
|
||||||
onExtrasChange={ this.onExtrasChange }
|
onExtrasChange={ this.onExtrasChange }
|
||||||
onFromAddressChange={ this.onFromAddressChange }
|
onFromAddressChange={ this.onFromAddressChange }
|
||||||
@ -484,20 +481,11 @@ class DeployContract extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (state) {
|
||||||
const { accounts } = initProps;
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
const fromAddresses = Object.keys(accounts);
|
return {
|
||||||
|
gasLimit
|
||||||
return (state) => {
|
|
||||||
const balances = pick(state.balances.balances, fromAddresses);
|
|
||||||
const { gasLimit } = state.nodeStatus;
|
|
||||||
|
|
||||||
return {
|
|
||||||
accounts,
|
|
||||||
balances,
|
|
||||||
gasLimit
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ export default class DetailsStep extends Component {
|
|||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
amount: PropTypes.string,
|
amount: PropTypes.string,
|
||||||
amountError: PropTypes.string,
|
amountError: PropTypes.string,
|
||||||
balances: PropTypes.object,
|
|
||||||
contract: PropTypes.object.isRequired,
|
contract: PropTypes.object.isRequired,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
fromAddressError: PropTypes.string,
|
fromAddressError: PropTypes.string,
|
||||||
@ -51,13 +50,12 @@ export default class DetailsStep extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
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 (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
balances={ balances }
|
|
||||||
error={ fromAddressError }
|
error={ fromAddressError }
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { pick } from 'lodash';
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
@ -58,7 +57,6 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
|
||||||
contract: PropTypes.object.isRequired,
|
contract: PropTypes.object.isRequired,
|
||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
@ -199,14 +197,16 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { onFromAddressChange } = this.props;
|
const { accounts, contract, fromAddress, onFromAddressChange } = this.props;
|
||||||
const { step } = this.state;
|
const { step } = this.state;
|
||||||
|
|
||||||
if (step === STEP_DETAILS) {
|
if (step === STEP_DETAILS) {
|
||||||
return (
|
return (
|
||||||
<DetailsStep
|
<DetailsStep
|
||||||
{ ...this.props }
|
|
||||||
{ ...this.state }
|
{ ...this.state }
|
||||||
|
accounts={ accounts }
|
||||||
|
contract={ contract }
|
||||||
|
fromAddress={ fromAddress }
|
||||||
onAmountChange={ this.onAmountChange }
|
onAmountChange={ this.onAmountChange }
|
||||||
onFromAddressChange={ onFromAddressChange }
|
onFromAddressChange={ onFromAddressChange }
|
||||||
onFuncChange={ this.onFuncChange }
|
onFuncChange={ this.onFuncChange }
|
||||||
@ -334,15 +334,10 @@ class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (state) {
|
||||||
const fromAddresses = Object.keys(initProps.accounts);
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
return (state) => {
|
return { gasLimit };
|
||||||
const balances = pick(state.balances.balances, fromAddresses);
|
|
||||||
const { gasLimit } = state.nodeStatus;
|
|
||||||
|
|
||||||
return { gasLimit, balances };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -38,10 +38,9 @@ export default class Details extends Component {
|
|||||||
extras: PropTypes.bool,
|
extras: PropTypes.bool,
|
||||||
sender: PropTypes.string,
|
sender: PropTypes.string,
|
||||||
senderError: PropTypes.string,
|
senderError: PropTypes.string,
|
||||||
sendersBalances: PropTypes.object,
|
|
||||||
recipient: PropTypes.string,
|
recipient: PropTypes.string,
|
||||||
recipientError: PropTypes.string,
|
recipientError: PropTypes.string,
|
||||||
tag: PropTypes.string,
|
token: PropTypes.object,
|
||||||
total: PropTypes.string,
|
total: PropTypes.string,
|
||||||
totalError: PropTypes.string,
|
totalError: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
@ -57,13 +56,13 @@ export default class Details extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { all, extras, tag, total, totalError, value, valueError } = this.props;
|
const { all, extras, token, total, totalError, value, valueError } = this.props;
|
||||||
const label = (
|
const label = (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='transfer.details.amount.label'
|
id='transfer.details.amount.label'
|
||||||
defaultMessage='amount to transfer (in {tag})'
|
defaultMessage='amount to transfer (in {tag})'
|
||||||
values={ {
|
values={ {
|
||||||
tag
|
tag: token.tag
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -140,7 +139,7 @@ export default class Details extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderFromAddress () {
|
renderFromAddress () {
|
||||||
const { sender, senderError, senders, sendersBalances } = this.props;
|
const { sender, senderError, senders } = this.props;
|
||||||
|
|
||||||
if (!senders) {
|
if (!senders) {
|
||||||
return null;
|
return null;
|
||||||
@ -165,7 +164,6 @@ export default class Details extends Component {
|
|||||||
}
|
}
|
||||||
value={ sender }
|
value={ sender }
|
||||||
onChange={ this.onEditSender }
|
onChange={ this.onEditSender }
|
||||||
balances={ sendersBalances }
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -198,19 +196,19 @@ export default class Details extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTokenSelect () {
|
renderTokenSelect () {
|
||||||
const { balance, tag } = this.props;
|
const { balance, token } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TokenSelect
|
<TokenSelect
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
tag={ tag }
|
|
||||||
onChange={ this.onChangeToken }
|
onChange={ this.onChangeToken }
|
||||||
|
value={ token.id }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeToken = (event, index, tag) => {
|
onChangeToken = (event, index, tokenId) => {
|
||||||
this.props.onChange('tag', tag);
|
this.props.onChange('token', tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditSender = (event, sender) => {
|
onEditSender = (event, sender) => {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { MenuItem } from 'material-ui';
|
import { MenuItem } from 'material-ui';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ import TokenImage from '~/ui/TokenImage';
|
|||||||
|
|
||||||
import styles from '../transfer.css';
|
import styles from '../transfer.css';
|
||||||
|
|
||||||
export default class TokenSelect extends Component {
|
class TokenSelect extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object
|
api: PropTypes.object
|
||||||
};
|
};
|
||||||
@ -32,7 +33,8 @@ export default class TokenSelect extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
balance: PropTypes.object.isRequired,
|
balance: PropTypes.object.isRequired,
|
||||||
tag: PropTypes.string.isRequired
|
tokens: PropTypes.object.isRequired,
|
||||||
|
value: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
@ -40,8 +42,10 @@ export default class TokenSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
const prevTokens = this.props.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`);
|
const prevTokens = Object.keys(this.props.balance)
|
||||||
const nextTokens = nextProps.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`);
|
.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)) {
|
if (!isEqual(prevTokens, nextTokens)) {
|
||||||
this.computeTokens(nextProps);
|
this.computeTokens(nextProps);
|
||||||
@ -50,23 +54,27 @@ export default class TokenSelect extends Component {
|
|||||||
|
|
||||||
computeTokens (props = this.props) {
|
computeTokens (props = this.props) {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { balance } = this.props;
|
const { balance, tokens } = this.props;
|
||||||
|
|
||||||
const items = balance.tokens
|
const items = Object.keys(balance)
|
||||||
.filter((token, index) => !index || token.value.gt(0))
|
.map((tokenId) => {
|
||||||
.map((balance, index) => {
|
const token = tokens[tokenId];
|
||||||
const token = balance.token;
|
const tokenValue = balance[tokenId];
|
||||||
const isEth = index === 0;
|
const isEth = token.native;
|
||||||
|
|
||||||
|
if (!isEth && tokenValue.eq(0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let value = 0;
|
let value = 0;
|
||||||
|
|
||||||
if (isEth) {
|
if (isEth) {
|
||||||
value = api.util.fromWei(balance.value).toFormat(3);
|
value = api.util.fromWei(tokenValue).toFormat(3);
|
||||||
} else {
|
} else {
|
||||||
const format = balance.token.format || 1;
|
const format = token.format || 1;
|
||||||
const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10));
|
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 = (
|
const label = (
|
||||||
@ -83,20 +91,21 @@ export default class TokenSelect extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={ `${index}_${token.tag}` }
|
key={ tokenId }
|
||||||
value={ token.tag }
|
value={ token.id }
|
||||||
label={ label }
|
label={ label }
|
||||||
>
|
>
|
||||||
{ label }
|
{ label }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
.filter((node) => node);
|
||||||
|
|
||||||
this.setState({ items });
|
this.setState({ items });
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { tag, onChange } = this.props;
|
const { onChange, value } = this.props;
|
||||||
const { items } = this.state;
|
const { items } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -104,7 +113,7 @@ export default class TokenSelect extends Component {
|
|||||||
className={ styles.tokenSelect }
|
className={ styles.tokenSelect }
|
||||||
label='type of token transfer'
|
label='type of token transfer'
|
||||||
hint='type of token to transfer'
|
hint='type of token to transfer'
|
||||||
value={ tag }
|
value={ value }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
>
|
>
|
||||||
{ items }
|
{ items }
|
||||||
@ -112,3 +121,11 @@ export default class TokenSelect extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
const { tokens } = state;
|
||||||
|
|
||||||
|
return { tokens };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(TokenSelect);
|
||||||
|
@ -18,11 +18,12 @@ import { noop } from 'lodash';
|
|||||||
import { observable, computed, action, transaction } from 'mobx';
|
import { observable, computed, action, transaction } from 'mobx';
|
||||||
import BigNumber from 'bignumber.js';
|
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 { fromWei } from '~/api/util/wei';
|
||||||
import Contract from '~/api/contract';
|
import Contract from '~/api/contract';
|
||||||
import ERRORS from './errors';
|
import ERRORS from './errors';
|
||||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||||
|
import { ETH_TOKEN } from '~/util/tokens';
|
||||||
import GasPriceStore from '~/ui/GasPriceEditor/store';
|
import GasPriceStore from '~/ui/GasPriceEditor/store';
|
||||||
import { getLogger, LOG_KEYS } from '~/config';
|
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 {
|
export default class TransferStore {
|
||||||
@observable stage = 0;
|
@observable stage = 0;
|
||||||
@observable extras = false;
|
@observable extras = false;
|
||||||
|
@observable isEth = true;
|
||||||
@observable valueAll = false;
|
@observable valueAll = false;
|
||||||
@observable sending = false;
|
@observable sending = false;
|
||||||
@observable tag = 'ETH';
|
@observable token = ETH_TOKEN;
|
||||||
@observable isEth = true;
|
|
||||||
|
|
||||||
@observable data = '';
|
@observable data = '';
|
||||||
@observable dataError = null;
|
@observable dataError = null;
|
||||||
@ -69,6 +70,8 @@ export default class TransferStore {
|
|||||||
onClose = noop;
|
onClose = noop;
|
||||||
senders = null;
|
senders = null;
|
||||||
isWallet = false;
|
isWallet = false;
|
||||||
|
tokenContract = null;
|
||||||
|
tokens = {};
|
||||||
wallet = null;
|
wallet = null;
|
||||||
|
|
||||||
gasStore = null;
|
gasStore = null;
|
||||||
@ -76,14 +79,16 @@ export default class TransferStore {
|
|||||||
constructor (api, props) {
|
constructor (api, props) {
|
||||||
this.api = api;
|
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.account = account;
|
||||||
this.balance = balance;
|
this.balance = balance;
|
||||||
this.isWallet = account && account.wallet;
|
this.isWallet = account && account.wallet;
|
||||||
this.newError = newError;
|
this.newError = newError;
|
||||||
|
this.tokens = tokens;
|
||||||
|
|
||||||
this.gasStore = new GasPriceStore(api, { gasLimit });
|
this.gasStore = new GasPriceStore(api, { gasLimit });
|
||||||
|
this.tokenContract = api.newContract(tokenAbi, '');
|
||||||
|
|
||||||
if (this.isWallet) {
|
if (this.isWallet) {
|
||||||
this.wallet = props.wallet;
|
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 = () => {
|
@action onNext = () => {
|
||||||
this.stage += 1;
|
this.stage += 1;
|
||||||
}
|
}
|
||||||
@ -166,8 +167,8 @@ export default class TransferStore {
|
|||||||
case 'sender':
|
case 'sender':
|
||||||
return this._onUpdateSender(value);
|
return this._onUpdateSender(value);
|
||||||
|
|
||||||
case 'tag':
|
case 'token':
|
||||||
return this._onUpdateTag(value);
|
return this._onUpdateToken(value);
|
||||||
|
|
||||||
case 'value':
|
case 'value':
|
||||||
return this._onUpdateValue(value);
|
return this._onUpdateValue(value);
|
||||||
@ -244,10 +245,10 @@ export default class TransferStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action _onUpdateTag = (tag) => {
|
@action _onUpdateToken = (tokenId) => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.tag = tag;
|
this.token = { ...this.tokens[tokenId] };
|
||||||
this.isEth = tag.toLowerCase().trim() === 'eth';
|
this.isEth = this.token.native;
|
||||||
|
|
||||||
this.recalculateGas();
|
this.recalculateGas();
|
||||||
});
|
});
|
||||||
@ -323,46 +324,15 @@ export default class TransferStore {
|
|||||||
return balance;
|
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
|
* Return the balance of the selected token
|
||||||
* (in WEI for ETH, without formating for other tokens)
|
* (in WEI for ETH, without formating for other tokens)
|
||||||
*/
|
*/
|
||||||
getTokenBalance (tag = this.tag, forceSender = false) {
|
getTokenBalance (token = this.token, forceSender = false) {
|
||||||
const token = this.getToken(tag, forceSender);
|
return new BigNumber(this.balance[token.id] || 0);
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return new BigNumber(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = new BigNumber(token.value || 0);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTokenValue (tag = this.tag, value = this.value, inverse = false) {
|
getTokenValue (token = this.token, 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);
|
|
||||||
|
|
||||||
let _value;
|
let _value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -371,19 +341,11 @@ export default class TransferStore {
|
|||||||
_value = new BigNumber(0);
|
_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) {
|
if (inverse) {
|
||||||
return _value.div(format);
|
return _value.div(token.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _value.mul(format);
|
return _value.mul(token.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValues (_gasTotal) {
|
getValues (_gasTotal) {
|
||||||
@ -425,7 +387,7 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, substract the gas estimate
|
// Otherwise, substract the gas estimate
|
||||||
const availableEth = this.getTokenBalance('ETH');
|
const availableEth = this.getTokenBalance(ETH_TOKEN);
|
||||||
const totalEthValue = availableEth.gt(gasTotal)
|
const totalEthValue = availableEth.gt(gasTotal)
|
||||||
? availableEth.minus(gasTotal)
|
? availableEth.minus(gasTotal)
|
||||||
: new BigNumber(0);
|
: new BigNumber(0);
|
||||||
@ -437,15 +399,7 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFormattedTokenValue (tokenValue) {
|
getFormattedTokenValue (tokenValue) {
|
||||||
const token = this.getToken();
|
return this.getTokenValue(this.token, tokenValue, true);
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
return new BigNumber(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tag = token.token && token.token.tag || '';
|
|
||||||
|
|
||||||
return this.getTokenValue(tag, tokenValue, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action recalculate = (redo = false) => {
|
@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 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 tokenBalance = this.getTokenBalance();
|
||||||
const { eth, token } = this.getValues(gasTotal);
|
const { eth, token } = this.getValues(gasTotal);
|
||||||
|
|
||||||
@ -545,7 +499,7 @@ export default class TransferStore {
|
|||||||
return this.wallet.instance.execute;
|
return this.wallet.instance.execute;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.token.contract.instance.transfer;
|
return this.tokenContract.at(this.token.address).instance.transfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getData (gas = false) {
|
_getData (gas = false) {
|
||||||
@ -558,7 +512,7 @@ export default class TransferStore {
|
|||||||
const func = this._getTransferMethod(gas, true);
|
const func = this._getTransferMethod(gas, true);
|
||||||
const { options, values } = this._getTransferParams(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) {
|
_getTransferParams (gas = false, forceToken = false) {
|
||||||
@ -587,7 +541,7 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isWallet && !forceToken) {
|
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 value = isEth ? token : new BigNumber(0);
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
@ -621,14 +575,7 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_validateDecimals (num) {
|
_validateDecimals (num) {
|
||||||
const { balance } = this;
|
const s = new BigNumber(num).mul(this.token.format || 1).toFixed();
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (s.indexOf('.') !== -1) {
|
if (s.indexOf('.') !== -1) {
|
||||||
return ERRORS.invalidDecimals;
|
return ERRORS.invalidDecimals;
|
||||||
|
@ -45,12 +45,13 @@ class Transfer extends Component {
|
|||||||
newError: PropTypes.func.isRequired,
|
newError: PropTypes.func.isRequired,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
|
|
||||||
senders: nullableProptype(PropTypes.object),
|
|
||||||
sendersBalances: nullableProptype(PropTypes.object),
|
|
||||||
account: PropTypes.object,
|
account: PropTypes.object,
|
||||||
balance: 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);
|
store = new TransferStore(this.context.api, this.props);
|
||||||
@ -144,8 +145,8 @@ class Transfer extends Component {
|
|||||||
|
|
||||||
renderDetailsPage () {
|
renderDetailsPage () {
|
||||||
const { account, balance, senders } = this.props;
|
const { account, balance, senders } = this.props;
|
||||||
const { recipient, recipientError, sender, senderError, sendersBalances } = this.store;
|
const { recipient, recipientError, sender, senderError } = this.store;
|
||||||
const { valueAll, extras, tag, total, totalError, value, valueError } = this.store;
|
const { valueAll, extras, token, total, totalError, value, valueError } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Details
|
<Details
|
||||||
@ -159,8 +160,7 @@ class Transfer extends Component {
|
|||||||
sender={ sender }
|
sender={ sender }
|
||||||
senderError={ senderError }
|
senderError={ senderError }
|
||||||
senders={ senders }
|
senders={ senders }
|
||||||
sendersBalances={ sendersBalances }
|
token={ token }
|
||||||
tag={ tag }
|
|
||||||
total={ total }
|
total={ total }
|
||||||
totalError={ totalError }
|
totalError={ totalError }
|
||||||
value={ value }
|
value={ value }
|
||||||
@ -272,6 +272,7 @@ class Transfer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (initState, initProps) {
|
||||||
|
const { tokens } = initState;
|
||||||
const { address } = initProps.account;
|
const { address } = initProps.account;
|
||||||
|
|
||||||
const isWallet = initProps.account && initProps.account.wallet;
|
const isWallet = initProps.account && initProps.account.wallet;
|
||||||
@ -291,9 +292,12 @@ function mapStateToProps (initState, initProps) {
|
|||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const { gasLimit } = state.nodeStatus;
|
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 };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ class VaultAccounts extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
balances: PropTypes.object.isRequired,
|
|
||||||
newError: PropTypes.func.isRequired,
|
newError: PropTypes.func.isRequired,
|
||||||
personalAccountsInfo: PropTypes.func.isRequired,
|
personalAccountsInfo: PropTypes.func.isRequired,
|
||||||
vaultStore: PropTypes.object.isRequired
|
vaultStore: PropTypes.object.isRequired
|
||||||
@ -108,13 +107,9 @@ class VaultAccounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccount = (account) => {
|
renderAccount = (account) => {
|
||||||
const { balances } = this.props;
|
|
||||||
const balance = balances[account.address];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -171,12 +166,10 @@ class VaultAccounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { balances } = state.balances;
|
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts
|
||||||
balances
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
import { throttle } from 'lodash';
|
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 { padRight } from '~/api/util/format';
|
||||||
|
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
@ -193,6 +194,7 @@ export default class Balances {
|
|||||||
return console.warn('_subscribeBlockNumber', error);
|
return console.warn('_subscribeBlockNumber', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._store.dispatch(queryTokensFilter());
|
||||||
return this.fetchAllBalances();
|
return this.fetchAllBalances();
|
||||||
})
|
})
|
||||||
.then((blockNumberSID) => {
|
.then((blockNumberSID) => {
|
||||||
@ -276,7 +278,6 @@ export default class Balances {
|
|||||||
.then((tokenreg) => {
|
.then((tokenreg) => {
|
||||||
this._tokenreg = tokenreg;
|
this._tokenreg = tokenreg;
|
||||||
|
|
||||||
this._store.dispatch(setTokenReg(tokenreg));
|
|
||||||
this._store.dispatch(loadTokens(options));
|
this._store.dispatch(loadTokens(options));
|
||||||
|
|
||||||
return this.attachToTokens(tokenreg);
|
return this.attachToTokens(tokenreg);
|
||||||
|
@ -14,119 +14,19 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { range, uniq, isEqual } from 'lodash';
|
import { uniq, isEqual } from 'lodash';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import { push } from 'react-router-redux';
|
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 { notifyTransaction } from '~/util/notifications';
|
||||||
|
import { ETH_TOKEN, fetchAccountsBalances } from '~/util/tokens';
|
||||||
import { LOG_KEYS, getLogger } from '~/config';
|
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 log = getLogger(LOG_KEYS.Balances);
|
||||||
|
|
||||||
const ETH = {
|
let tokensFilter = {};
|
||||||
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));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function _setBalances (balances) {
|
function _setBalances (balances) {
|
||||||
return {
|
return {
|
||||||
@ -135,129 +35,88 @@ function _setBalances (balances) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTokens (tokens) {
|
/**
|
||||||
return {
|
* @param {Object} _balances - In the shape:
|
||||||
type: 'setTokens',
|
* {
|
||||||
tokens
|
* [ who ]: { [ tokenId ]: BigNumber } // The balances of `who`
|
||||||
};
|
* }
|
||||||
}
|
* @param {Boolean} skipNotifications [description]
|
||||||
|
*/
|
||||||
export function setTokenReg (tokenreg) {
|
function setBalances (updates, skipNotifications = false) {
|
||||||
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 : '');
|
|
||||||
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { tokenreg } = getState().balances;
|
const { tokens, balances } = getState();
|
||||||
|
|
||||||
return tokenreg.instance.tokenCount
|
const prevBalances = balances;
|
||||||
.call()
|
const nextBalances = { ...prevBalances };
|
||||||
.then((numTokens) => {
|
|
||||||
const tokenIds = range(numTokens.toNumber());
|
|
||||||
|
|
||||||
dispatch(fetchTokens(tokenIds, options));
|
Object.keys(updates)
|
||||||
})
|
.forEach((who) => {
|
||||||
.catch((error) => {
|
const accountUpdates = updates[who];
|
||||||
console.warn('balances::loadTokens', error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchTokens (_tokenIds, options = {}) {
|
Object.keys(accountUpdates)
|
||||||
const tokenIds = uniq(_tokenIds || []);
|
.forEach((tokenId) => {
|
||||||
|
const token = tokens[tokenId];
|
||||||
|
const prevTokenValue = (prevBalances[who] || {})[tokenId];
|
||||||
|
const nextTokenValue = accountUpdates[tokenId];
|
||||||
|
|
||||||
return (dispatch, getState) => {
|
if (prevTokenValue && prevTokenValue.lt(nextTokenValue)) {
|
||||||
const { api, images, balances } = getState();
|
dispatch(notifyBalanceChange(who, prevTokenValue, nextTokenValue, token));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setTokenImage(address, image));
|
// Add the token if it's native ETH or if it has a value
|
||||||
dispatch(setAddressImage(address, image, true));
|
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) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal } = getState();
|
const account = getState().personal.accounts[who];
|
||||||
const { visibleAccounts, accounts } = personal;
|
|
||||||
|
|
||||||
const addresses = uniq(_addresses || visibleAccounts || []);
|
if (account) {
|
||||||
|
const txValue = toValue.minus(fromValue);
|
||||||
|
|
||||||
// With only a single account, more info will be displayed.
|
const redirectToAccount = () => {
|
||||||
const fullFetch = addresses.length === 1;
|
const basePath = account.wallet
|
||||||
|
? 'wallet'
|
||||||
|
: 'accounts';
|
||||||
|
|
||||||
// Add accounts addresses (for notifications, accounts selection, etc.)
|
const route = `/${basePath}/${account.address}`;
|
||||||
const addressesToFetch = uniq(addresses.concat(Object.keys(accounts)));
|
|
||||||
|
|
||||||
return Promise
|
dispatch(push(route));
|
||||||
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))
|
};
|
||||||
.then((accountsBalances) => {
|
|
||||||
const balances = {};
|
|
||||||
|
|
||||||
addressesToFetch.forEach((addr, idx) => {
|
notifyTransaction(account, token, txValue, redirectToAccount);
|
||||||
balances[addr] = accountsBalances[idx];
|
}
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(setBalances(balances, skipNotifications));
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn('balances::fetchBalances', error);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: fetch txCount when needed
|
||||||
|
export function fetchBalances (_addresses, skipNotifications = false) {
|
||||||
|
return fetchTokensBalances(_addresses, [ ETH_TOKEN ], skipNotifications);
|
||||||
|
}
|
||||||
|
|
||||||
export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, balances, personal } = getState();
|
const { api, personal, tokens } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
const { tokensFilter } = balances;
|
|
||||||
|
|
||||||
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
const addresses = uniq(_addresses || addressesToFetch || []).sort();
|
const addresses = uniq(_addresses || addressesToFetch || []).sort();
|
||||||
|
|
||||||
const tokens = _tokens || Object.values(balances.tokens) || [];
|
const tokensToUpdate = _tokens || Object.values(tokens);
|
||||||
const tokenAddresses = tokens.map((t) => t.address).sort();
|
const tokenAddresses = tokensToUpdate
|
||||||
|
.map((t) => t.address)
|
||||||
|
.filter((address) => address)
|
||||||
|
.sort();
|
||||||
|
|
||||||
if (tokensFilter.filterFromId || tokensFilter.filterToId) {
|
if (tokensFilter.filterFromId || tokensFilter.filterToId) {
|
||||||
// Has the tokens addresses changed (eg. a network change)
|
// 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 promise = Promise.all(promises);
|
||||||
|
|
||||||
const TRANSFER_SIGNATURE = api.util.sha3('Transfer(address,address,uint256)');
|
|
||||||
const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ];
|
const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ];
|
||||||
const topicsTo = [ TRANSFER_SIGNATURE, null, addresses ];
|
const topicsTo = [ TRANSFER_SIGNATURE, null, addresses ];
|
||||||
|
|
||||||
const options = {
|
const filterOptions = {
|
||||||
fromBlock: 0,
|
fromBlock: 0,
|
||||||
toBlock: 'pending',
|
toBlock: 'pending',
|
||||||
address: tokenAddresses
|
address: tokenAddresses
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionsFrom = {
|
const optionsFrom = {
|
||||||
...options,
|
...filterOptions,
|
||||||
topics: topicsFrom
|
topics: topicsFrom
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionsTo = {
|
const optionsTo = {
|
||||||
...options,
|
...filterOptions,
|
||||||
topics: topicsTo
|
topics: topicsTo
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -322,8 +180,8 @@ export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
|||||||
|
|
||||||
const { skipNotifications } = options;
|
const { skipNotifications } = options;
|
||||||
|
|
||||||
dispatch(setTokensFilter(nextTokensFilter));
|
tokensFilter = nextTokensFilter;
|
||||||
fetchTokensBalances(addresses, tokens, skipNotifications)(dispatch, getState);
|
fetchTokensBalances(addresses, tokensToUpdate, skipNotifications)(dispatch, getState);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('balances::updateTokensFilter', 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) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, balances } = getState();
|
const { api, personal, tokens } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
|
|
||||||
const visibleAddresses = visibleAccounts.map((a) => a.toLowerCase());
|
const allAddresses = visibleAccounts.concat(Object.keys(accounts));
|
||||||
const addressesToFetch = uniq(visibleAddresses.concat(Object.keys(accounts)));
|
const addressesToFetch = uniq(allAddresses);
|
||||||
|
const lcAddresses = addressesToFetch.map((a) => a.toLowerCase());
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all([
|
.all([
|
||||||
@ -347,21 +206,28 @@ export function queryTokensFilter (tokensFilter) {
|
|||||||
.then(([ logsFrom, logsTo ]) => {
|
.then(([ logsFrom, logsTo ]) => {
|
||||||
const addresses = [];
|
const addresses = [];
|
||||||
const tokenAddresses = [];
|
const tokenAddresses = [];
|
||||||
|
const logs = logsFrom.concat(logsTo);
|
||||||
|
|
||||||
logsFrom
|
if (logs.length > 0) {
|
||||||
.concat(logsTo)
|
log.debug('got tokens filter logs', logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
logs
|
||||||
.forEach((log) => {
|
.forEach((log) => {
|
||||||
const tokenAddress = log.address;
|
const tokenAddress = log.address;
|
||||||
|
|
||||||
const fromAddress = '0x' + log.topics[1].slice(-40);
|
const fromAddress = '0x' + log.topics[1].slice(-40);
|
||||||
const toAddress = '0x' + log.topics[2].slice(-40);
|
const toAddress = '0x' + log.topics[2].slice(-40);
|
||||||
|
|
||||||
if (addressesToFetch.includes(fromAddress)) {
|
const fromAddressIndex = lcAddresses.indexOf(fromAddress);
|
||||||
addresses.push(fromAddress);
|
const toAddressIndex = lcAddresses.indexOf(toAddress);
|
||||||
|
|
||||||
|
if (fromAddressIndex > -1) {
|
||||||
|
addresses.push(addressesToFetch[fromAddressIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addressesToFetch.includes(toAddress)) {
|
if (toAddressIndex > -1) {
|
||||||
addresses.push(toAddress);
|
addresses.push(addressesToFetch[toAddressIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenAddresses.push(tokenAddress);
|
tokenAddresses.push(tokenAddress);
|
||||||
@ -371,36 +237,35 @@ export function queryTokensFilter (tokensFilter) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = Object.values(balances.tokens)
|
const tokensToUpdate = Object.values(tokens)
|
||||||
.filter((t) => tokenAddresses.includes(t.address));
|
.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) {
|
export function fetchTokensBalances (_addresses = null, _tokens = null, skipNotifications = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, balances } = getState();
|
const { api, personal, tokens } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
|
const allTokens = Object.values(tokens);
|
||||||
|
|
||||||
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
const addresses = _addresses || addressesToFetch;
|
const addresses = _addresses || addressesToFetch;
|
||||||
const tokens = _tokens || Object.values(balances.tokens);
|
const tokensToUpdate = _tokens || allTokens;
|
||||||
|
|
||||||
if (addresses.length === 0) {
|
if (addresses.length === 0) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise
|
const updates = addresses.reduce((updates, who) => {
|
||||||
.all(addresses.map((addr) => fetchTokensBalance(addr, tokens, api)))
|
updates[who] = tokensToUpdate.map((token) => token.id);
|
||||||
.then((tokensBalances) => {
|
return updates;
|
||||||
const balances = {};
|
}, {});
|
||||||
|
|
||||||
addresses.forEach((addr, idx) => {
|
|
||||||
balances[addr] = tokensBalances[idx];
|
|
||||||
});
|
|
||||||
|
|
||||||
|
return fetchAccountsBalances(api, allTokens, updates)
|
||||||
|
.then((balances) => {
|
||||||
dispatch(setBalances(balances, skipNotifications));
|
dispatch(setBalances(balances, skipNotifications));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -16,72 +16,12 @@
|
|||||||
|
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {};
|
||||||
balances: {},
|
|
||||||
tokens: {},
|
|
||||||
tokenreg: null,
|
|
||||||
tokensFilter: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handleActions({
|
export default handleActions({
|
||||||
setBalances (state, action) {
|
setBalances (state, action) {
|
||||||
const { balances } = action;
|
const { balances } = action;
|
||||||
|
|
||||||
return Object.assign({}, state, { balances });
|
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 });
|
|
||||||
}
|
}
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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);
|
|
@ -21,7 +21,6 @@ export Status from './status';
|
|||||||
|
|
||||||
export apiReducer from './apiReducer';
|
export apiReducer from './apiReducer';
|
||||||
export balancesReducer from './balancesReducer';
|
export balancesReducer from './balancesReducer';
|
||||||
export blockchainReducer from './blockchainReducer';
|
|
||||||
export workerReducer from './workerReducer';
|
export workerReducer from './workerReducer';
|
||||||
export imagesReducer from './imagesReducer';
|
export imagesReducer from './imagesReducer';
|
||||||
export personalReducer from './personalReducer';
|
export personalReducer from './personalReducer';
|
||||||
@ -29,4 +28,5 @@ export requestsReducer from './requestsReducer';
|
|||||||
export signerReducer from './signerReducer';
|
export signerReducer from './signerReducer';
|
||||||
export snackbarReducer from './snackbarReducer';
|
export snackbarReducer from './snackbarReducer';
|
||||||
export statusReducer from './statusReducer';
|
export statusReducer from './statusReducer';
|
||||||
|
export tokensReducer from './tokensReducer';
|
||||||
export walletReducer from './walletReducer';
|
export walletReducer from './walletReducer';
|
||||||
|
@ -35,6 +35,12 @@ export default class Personal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the address to each accounts
|
||||||
|
Object.keys(accountsInfo)
|
||||||
|
.forEach((address) => {
|
||||||
|
accountsInfo[address].address = address;
|
||||||
|
});
|
||||||
|
|
||||||
this._store.dispatch(personalAccountsInfo(accountsInfo));
|
this._store.dispatch(personalAccountsInfo(accountsInfo));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
88
js/src/redux/providers/tokensActions.js
Normal file
88
js/src/redux/providers/tokensActions.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
34
js/src/redux/providers/tokensReducer.js
Normal file
34
js/src/redux/providers/tokensReducer.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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);
|
@ -18,10 +18,10 @@ import { combineReducers } from 'redux';
|
|||||||
import { routerReducer } from 'react-router-redux';
|
import { routerReducer } from 'react-router-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
apiReducer, balancesReducer, blockchainReducer,
|
apiReducer, balancesReducer,
|
||||||
workerReducer, imagesReducer, personalReducer, requestsReducer,
|
workerReducer, imagesReducer, personalReducer, requestsReducer,
|
||||||
signerReducer, statusReducer as nodeStatusReducer,
|
signerReducer, statusReducer as nodeStatusReducer,
|
||||||
snackbarReducer, walletReducer
|
snackbarReducer, tokensReducer, walletReducer
|
||||||
} from './providers';
|
} from './providers';
|
||||||
import certificationsReducer from './providers/certifications/reducer';
|
import certificationsReducer from './providers/certifications/reducer';
|
||||||
import registryReducer from './providers/registry/reducer';
|
import registryReducer from './providers/registry/reducer';
|
||||||
@ -40,7 +40,6 @@ export default function () {
|
|||||||
|
|
||||||
balances: balancesReducer,
|
balances: balancesReducer,
|
||||||
certifications: certificationsReducer,
|
certifications: certificationsReducer,
|
||||||
blockchain: blockchainReducer,
|
|
||||||
images: imagesReducer,
|
images: imagesReducer,
|
||||||
nodeStatus: nodeStatusReducer,
|
nodeStatus: nodeStatusReducer,
|
||||||
personal: personalReducer,
|
personal: personalReducer,
|
||||||
@ -48,6 +47,7 @@ export default function () {
|
|||||||
requests: requestsReducer,
|
requests: requestsReducer,
|
||||||
signer: signerReducer,
|
signer: signerReducer,
|
||||||
snackbar: snackbarReducer,
|
snackbar: snackbarReducer,
|
||||||
|
tokens: tokensReducer,
|
||||||
wallet: walletReducer,
|
wallet: walletReducer,
|
||||||
worker: workerReducer
|
worker: workerReducer
|
||||||
});
|
});
|
||||||
|
@ -84,6 +84,7 @@ export default class AccountCard extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Balance
|
<Balance
|
||||||
|
address={ address }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
className={ styles.balance }
|
className={ styles.balance }
|
||||||
showOnlyEth
|
showOnlyEth
|
||||||
|
@ -14,10 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import { ETH_TOKEN } from '~/util/tokens';
|
||||||
|
|
||||||
import AccountCard from './';
|
import AccountCard from './';
|
||||||
|
|
||||||
const TEST_ADDRESS = '0x1234567890123456789012345678901234567890';
|
const TEST_ADDRESS = '0x1234567890123456789012345678901234567890';
|
||||||
@ -27,6 +30,21 @@ let component;
|
|||||||
let onClick;
|
let onClick;
|
||||||
let onFocus;
|
let onFocus;
|
||||||
|
|
||||||
|
function reduxStore () {
|
||||||
|
const getState = () => ({
|
||||||
|
balances: {},
|
||||||
|
tokens: {
|
||||||
|
[ETH_TOKEN.id]: ETH_TOKEN
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getState,
|
||||||
|
dispatch: () => null,
|
||||||
|
subscribe: () => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function render (props = {}) {
|
function render (props = {}) {
|
||||||
if (!props.account) {
|
if (!props.account) {
|
||||||
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();
|
onClick = sinon.stub();
|
||||||
onFocus = sinon.stub();
|
onFocus = sinon.stub();
|
||||||
|
|
||||||
@ -67,7 +91,9 @@ describe('ui/AccountCard', () => {
|
|||||||
let balance;
|
let balance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
balance = component.find('Balance');
|
balance = component.find('Connect(Balance)').shallow({
|
||||||
|
context: { store: reduxStore() }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the balance', () => {
|
it('renders the balance', () => {
|
||||||
|
@ -17,18 +17,21 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import TokenImage from '~/ui/TokenImage';
|
import TokenImage from '~/ui/TokenImage';
|
||||||
|
|
||||||
import styles from './balance.css';
|
import styles from './balance.css';
|
||||||
|
|
||||||
export default class Balance extends Component {
|
export class Balance extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object
|
api: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
balance: PropTypes.object,
|
balance: PropTypes.object.isRequired,
|
||||||
|
tokens: PropTypes.object.isRequired,
|
||||||
|
address: PropTypes.string,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
showOnlyEth: PropTypes.bool,
|
showOnlyEth: PropTypes.bool,
|
||||||
showZeroValues: PropTypes.bool
|
showZeroValues: PropTypes.bool
|
||||||
@ -40,44 +43,38 @@ export default class Balance extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { api } = this.context;
|
const { balance, className, showOnlyEth, tokens } = this.props;
|
||||||
const { balance, className, showOnlyEth } = this.props;
|
|
||||||
|
|
||||||
if (!balance || !balance.tokens) {
|
if (Object.keys(balance).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = balance.tokens
|
let body = Object.keys(balance)
|
||||||
.filter((balance) => {
|
.map((tokenId) => {
|
||||||
const isEthToken = (balance.token.tag || '').toLowerCase() === 'eth';
|
const token = tokens[tokenId];
|
||||||
const hasBalance = new BigNumber(balance.value).gt(0);
|
const balanceValue = balance[tokenId];
|
||||||
|
|
||||||
return hasBalance || isEthToken;
|
const isEthToken = token.native;
|
||||||
})
|
const isFullToken = !showOnlyEth || isEthToken;
|
||||||
.map((balance, index) => {
|
const hasBalance = balanceValue.gt(0);
|
||||||
const isFullToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth';
|
|
||||||
const token = balance.token;
|
|
||||||
|
|
||||||
let value;
|
if (!hasBalance && !isEthToken) {
|
||||||
|
return null;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
const classNames = [styles.balance];
|
||||||
let details = null;
|
let details = null;
|
||||||
|
|
||||||
@ -104,13 +101,14 @@ export default class Balance extends Component {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={ classNames.join(' ') }
|
className={ classNames.join(' ') }
|
||||||
key={ `${index}_${token.tag}` }
|
key={ tokenId }
|
||||||
>
|
>
|
||||||
<TokenImage token={ token } />
|
<TokenImage token={ token } />
|
||||||
{ details }
|
{ details }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
.filter((node) => node);
|
||||||
|
|
||||||
if (!body.length) {
|
if (!body.length) {
|
||||||
body = (
|
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);
|
||||||
|
@ -14,19 +14,24 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import apiutil from '~/api/util';
|
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 = {
|
const BALANCE = {
|
||||||
tokens: [
|
'eth': new BigNumber(122),
|
||||||
{ value: '122', token: { tag: 'ETH' } },
|
'gav': new BigNumber(345),
|
||||||
{ value: '345', token: { tag: 'GAV', format: 1 } },
|
'tst': new BigNumber(0)
|
||||||
{ value: '0', token: { tag: 'TST', format: 1 } }
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let api;
|
let api;
|
||||||
@ -46,6 +51,10 @@ function render (props = {}) {
|
|||||||
props.balance = BALANCE;
|
props.balance = BALANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.tokens) {
|
||||||
|
props.tokens = TOKENS;
|
||||||
|
}
|
||||||
|
|
||||||
const api = createApi();
|
const api = createApi();
|
||||||
|
|
||||||
component = shallow(
|
component = shallow(
|
||||||
|
@ -32,6 +32,7 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
|
|||||||
|
|
||||||
.hoverOverlay {
|
.hoverOverlay {
|
||||||
background: $background;
|
background: $background;
|
||||||
|
display: none;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin-top: -1.5em;
|
margin-top: -1.5em;
|
||||||
margin-bottom: 3em;
|
margin-bottom: 3em;
|
||||||
@ -52,6 +53,7 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
|
|||||||
|
|
||||||
.hoverOverlay {
|
.hoverOverlay {
|
||||||
background: $backgroundHover;
|
background: $backgroundHover;
|
||||||
|
display: block;
|
||||||
/*transform: scale(1, 1);*/
|
/*transform: scale(1, 1);*/
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,6 @@ class AddressSelect extends Component {
|
|||||||
// Redux props
|
// Redux props
|
||||||
accountsInfo: PropTypes.object,
|
accountsInfo: PropTypes.object,
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
|
||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object,
|
||||||
contracts: PropTypes.object,
|
contracts: PropTypes.object,
|
||||||
tokens: PropTypes.object,
|
tokens: PropTypes.object,
|
||||||
@ -356,10 +355,9 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccountCard (_account) {
|
renderAccountCard (_account) {
|
||||||
const { balances, accountsInfo } = this.props;
|
const { accountsInfo } = this.props;
|
||||||
const { address, index = null } = _account;
|
const { address, index = null } = _account;
|
||||||
|
|
||||||
const balance = balances[address];
|
|
||||||
const account = {
|
const account = {
|
||||||
...accountsInfo[address],
|
...accountsInfo[address],
|
||||||
..._account
|
..._account
|
||||||
@ -368,7 +366,6 @@ class AddressSelect extends Component {
|
|||||||
return (
|
return (
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
className={ styles.account }
|
className={ styles.account }
|
||||||
key={ `account_${index}` }
|
key={ `account_${index}` }
|
||||||
onClick={ this.handleClick }
|
onClick={ this.handleClick }
|
||||||
@ -650,12 +647,10 @@ class AddressSelect extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accountsInfo } = state.personal;
|
const { accountsInfo } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
const { reverse } = state.registry;
|
const { reverse } = state.registry;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
balances,
|
|
||||||
reverse
|
reverse
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -175,14 +175,12 @@ function mapStateToProps (state, props) {
|
|||||||
|
|
||||||
const lcValue = value.toLowerCase();
|
const lcValue = value.toLowerCase();
|
||||||
const { accountsInfo } = state.personal;
|
const { accountsInfo } = state.personal;
|
||||||
const { tokens } = state.balances;
|
const { tokens } = state;
|
||||||
|
|
||||||
const accountsInfoAddress = Object.keys(accountsInfo).find((address) => address.toLowerCase() === lcValue);
|
const accountInfo = Object.values(accountsInfo).find((account) => account.address.toLowerCase() === lcValue);
|
||||||
const tokensAddress = Object.keys(tokens).find((address) => address.toLowerCase() === lcValue);
|
const token = Object.values(tokens).find((token) => token.address.toLowerCase() === lcValue);
|
||||||
|
|
||||||
const account = (accountsInfoAddress && accountsInfo[accountsInfoAddress]) ||
|
const account = accountInfo || token || null;
|
||||||
(tokensAddress && tokens[tokensAddress]) ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account
|
account
|
||||||
|
@ -34,21 +34,19 @@ const defaultNameNull = (
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
class IdentityName extends Component {
|
export class IdentityName extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accountsInfo: PropTypes.object,
|
account: PropTypes.object,
|
||||||
address: PropTypes.string,
|
address: PropTypes.string,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
empty: PropTypes.bool,
|
empty: PropTypes.bool,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
shorten: PropTypes.bool,
|
shorten: PropTypes.bool,
|
||||||
tokens: PropTypes.object,
|
|
||||||
unknown: PropTypes.bool
|
unknown: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { address, accountsInfo, className, empty, name, shorten, tokens, unknown } = this.props;
|
const { account, address, className, empty, name, shorten, unknown } = this.props;
|
||||||
const account = accountsInfo[address] || tokens[address];
|
|
||||||
|
|
||||||
if (!account && empty) {
|
if (!account && empty) {
|
||||||
return null;
|
return null;
|
||||||
@ -76,13 +74,14 @@ class IdentityName extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state, props) {
|
||||||
const { accountsInfo } = state.personal;
|
const { address } = props;
|
||||||
const { tokens } = state.balances;
|
const { personal, tokens } = state;
|
||||||
|
|
||||||
|
const account = personal.accountsInfo[address] || Object.values(tokens).find((token) => token.address === address);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountsInfo,
|
account
|
||||||
tokens
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,40 +17,19 @@
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import sinon from 'sinon';
|
import { IdentityName } from './identityName';
|
||||||
|
|
||||||
import IdentityName from './identityName';
|
|
||||||
|
|
||||||
const ADDR_A = '0x123456789abcdef0123456789A';
|
const ADDR_A = '0x123456789abcdef0123456789A';
|
||||||
const ADDR_B = '0x123456789abcdef0123456789B';
|
|
||||||
const ADDR_C = '0x123456789abcdef0123456789C';
|
const ADDR_C = '0x123456789abcdef0123456789C';
|
||||||
const ADDR_NULL = '0x0000000000000000000000000000000000000000';
|
const ADDR_NULL = '0x0000000000000000000000000000000000000000';
|
||||||
const NAME_JIMMY = 'Jimmy Test';
|
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) {
|
function render (props) {
|
||||||
return shallow(
|
return shallow(
|
||||||
<IdentityName
|
<IdentityName
|
||||||
store={ STORE }
|
|
||||||
{ ...props }
|
{ ...props }
|
||||||
/>
|
/>
|
||||||
).find('IdentityName').shallow();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ui/IdentityName', () => {
|
describe('ui/IdentityName', () => {
|
||||||
|
@ -653,10 +653,10 @@ class MethodDecoding extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (initState, initProps) {
|
||||||
const { tokens } = initState.balances;
|
const { tokens } = initState;
|
||||||
const { transaction } = initProps;
|
const { transaction } = initProps;
|
||||||
|
|
||||||
const token = (tokens || {})[transaction.to];
|
const token = Object.values(tokens).find((token) => token.address === transaction.to);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return { token };
|
return { token };
|
||||||
|
@ -17,20 +17,12 @@
|
|||||||
import Push from 'push.js';
|
import Push from 'push.js';
|
||||||
import BigNumber from 'bignumber.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';
|
import unkownIcon from '~/../assets/images/contracts/unknown-64x64.png';
|
||||||
|
|
||||||
export function notifyTransaction (account, token, _value, onClick) {
|
export function notifyTransaction (account, token, _value, onClick) {
|
||||||
const name = account.name || account.address;
|
const name = account.name || account.address;
|
||||||
const value = token.tag.toLowerCase() === 'eth'
|
const value = _value.div(new BigNumber(token.format || 1));
|
||||||
? fromWei(_value)
|
const icon = token.image || unkownIcon;
|
||||||
: _value.div(new BigNumber(token.format || 1));
|
|
||||||
|
|
||||||
const icon = token.tag.toLowerCase() === 'eth'
|
|
||||||
? ethereumIcon
|
|
||||||
: (token.image || unkownIcon);
|
|
||||||
|
|
||||||
let _notification = null;
|
let _notification = null;
|
||||||
|
|
||||||
|
133
js/src/util/tokens.js
Normal file
133
js/src/util/tokens.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
});
|
||||||
|
}
|
@ -22,9 +22,12 @@ import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, Id
|
|||||||
import styles from './header.css';
|
import styles from './header.css';
|
||||||
|
|
||||||
export default class Header extends Component {
|
export default class Header extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
api: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object,
|
account: PropTypes.object,
|
||||||
balance: PropTypes.object,
|
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
@ -39,8 +42,49 @@ export default class Header extends Component {
|
|||||||
isContract: false
|
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 () {
|
render () {
|
||||||
const { account, balance, children, className, disabled, hideName } = this.props;
|
const { account, children, className, disabled, hideName } = this.props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
@ -76,8 +120,7 @@ export default class Header extends Component {
|
|||||||
{ this.renderTxCount() }
|
{ this.renderTxCount() }
|
||||||
<div className={ styles.balances }>
|
<div className={ styles.balances }>
|
||||||
<Balance
|
<Balance
|
||||||
account={ account }
|
address={ address }
|
||||||
balance={ balance }
|
|
||||||
/>
|
/>
|
||||||
<Certifications address={ address } />
|
<Certifications address={ address } />
|
||||||
{ this.renderVault() }
|
{ this.renderVault() }
|
||||||
@ -115,15 +158,10 @@ export default class Header extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTxCount () {
|
renderTxCount () {
|
||||||
const { balance, isContract } = this.props;
|
const { isContract } = this.props;
|
||||||
|
const { txCount } = this.state;
|
||||||
|
|
||||||
if (!balance || isContract) {
|
if (!txCount || isContract) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { txCount } = balance;
|
|
||||||
|
|
||||||
if (!txCount) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ import BigNumber from 'bignumber.js';
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ETH_TOKEN } from '~/util/tokens';
|
||||||
|
|
||||||
import Header from './';
|
import Header from './';
|
||||||
|
|
||||||
const ACCOUNT = {
|
const ACCOUNT = {
|
||||||
@ -28,17 +30,44 @@ const ACCOUNT = {
|
|||||||
},
|
},
|
||||||
uuid: '0xabcdef'
|
uuid: '0xabcdef'
|
||||||
};
|
};
|
||||||
|
const subscriptions = {};
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
let instance;
|
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 = {}) {
|
function render (props = {}) {
|
||||||
if (props && !props.account) {
|
if (props && !props.account) {
|
||||||
props.account = ACCOUNT;
|
props.account = ACCOUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
component = shallow(
|
component = shallow(
|
||||||
<Header { ...props } />
|
<Header { ...props } />,
|
||||||
|
{ context: { api } }
|
||||||
);
|
);
|
||||||
instance = component.instance();
|
instance = component.instance();
|
||||||
|
|
||||||
@ -72,8 +101,9 @@ describe('views/Account/Header', () => {
|
|||||||
let balance;
|
let balance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render({ balance: { balance: 'testing' } });
|
render();
|
||||||
balance = component.find('Balance');
|
balance = component.find('Connect(Balance)')
|
||||||
|
.shallow({ context: { store: reduxStore() } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
@ -81,11 +111,7 @@ describe('views/Account/Header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('passes the account', () => {
|
it('passes the account', () => {
|
||||||
expect(balance.props().account).to.deep.equal(ACCOUNT);
|
expect(balance.props().address).to.deep.equal(ACCOUNT.address);
|
||||||
});
|
|
||||||
|
|
||||||
it('passes the balance', () => {
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -177,24 +203,33 @@ describe('views/Account/Header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('renderTxCount', () => {
|
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', () => {
|
it('renders null when txCount is null', () => {
|
||||||
render({ balance: { txCount: null }, isContract: false });
|
render();
|
||||||
expect(instance.renderTxCount()).to.be.null;
|
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', () => {
|
it('renders the tx count', () => {
|
||||||
render({ balance: { txCount: new BigNumber(1) }, isContract: false });
|
render();
|
||||||
expect(instance.renderTxCount()).not.to.be.null;
|
|
||||||
|
subscriptions['eth_blockNumber'].forEach((callback) => {
|
||||||
|
callback();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(instance.renderTxCount()).not.to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,8 +46,7 @@ class Account extends Component {
|
|||||||
fetchCertifications: PropTypes.func.isRequired,
|
fetchCertifications: PropTypes.func.isRequired,
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
accounts: PropTypes.object,
|
account: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
|
||||||
certifications: PropTypes.object,
|
certifications: PropTypes.object,
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
params: PropTypes.object
|
params: PropTypes.object
|
||||||
@ -83,12 +82,9 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, balances } = this.props;
|
const { account } = this.props;
|
||||||
const { address } = this.props.params;
|
const { address } = this.props.params;
|
||||||
|
|
||||||
const account = (accounts || {})[address];
|
|
||||||
const balance = (balances || {})[address];
|
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -102,17 +98,15 @@ class Account extends Component {
|
|||||||
{ this.renderFaucetDialog() }
|
{ this.renderFaucetDialog() }
|
||||||
{ this.renderFundDialog() }
|
{ this.renderFundDialog() }
|
||||||
{ this.renderPasswordDialog(account) }
|
{ this.renderPasswordDialog(account) }
|
||||||
{ this.renderTransferDialog(account, balance) }
|
{ this.renderTransferDialog(account) }
|
||||||
{ this.renderVerificationDialog() }
|
{ this.renderVerificationDialog() }
|
||||||
{ this.renderActionbar(account, balance) }
|
{ this.renderActionbar(account) }
|
||||||
<Page padded>
|
<Page padded>
|
||||||
<Header
|
<Header
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
disabled={ !isAvailable }
|
disabled={ !isAvailable }
|
||||||
/>
|
/>
|
||||||
<Transactions
|
<Transactions
|
||||||
accounts={ accounts }
|
|
||||||
address={ address }
|
address={ address }
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
@ -143,16 +137,14 @@ class Account extends Component {
|
|||||||
return certifications.length !== 0;
|
return certifications.length !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActionbar (account, balance) {
|
renderActionbar (account) {
|
||||||
const { certifications, netVersion } = this.props;
|
const { certifications, netVersion } = this.props;
|
||||||
const { address } = this.props.params;
|
const { address } = this.props.params;
|
||||||
const showTransferButton = !!(balance && balance.tokens);
|
|
||||||
const isVerifiable = this.isMainnet(netVersion);
|
const isVerifiable = this.isMainnet(netVersion);
|
||||||
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
|
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
disabled={ !showTransferButton }
|
|
||||||
icon={ <SendIcon /> }
|
icon={ <SendIcon /> }
|
||||||
key='transferFunds'
|
key='transferFunds'
|
||||||
label={
|
label={
|
||||||
@ -374,18 +366,14 @@ class Account extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTransferDialog (account, balance) {
|
renderTransferDialog (account) {
|
||||||
if (!this.store.isTransferVisible) {
|
if (!this.store.isTransferVisible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { balances } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transfer
|
<Transfer
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
balances={ balances }
|
|
||||||
onClose={ this.store.toggleTransferDialog }
|
onClose={ this.store.toggleTransferDialog }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -407,15 +395,17 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state, props) {
|
||||||
|
const { address } = props.params;
|
||||||
|
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
const certifications = state.certifications;
|
const certifications = state.certifications;
|
||||||
const { netVersion } = state.nodeStatus;
|
const { netVersion } = state.nodeStatus;
|
||||||
|
|
||||||
|
const account = (accounts || {})[address];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
account,
|
||||||
balances,
|
|
||||||
certifications,
|
certifications,
|
||||||
netVersion
|
netVersion
|
||||||
};
|
};
|
||||||
|
@ -14,21 +14,23 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { pick } from 'lodash';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import { Container, SectionList } from '~/ui';
|
import { Container, SectionList } from '~/ui';
|
||||||
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
||||||
|
import { ETH_TOKEN } from '~/util/tokens';
|
||||||
|
|
||||||
import Summary from '../Summary';
|
import Summary from '../Summary';
|
||||||
import styles from './list.css';
|
import styles from './list.css';
|
||||||
|
|
||||||
class List extends Component {
|
class List extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object,
|
balances: PropTypes.object.isRequired,
|
||||||
balances: PropTypes.object,
|
|
||||||
certifications: PropTypes.object.isRequired,
|
certifications: PropTypes.object.isRequired,
|
||||||
|
accounts: PropTypes.object,
|
||||||
disabled: PropTypes.object,
|
disabled: PropTypes.object,
|
||||||
empty: PropTypes.bool,
|
empty: PropTypes.bool,
|
||||||
link: PropTypes.string,
|
link: PropTypes.string,
|
||||||
@ -51,7 +53,7 @@ class List extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, balances, disabled, empty } = this.props;
|
const { accounts, disabled, empty } = this.props;
|
||||||
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return (
|
return (
|
||||||
@ -67,13 +69,11 @@ class List extends Component {
|
|||||||
.getAddresses()
|
.getAddresses()
|
||||||
.map((address) => {
|
.map((address) => {
|
||||||
const account = accounts[address] || {};
|
const account = accounts[address] || {};
|
||||||
const balance = balances[address] || {};
|
|
||||||
const isDisabled = disabled ? disabled[address] : false;
|
const isDisabled = disabled ? disabled[address] : false;
|
||||||
const owners = account.owners || null;
|
const owners = account.owners || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
balance,
|
|
||||||
isDisabled,
|
isDisabled,
|
||||||
owners
|
owners
|
||||||
};
|
};
|
||||||
@ -88,13 +88,12 @@ class List extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSummary = (item) => {
|
renderSummary = (item) => {
|
||||||
const { account, balance, isDisabled, owners } = item;
|
const { account, isDisabled, owners } = item;
|
||||||
const { handleAddSearchToken, link } = this.props;
|
const { handleAddSearchToken, link } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Summary
|
<Summary
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
disabled={ isDisabled }
|
disabled={ isDisabled }
|
||||||
handleAddSearchToken={ handleAddSearchToken }
|
handleAddSearchToken={ handleAddSearchToken }
|
||||||
link={ link }
|
link={ link }
|
||||||
@ -160,8 +159,8 @@ class List extends Component {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ethA = balanceA.tokens.find(token => token.token.tag.toLowerCase() === 'eth');
|
const ethA = balanceA[ETH_TOKEN.id];
|
||||||
const ethB = balanceB.tokens.find(token => token.token.tag.toLowerCase() === 'eth');
|
const ethB = balanceB[ETH_TOKEN.id];
|
||||||
|
|
||||||
if (!ethA && !ethB) {
|
if (!ethA && !ethB) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -171,7 +170,7 @@ class List extends Component {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1 * ethA.value.comparedTo(ethB.value);
|
return -1 * ethA.comparedTo(ethB);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'tags') {
|
if (key === 'tags') {
|
||||||
@ -257,10 +256,12 @@ class List extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state, props) {
|
||||||
|
const addresses = Object.keys(props.accounts);
|
||||||
|
const balances = pick(state.balances, addresses);
|
||||||
const { certifications } = state;
|
const { certifications } = state;
|
||||||
|
|
||||||
return { certifications };
|
return { balances, certifications };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
|
@ -36,7 +36,6 @@ class Summary extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
accountsInfo: PropTypes.object.isRequired,
|
accountsInfo: PropTypes.object.isRequired,
|
||||||
balance: PropTypes.object,
|
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
link: PropTypes.string,
|
link: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
@ -74,20 +73,6 @@ class Summary extends Component {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevTokens = this.props.balance.tokens || [];
|
|
||||||
const nextTokens = nextProps.balance.tokens || [];
|
|
||||||
|
|
||||||
if (prevTokens.length !== nextTokens.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevValues = prevTokens.map((t) => ({ value: t.value.toNumber(), image: t.token.image }));
|
|
||||||
const nextValues = nextTokens.map((t) => ({ value: t.value.toNumber(), image: t.token.image }));
|
|
||||||
|
|
||||||
if (!isEqual(prevValues, nextValues)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevOwners = this.props.owners;
|
const prevOwners = this.props.owners;
|
||||||
const nextOwners = nextProps.owners;
|
const nextOwners = nextProps.owners;
|
||||||
|
|
||||||
@ -249,15 +234,11 @@ class Summary extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderBalance (onlyEth) {
|
renderBalance (onlyEth) {
|
||||||
const { balance } = this.props;
|
const { account } = this.props;
|
||||||
|
|
||||||
if (!balance) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Balance
|
<Balance
|
||||||
balance={ balance }
|
address={ account.address }
|
||||||
className={
|
className={
|
||||||
onlyEth
|
onlyEth
|
||||||
? styles.ethBalances
|
? styles.ethBalances
|
||||||
|
@ -41,7 +41,6 @@ class Accounts extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
accountsInfo: PropTypes.object.isRequired,
|
accountsInfo: PropTypes.object.isRequired,
|
||||||
balances: PropTypes.object,
|
|
||||||
hasAccounts: PropTypes.bool.isRequired,
|
hasAccounts: PropTypes.bool.isRequired,
|
||||||
setVisibleAccounts: PropTypes.func.isRequired
|
setVisibleAccounts: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
@ -133,7 +132,7 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
const { accounts, balances } = this.props;
|
const { accounts } = this.props;
|
||||||
const _accounts = pickBy(accounts, (account) => account.uuid);
|
const _accounts = pickBy(accounts, (account) => account.uuid);
|
||||||
const _hasAccounts = Object.keys(_accounts).length > 0;
|
const _hasAccounts = Object.keys(_accounts).length > 0;
|
||||||
|
|
||||||
@ -147,7 +146,6 @@ class Accounts extends Component {
|
|||||||
<List
|
<List
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ _accounts }
|
accounts={ _accounts }
|
||||||
balances={ balances }
|
|
||||||
empty={ !_hasAccounts }
|
empty={ !_hasAccounts }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken }
|
handleAddSearchToken={ this.onAddSearchToken }
|
||||||
@ -156,7 +154,7 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderWallets () {
|
renderWallets () {
|
||||||
const { accounts, balances } = this.props;
|
const { accounts } = this.props;
|
||||||
const wallets = pickBy(accounts, (account) => account.wallet);
|
const wallets = pickBy(accounts, (account) => account.wallet);
|
||||||
const hasWallets = Object.keys(wallets).length > 0;
|
const hasWallets = Object.keys(wallets).length > 0;
|
||||||
|
|
||||||
@ -175,7 +173,6 @@ class Accounts extends Component {
|
|||||||
link='wallet'
|
link='wallet'
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ wallets }
|
accounts={ wallets }
|
||||||
balances={ balances }
|
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken }
|
handleAddSearchToken={ this.onAddSearchToken }
|
||||||
/>
|
/>
|
||||||
@ -183,7 +180,7 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderExternalAccounts () {
|
renderExternalAccounts () {
|
||||||
const { accounts, balances } = this.props;
|
const { accounts } = this.props;
|
||||||
const { wallets } = this.hwstore;
|
const { wallets } = this.hwstore;
|
||||||
const hardware = pickBy(accounts, (account) => account.hardware);
|
const hardware = pickBy(accounts, (account) => account.hardware);
|
||||||
const external = pickBy(accounts, (account) => account.external);
|
const external = pickBy(accounts, (account) => account.external);
|
||||||
@ -210,7 +207,6 @@ class Accounts extends Component {
|
|||||||
<List
|
<List
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ all }
|
accounts={ all }
|
||||||
balances={ balances }
|
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken }
|
handleAddSearchToken={ this.onAddSearchToken }
|
||||||
@ -408,12 +404,10 @@ class Accounts extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, accountsInfo, hasAccounts } = state.personal;
|
const { accounts, accountsInfo, hasAccounts } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
balances,
|
|
||||||
hasAccounts
|
hasAccounts
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,6 @@ class Address extends Component {
|
|||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
|
||||||
params: PropTypes.object
|
params: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ class Address extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { contacts, balances } = this.props;
|
const { contacts } = this.props;
|
||||||
const { address } = this.props.params;
|
const { address } = this.props.params;
|
||||||
|
|
||||||
if (Object.keys(contacts).length === 0) {
|
if (Object.keys(contacts).length === 0) {
|
||||||
@ -81,7 +80,6 @@ class Address extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const contact = (contacts || {})[address];
|
const contact = (contacts || {})[address];
|
||||||
const balance = (balances || {})[address];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -92,7 +90,6 @@ class Address extends Component {
|
|||||||
<Page padded>
|
<Page padded>
|
||||||
<Header
|
<Header
|
||||||
account={ contact || { address, meta: {} } }
|
account={ contact || { address, meta: {} } }
|
||||||
balance={ balance }
|
|
||||||
hideName={ !contact }
|
hideName={ !contact }
|
||||||
/>
|
/>
|
||||||
<Transactions
|
<Transactions
|
||||||
@ -240,11 +237,9 @@ class Address extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { contacts } = state.personal;
|
const { contacts } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contacts,
|
contacts
|
||||||
balances
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import { uniq, isEqual } from 'lodash';
|
|||||||
import List from '../Accounts/List';
|
import List from '../Accounts/List';
|
||||||
import Summary from '../Accounts/Summary';
|
import Summary from '../Accounts/Summary';
|
||||||
import { AddAddress } from '~/modals';
|
import { AddAddress } from '~/modals';
|
||||||
import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page, Loading } from '~/ui';
|
import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui';
|
||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
|
|
||||||
import styles from './addresses.css';
|
import styles from './addresses.css';
|
||||||
@ -37,7 +37,6 @@ class Addresses extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
balances: PropTypes.object,
|
|
||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object,
|
||||||
hasContacts: PropTypes.bool
|
hasContacts: PropTypes.bool
|
||||||
}
|
}
|
||||||
@ -86,21 +85,14 @@ class Addresses extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccountsList () {
|
renderAccountsList () {
|
||||||
const { balances, contacts, hasContacts } = this.props;
|
const { contacts, hasContacts } = this.props;
|
||||||
const { searchValues, sortOrder } = this.state;
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
if (hasContacts && Object.keys(balances).length === 0) {
|
|
||||||
return (
|
|
||||||
<Loading />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
link='addresses'
|
link='addresses'
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ contacts }
|
accounts={ contacts }
|
||||||
balances={ balances }
|
|
||||||
empty={ !hasContacts }
|
empty={ !hasContacts }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken }
|
handleAddSearchToken={ this.onAddSearchToken }
|
||||||
@ -282,11 +274,9 @@ class Addresses extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { balances } = state.balances;
|
|
||||||
const { contacts, hasContacts } = state.personal;
|
const { contacts, hasContacts } = state.personal;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
balances,
|
|
||||||
contacts,
|
contacts,
|
||||||
hasContacts
|
hasContacts
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,6 @@ class Contract extends Component {
|
|||||||
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
accountsInfo: PropTypes.object,
|
accountsInfo: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
|
||||||
contracts: PropTypes.object,
|
contracts: PropTypes.object,
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
params: PropTypes.object
|
params: PropTypes.object
|
||||||
@ -115,10 +114,9 @@ class Contract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accountsInfo, balances, contracts, netVersion, params } = this.props;
|
const { accountsInfo, contracts, netVersion, params } = this.props;
|
||||||
const { allEvents, contract, queryValues, loadingEvents } = this.state;
|
const { allEvents, contract, queryValues, loadingEvents } = this.state;
|
||||||
const account = contracts[params.address];
|
const account = contracts[params.address];
|
||||||
const balance = balances[params.address];
|
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
@ -133,7 +131,6 @@ class Contract extends Component {
|
|||||||
<Page padded>
|
<Page padded>
|
||||||
<Header
|
<Header
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
isContract
|
isContract
|
||||||
>
|
>
|
||||||
{ this.renderBlockNumber(account.meta) }
|
{ this.renderBlockNumber(account.meta) }
|
||||||
@ -520,13 +517,11 @@ class Contract extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, accountsInfo, contracts } = state.personal;
|
const { accounts, accountsInfo, contracts } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
const { netVersion } = state.nodeStatus;
|
const { netVersion } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
balances,
|
|
||||||
contracts,
|
contracts,
|
||||||
netVersion
|
netVersion
|
||||||
};
|
};
|
||||||
|
@ -57,7 +57,6 @@ class Contracts extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
balances: PropTypes.object,
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
contracts: PropTypes.object,
|
contracts: PropTypes.object,
|
||||||
hasContracts: PropTypes.bool
|
hasContracts: PropTypes.bool
|
||||||
@ -96,7 +95,7 @@ class Contracts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { contracts, hasContracts, balances } = this.props;
|
const { contracts, hasContracts } = this.props;
|
||||||
const { searchValues, sortOrder } = this.state;
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -109,7 +108,6 @@ class Contracts extends Component {
|
|||||||
link='contracts'
|
link='contracts'
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ contracts }
|
accounts={ contracts }
|
||||||
balances={ balances }
|
|
||||||
empty={ !hasContracts }
|
empty={ !hasContracts }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
orderFallback='name'
|
orderFallback='name'
|
||||||
@ -267,13 +265,11 @@ class Contracts extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, contracts, hasContracts } = state.personal;
|
const { accounts, contracts, hasContracts } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
contracts,
|
contracts,
|
||||||
hasContracts,
|
hasContracts
|
||||||
balances
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@ class ParityBar extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
balances: PropTypes.object,
|
|
||||||
dapp: PropTypes.bool,
|
dapp: PropTypes.bool,
|
||||||
externalLink: PropTypes.string,
|
externalLink: PropTypes.string,
|
||||||
pending: PropTypes.array
|
pending: PropTypes.array
|
||||||
@ -370,13 +369,9 @@ class ParityBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccount = (account) => {
|
renderAccount = (account) => {
|
||||||
const { balances } = this.props;
|
|
||||||
const balance = balances[account.address];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -700,11 +695,9 @@ class ParityBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { balances } = state.balances;
|
|
||||||
const { pending } = state.signer;
|
const { pending } = state.signer;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
balances,
|
|
||||||
pending
|
pending
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,6 @@ class Wallet extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
balance: nullableProptype(PropTypes.object.isRequired),
|
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
owned: PropTypes.bool.isRequired,
|
owned: PropTypes.bool.isRequired,
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
@ -105,7 +104,7 @@ class Wallet extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { walletAccount, balance, wallet } = this.props;
|
const { walletAccount, wallet } = this.props;
|
||||||
|
|
||||||
if (!walletAccount) {
|
if (!walletAccount) {
|
||||||
return null;
|
return null;
|
||||||
@ -125,7 +124,6 @@ class Wallet extends Component {
|
|||||||
<Header
|
<Header
|
||||||
className={ styles.header }
|
className={ styles.header }
|
||||||
account={ walletAccount }
|
account={ walletAccount }
|
||||||
balance={ balance }
|
|
||||||
isContract
|
isContract
|
||||||
>
|
>
|
||||||
{ this.renderInfos() }
|
{ this.renderInfos() }
|
||||||
@ -212,15 +210,13 @@ class Wallet extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderActionbar () {
|
renderActionbar () {
|
||||||
const { balance, owned } = this.props;
|
const { owned } = this.props;
|
||||||
const showTransferButton = !!(balance && balance.tokens);
|
|
||||||
|
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (owned) {
|
if (owned) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
disabled={ !showTransferButton }
|
|
||||||
icon={ <SendIcon /> }
|
icon={ <SendIcon /> }
|
||||||
key='transferFunds'
|
key='transferFunds'
|
||||||
label={
|
label={
|
||||||
@ -343,12 +339,11 @@ class Wallet extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { walletAccount, balance } = this.props;
|
const { walletAccount } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transfer
|
<Transfer
|
||||||
account={ walletAccount }
|
account={ walletAccount }
|
||||||
balance={ balance }
|
|
||||||
onClose={ this.onTransferClose }
|
onClose={ this.onTransferClose }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -391,7 +386,6 @@ function mapStateToProps (_, initProps) {
|
|||||||
return (state) => {
|
return (state) => {
|
||||||
const { netVersion } = state.nodeStatus;
|
const { netVersion } = state.nodeStatus;
|
||||||
const { accountsInfo = {}, accounts = {} } = state.personal;
|
const { accountsInfo = {}, accounts = {} } = state.personal;
|
||||||
const { balances } = state.balances;
|
|
||||||
const walletAccount = accounts[address] || accountsInfo[address] || null;
|
const walletAccount = accounts[address] || accountsInfo[address] || null;
|
||||||
|
|
||||||
if (walletAccount) {
|
if (walletAccount) {
|
||||||
@ -399,12 +393,10 @@ function mapStateToProps (_, initProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const wallet = state.wallet.wallets[address] || {};
|
const wallet = state.wallet.wallets[address] || {};
|
||||||
const balance = balances[address] || null;
|
|
||||||
const owned = !!accounts[address];
|
const owned = !!accounts[address];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address,
|
address,
|
||||||
balance,
|
|
||||||
netVersion,
|
netVersion,
|
||||||
owned,
|
owned,
|
||||||
wallet,
|
wallet,
|
||||||
|
@ -446,9 +446,9 @@ class WriteContract extends Component {
|
|||||||
return (
|
return (
|
||||||
<DeployContract
|
<DeployContract
|
||||||
abi={ contract.interface }
|
abi={ contract.interface }
|
||||||
|
accounts={ this.props.accounts }
|
||||||
code={ `0x${contract.bytecode}` }
|
code={ `0x${contract.bytecode}` }
|
||||||
source={ sourcecode }
|
source={ sourcecode }
|
||||||
accounts={ this.props.accounts }
|
|
||||||
onClose={ this.store.handleCloseDeployModal }
|
onClose={ this.store.handleCloseDeployModal }
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user