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) {
|
||||
return logs.map((log) => {
|
||||
const signature = log.topics[0].substr(2);
|
||||
const event = this.events.find((evt) => evt.signature === signature);
|
||||
return logs
|
||||
.map((log) => {
|
||||
const signature = log.topics[0].substr(2);
|
||||
const event = this.events.find((evt) => evt.signature === signature);
|
||||
|
||||
if (!event) {
|
||||
throw new Error(`Unable to find event matching signature ${signature}`);
|
||||
}
|
||||
if (!event) {
|
||||
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.event = event.name;
|
||||
log.params = {};
|
||||
log.event = event.name;
|
||||
|
||||
decoded.params.forEach((param) => {
|
||||
const { type, value } = param.token;
|
||||
decoded.params.forEach((param) => {
|
||||
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) {
|
||||
|
@ -24,6 +24,7 @@ import owned from './owned.json';
|
||||
import registry from './registry.json';
|
||||
import signaturereg from './signaturereg.json';
|
||||
import tokenreg from './tokenreg.json';
|
||||
import wallet from './wallet.json';
|
||||
|
||||
export {
|
||||
basiccoin,
|
||||
@ -35,5 +36,6 @@ export {
|
||||
owned,
|
||||
registry,
|
||||
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 ContentAdd from 'material-ui/svg-icons/content/add';
|
||||
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 { 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 {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
@ -34,44 +62,101 @@ export default class AddContract extends Component {
|
||||
state = {
|
||||
abi: '',
|
||||
abiError: ERRORS.invalidAbi,
|
||||
abiType: ABI_TYPES[2],
|
||||
abiTypeIndex: 2,
|
||||
abiParsed: null,
|
||||
address: '',
|
||||
addressError: ERRORS.invalidAddress,
|
||||
name: '',
|
||||
nameError: ERRORS.invalidName,
|
||||
description: ''
|
||||
description: '',
|
||||
step: 0
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.onChangeABIType(null, this.state.abiTypeIndex);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { step } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible
|
||||
actions={ this.renderDialogActions() }
|
||||
title='watch contract'>
|
||||
{ this.renderFields() }
|
||||
steps={ STEPS }
|
||||
current={ step }
|
||||
>
|
||||
{ this.renderStep(step) }
|
||||
</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 () {
|
||||
const { addressError, nameError } = this.state;
|
||||
const { addressError, nameError, step } = this.state;
|
||||
const hasError = !!(addressError || nameError);
|
||||
|
||||
return ([
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <ContentClear /> }
|
||||
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
|
||||
icon={ <ContentAdd /> }
|
||||
label='Add Contract'
|
||||
disabled={ hasError }
|
||||
onClick={ this.onAdd } />
|
||||
]);
|
||||
);
|
||||
|
||||
return [ cancelBtn, prevBtn, addBtn ];
|
||||
}
|
||||
|
||||
renderFields () {
|
||||
const { abi, abiError, address, addressError, description, name, nameError } = this.state;
|
||||
const { abi, abiError, address, addressError, description, name, nameError, abiType } = this.state;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@ -80,7 +165,9 @@ export default class AddContract extends Component {
|
||||
hint='the network address for the contract'
|
||||
error={ addressError }
|
||||
value={ address }
|
||||
onSubmit={ this.onEditAddress } />
|
||||
onSubmit={ this.onEditAddress }
|
||||
onChange={ this.onChangeAddress }
|
||||
/>
|
||||
<Input
|
||||
label='contract name'
|
||||
hint='a descriptive name for the contract'
|
||||
@ -94,20 +181,57 @@ export default class AddContract extends Component {
|
||||
hint='an expanded description for the entry'
|
||||
value={ description }
|
||||
onSubmit={ this.onEditDescription } />
|
||||
|
||||
<Input
|
||||
label='contract abi'
|
||||
hint='the abi for the contract'
|
||||
error={ abiError }
|
||||
value={ abi }
|
||||
onSubmit={ this.onEditAbi } />
|
||||
readOnly={ abiType.readOnly }
|
||||
onSubmit={ this.onEditAbi }
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
onEditAbi = (abi) => {
|
||||
const { api } = this.context;
|
||||
renderAbiTypes () {
|
||||
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) => {
|
||||
@ -138,7 +262,7 @@ export default class AddContract extends Component {
|
||||
|
||||
onAdd = () => {
|
||||
const { api } = this.context;
|
||||
const { abiParsed, address, name, description } = this.state;
|
||||
const { abiParsed, address, name, description, abiType } = this.state;
|
||||
|
||||
Promise.all([
|
||||
api.parity.setAccountName(address, name),
|
||||
@ -147,6 +271,7 @@ export default class AddContract extends Component {
|
||||
deleted: false,
|
||||
timestamp: Date.now(),
|
||||
abi: abiParsed,
|
||||
type: abiType.type,
|
||||
description
|
||||
})
|
||||
]).catch((error) => {
|
||||
|
@ -38,10 +38,22 @@ export function validateAbi (abi, api) {
|
||||
abiParsed = JSON.parse(abi);
|
||||
|
||||
if (!api.util.isArray(abiParsed) || !abiParsed.length) {
|
||||
abiError = ERRORS.inavlidAbi;
|
||||
} else {
|
||||
abi = JSON.stringify(abiParsed);
|
||||
abiError = ERRORS.invalidAbi;
|
||||
return { abi, abiError, 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) {
|
||||
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) {
|
||||
let addressError = null;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user