Default contract type on UI (#3310)
* Added Token and Wallet ABI in Watch Contract #3126 * Improved ABI Validator #3281 * Select contract type on first screen #3126 * Added types decsription * Add ABI type to Contract metadata // Custom as default type #3310
This commit is contained in:
parent
2f98169539
commit
67ac05ef39
@ -136,27 +136,30 @@ export default class Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseEventLogs (logs) {
|
parseEventLogs (logs) {
|
||||||
return logs.map((log) => {
|
return logs
|
||||||
const signature = log.topics[0].substr(2);
|
.map((log) => {
|
||||||
const event = this.events.find((evt) => evt.signature === signature);
|
const signature = log.topics[0].substr(2);
|
||||||
|
const event = this.events.find((evt) => evt.signature === signature);
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
throw new Error(`Unable to find event matching signature ${signature}`);
|
console.warn(`Unable to find event matching signature ${signature}`);
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const decoded = event.decodeLog(log.topics, log.data);
|
const decoded = event.decodeLog(log.topics, log.data);
|
||||||
|
|
||||||
log.params = {};
|
log.params = {};
|
||||||
log.event = event.name;
|
log.event = event.name;
|
||||||
|
|
||||||
decoded.params.forEach((param) => {
|
decoded.params.forEach((param) => {
|
||||||
const { type, value } = param.token;
|
const { type, value } = param.token;
|
||||||
|
|
||||||
log.params[param.name] = { type, value };
|
log.params[param.name] = { type, value };
|
||||||
});
|
});
|
||||||
|
|
||||||
return log;
|
return log;
|
||||||
});
|
})
|
||||||
|
.filter((log) => log);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseTransactionEvents (receipt) {
|
parseTransactionEvents (receipt) {
|
||||||
|
@ -24,6 +24,7 @@ import owned from './owned.json';
|
|||||||
import registry from './registry.json';
|
import registry from './registry.json';
|
||||||
import signaturereg from './signaturereg.json';
|
import signaturereg from './signaturereg.json';
|
||||||
import tokenreg from './tokenreg.json';
|
import tokenreg from './tokenreg.json';
|
||||||
|
import wallet from './wallet.json';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
basiccoin,
|
basiccoin,
|
||||||
@ -35,5 +36,6 @@ export {
|
|||||||
owned,
|
owned,
|
||||||
registry,
|
registry,
|
||||||
signaturereg,
|
signaturereg,
|
||||||
tokenreg
|
tokenreg,
|
||||||
|
wallet
|
||||||
};
|
};
|
||||||
|
1
js/src/contracts/abi/wallet.json
Normal file
1
js/src/contracts/abi/wallet.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]
|
32
js/src/modals/AddContract/addContract.css
Normal file
32
js/src/modals/AddContract/addContract.css
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.spaced {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
color: #ccc;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,38 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
|
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
|
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||||
|
|
||||||
|
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
||||||
|
|
||||||
import { Button, Modal, Form, Input, InputAddress } from '../../ui';
|
import { Button, Modal, Form, Input, InputAddress } from '../../ui';
|
||||||
import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation';
|
import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation';
|
||||||
|
|
||||||
|
import { eip20, wallet } from '../../contracts/abi';
|
||||||
|
import styles from './addContract.css';
|
||||||
|
|
||||||
|
const ABI_TYPES = [
|
||||||
|
{
|
||||||
|
label: 'Token', readOnly: true, value: JSON.stringify(eip20),
|
||||||
|
type: 'token',
|
||||||
|
description: (<span>A standard <a href='https://github.com/ethereum/EIPs/issues/20' target='_blank'>ERC 20</a> token</span>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Multisig Wallet', readOnly: true,
|
||||||
|
type: 'multisig',
|
||||||
|
value: JSON.stringify(wallet),
|
||||||
|
description: (<span>Official Multisig contract: <a href='https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol' target='_blank'>see contract code</a></span>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Custom Contract', value: '',
|
||||||
|
type: 'custom',
|
||||||
|
description: 'Contract created from custom ABI'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const STEPS = [ 'choose a contract type', 'enter contract details' ];
|
||||||
|
|
||||||
export default class AddContract extends Component {
|
export default class AddContract extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
@ -34,44 +62,101 @@ export default class AddContract extends Component {
|
|||||||
state = {
|
state = {
|
||||||
abi: '',
|
abi: '',
|
||||||
abiError: ERRORS.invalidAbi,
|
abiError: ERRORS.invalidAbi,
|
||||||
|
abiType: ABI_TYPES[2],
|
||||||
|
abiTypeIndex: 2,
|
||||||
abiParsed: null,
|
abiParsed: null,
|
||||||
address: '',
|
address: '',
|
||||||
addressError: ERRORS.invalidAddress,
|
addressError: ERRORS.invalidAddress,
|
||||||
name: '',
|
name: '',
|
||||||
nameError: ERRORS.invalidName,
|
nameError: ERRORS.invalidName,
|
||||||
description: ''
|
description: '',
|
||||||
|
step: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.onChangeABIType(null, this.state.abiTypeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const { step } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible
|
visible
|
||||||
actions={ this.renderDialogActions() }
|
actions={ this.renderDialogActions() }
|
||||||
title='watch contract'>
|
steps={ STEPS }
|
||||||
{ this.renderFields() }
|
current={ step }
|
||||||
|
>
|
||||||
|
{ this.renderStep(step) }
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderStep (step) {
|
||||||
|
switch (step) {
|
||||||
|
case 0:
|
||||||
|
return this.renderContractTypeSelector();
|
||||||
|
default:
|
||||||
|
return this.renderFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContractTypeSelector () {
|
||||||
|
const { abiTypeIndex } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadioButtonGroup
|
||||||
|
valueSelected={ abiTypeIndex }
|
||||||
|
name='contractType'
|
||||||
|
onChange={ this.onChangeABIType }
|
||||||
|
>
|
||||||
|
{ this.renderAbiTypes() }
|
||||||
|
</RadioButtonGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
const { addressError, nameError } = this.state;
|
const { addressError, nameError, step } = this.state;
|
||||||
const hasError = !!(addressError || nameError);
|
const hasError = !!(addressError || nameError);
|
||||||
|
|
||||||
return ([
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <ContentClear /> }
|
icon={ <ContentClear /> }
|
||||||
label='Cancel'
|
label='Cancel'
|
||||||
onClick={ this.onClose } />,
|
onClick={ this.onClose } />
|
||||||
|
);
|
||||||
|
|
||||||
|
if (step === 0) {
|
||||||
|
const nextBtn = (
|
||||||
|
<Button
|
||||||
|
icon={ <NavigationArrowForward /> }
|
||||||
|
label='Next'
|
||||||
|
onClick={ this.onNext } />
|
||||||
|
);
|
||||||
|
|
||||||
|
return [ cancelBtn, nextBtn ];
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevBtn = (
|
||||||
|
<Button
|
||||||
|
icon={ <NavigationArrowBack /> }
|
||||||
|
label='Back'
|
||||||
|
onClick={ this.onPrev } />
|
||||||
|
);
|
||||||
|
|
||||||
|
const addBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <ContentAdd /> }
|
icon={ <ContentAdd /> }
|
||||||
label='Add Contract'
|
label='Add Contract'
|
||||||
disabled={ hasError }
|
disabled={ hasError }
|
||||||
onClick={ this.onAdd } />
|
onClick={ this.onAdd } />
|
||||||
]);
|
);
|
||||||
|
|
||||||
|
return [ cancelBtn, prevBtn, addBtn ];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFields () {
|
renderFields () {
|
||||||
const { abi, abiError, address, addressError, description, name, nameError } = this.state;
|
const { abi, abiError, address, addressError, description, name, nameError, abiType } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
@ -80,7 +165,9 @@ export default class AddContract extends Component {
|
|||||||
hint='the network address for the contract'
|
hint='the network address for the contract'
|
||||||
error={ addressError }
|
error={ addressError }
|
||||||
value={ address }
|
value={ address }
|
||||||
onSubmit={ this.onEditAddress } />
|
onSubmit={ this.onEditAddress }
|
||||||
|
onChange={ this.onChangeAddress }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
label='contract name'
|
label='contract name'
|
||||||
hint='a descriptive name for the contract'
|
hint='a descriptive name for the contract'
|
||||||
@ -94,20 +181,57 @@ export default class AddContract extends Component {
|
|||||||
hint='an expanded description for the entry'
|
hint='an expanded description for the entry'
|
||||||
value={ description }
|
value={ description }
|
||||||
onSubmit={ this.onEditDescription } />
|
onSubmit={ this.onEditDescription } />
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label='contract abi'
|
label='contract abi'
|
||||||
hint='the abi for the contract'
|
hint='the abi for the contract'
|
||||||
error={ abiError }
|
error={ abiError }
|
||||||
value={ abi }
|
value={ abi }
|
||||||
onSubmit={ this.onEditAbi } />
|
readOnly={ abiType.readOnly }
|
||||||
|
onSubmit={ this.onEditAbi }
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditAbi = (abi) => {
|
renderAbiTypes () {
|
||||||
const { api } = this.context;
|
return ABI_TYPES.map((type, index) => (
|
||||||
|
<RadioButton
|
||||||
|
className={ styles.spaced }
|
||||||
|
value={ index }
|
||||||
|
label={ (
|
||||||
|
<div className={ styles.typeContainer }>
|
||||||
|
<span>{ type.label }</span>
|
||||||
|
<span className={ styles.desc }>{ type.description }</span>
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
key={ index }
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
this.setState(validateAbi(abi, api));
|
onNext = () => {
|
||||||
|
this.setState({ step: this.state.step + 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
onPrev = () => {
|
||||||
|
this.setState({ step: this.state.step - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeABIType = (event, index) => {
|
||||||
|
const abiType = ABI_TYPES[index];
|
||||||
|
this.setState({ abiTypeIndex: index, abiType });
|
||||||
|
this.onEditAbi(abiType.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditAbi = (abiIn) => {
|
||||||
|
const { api } = this.context;
|
||||||
|
const { abi, abiError, abiParsed } = validateAbi(abiIn, api);
|
||||||
|
this.setState({ abi, abiError, abiParsed });
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeAddress = (event, value) => {
|
||||||
|
this.onEditAddress(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditAddress = (_address) => {
|
onEditAddress = (_address) => {
|
||||||
@ -138,7 +262,7 @@ export default class AddContract extends Component {
|
|||||||
|
|
||||||
onAdd = () => {
|
onAdd = () => {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { abiParsed, address, name, description } = this.state;
|
const { abiParsed, address, name, description, abiType } = this.state;
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
api.parity.setAccountName(address, name),
|
api.parity.setAccountName(address, name),
|
||||||
@ -147,6 +271,7 @@ export default class AddContract extends Component {
|
|||||||
deleted: false,
|
deleted: false,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
abi: abiParsed,
|
abi: abiParsed,
|
||||||
|
type: abiType.type,
|
||||||
description
|
description
|
||||||
})
|
})
|
||||||
]).catch((error) => {
|
]).catch((error) => {
|
||||||
|
@ -38,10 +38,22 @@ export function validateAbi (abi, api) {
|
|||||||
abiParsed = JSON.parse(abi);
|
abiParsed = JSON.parse(abi);
|
||||||
|
|
||||||
if (!api.util.isArray(abiParsed) || !abiParsed.length) {
|
if (!api.util.isArray(abiParsed) || !abiParsed.length) {
|
||||||
abiError = ERRORS.inavlidAbi;
|
abiError = ERRORS.invalidAbi;
|
||||||
} else {
|
return { abi, abiError, abiParsed };
|
||||||
abi = JSON.stringify(abiParsed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate each elements of the Array
|
||||||
|
const invalidIndex = abiParsed
|
||||||
|
.map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api))
|
||||||
|
.findIndex((valid) => !valid);
|
||||||
|
|
||||||
|
if (invalidIndex !== -1) {
|
||||||
|
const invalid = abiParsed[invalidIndex];
|
||||||
|
abiError = `${ERRORS.invalidAbi} (#${invalidIndex}: ${invalid.name || invalid.type})`;
|
||||||
|
return { abi, abiError, abiParsed };
|
||||||
|
}
|
||||||
|
|
||||||
|
abi = JSON.stringify(abiParsed);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
abiError = ERRORS.invalidAbi;
|
abiError = ERRORS.invalidAbi;
|
||||||
}
|
}
|
||||||
@ -53,6 +65,25 @@ export function validateAbi (abi, api) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidAbiFunction (object, api) {
|
||||||
|
if (!object) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((object.type === 'function' && object.name) || object.type === 'constructor') &&
|
||||||
|
(object.inputs && api.util.isArray(object.inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidAbiEvent (object, api) {
|
||||||
|
if (!object) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (object.type === 'event') &&
|
||||||
|
(object.name) &&
|
||||||
|
(object.inputs && api.util.isArray(object.inputs));
|
||||||
|
}
|
||||||
|
|
||||||
export function validateAddress (address) {
|
export function validateAddress (address) {
|
||||||
let addressError = null;
|
let addressError = null;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user