Dapps use defaultAccount instead of own selectors (#4386)

* Remove account selection from GitHubHint

* Fix naming

* Update to match BasicCoin

* BasicCoin defaultAddress

* typo

* method registry without selector

* Update after manual tests

* IdentityIcon for localtx

* Fix non-secure personal subscriptions

* Query defaultAccount for non-secure apps on send
This commit is contained in:
Jaco Greeff 2017-02-03 13:54:53 +01:00 committed by Gav Wood
parent 3af45c6ad9
commit acf41d6f27
13 changed files with 115 additions and 172 deletions

View File

@ -54,14 +54,20 @@ export default class Personal {
} }
_accountsInfo = () => { _accountsInfo = () => {
return Promise return this._api.parity
.all([ .accountsInfo()
this._api.parity.accountsInfo(), .then((info) => {
this._api.parity.allAccountsInfo()
])
.then(([info, allInfo]) => {
this._updateSubscriptions('parity_accountsInfo', null, info); this._updateSubscriptions('parity_accountsInfo', null, info);
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
return this._api.parity
.allAccountsInfo()
.catch(() => {
// NOTE: This fails on non-secure APIs, swallow error
return {};
})
.then((allInfo) => {
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
});
}); });
} }

View File

@ -45,7 +45,7 @@ export default class Application extends Component {
} }
componentDidMount () { componentDidMount () {
this.attachInstance(); return this.attachInstance();
} }
render () { render () {
@ -80,12 +80,12 @@ export default class Application extends Component {
} }
attachInstance () { attachInstance () {
Promise return Promise
.all([ .all([
attachInstances(), api.parity.accountsInfo(),
api.parity.accountsInfo() attachInstances()
]) ])
.then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { .then(([accountsInfo, { managerInstance, registryInstance, tokenregInstance }]) => {
accountsInfo = accountsInfo || {}; accountsInfo = accountsInfo || {};
this.setState({ this.setState({
loading: false, loading: false,

View File

@ -17,7 +17,6 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { api } from '../../parity'; import { api } from '../../parity';
import AddressSelect from '../../AddressSelect';
import Container from '../../Container'; import Container from '../../Container';
import styles from './deployment.css'; import styles from './deployment.css';
@ -122,36 +121,13 @@ export default class Deployment extends Component {
} }
renderForm () { renderForm () {
const { accounts } = this.context;
const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
const hasError = !!(nameError || tlaError || totalSupplyError); const hasError = !!(nameError || tlaError || totalSupplyError);
const error = `${styles.input} ${styles.error}`; const error = `${styles.input} ${styles.error}`;
const addresses = Object.keys(accounts);
// <div className={ styles.input }>
// <label>global registration</label>
// <select onChange={ this.onChangeRegistrar }>
// <option value='no'>No, only for me</option>
// <option value='yes'>Yes, for everybody</option>
// </select>
// <div className={ styles.hint }>
// register on network (fee: { globalFeeText }ETH)
// </div>
// </div>
return ( return (
<Container> <Container>
<div className={ styles.form }> <div className={ styles.form }>
<div className={ styles.input }>
<label>deployment account</label>
<AddressSelect
addresses={ addresses }
onChange={ this.onChangeFrom }
/>
<div className={ styles.hint }>
the owner account to deploy from
</div>
</div>
<div className={ nameError ? error : styles.input }> <div className={ nameError ? error : styles.input }>
<label>token name</label> <label>token name</label>
<input <input
@ -206,12 +182,6 @@ export default class Deployment extends Component {
); );
} }
onChangeFrom = (event) => {
const fromAddress = event.target.value;
this.setState({ fromAddress });
}
onChangeName = (event) => { onChangeName = (event) => {
const name = event.target.value; const name = event.target.value;
const nameError = name && (name.length > 2) && (name.length < 32) const nameError = name && (name.length > 2) && (name.length < 32)
@ -271,7 +241,7 @@ export default class Deployment extends Component {
onDeploy = () => { onDeploy = () => {
const { managerInstance, registryInstance, tokenregInstance } = this.context; const { managerInstance, registryInstance, tokenregInstance } = this.context;
const { base, deployBusy, fromAddress, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state; const { base, deployBusy, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
const hasError = !!(nameError || tlaError || totalSupplyError); const hasError = !!(nameError || tlaError || totalSupplyError);
if (hasError || deployBusy) { if (hasError || deployBusy) {
@ -281,14 +251,18 @@ export default class Deployment extends Component {
const tokenreg = (globalReg ? tokenregInstance : registryInstance).address; const tokenreg = (globalReg ? tokenregInstance : registryInstance).address;
const values = [base.mul(totalSupply), tla, name, tokenreg]; const values = [base.mul(totalSupply), tla, name, tokenreg];
const options = { const options = {
from: fromAddress,
value: globalReg ? globalFee : 0 value: globalReg ? globalFee : 0
}; };
this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' }); this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' });
managerInstance return api.parity
.deploy.estimateGas(options, values) .defaultAccount()
.then((defaultAddress) => {
options.from = defaultAddress;
return managerInstance.deploy.estimateGas(options, values);
})
.then((gas) => { .then((gas) => {
this.setState({ deployState: 'Gas estimated, Posting transaction to the network' }); this.setState({ deployState: 'Gas estimated, Posting transaction to the network' });

View File

@ -25,6 +25,8 @@ let registryInstance;
const registries = {}; const registries = {};
const subscriptions = {}; const subscriptions = {};
let defaultSubscriptionId;
let nextSubscriptionId = 1000; let nextSubscriptionId = 1000;
let isTest = false; let isTest = false;
@ -65,6 +67,20 @@ export function unsubscribeEvents (subscriptionId) {
delete subscriptions[subscriptionId]; delete subscriptions[subscriptionId];
} }
export function subscribeDefaultAddress (callback) {
return api
.subscribe('parity_defaultAccount', callback)
.then((subscriptionId) => {
defaultSubscriptionId = subscriptionId;
return defaultSubscriptionId;
});
}
export function unsubscribeDefaultAddress () {
return api.unsubscribe(defaultSubscriptionId);
}
function pollEvents () { function pollEvents () {
const loop = Object.values(subscriptions); const loop = Object.values(subscriptions);
const timeout = () => setTimeout(pollEvents, 1000); const timeout = () => setTimeout(pollEvents, 1000);

View File

@ -17,10 +17,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { api } from '../parity'; import { api } from '../parity';
import { attachInterface } from '../services'; import { attachInterface, subscribeDefaultAddress, unsubscribeDefaultAddress } from '../services';
import Button from '../Button'; import Button from '../Button';
import Events from '../Events'; import Events from '../Events';
import IdentityIcon from '../IdentityIcon';
import Loading from '../Loading'; import Loading from '../Loading';
import styles from './application.css'; import styles from './application.css';
@ -32,7 +31,7 @@ let nextEventId = 0;
export default class Application extends Component { export default class Application extends Component {
state = { state = {
fromAddress: null, defaultAddress: null,
loading: true, loading: true,
url: '', url: '',
urlError: null, urlError: null,
@ -47,19 +46,32 @@ export default class Application extends Component {
registerType: 'file', registerType: 'file',
repo: '', repo: '',
repoError: null, repoError: null,
subscriptionId: null,
events: {}, events: {},
eventIds: [] eventIds: []
} }
componentDidMount () { componentDidMount () {
attachInterface() return Promise
.then((state) => { .all([
this.setState(state, () => { attachInterface(),
this.setState({ loading: false }); subscribeDefaultAddress((error, defaultAddress) => {
}); if (!error) {
this.setState({ defaultAddress });
}
})
])
.then(([state]) => {
this.setState(Object.assign({}, state, {
loading: false
}));
}); });
} }
componentWillUnmount () {
return unsubscribeDefaultAddress();
}
render () { render () {
const { loading } = this.state; const { loading } = this.state;
@ -75,12 +87,14 @@ export default class Application extends Component {
} }
renderPage () { renderPage () {
const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state; const { defaultAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state;
let hashClass = null; let hashClass = null;
if (contentHashError) { if (contentHashError) {
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning; hashClass = contentHashOwner !== defaultAddress
? styles.hashError
: styles.hashWarning;
} else if (contentHash) { } else if (contentHash) {
hashClass = styles.hashOk; hashClass = styles.hashOk;
} }
@ -166,20 +180,13 @@ export default class Application extends Component {
} }
renderButtons () { renderButtons () {
const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state; const { defaultAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state;
const account = accounts[fromAddress];
return ( return (
<div className={ styles.buttons }> <div className={ styles.buttons }>
<div className={ styles.addressSelect }>
<Button invert onClick={ this.onSelectFromAddress }>
<IdentityIcon address={ account.address } />
<div>{ account.name || account.address }</div>
</Button>
</div>
<Button <Button
onClick={ this.onClickRegister } onClick={ this.onClickRegister }
disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError } disabled={ (contentHashError && contentHashOwner !== defaultAddress) || urlError || repoError || commitError }
>register url</Button> >register url</Button>
</div> </div>
); );
@ -294,11 +301,11 @@ export default class Application extends Component {
} }
onClickRegister = () => { onClickRegister = () => {
const { commit, commitError, contentHashError, contentHashOwner, fromAddress, url, urlError, registerType, repo, repoError } = this.state; const { defaultAddress, commit, commitError, contentHashError, contentHashOwner, url, urlError, registerType, repo, repoError } = this.state;
// TODO: No errors are currently set, validation to be expanded and added for each // TODO: No errors are currently set, validation to be expanded and added for each
// field (query is fast to pick up the issues, so not burning atm) // field (query is fast to pick up the issues, so not burning atm)
if ((contentHashError && contentHashOwner !== fromAddress) || repoError || urlError || commitError) { if ((contentHashError && contentHashOwner !== defaultAddress) || repoError || urlError || commitError) {
return; return;
} }
@ -368,13 +375,15 @@ export default class Application extends Component {
} }
registerContent (contentRepo, contentCommit) { registerContent (contentRepo, contentCommit) {
const { contentHash, fromAddress, instance } = this.state; const { defaultAddress, contentHash, instance } = this.state;
contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`; contentCommit = contentCommit.substr(0, 2) === '0x'
? contentCommit
: `0x${contentCommit}`;
const eventId = nextEventId++; const eventId = nextEventId++;
const values = [contentHash, contentRepo, contentCommit]; const values = [contentHash, contentRepo, contentCommit];
const options = { from: fromAddress }; const options = { from: defaultAddress };
this.setState({ this.setState({
eventIds: [eventId].concat(this.state.eventIds), eventIds: [eventId].concat(this.state.eventIds),
@ -383,7 +392,7 @@ export default class Application extends Component {
contentHash, contentHash,
contentRepo, contentRepo,
contentCommit, contentCommit,
fromAddress, defaultAddress,
registerBusy: true, registerBusy: true,
registerState: 'Estimating gas for the transaction', registerState: 'Estimating gas for the transaction',
timestamp: new Date() timestamp: new Date()
@ -421,11 +430,11 @@ export default class Application extends Component {
} }
registerUrl (contentUrl) { registerUrl (contentUrl) {
const { contentHash, fromAddress, instance } = this.state; const { contentHash, defaultAddress, instance } = this.state;
const eventId = nextEventId++; const eventId = nextEventId++;
const values = [contentHash, contentUrl]; const values = [contentHash, contentUrl];
const options = { from: fromAddress }; const options = { from: defaultAddress };
this.setState({ this.setState({
eventIds: [eventId].concat(this.state.eventIds), eventIds: [eventId].concat(this.state.eventIds),
@ -433,7 +442,7 @@ export default class Application extends Component {
[eventId]: { [eventId]: {
contentHash, contentHash,
contentUrl, contentUrl,
fromAddress, defaultAddress,
registerBusy: true, registerBusy: true,
registerState: 'Estimating gas for the transaction', registerState: 'Estimating gas for the transaction',
timestamp: new Date() timestamp: new Date()
@ -470,25 +479,6 @@ export default class Application extends Component {
); );
} }
onSelectFromAddress = () => {
const { accounts, fromAddress } = this.state;
const addresses = Object.keys(accounts);
let index = 0;
addresses.forEach((address, _index) => {
if (address === fromAddress) {
index = _index;
}
});
index++;
if (index >= addresses.length) {
index = 0;
}
this.setState({ fromAddress: addresses[index] });
}
lookupHash (url) { lookupHash (url) {
const { instance } = this.state; const { instance } = this.state;

View File

@ -17,48 +17,44 @@
import * as abis from '~/contracts/abi'; import * as abis from '~/contracts/abi';
import { api } from './parity'; import { api } from './parity';
let defaultSubscriptionId;
export function attachInterface () { export function attachInterface () {
return api.parity return api.parity
.registryAddress() .registryAddress()
.then((registryAddress) => { .then((registryAddress) => {
console.log(`the registry was found at ${registryAddress}`); console.log(`the registry was found at ${registryAddress}`);
const registry = api.newContract(abis.registry, registryAddress).instance; return api
.newContract(abis.registry, registryAddress).instance
return Promise .getAddress.call({}, [api.util.sha3('githubhint'), 'A']);
.all([
registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']),
api.parity.accountsInfo()
]);
}) })
.then(([address, accountsInfo]) => { .then((address) => {
console.log(`githubhint was found at ${address}`); console.log(`githubhint was found at ${address}`);
const contract = api.newContract(abis.githubhint, address); const contract = api.newContract(abis.githubhint, address);
const accounts = Object
.keys(accountsInfo)
.reduce((obj, address) => {
const account = accountsInfo[address];
return Object.assign(obj, {
[address]: {
address,
name: account.name
}
});
}, {});
const fromAddress = Object.keys(accounts)[0];
return { return {
accounts,
address, address,
accountsInfo,
contract, contract,
instance: contract.instance, instance: contract.instance
fromAddress
}; };
}) })
.catch((error) => { .catch((error) => {
console.error('attachInterface', error); console.error('attachInterface', error);
}); });
} }
export function subscribeDefaultAddress (callback) {
return api
.subscribe('parity_defaultAccount', callback)
.then((subscriptionId) => {
defaultSubscriptionId = subscriptionId;
return defaultSubscriptionId;
});
}
export function unsubscribeDefaultAddress () {
return api.unsubscribe(defaultSubscriptionId);
}

View File

@ -22,7 +22,7 @@ import { api } from '../parity';
import styles from './transaction.css'; import styles from './transaction.css';
import IdentityIcon from '../../githubhint/IdentityIcon'; import IdentityIcon from '../IdentityIcon';
class BaseTransaction extends Component { class BaseTransaction extends Component {
shortHash (hash) { shortHash (hash) {

View File

@ -30,7 +30,6 @@ export default class Application extends Component {
state = { state = {
accounts: {}, accounts: {},
address: null, address: null,
fromAddress: null,
accountsInfo: {}, accountsInfo: {},
blockNumber: new BigNumber(0), blockNumber: new BigNumber(0),
contract: null, contract: null,
@ -41,11 +40,9 @@ export default class Application extends Component {
} }
componentDidMount () { componentDidMount () {
attachInterface() return attachInterface()
.then((state) => { .then((state) => {
this.setState(state, () => { this.setState(Object.assign({}, state, { loading: false }));
this.setState({ loading: false });
});
return attachBlockNumber(state.instance, (state) => { return attachBlockNumber(state.instance, (state) => {
this.setState(state); this.setState(state);
@ -86,17 +83,14 @@ export default class Application extends Component {
} }
renderImport () { renderImport () {
const { accounts, fromAddress, instance, showImport } = this.state; const { instance, showImport } = this.state;
if (showImport) { if (showImport) {
return ( return (
<Import <Import
accounts={ accounts }
fromAddress={ fromAddress }
instance={ instance } instance={ instance }
visible={ showImport } visible={ showImport }
onClose={ this.toggleImport } onClose={ this.toggleImport }
onSetFromAddress={ this.setFromAddress }
/> />
); );
} }
@ -124,10 +118,4 @@ export default class Application extends Component {
showImport: !this.state.showImport showImport: !this.state.showImport
}); });
} }
setFromAddress = (fromAddress) => {
this.setState({
fromAddress
});
}
} }

View File

@ -19,18 +19,14 @@ import React, { Component, PropTypes } from 'react';
import { api } from '../parity'; import { api } from '../parity';
import { callRegister, postRegister } from '../services'; import { callRegister, postRegister } from '../services';
import Button from '../Button'; import Button from '../Button';
import IdentityIcon from '../IdentityIcon';
import styles from './import.css'; import styles from './import.css';
export default class Import extends Component { export default class Import extends Component {
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired,
fromAddress: PropTypes.string.isRequired,
instance: PropTypes.object.isRequired, instance: PropTypes.object.isRequired,
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired
onSetFromAddress: PropTypes.func.isRequired
} }
state = { state = {
@ -83,21 +79,12 @@ export default class Import extends Component {
} }
renderRegister () { renderRegister () {
const { accounts, fromAddress } = this.props;
const account = accounts[fromAddress];
const count = this.countFunctions(); const count = this.countFunctions();
let buttons = null; let buttons = null;
if (count) { if (count) {
buttons = ( buttons = (
<div className={ styles.buttonrow }> <div className={ styles.buttonrow }>
<div className={ styles.addressSelect }>
<Button invert onClick={ this.onSelectFromAddress }>
<IdentityIcon address={ account.address } />
<div>{ account.name || account.address }</div>
</Button>
</div>
<Button onClick={ this.onRegister }> <Button onClick={ this.onRegister }>
register functions register functions
</Button> </Button>
@ -197,15 +184,15 @@ export default class Import extends Component {
} }
onRegister = () => { onRegister = () => {
const { instance, fromAddress, onClose } = this.props; const { instance, onClose } = this.props;
const { functions, fnstate } = this.state; const { functions, fnstate } = this.state;
Promise return Promise
.all( .all(
functions functions
.filter((fn) => !fn.constant) .filter((fn) => !fn.constant)
.filter((fn) => fnstate[fn.signature] === 'fntodo') .filter((fn) => fnstate[fn.signature] === 'fntodo')
.map((fn) => postRegister(instance, fn.id, { from: fromAddress })) .map((fn) => postRegister(instance, fn.id, {}))
) )
.then(() => { .then(() => {
onClose(); onClose();
@ -214,23 +201,4 @@ export default class Import extends Component {
console.error('onRegister', error); console.error('onRegister', error);
}); });
} }
onSelectFromAddress = () => {
const { accounts, fromAddress, onSetFromAddress } = this.props;
const addresses = Object.keys(accounts);
let index = 0;
addresses.forEach((address, _index) => {
if (address === fromAddress) {
index = _index;
}
});
index++;
if (index >= addresses.length) {
index = 0;
}
onSetFromAddress(addresses[index]);
}
} }

View File

@ -166,8 +166,13 @@ export function callRegister (instance, id, options = {}) {
} }
export function postRegister (instance, id, options = {}) { export function postRegister (instance, id, options = {}) {
return instance.register return api.parity
.estimateGas(options, [id]) .defaultAccount()
.then((defaultAddress) => {
options.from = defaultAddress;
return instance.register.estimateGas(options, [id]);
})
.then((gas) => { .then((gas) => {
options.gas = gas.mul(1.2).toFixed(0); options.gas = gas.mul(1.2).toFixed(0);
console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`); console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`);