Make Wallet first-class citizens (#3990)

* Fixed hint in Address Select + Wallet as first-class-citizen

* Separate Owned and not Owned Wallets

* Fix balance not updating

* Fix MethodDecoding for Contract Deployment

* Fix TypedInput params

* Fix Token Transfer for Wallet

* Small change to contracts

* Fix wallets shown twice

* Fix separation of accounts and wallets in Accounts

* Fix linting

* Execute contract methods from Wallet ✓

* Fixing linting

* Wallet as first-class citizen: Part 1 (Manual) #3784

* Lower level wallet transaction convertion

* Fix linting

* Proper autoFocus on right Signer input

* PR Grumble: don't show Wallets in dApps Permissions

* Add postTransaction and gasEstimate wrapper methods

* Extract Wallet postTx and gasEstimate to utils + PATCH api

* Remove invalid test

It's totally valid for input's length not to be a multiple of 32 bytes. EG. for Wallet Contracts

* Merge master

* Fix linting

* Fix merge issue

* Rename Portal

* Rename Protal => Portal (typo)
This commit is contained in:
Nicolas Gotchac
2016-12-30 12:28:12 +01:00
committed by Gav Wood
parent 88c0329a31
commit fd41a10319
46 changed files with 570 additions and 230 deletions

View File

@@ -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;
}

View File

@@ -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 (
<div

View File

@@ -157,7 +157,11 @@ export default class Summary extends Component {
const { link, noLink, account, name } = this.props;
const { address } = account;
const viewLink = `/${link || 'accounts'}/${address}`;
const baseLink = account.wallet
? 'wallet'
: link || 'accounts';
const viewLink = `/${baseLink}/${address}`;
const content = (
<IdentityName address={ address } name={ name } unknown />

View File

@@ -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 (
<List
search={ searchValues }
accounts={ accounts }
accounts={ _accounts }
balances={ balances }
empty={ !hasAccounts }
empty={ !_hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
);
}
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
};
}

View File

@@ -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
};
}

View File

