-
+
);
}
diff --git a/js/src/ui/TxHash/txHash.js b/js/src/ui/TxHash/txHash.js
index fcbe374e1..09905d594 100644
--- a/js/src/ui/TxHash/txHash.js
+++ b/js/src/ui/TxHash/txHash.js
@@ -134,7 +134,7 @@ class TxHash extends Component {
const { api } = this.context;
const { hash } = this.props;
- if (error) {
+ if (error || !hash || /^(0x)?0*$/.test(hash)) {
return;
}
diff --git a/js/src/util/tx.js b/js/src/util/tx.js
index 591931ff3..90c78a42d 100644
--- a/js/src/util/tx.js
+++ b/js/src/util/tx.js
@@ -14,10 +14,93 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see
.
+import WalletsUtils from '~/util/wallets';
+
const isValidReceipt = (receipt) => {
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
};
+function getTxArgs (func, options, values = []) {
+ const { contract } = func;
+ const { api } = contract;
+ const address = options.from;
+
+ if (!address) {
+ return Promise.resolve({ func, options, values });
+ }
+
+ return WalletsUtils
+ .isWallet(api, address)
+ .then((isWallet) => {
+ if (!isWallet) {
+ return { func, options, values };
+ }
+
+ options.data = contract.getCallData(func, options, values);
+ options.to = options.to || contract.address;
+
+ if (!options.to) {
+ return { func, options, values };
+ }
+
+ return WalletsUtils
+ .getCallArgs(api, options, values)
+ .then((callArgs) => {
+ if (!callArgs) {
+ return { func, options, values };
+ }
+
+ return callArgs;
+ });
+ });
+}
+
+export function estimateGas (_func, _options, _values = []) {
+ return getTxArgs(_func, _options, _values)
+ .then((callArgs) => {
+ const { func, options, values } = callArgs;
+ return func._estimateGas(options, values);
+ })
+ .then((gas) => {
+ return WalletsUtils
+ .isWallet(_func.contract.api, _options.from)
+ .then((isWallet) => {
+ if (isWallet) {
+ return gas.mul(1.5);
+ }
+
+ return gas;
+ });
+ });
+}
+
+export function postTransaction (_func, _options, _values = []) {
+ return getTxArgs(_func, _options, _values)
+ .then((callArgs) => {
+ const { func, options, values } = callArgs;
+ return func._postTransaction(options, values);
+ });
+}
+
+export function patchApi (api) {
+ api.patch = {
+ ...api.patch,
+ contract: patchContract
+ };
+}
+
+export function patchContract (contract) {
+ contract._functions.forEach((func) => {
+ if (!func.constant) {
+ func._postTransaction = func.postTransaction;
+ func._estimateGas = func.estimateGas;
+
+ func.postTransaction = postTransaction.bind(contract, func);
+ func.estimateGas = estimateGas.bind(contract, func);
+ }
+ });
+}
+
export function checkIfTxFailed (api, tx, gasSent) {
return api.pollMethod('eth_getTransactionReceipt', tx)
.then((receipt) => {
diff --git a/js/src/util/wallets.js b/js/src/util/wallets.js
index 3732840d8..739a1e3c2 100644
--- a/js/src/util/wallets.js
+++ b/js/src/util/wallets.js
@@ -14,13 +14,92 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see
.
-import { range, uniq } from 'lodash';
+import BigNumber from 'bignumber.js';
+import { intersection, range, uniq } from 'lodash';
+import Contract from '~/api/contract';
import { bytesToHex, toHex } from '~/api/util/format';
import { validateAddress } from '~/util/validation';
+import WalletAbi from '~/contracts/abi/wallet.json';
+
+const _cachedWalletLookup = {};
export default class WalletsUtils {
+ static getCallArgs (api, options, values = []) {
+ const walletContract = new Contract(api, WalletAbi);
+
+ const promises = [
+ api.parity.accountsInfo(),
+ WalletsUtils.fetchOwners(walletContract.at(options.from))
+ ];
+
+ return Promise
+ .all(promises)
+ .then(([ accounts, owners ]) => {
+ const addresses = Object.keys(accounts);
+ const owner = intersection(addresses, owners).pop();
+
+ if (!owner) {
+ return false;
+ }
+
+ return owner;
+ })
+ .then((owner) => {
+ if (!owner) {
+ return false;
+ }
+
+ const _options = Object.assign({}, options);
+ const { from, to, value = new BigNumber(0), data } = options;
+
+ delete _options.data;
+
+ const nextValues = [ to, value, data ];
+ const nextOptions = {
+ ..._options,
+ from: owner,
+ to: from,
+ value: new BigNumber(0)
+ };
+
+ const execFunc = walletContract.instance.execute;
+
+ return { func: execFunc, options: nextOptions, values: nextValues };
+ });
+ }
+
+ /**
+ * Check whether the given address could be
+ * a Wallet. The result is cached in order not
+ * to make unnecessary calls on non-wallet accounts
+ */
+ static isWallet (api, address) {
+ if (!_cachedWalletLookup[address]) {
+ const walletContract = new Contract(api, WalletAbi);
+
+ _cachedWalletLookup[address] = walletContract
+ .at(address)
+ .instance
+ .m_numOwners
+ .call()
+ .then((result) => {
+ if (!result || result.equals(0)) {
+ return false;
+ }
+
+ return true;
+ })
+ .then((bool) => {
+ _cachedWalletLookup[address] = Promise.resolve(bool);
+ return bool;
+ });
+ }
+
+ return _cachedWalletLookup[address];
+ }
+
static fetchRequire (walletContract) {
return walletContract.instance.m_required.call();
}
diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js
index 058c20db3..6e508d05e 100644
--- a/js/src/views/Account/Header/header.js
+++ b/js/src/views/Account/Header/header.js
@@ -42,7 +42,6 @@ export default class Header extends Component {
render () {
const { account, balance, className, children, hideName } = this.props;
const { address, meta, uuid } = account;
-
if (!account) {
return null;
}
diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js
index 2bd7fadc1..9cebdda6e 100644
--- a/js/src/views/Accounts/List/list.js
+++ b/js/src/views/Accounts/List/list.js
@@ -34,7 +34,6 @@ class List extends Component {
order: PropTypes.string,
orderFallback: PropTypes.string,
search: PropTypes.array,
- walletsOwners: PropTypes.object,
fetchCertifiers: PropTypes.func.isRequired,
fetchCertifications: PropTypes.func.isRequired,
@@ -58,7 +57,7 @@ class List extends Component {
}
renderAccounts () {
- const { accounts, balances, empty, link, walletsOwners, handleAddSearchToken } = this.props;
+ const { accounts, balances, empty, link, handleAddSearchToken } = this.props;
if (empty) {
return (
@@ -76,7 +75,7 @@ class List extends Component {
const account = accounts[address] || {};
const balance = balances[address] || {};
- const owners = walletsOwners && walletsOwners[address] || null;
+ const owners = account.owners || null;
return (
diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js
index 70b6f770a..06322e436 100644
--- a/js/src/views/Accounts/accounts.js
+++ b/js/src/views/Accounts/accounts.js
@@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ContentAdd from 'material-ui/svg-icons/content/add';
-import { uniq, isEqual } from 'lodash';
+import { uniq, isEqual, pickBy, omitBy } from 'lodash';
import List from './List';
import { CreateAccount, CreateWallet } from '~/modals';
@@ -36,9 +36,6 @@ class Accounts extends Component {
setVisibleAccounts: PropTypes.func.isRequired,
accounts: PropTypes.object.isRequired,
hasAccounts: PropTypes.bool.isRequired,
- wallets: PropTypes.object.isRequired,
- walletsOwners: PropTypes.object.isRequired,
- hasWallets: PropTypes.bool.isRequired,
balances: PropTypes.object
}
@@ -62,8 +59,8 @@ class Accounts extends Component {
}
componentWillReceiveProps (nextProps) {
- const prevAddresses = Object.keys({ ...this.props.accounts, ...this.props.wallets });
- const nextAddresses = Object.keys({ ...nextProps.accounts, ...nextProps.wallets });
+ const prevAddresses = Object.keys(this.props.accounts);
+ const nextAddresses = Object.keys(nextProps.accounts);
if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) {
this.setVisibleAccounts(nextProps);
@@ -75,8 +72,8 @@ class Accounts extends Component {
}
setVisibleAccounts (props = this.props) {
- const { accounts, wallets, setVisibleAccounts } = props;
- const addresses = Object.keys({ ...accounts, ...wallets });
+ const { accounts, setVisibleAccounts } = props;
+ const addresses = Object.keys(accounts);
setVisibleAccounts(addresses);
}
@@ -115,30 +112,38 @@ class Accounts extends Component {
}
renderAccounts () {
+ const { accounts, balances } = this.props;
+
+ const _accounts = omitBy(accounts, (a) => a.wallet);
+ const _hasAccounts = Object.keys(_accounts).length > 0;
+
if (!this.state.show) {
- return this.renderLoading(this.props.accounts);
+ return this.renderLoading(_accounts);
}
- const { accounts, hasAccounts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return (
);
}
renderWallets () {
+ const { accounts, balances } = this.props;
+
+ const wallets = pickBy(accounts, (a) => a.wallet);
+ const hasWallets = Object.keys(wallets).length > 0;
+
if (!this.state.show) {
- return this.renderLoading(this.props.wallets);
+ return this.renderLoading(wallets);
}
- const { wallets, hasWallets, balances, walletsOwners } = this.props;
const { searchValues, sortOrder } = this.state;
if (!wallets || Object.keys(wallets).length === 0) {
@@ -154,7 +159,6 @@ class Accounts extends Component {
empty={ !hasWallets }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken }
- walletsOwners={ walletsOwners }
/>
);
}
@@ -287,34 +291,12 @@ class Accounts extends Component {
}
function mapStateToProps (state) {
- const { accounts, hasAccounts, wallets, hasWallets, accountsInfo } = state.personal;
+ const { accounts, hasAccounts } = state.personal;
const { balances } = state.balances;
- const walletsInfo = state.wallet.wallets;
-
- const walletsOwners = Object
- .keys(walletsInfo)
- .map((wallet) => {
- const owners = walletsInfo[wallet].owners || [];
-
- return {
- owners: owners.map((owner) => ({
- address: owner,
- name: accountsInfo[owner] && accountsInfo[owner].name || owner
- })),
- address: wallet
- };
- })
- .reduce((walletsOwners, wallet) => {
- walletsOwners[wallet.address] = wallet.owners;
- return walletsOwners;
- }, {});
return {
- accounts,
- hasAccounts,
- wallets,
- walletsOwners,
- hasWallets,
+ accounts: accounts,
+ hasAccounts: hasAccounts,
balances
};
}
diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js
index 6462e1af9..e800263e4 100644
--- a/js/src/views/Dapps/dapps.js
+++ b/js/src/views/Dapps/dapps.js
@@ -20,6 +20,7 @@ import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import { omitBy } from 'lodash';
import { AddDapps, DappPermissions } from '~/modals';
import PermissionStore from '~/modals/DappPermissions/store';
@@ -150,8 +151,15 @@ class Dapps extends Component {
function mapStateToProps (state) {
const { accounts } = state.personal;
+ /**
+ * Do not show the Wallet Accounts in the Dapps
+ * Permissions Modal. This will come in v1.6, but
+ * for now it would break dApps using Web3...
+ */
+ const _accounts = omitBy(accounts, (account) => account.wallet);
+
return {
- accounts
+ accounts: _accounts
};
}
diff --git a/js/src/views/Signer/components/RequestPending/requestPending.js b/js/src/views/Signer/components/RequestPending/requestPending.js
index 84783a614..3e8586063 100644
--- a/js/src/views/Signer/components/RequestPending/requestPending.js
+++ b/js/src/views/Signer/components/RequestPending/requestPending.js
@@ -23,6 +23,7 @@ export default class RequestPending extends Component {
static propTypes = {
className: PropTypes.string,
date: PropTypes.instanceOf(Date).isRequired,
+ focus: PropTypes.bool,
gasLimit: PropTypes.object.isRequired,
id: PropTypes.object.isRequired,
isSending: PropTypes.bool.isRequired,
@@ -38,6 +39,7 @@ export default class RequestPending extends Component {
};
static defaultProps = {
+ focus: false,
isSending: false
};
@@ -49,7 +51,7 @@ export default class RequestPending extends Component {
};
render () {
- const { className, date, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
+ const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
if (payload.sign) {
const { sign } = payload;
@@ -58,6 +60,7 @@ export default class RequestPending extends Component {
diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
index 360125d9f..02b7ef266 100644
--- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
+++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
@@ -15,6 +15,7 @@
// along with Parity. If not, see
.
import React, { Component, PropTypes } from 'react';
+import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import RaisedButton from 'material-ui/RaisedButton';
@@ -26,11 +27,16 @@ import styles from './transactionPendingFormConfirm.css';
class TransactionPendingFormConfirm extends Component {
static propTypes = {
- accounts: PropTypes.object.isRequired,
+ account: PropTypes.object.isRequired,
address: PropTypes.string.isRequired,
isSending: PropTypes.bool.isRequired,
- onConfirm: PropTypes.func.isRequired
- }
+ onConfirm: PropTypes.func.isRequired,
+ focus: PropTypes.bool
+ };
+
+ static defaultProps = {
+ focus: false
+ };
id = Math.random(); // for tooltip
@@ -40,10 +46,39 @@ class TransactionPendingFormConfirm extends Component {
walletError: null
}
+ componentDidMount () {
+ this.focus();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (!this.props.focus && nextProps.focus) {
+ this.focus(nextProps);
+ }
+ }
+
+ /**
+ * Properly focus on the input element when needed.
+ * This might be fixed some day in MaterialUI with
+ * an autoFocus prop.
+ *
+ * @see https://github.com/callemall/material-ui/issues/5632
+ */
+ focus (props = this.props) {
+ if (props.focus) {
+ const textNode = ReactDOM.findDOMNode(this.refs.input);
+
+ if (!textNode) {
+ return;
+ }
+
+ const inputNode = textNode.querySelector('input');
+ inputNode && inputNode.focus();
+ }
+ }
+
render () {
- const { accounts, address, isSending } = this.props;
+ const { account, address, isSending } = this.props;
const { password, wallet, walletError } = this.state;
- const account = accounts[address] || {};
const isExternal = !account.uuid;
const passwordHint = account.meta && account.meta.passwordHint
@@ -72,8 +107,10 @@ class TransactionPendingFormConfirm extends Component {
}
onChange={ this.onModifyPassword }
onKeyDown={ this.onKeyDown }
+ ref='input'
type='password'
- value={ password } />
+ value={ password }
+ />
{ passwordHint }
@@ -178,11 +215,14 @@ class TransactionPendingFormConfirm extends Component {
}
}
-function mapStateToProps (state) {
- const { accounts } = state.personal;
+function mapStateToProps (initState, initProps) {
+ const { accounts } = initState.personal;
+ const { address } = initProps;
- return {
- accounts
+ const account = accounts[address] || {};
+
+ return () => {
+ return { account };
};
}
diff --git a/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js
index f6e92761f..f0b167f27 100644
--- a/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js
+++ b/js/src/views/Signer/components/TransactionPendingForm/transactionPendingForm.js
@@ -28,7 +28,12 @@ export default class TransactionPendingForm extends Component {
isSending: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
- className: PropTypes.string
+ className: PropTypes.string,
+ focus: PropTypes.bool
+ };
+
+ static defaultProps = {
+ focus: false
};
state = {
@@ -47,7 +52,7 @@ export default class TransactionPendingForm extends Component {
}
renderForm () {
- const { address, isSending, onConfirm, onReject } = this.props;
+ const { address, focus, isSending, onConfirm, onReject } = this.props;
if (this.state.isRejectOpen) {
return (
@@ -59,8 +64,10 @@ export default class TransactionPendingForm extends Component {
return (
+ onConfirm={ onConfirm }
+ />
);
}
diff --git a/js/src/views/Signer/containers/Embedded/embedded.js b/js/src/views/Signer/containers/Embedded/embedded.js
index 3fa450473..b79657430 100644
--- a/js/src/views/Signer/containers/Embedded/embedded.js
+++ b/js/src/views/Signer/containers/Embedded/embedded.js
@@ -78,7 +78,7 @@ class Embedded extends Component {
);
}
- renderPending = (data) => {
+ renderPending = (data, index) => {
const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data;
@@ -86,6 +86,7 @@ class Embedded extends Component {
{
+ renderPending = (data, index) => {
const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data;
@@ -112,6 +112,7 @@ class RequestsPage extends Component {
(
-
- ));
+ const ownersList = owners.map((owner, idx) => {
+ const address = typeof owner === 'object'
+ ? owner.address
+ : owner;
+
+ return (
+
+ );
+ });
return (
diff --git a/js/src/views/Wallet/Transactions/transactions.js b/js/src/views/Wallet/Transactions/transactions.js
index aff1623a4..0f2558dfc 100644
--- a/js/src/views/Wallet/Transactions/transactions.js
+++ b/js/src/views/Wallet/Transactions/transactions.js
@@ -57,12 +57,12 @@ export default class WalletTransactions extends Component {
);
}
- const txRows = transactions.map((transaction) => {
+ const txRows = transactions.slice(0, 15).map((transaction, index) => {
const { transactionHash, blockNumber, from, to, value, data } = transaction;
return (
- { this.renderEditDialog(wallet) }
+ { this.renderEditDialog(walletAccount) }
{ this.renderSettingsDialog() }
{ this.renderTransferDialog() }
- { this.renderDeleteDialog(wallet) }
+ { this.renderDeleteDialog(walletAccount) }
{ this.renderActionbar() }
@@ -209,32 +208,47 @@ class Wallet extends Component {
}
renderActionbar () {
- const { balance } = this.props;
+ const { balance, owned } = this.props;
const showTransferButton = !!(balance && balance.tokens);
- const buttons = [
- }
- label='transfer'
- disabled={ !showTransferButton }
- onClick={ this.onTransferClick } />,
+ const buttons = [];
+
+ if (owned) {
+ buttons.push(
+ }
+ label='transfer'
+ disabled={ !showTransferButton }
+ onClick={ this.onTransferClick } />
+ );
+ }
+
+ buttons.push(
}
label='delete'
- onClick={ this.showDeleteDialog } />,
+ onClick={ this.showDeleteDialog } />
+ );
+
+ buttons.push(
}
label='edit'
- onClick={ this.onEditClick } />,
- }
- label='settings'
- onClick={ this.onSettingsClick } />
- ];
+ onClick={ this.onEditClick } />
+ );
+
+ if (owned) {
+ buttons.push(
+ }
+ label='settings'
+ onClick={ this.onSettingsClick } />
+ );
+ }
return (
{
const { isTest } = state.nodeStatus;
- const { wallets } = state.personal;
+ const { accountsInfo = {}, accounts = {} } = state.personal;
const { balances } = state.balances;
const { images } = state;
+ const walletAccount = accounts[address] || accountsInfo[address] || null;
+
+ if (walletAccount) {
+ walletAccount.address = address;
+ }
const wallet = state.wallet.wallets[address] || {};
const balance = balances[address] || null;
+ const owned = !!accounts[address];
return {
- isTest,
- wallets,
+ address,
balance,
images,
- address,
- wallet
+ isTest,
+ owned,
+ wallet,
+ walletAccount
};
};
}
diff --git a/js/webpack/libraries.js b/js/webpack/libraries.js
index 3b8d8b92c..72bbd060f 100644
--- a/js/webpack/libraries.js
+++ b/js/webpack/libraries.js
@@ -38,6 +38,13 @@ module.exports = {
library: '[name].js',
libraryTarget: 'umd'
},
+
+ resolve: {
+ alias: {
+ '~': path.resolve(__dirname, '../src')
+ }
+ },
+
module: {
rules: [
{
diff --git a/js/webpack/npm.js b/js/webpack/npm.js
index a5f4b383f..ae860d00e 100644
--- a/js/webpack/npm.js
+++ b/js/webpack/npm.js
@@ -69,6 +69,9 @@ module.exports = {
},
resolve: {
+ alias: {
+ '~': path.resolve(__dirname, '../src')
+ },
modules: [
path.resolve('./src'),
path.join(__dirname, '../node_modules')
diff --git a/js/webpack/vendor.js b/js/webpack/vendor.js
index daacab379..1ff9d66a8 100644
--- a/js/webpack/vendor.js
+++ b/js/webpack/vendor.js
@@ -64,6 +64,13 @@ module.exports = {
}
]
},
+
+ resolve: {
+ alias: {
+ '~': path.resolve(__dirname, '../src')
+ }
+ },
+
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../', `${DEST}/`),