@@ -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 {
<SignRequest
address={ sign.address }
className={ className }
focus={ focus }
hash={ sign.hash }
id={ id }
isFinished={ false }
@@ -75,6 +78,7 @@ export default class RequestPending extends Component {
<TransactionPending
className={ className }
date={ date }
focus={ focus }
gasLimit={ gasLimit }
id={ id }
isSending={ isSending }

View File

@@ -30,13 +30,19 @@ export default class SignRequest extends Component {
address: PropTypes.string.isRequired,
hash: PropTypes.string.isRequired,
isFinished: PropTypes.bool.isRequired,
isTest: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired,
className: PropTypes.string,
focus: PropTypes.bool,
isSending: PropTypes.bool,
onConfirm: PropTypes.func,
onReject: PropTypes.func,
status: PropTypes.string,
className: PropTypes.string,
isTest: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired
status: PropTypes.string
};
static defaultProps = {
focus: false
};
componentWillMount () {
@@ -81,7 +87,7 @@ export default class SignRequest extends Component {
}
renderActions () {
const { address, isFinished, status } = this.props;
const { address, focus, isFinished, status } = this.props;
if (isFinished) {
if (status === 'confirmed') {
@@ -111,6 +117,7 @@ export default class SignRequest extends Component {
return (
<TransactionPendingForm
address={ address }
focus={ focus }
isSending={ this.props.isSending }
onConfirm={ this.onConfirm }
onReject={ this.onReject }

View File

@@ -35,6 +35,7 @@ export default class TransactionPending extends Component {
static propTypes = {
className: PropTypes.string,
date: PropTypes.instanceOf(Date).isRequired,
focus: PropTypes.bool,
gasLimit: PropTypes.object,
id: PropTypes.object.isRequired,
isSending: PropTypes.bool.isRequired,
@@ -53,6 +54,10 @@ export default class TransactionPending extends Component {
}).isRequired
};
static defaultProps = {
focus: false
};
gasStore = new GasPriceEditor.Store(this.context.api, {
gas: this.props.transaction.gas.toFixed(),
gasLimit: this.props.gasLimit,
@@ -80,7 +85,7 @@ export default class TransactionPending extends Component {
}
renderTransaction () {
const { className, id, isSending, isTest, store, transaction } = this.props;
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
const { totalValue } = this.state;
const { from, value } = transaction;
@@ -100,6 +105,7 @@ export default class TransactionPending extends Component {
value={ value } />
<TransactionPendingForm
address={ from }
focus={ focus }
isSending={ isSending }
onConfirm={ this.onConfirm }
onReject={ this.onReject } />

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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 }
/>
<div className={ styles.passwordHint }>
{ passwordHint }
</div>
@@ -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 };
};
}

View File

@@ -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 (
<TransactionPendingFormConfirm
address={ address }
focus={ focus }
isSending={ isSending }
onConfirm={ onConfirm } />
onConfirm={ onConfirm }
/>
);
}

View File

@@ -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 {
<RequestPending
className={ styles.request }
date={ date }
focus={ index === 0 }
gasLimit={ gasLimit }
id={ id }
isSending={ isSending }

View File

@@ -104,7 +104,7 @@ class RequestsPage extends Component {
);
}
renderPending = (data) => {
renderPending = (data, index) => {
const { actions, gasLimit, isTest } = this.props;
const { date, id, isSending, payload } = data;
@@ -112,6 +112,7 @@ class RequestsPage extends Component {
<RequestPending
className={ styles.request }
date={ date }
focus={ index === 0 }
gasLimit={ gasLimit }
id={ id }
isSending={ isSending }

View File

@@ -55,14 +55,20 @@ export default class WalletDetails extends Component {
return null;
}
const ownersList = owners.map((address, idx) => (
<InputAddress
key={ `${idx}_${address}` }
value={ address }
disabled
text
/>
));
const ownersList = owners.map((owner, idx) => {
const address = typeof owner === 'object'
? owner.address
: owner;
return (
<InputAddress
key={ `${idx}_${address}` }
value={ address }
disabled
text
/>
);
});
return (
<div>

View File

@@ -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 (
<TxRow
key={ transactionHash }
key={ `${transactionHash}_${index}` }
tx={ {
hash: transactionHash,
input: data && bytesToHex(data) || '',

View File

@@ -64,13 +64,14 @@ class Wallet extends Component {
};
static propTypes = {
setVisibleAccounts: PropTypes.func.isRequired,
address: PropTypes.string.isRequired,
balance: nullableProptype(PropTypes.object.isRequired),
images: PropTypes.object.isRequired,
address: PropTypes.string.isRequired,
wallets: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired,
owned: PropTypes.bool.isRequired,
setVisibleAccounts: PropTypes.func.isRequired,
wallet: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired
walletAccount: nullableProptype(PropTypes.object).isRequired
};
state = {
@@ -104,28 +105,26 @@ class Wallet extends Component {
}
render () {
const { wallets, balance, address } = this.props;
const { walletAccount, balance, wallet } = this.props;
const wallet = (wallets || {})[address];
if (!wallet) {
if (!walletAccount) {
return null;
}
const { owners, require, dailylimit } = this.props.wallet;
const { owners, require, dailylimit } = wallet;
return (
<div className={ styles.wallet }>
{ this.renderEditDialog(wallet) }
{ this.renderEditDialog(walletAccount) }
{ this.renderSettingsDialog() }
{ this.renderTransferDialog() }
{ this.renderDeleteDialog(wallet) }
{ this.renderDeleteDialog(walletAccount) }
{ this.renderActionbar() }
<Page>
<div className={ styles.info }>
<Header
className={ styles.header }
account={ wallet }
account={ walletAccount }
balance={ balance }
isContract
>
@@ -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 = [
<Button
key='transferFunds'
icon={ <ContentSend /> }
label='transfer'
disabled={ !showTransferButton }
onClick={ this.onTransferClick } />,
const buttons = [];
if (owned) {
buttons.push(
<Button
key='transferFunds'
icon={ <ContentSend /> }
label='transfer'
disabled={ !showTransferButton }
onClick={ this.onTransferClick } />
);
}
buttons.push(
<Button
key='delete'
icon={ <ActionDelete /> }
label='delete'
onClick={ this.showDeleteDialog } />,
onClick={ this.showDeleteDialog } />
);
buttons.push(
<Button
key='editmeta'
icon={ <ContentCreate /> }
label='edit'
onClick={ this.onEditClick } />,
<Button
key='settings'
icon={ <SettingsIcon /> }
label='settings'
onClick={ this.onSettingsClick } />
];
onClick={ this.onEditClick } />
);
if (owned) {
buttons.push(
<Button
key='settings'
icon={ <SettingsIcon /> }
label='settings'
onClick={ this.onSettingsClick } />
);
}
return (
<Actionbar
@@ -293,12 +307,11 @@ class Wallet extends Component {
return null;
}
const { wallets, balance, images, address } = this.props;
const wallet = wallets[address];
const { walletAccount, balance, images } = this.props;
return (
<Transfer
account={ wallet }
account={ walletAccount }
balance={ balance }
images={ images }
onClose={ this.onTransferClose }
@@ -342,20 +355,27 @@ function mapStateToProps (_, initProps) {
return (state) => {
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
};
};
}