Initial new UI source code import (#2607)

* address -> name mappings

* expanding, loading all coin details

* send use only actual BasicCoin tokens registered (any reg)

* sending token & accounts

* form styling updates

* send form layout in place

* coin send working as expected

* api subscriptions on multiple addresses

* bring in events

* simplify

* basic events display in-place, functionally complete

* basic functionality in-place

* fix horrible event address issue

* rwork display of events slightly

* test TLA availability

* table for owner -> tokens

* fix signature lookup address

* fix signature lookup address

* basic overview styling

* txhash links

* page layout adjustments

* background import

* adjust colors

* no global registration, simplify color selection

* updated styling

* connection dialog for "busy connecting"

* initial token connection - WIP

* init token updates take place

* basic test for manual token

* rework connection display

* allow updates of the secure token

* first stab at making the build build

* update runner tags

* fix linting issues

* skip tests requiring network (should be e2e, TODO)

* re-enable javascript tag/runner

* release push does the trick

* push to any branch, CI name

* javscript-test runner as well

* swap dependencies build requires test

* revert stages swap

* retrieve images associated with tokens

* remove js build deps order

* null image when hash = 0x0

* 6x64 images (hashes for registries)

* don't pass tokens as prop to IdentityIcon

* check images against content hash pictures

* cleanup signer after connection changes

* fix naming typo

* display unknownImages for balances (not available as content hash)

* unknownImage for transfer dialog

* basic githubhint layout

* single input for commit/filename

* ethcore_hashContent call

* lookup hash

* registration in place

* fixes

* events is using a proper table

* pass value through as-is

* stop wrongly using main app IdentityIcon

* NEVER export class instance functions

* alignment back to normal

* typo  in definition

* set & get images working (mostly)

* show content retrieval info

* set exitcode via ||

* use javascript:latest images

* disable npm progress bar

* rename phase I

* rename phase II

* only send build output to GitHub on major branches

* also run the build step as part of the test (until comprehensive)

* ci-specific build (no webpack progress)

* allow for account creation via recovery phrase

* display account uuid (where available), closes #2546

* connection dialog now shows up in dapps as well, closes #2538

* token images show up as expected

* IdentityName component added and deployed

* fix padding tests

* adjust tests to map to stricter 0x-prefixed hex

* render names via common component for the address -> name

* split lint into seperate script (early exit)

* test phases changed to lint, test & pack

* pack part of test phase

* remove files marked for deletion (cleanup)

* Signer cleanups, start moving in the direction of the rest

* add personal signer methods

* basic signer request subscription

* don't poll blockNumber when not connected

* missing return, creating massive ws queue backlogs

* ΞTH -> ETH

* fix failing tests

* registry uses setAddress to actually set addresses now

* bytes mapping operates on lowerCase hex strings

* sha3 ids for each application

* add dappreg to list of contracts

* adjust alignment of queries

* show gas estimation log

* abi with payable for register function

* add key as required

* image retrieval from dappreg

* use proper Image urls

* embed and link apps from Parity, retrieved via /api/apps

* filter apps that has been replaced

* proxy entry for parity-utils

* add basiccoin abi

* add support for fallback abi type

* capture constructor paramaters

* merge master into js

* move images to assets/images/

* add font assets

* import fonts as part of build

* don't inline woff files

* Revert "merge master into js"

This reverts commit cfcfa81bd26f1b3cbc748d3afa1eb5c670b363fe.

* remove unused npm packages

* information on gas estimates (like almost everywhere else)

* don't pass gas & gasPrice to estimation

* display account passwordhint when available

* signer subscriptions based on polling & function trapping

* pending requests retrieved via jsapi

* update signer middleware

* remove all web3 instances

* remove web3 package

* last web3 dependencies removed

* no need to toChecksumAddress - api takes care of it

* expand description for personal_confirmRequest

* Signer conversion from web3 -> parity.js completed

* explicit in no return

* green circle background

* remove generated background

* convert /api/* paths to localhost:8080/api/* paths (hard-coded, temporary)

* change dapps to load from localhost:8080/ui/*

* remove dangling web3 files

* update manager test for signer

* /api/ping -> /

* additional token images

* additional token images

* add missing styles.css for 8180 error pages

* cater for txhash returning null/empty object

* adjust output directories

* Release merge with origin with ours strategy

* additional token images

* cater for development server

* s/localhost/127.0.0.1/ (cater for origin)

* Fix address selection for contract deployment

* Adjust z-index for error overlay

* better text on unique background pattern

* fix signer rejections

* Don't allow gavcoin transfer with no balance

* fix txhash rendering in signer

* remove unnecessary ParityBackground

* script to update js-precompiled

* Redirect from :8080 to :8180

* Remove extra return

* Dapp logo images
This commit is contained in:
Jaco Greeff
2016-10-18 11:52:56 +02:00
committed by Gav Wood
parent 6c7af57529
commit 1e6a2cb378
969 changed files with 57315 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
// 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/>.
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 { Button, Modal, Form, Input, InputAddress } from '../../ui';
import { ERRORS, validateAddress, validateName } from '../../util/validation';
export default class AddAddress extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
contacts: PropTypes.object.isRequired,
onClose: PropTypes.func
};
state = {
address: '',
addressError: ERRORS.invalidAddress,
name: '',
nameError: ERRORS.invalidName,
description: ''
};
render () {
return (
<Modal
visible
actions={ this.renderDialogActions() }
title='add saved address'>
{ this.renderFields() }
</Modal>
);
}
renderDialogActions () {
const { addressError, nameError } = this.state;
const hasError = !!(addressError || nameError);
return ([
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />,
<Button
icon={ <ContentAdd /> }
label='Save Address'
disabled={ hasError }
onClick={ this.onAdd } />
]);
}
renderFields () {
const { address, addressError, description, name, nameError } = this.state;
return (
<Form>
<InputAddress
label='network address'
hint='the network address for the entry'
error={ addressError }
value={ address }
onChange={ this.onEditAddress } />
<Input
label='address name'
hint='a descriptive name for the entry'
error={ nameError }
value={ name }
onChange={ this.onEditName } />
<Input
multiLine
rows={ 1 }
label='(optional) address description'
hint='an expanded description for the entry'
value={ description }
onChange={ this.onEditDescription } />
</Form>
);
}
onEditAddress = (event, _address) => {
const { contacts } = this.props;
let { address, addressError } = validateAddress(_address);
if (!addressError) {
const contact = contacts[address];
if (contact && !contact.meta.deleted) {
addressError = ERRORS.duplicateAddress;
}
}
this.setState({
address,
addressError
});
}
onEditDescription = (event, description) => {
this.setState({
description
});
}
onEditName = (event, _name) => {
const { name, nameError } = validateName(_name);
this.setState({
name,
nameError
});
}
onAdd = () => {
const { api } = this.context;
const { address, name, description } = this.state;
Promise.all([
api.personal.setAccountName(address, name),
api.personal.setAccountMeta(address, {
description,
deleted: false
})
]).catch((error) => {
console.error('onAdd', error);
});
this.props.onClose();
}
onClose = () => {
this.props.onClose();
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './addAddress';

View File

@@ -0,0 +1,161 @@
// 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/>.
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 { Button, Modal, Form, Input, InputAddress } from '../../ui';
import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation';
export default class AddContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
contracts: PropTypes.object.isRequired,
onClose: PropTypes.func
};
state = {
abi: '',
abiError: ERRORS.invalidAbi,
abiParsed: null,
address: '',
addressError: ERRORS.invalidAddress,
name: '',
nameError: ERRORS.invalidName,
description: ''
};
render () {
return (
<Modal
visible
actions={ this.renderDialogActions() }
title='watch contract'>
{ this.renderFields() }
</Modal>
);
}
renderDialogActions () {
const { addressError, nameError } = this.state;
const hasError = !!(addressError || nameError);
return ([
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />,
<Button
icon={ <ContentAdd /> }
label='Add Contract'
disabled={ hasError }
onClick={ this.onAdd } />
]);
}
renderFields () {
const { abi, abiError, address, addressError, description, name, nameError } = this.state;
return (
<Form>
<InputAddress
label='network address'
hint='the network address for the contract'
error={ addressError }
value={ address }
onSubmit={ this.onEditAddress } />
<Input
label='contract name'
hint='a descriptive name for the contract'
error={ nameError }
value={ name }
onSubmit={ this.onEditName } />
<Input
multiLine
rows={ 1 }
label='(optional) contract description'
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 } />
</Form>
);
}
onEditAbi = (abi) => {
const { api } = this.context;
this.setState(validateAbi(abi, api));
}
onEditAddress = (_address) => {
const { contracts } = this.props;
let { address, addressError } = validateAddress(_address);
if (!addressError) {
const contract = contracts[address];
if (contract && !contract.meta.deleted) {
addressError = ERRORS.duplicateAddress;
}
}
this.setState({
address,
addressError
});
}
onEditDescription = (description) => {
this.setState({ description });
}
onEditName = (name) => {
this.setState(validateName(name));
}
onAdd = () => {
const { api } = this.context;
const { abiParsed, address, name, description } = this.state;
Promise.all([
api.personal.setAccountName(address, name),
api.personal.setAccountMeta(address, {
contract: true,
deleted: false,
abi: abiParsed,
description
})
]).catch((error) => {
console.error('onAdd', error);
});
this.props.onClose();
}
onClose = () => {
this.props.onClose();
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './addContract';

View File

@@ -0,0 +1,65 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { Form, Input, InputAddress } from '../../../ui';
export default class AccountDetails extends Component {
static propTypes = {
address: PropTypes.string,
name: PropTypes.string,
phrase: PropTypes.string
}
render () {
const { address, name } = this.props;
return (
<Form>
<Input
disabled
hint='a descriptive name for the account'
label='account name'
value={ name } />
<InputAddress
disabled
hint='the network address for the account'
label='address'
value={ address } />
{ this.renderPhrase() }
</Form>
);
}
renderPhrase () {
const { phrase } = this.props;
if (!phrase) {
return null;
}
return (
<Input
disabled
hint='the account recovery phrase'
label='account recovery phrase (take note)'
multiLine
rows={ 1 }
value={ phrase } />
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './accountDetails';

View File

@@ -0,0 +1,22 @@
/* 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/>.
*/
.address {
color: #999;
padding-top: 1em;
line-height: 1.618em;
padding-left: 2em;
}

View File

@@ -0,0 +1,41 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import styles from './accountDetailsGeth.css';
export default class AccountDetailsGeth extends Component {
static propTypes = {
addresses: PropTypes.array
}
render () {
const { addresses } = this.props;
const formatted = addresses.map((address, idx) => {
const comma = !idx ? '' : ((idx === addresses.length - 1) ? ' & ' : ', ');
return `${comma}${address}`;
}).join('');
return (
<div>
<div>You have imported { addresses.length } addresses from the Geth keystore:</div>
<div className={ styles.address }>{ formatted }</div>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './accountDetailsGeth';

View File

@@ -0,0 +1,61 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import styles from '../createAccount.css';
export default class CreationType extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired
}
componentWillMount () {
this.props.onChange('fromNew');
}
render () {
return (
<div className={ styles.spaced }>
<RadioButtonGroup
defaultSelected='fromNew'
name='creationType'
onChange={ this.onChange }>
<RadioButton
label='Create new account manually'
value='fromNew' />
<RadioButton
label='Recover account from recovery phrase'
value='fromPhrase' />
<RadioButton
label='Import accounts from Geth keystore'
value='fromGeth' />
<RadioButton
label='Import account from a backup JSON file'
value='fromJSON' />
<RadioButton
label='Import account from an Ethereum pre-sale wallet'
value='fromPresale' />
</RadioButtonGroup>
</div>
);
}
onChange = (event) => {
this.props.onChange(event.target.value);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './creationType';

View File

@@ -0,0 +1,23 @@
// 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/>.
import { ERRORS } from './newAccount';
export default from './newAccount';
export {
ERRORS
};

View File

@@ -0,0 +1,298 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import IconButton from 'material-ui/IconButton';
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import ActionAutorenew from 'material-ui/svg-icons/action/autorenew';
import { Form, Input, IdentityIcon } from '../../../ui';
import styles from '../createAccount.css';
const ERRORS = {
noName: 'you need to specify a valid name for the account',
noPhrase: 'you need to specify the recovery phrase',
invalidPassword: 'you need to specify a password >= 8 characters',
noMatchPassword: 'the supplied passwords does not match'
};
export {
ERRORS
};
export default class CreateAccount extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
onChange: PropTypes.func.isRequired
}
state = {
accountName: '',
accountNameError: ERRORS.noName,
passwordHint: '',
password1: '',
password1Error: ERRORS.invalidPassword,
password2: '',
password2Error: ERRORS.noMatchPassword,
accounts: null,
selectedAddress: '',
isValidPass: false,
isValidName: false
}
componentWillMount () {
this.createIdentities();
this.props.onChange(false, {});
}
render () {
const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error } = this.state;
return (
<Form>
<Input
label='account name'
hint='a descriptive name for the account'
error={ accountNameError }
value={ accountName }
onChange={ this.onEditAccountName } />
<Input
label='password hint'
hint='(optional) a hint to help with remembering the password'
value={ passwordHint }
onChange={ this.onEditPasswordHint } />
<div className={ styles.passwords }>
<div className={ styles.password }>
<Input
className={ styles.password }
label='password'
hint='a strong, unique password'
type='password'
error={ password1Error }
value={ password1 }
onChange={ this.onEditPassword1 } />
</div>
<div className={ styles.password }>
<Input
className={ styles.password }
label='password (repeat)'
hint='verify your password'
type='password'
error={ password2Error }
value={ password2 }
onChange={ this.onEditPassword2 } />
</div>
</div>
{ this.renderIdentitySelector() }
{ this.renderIdentities() }
</Form>
);
}
renderIdentitySelector () {
const { accounts, selectedAddress } = this.state;
if (!accounts) {
return null;
}
const buttons = Object.keys(accounts).map((address) => {
return (
<RadioButton
className={ styles.button }
key={ address }
value={ address } />
);
});
return (
<RadioButtonGroup
valueSelected={ selectedAddress }
className={ styles.selector }
name='identitySelector'
onChange={ this.onChangeIdentity }>
{ buttons }
</RadioButtonGroup>
);
}
renderIdentities () {
const { accounts } = this.state;
if (!accounts) {
return null;
}
const identities = Object.keys(accounts).map((address) => {
return (
<div
className={ styles.identity }
key={ address }
onTouchTap={ this.onChangeIdentity }>
<IdentityIcon
address={ address }
center />
</div>
);
});
return (
<div className={ styles.identities }>
{ identities }
<div className={ styles.refresh }>
<IconButton
onTouchTap={ this.createIdentities }>
<ActionAutorenew
color='rgb(0, 151, 167)' />
</IconButton>
</div>
</div>
);
}
createIdentities = () => {
const { api } = this.context;
Promise
.all([
api.ethcore.generateSecretPhrase(),
api.ethcore.generateSecretPhrase(),
api.ethcore.generateSecretPhrase(),
api.ethcore.generateSecretPhrase(),
api.ethcore.generateSecretPhrase()
])
.then((phrases) => {
return Promise
.all(phrases.map((phrase) => api.ethcore.phraseToAddress(phrase)))
.then((addresses) => {
const accounts = {};
phrases.forEach((phrase, idx) => {
accounts[addresses[idx]] = {
address: addresses[idx],
phrase: phrase
};
});
console.log(accounts);
this.setState({
selectedAddress: addresses[0],
accounts: accounts
});
});
})
.catch((error) => {
console.log('createIdentities', error);
setTimeout(this.createIdentities, 1000);
this.newError(error);
});
}
updateParent = () => {
const { isValidName, isValidPass, accounts, accountName, passwordHint, password1, selectedAddress } = this.state;
const isValid = isValidName && isValidPass;
this.props.onChange(isValid, {
address: selectedAddress,
name: accountName,
passwordHint,
password: password1,
phrase: accounts[selectedAddress].phrase
});
}
onChangeIdentity = (event) => {
const address = event.target.value || event.target.getAttribute('value');
if (!address) {
return;
}
this.setState({
selectedAddress: address
}, this.updateParent);
}
onEditPasswordHint = (event, value) => {
this.setState({
passwordHint: value
});
}
onEditAccountName = (event) => {
const value = event.target.value;
let error = null;
if (!value || value.trim().length < 2) {
error = ERRORS.noName;
}
this.setState({
accountName: value,
accountNameError: error,
isValidName: !error
}, this.updateParent);
}
onEditPassword1 = (event) => {
const value = event.target.value;
let error1 = null;
let error2 = null;
if (!value || value.trim().length < 8) {
error1 = ERRORS.invalidPassword;
}
if (value !== this.state.password2) {
error2 = ERRORS.noMatchPassword;
}
this.setState({
password1: value,
password1Error: error1,
password2Error: error2,
isValidPass: !error1 && !error2
}, this.updateParent);
}
onEditPassword2 = (event) => {
const value = event.target.value;
let error2 = null;
if (value !== this.state.password1) {
error2 = ERRORS.noMatchPassword;
}
this.setState({
password2: value,
password2Error: error2,
isValidPass: !error2
}, this.updateParent);
}
newError = (error) => {
const { store } = this.context;
store.dispatch({ type: 'newError', error });
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './newGeth';

View File

@@ -0,0 +1,43 @@
/* 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/>.
*/
.list {
}
.list input+div>div {
top: 13px;
}
.selection {
display: inline-block;
margin-bottom: 0.5em;
}
.selection .icon {
display: inline-block;
}
.selection .detail {
display: inline-block;
}
.detail .address {
color: #aaa;
}
.detail .balance {
font-family: 'Roboto Mono', monospace;
}

View File

@@ -0,0 +1,128 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { Checkbox } from 'material-ui';
import { IdentityIcon } from '../../../ui';
import styles from './newGeth.css';
export default class NewGeth extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired
}
state = {
available: []
}
componentDidMount () {
this.loadAvailable();
}
render () {
const { available } = this.state;
if (!available.length) {
return (
<div className={ styles.list }>There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance</div>
);
}
const checkboxes = available.map((account) => {
const label = (
<div className={ styles.selection }>
<div className={ styles.icon }>
<IdentityIcon
center inline
address={ account.address } />
</div>
<div className={ styles.detail }>
<div className={ styles.address }>{ account.address }</div>
<div className={ styles.balance }>{ account.balance } ETH</div>
</div>
</div>
);
return (
<Checkbox
key={ account.address }
checked={ account.checked }
label={ label }
data-address={ account.address }
onCheck={ this.onSelect } />
);
});
return (
<div className={ styles.list }>
{ checkboxes }
</div>
);
}
onSelect = (event, checked) => {
const address = event.target.getAttribute('data-address');
if (!address) {
return;
}
const { available } = this.state;
const account = available.find((_account) => _account.address === address);
account.checked = checked;
const selected = available.filter((_account) => _account.checked);
this.setState({
available
});
this.props.onChange(selected.length, selected.map((account) => account.address));
}
loadAvailable = () => {
const { api } = this.context;
const { accounts } = this.props;
api.personal
.listGethAccounts()
.then((_addresses) => {
const addresses = (addresses || []).filter((address) => !accounts[address]);
return Promise
.all(addresses.map((address) => api.eth.getBalance(address)))
.then((balances) => {
this.setState({
available: addresses.map((address, idx) => {
return {
address,
balance: api.util.fromWei(balances[idx]).toFormat(5),
checked: false
};
})
});
});
})
.catch((error) => {
console.error('loadAvailable', error);
});
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './newImport';

View File

@@ -0,0 +1,182 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { FloatingActionButton } from 'material-ui';
import EditorAttachFile from 'material-ui/svg-icons/editor/attach-file';
import { Form, Input } from '../../../ui';
import styles from '../createAccount.css';
const FAKEPATH = 'C:\\fakepath\\';
const STYLE_HIDDEN = { display: 'none' };
const ERRORS = {
noName: 'you need to specify a valid name for the account',
noPassword: 'supply a valid password to confirm the transaction',
noFile: 'select a valid wallet file to import'
};
export default class NewImport extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired
}
state = {
accountName: '',
accountNameError: ERRORS.noName,
passwordHint: '',
password: '',
passwordError: ERRORS.noPassword,
walletFile: '',
walletFileError: ERRORS.noFile,
walletJson: '',
isValidPass: false,
isValidName: false,
isValidFile: false
}
componentWillMount () {
this.props.onChange(false, {});
}
render () {
return (
<Form>
<Input
label='account name'
hint='a descriptive name for the account'
error={ this.state.accountNameError }
value={ this.state.accountName }
onChange={ this.onEditAccountName } />
<Input
label='password hint'
hint='(optional) a hint to help with remembering the password'
value={ this.state.passwordHint }
onChange={ this.onEditpasswordHint } />
<div className={ styles.passwords }>
<div className={ styles.password }>
<Input
className={ styles.password }
label='password'
hint='the password to unlock the wallet'
type='password'
error={ this.state.passwordError }
value={ this.state.password }
onChange={ this.onEditPassword } />
</div>
</div>
<div>
<Input
disabled
label='wallet file'
hint='the wallet file for import'
error={ this.state.walletFileError }
value={ this.state.walletFile } />
<div className={ styles.upload }>
<FloatingActionButton
mini
onTouchTap={ this.openFileDialog }>
<EditorAttachFile />
</FloatingActionButton>
<input
ref='fileUpload'
type='file'
style={ STYLE_HIDDEN }
onChange={ this.onFileChange } />
</div>
</div>
</Form>
);
}
onFileChange = (event) => {
const el = event.target;
const error = ERRORS.noFile;
if (el.files.length) {
const reader = new FileReader();
reader.onload = (event) => {
this.setState({
walletJson: event.target.result,
walletFileError: null,
isValidFile: true
}, this.updateParent);
};
reader.readAsText(el.files[0]);
}
this.setState({
walletFile: el.value.replace(FAKEPATH, ''),
walletFileError: error,
isValidFile: false
}, this.updateParent);
}
openFileDialog = () => {
ReactDOM.findDOMNode(this.refs.fileUpload).click();
}
updateParent = () => {
const valid = this.state.isValidName && this.state.isValidPass && this.state.isValidFile;
this.props.onChange(valid, {
name: this.state.accountName,
passwordHint: this.state.passwordHint,
password: this.state.password,
phrase: null,
json: this.state.walletJson
});
}
onEditPasswordHint = (event, value) => {
this.setState({
passwordHint: value
});
}
onEditAccountName = (event) => {
const value = event.target.value;
let error = null;
if (!value || value.trim().length < 2) {
error = ERRORS.noName;
}
this.setState({
accountName: value,
accountNameError: error,
isValidName: !error
}, this.updateParent);
}
onEditPassword = (event) => {
let error = null;
const value = event.target.value;
if (!value || !value.length) {
error = ERRORS.noPassword;
}
this.setState({
password: value,
passwordError: error,
isValidPass: !error
}, this.updateParent);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './recoveryPhrase';

View File

@@ -0,0 +1,181 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { Form, Input } from '../../../ui';
import styles from '../createAccount.css';
import { ERRORS } from '../NewAccount';
export default class RecoveryPhrase extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired
}
state = {
recoveryPhrase: '',
recoveryPhraseError: ERRORS.noPhrase,
accountName: '',
accountNameError: ERRORS.noName,
passwordHint: '',
password1: '',
password1Error: ERRORS.invalidPassword,
password2: '',
password2Error: ERRORS.noMatchPassword,
isValidPass: false,
isValidName: false,
isValidPhrase: false
}
componentWillMount () {
this.props.onChange(false, {});
}
render () {
const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error, recoveryPhrase } = this.state;
return (
<Form>
<Input
hint='the account recovery phrase'
label='account recovery phrase'
multiLine
rows={ 1 }
value={ recoveryPhrase }
onChange={ this.onEditPhrase } />
<Input
label='account name'
hint='a descriptive name for the account'
error={ accountNameError }
value={ accountName }
onChange={ this.onEditAccountName } />
<Input
label='password hint'
hint='(optional) a hint to help with remembering the password'
value={ passwordHint }
onChange={ this.onEditPasswordHint } />
<div className={ styles.passwords }>
<div className={ styles.password }>
<Input
className={ styles.password }
label='password'
hint='a strong, unique password'
type='password'
error={ password1Error }
value={ password1 }
onChange={ this.onEditPassword1 } />
</div>
<div className={ styles.password }>
<Input
className={ styles.password }
label='password (repeat)'
hint='verify your password'
type='password'
error={ password2Error }
value={ password2 }
onChange={ this.onEditPassword2 } />
</div>
</div>
</Form>
);
}
updateParent = () => {
const { isValidName, isValidPass, isValidPhrase, accountName, passwordHint, password1, recoveryPhrase } = this.state;
const isValid = isValidName && isValidPass && isValidPhrase;
this.props.onChange(isValid, {
name: accountName,
passwordHint,
password: password1,
phrase: recoveryPhrase
});
}
onEditPasswordHint = (event, value) => {
this.setState({
passwordHint: value
});
}
onEditPhrase = (event) => {
const value = event.target.value;
let error = null;
if (!value || value.trim().length < 25) {
error = ERRORS.noPhrase;
}
this.setState({
recoveryPhrase: value,
recoveryPhraseError: error,
isValidPhrase: !error
}, this.updateParent);
}
onEditAccountName = (event) => {
const value = event.target.value;
let error = null;
if (!value || value.trim().length < 2) {
error = ERRORS.noName;
}
this.setState({
accountName: value,
accountNameError: error,
isValidName: !error
}, this.updateParent);
}
onEditPassword1 = (event) => {
const value = event.target.value;
let error1 = null;
let error2 = null;
if (!value || value.trim().length < 8) {
error1 = ERRORS.invalidPassword;
}
if (value !== this.state.password2) {
error2 = ERRORS.noMatchPassword;
}
this.setState({
password1: value,
password1Error: error1,
password2Error: error2,
isValidPass: !error1 && !error2
}, this.updateParent);
}
onEditPassword2 = (event) => {
const value = event.target.value;
let error2 = null;
if (value !== this.state.password1) {
error2 = ERRORS.noMatchPassword;
}
this.setState({
password2: value,
password2Error: error2,
isValidPass: !error2
}, this.updateParent);
}
}

View File

@@ -0,0 +1,60 @@
/* 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 {
line-height: 1.618em;
}
.password {
flex: 0 1 50%;
width: 50%;
}
.passwords {
display: flex;
flex-wrap: wrap;
}
.identities, .selector {
display: flex;
}
.selector {
margin-top: 1.5em;
}
.identities .identity, .selector .button {
flex: 0 1 18%;
width: 18% !important;
text-align: center;
cursor: pointer;
}
.refresh {
flex: 0 1 10%;
width: 10% !important;
}
.upload {
text-align: right;
float: right;
margin-left: -100%;
margin-top: 28px;
}
.upload>div {
margin-right: 0.5em;
}

View File

@@ -0,0 +1,324 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ActionDone from 'material-ui/svg-icons/action/done';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { Button, Modal } from '../../ui';
import AccountDetails from './AccountDetails';
import AccountDetailsGeth from './AccountDetailsGeth';
import CreationType from './CreationType';
import NewAccount from './NewAccount';
import NewGeth from './NewGeth';
import NewImport from './NewImport';
import RecoveryPhrase from './RecoveryPhrase';
const TITLES = {
type: 'creation type',
create: 'create account',
info: 'account information',
import: 'import wallet'
};
const STAGE_NAMES = [TITLES.type, TITLES.create, TITLES.info];
const STAGE_IMPORT = [TITLES.type, TITLES.import, TITLES.info];
export default class CreateAccount extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.object.isRequired,
onClose: PropTypes.func,
onUpdate: PropTypes.func
}
state = {
address: null,
name: null,
passwordHint: null,
password: null,
phrase: null,
json: null,
canCreate: false,
createType: null,
gethAddresses: [],
stage: 0
}
render () {
const { createType, stage } = this.state;
const steps = createType === 'fromNew'
? STAGE_NAMES
: STAGE_IMPORT;
return (
<Modal
visible
actions={ this.renderDialogActions() }
current={ stage }
steps={ steps }>
{ this.renderPage() }
</Modal>
);
}
renderPage () {
const { createType, stage } = this.state;
const { accounts } = this.props;
switch (stage) {
case 0:
return (
<CreationType
onChange={ this.onChangeType } />
);
case 1:
if (createType === 'fromNew') {
return (
<NewAccount
onChange={ this.onChangeDetails } />
);
} else if (createType === 'fromGeth') {
return (
<NewGeth
accounts={ accounts }
onChange={ this.onChangeGeth } />
);
} else if (createType === 'fromPhrase') {
return (
<RecoveryPhrase
onChange={ this.onChangeDetails } />
);
}
return (
<NewImport
onChange={ this.onChangeWallet } />
);
case 2:
if (createType === 'fromGeth') {
return (
<AccountDetailsGeth
addresses={ this.state.gethAddresses } />
);
}
return (
<AccountDetails
address={ this.state.address }
name={ this.state.name }
phrase={ this.state.phrase } />
);
}
}
renderDialogActions () {
const { createType, stage } = this.state;
switch (stage) {
case 0:
return [
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />,
<Button
icon={ <NavigationArrowForward /> }
label='Next'
onClick={ this.onNext } />
];
case 1:
const createLabel = createType === 'fromNew'
? 'Create'
: 'Import';
return [
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />,
<Button
icon={ <NavigationArrowBack /> }
label='Back'
onClick={ this.onPrev } />,
<Button
icon={ <ActionDone /> }
label={ createLabel }
disabled={ !this.state.canCreate }
onClick={ this.onCreate } />
];
case 2:
return (
<Button
icon={ <ActionDoneAll /> }
label='Close'
onClick={ this.onClose } />
);
}
}
onNext = () => {
this.setState({
stage: this.state.stage + 1
});
}
onPrev = () => {
this.setState({
stage: this.state.stage - 1
});
}
onCreate = () => {
const { createType } = this.state;
const { api } = this.context;
this.setState({
canCreate: false
});
if (createType === 'fromNew' || createType === 'fromPhrase') {
return api.personal
.newAccountFromPhrase(this.state.phrase, this.state.password)
.then((address) => {
this.setState({ address });
return api.personal
.setAccountName(address, this.state.name)
.then(() => api.personal.setAccountMeta(address, { passwordHint: this.state.passwordHint }));
})
.then(() => {
this.onNext();
this.props.onUpdate && this.props.onUpdate();
})
.catch((error) => {
console.error('onCreate', error);
this.setState({
canCreate: true
});
this.newError(error);
});
} else if (createType === 'fromGeth') {
return api.personal
.importGethAccounts(this.state.gethAddresses)
.then((result) => {
console.log('result', result);
return Promise.all(this.state.gethAddresses.map((address) => {
return api.personal.setAccountName(address, 'Geth Import');
}));
})
.then(() => {
this.onNext();
this.props.onUpdate && this.props.onUpdate();
})
.catch((error) => {
console.error('onCreate', error);
this.setState({
canCreate: true
});
this.newError(error);
});
}
return api.personal
.newAccountFromWallet(this.state.json, this.state.password)
.then((address) => {
this.setState({
address: address
});
return api.personal
.setAccountName(address, this.state.name)
.then(() => api.personal.setAccountMeta(address, { passwordHint: this.state.passwordHint }));
})
.then(() => {
this.onNext();
this.props.onUpdate && this.props.onUpdate();
})
.catch((error) => {
console.error('onCreate', error);
this.setState({
canCreate: true
});
this.newError(error);
});
}
onClose = () => {
this.setState({
stage: 0,
canCreate: false
}, () => {
this.props.onClose && this.props.onClose();
});
}
onChangeType = (value) => {
this.setState({
createType: value
});
}
onChangeDetails = (valid, { name, passwordHint, address, password, phrase }) => {
this.setState({
canCreate: valid,
name,
passwordHint,
address,
password,
phrase
});
}
onChangeGeth = (valid, gethAddresses) => {
this.setState({
canCreate: valid,
gethAddresses
});
}
onChangeWallet = (valid, { name, passwordHint, password, json }) => {
this.setState({
canCreate: valid,
name,
passwordHint,
password,
json
});
}
newError = (error) => {
const { store } = this.context;
store.dispatch({ type: 'newError', error });
}
}

View File

@@ -0,0 +1,21 @@
// 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/>.
import AccountDetails from './AccountDetails';
import NewAccount from './NewAccount';
export default from './createAccount';
export { AccountDetails, NewAccount };

View File

@@ -0,0 +1,192 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { AddressSelect, Form, Input, InputAddressSelect } from '../../../ui';
import { validateAbi } from '../../../util/validation';
import styles from '../deployContract.css';
export default class DetailsStep extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.object.isRequired,
abi: PropTypes.string,
abiError: PropTypes.string,
code: PropTypes.string,
codeError: PropTypes.string,
description: PropTypes.string,
descriptionError: PropTypes.string,
fromAddress: PropTypes.string,
fromAddressError: PropTypes.string,
name: PropTypes.string,
nameError: PropTypes.string,
params: PropTypes.array,
paramsError: PropTypes.array,
onAbiChange: PropTypes.func.isRequired,
onCodeChange: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired,
onNameChange: PropTypes.func.isRequired,
onParamsChange: PropTypes.func.isRequired
}
state = {
inputs: []
}
render () {
const { accounts } = this.props;
const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError } = this.props;
return (
<Form>
<AddressSelect
label='from account (contract owner)'
hint='the owner account for this contract'
value={ fromAddress }
error={ fromAddressError }
accounts={ accounts }
onChange={ this.onFromAddressChange } />
<Input
label='contract name'
hint='a name for the deployed contract'
error={ nameError }
value={ name }
onSubmit={ this.onNameChange } />
<Input
label='abi'
hint='the abi of the contract to deploy'
error={ abiError }
value={ abi }
onSubmit={ this.onAbiChange } />
<Input
label='code'
hint='the compiled code of the contract to deploy'
error={ codeError }
value={ code }
onSubmit={ this.onCodeChange } />
{ this.renderConstructorInputs() }
</Form>
);
}
renderConstructorInputs () {
const { accounts, params, paramsError } = this.props;
const { inputs } = this.state;
if (!inputs || !inputs.length) {
return null;
}
return inputs.map((input, index) => {
const onChange = (event, value) => this.onParamChange(index, value);
const onSubmit = (value) => this.onParamChange(index, value);
const label = `${input.name}: ${input.type}`;
let inputBox = null;
switch (input.type) {
case 'address':
inputBox = (
<InputAddressSelect
accounts={ accounts }
editing
label={ label }
value={ params[index] }
error={ paramsError[index] }
onChange={ onChange } />
);
break;
default:
inputBox = (
<Input
label={ label }
value={ params[index] }
error={ paramsError[index] }
onSubmit={ onSubmit } />
);
break;
}
return (
<div key={ index } className={ styles.funcparams }>
{ inputBox }
</div>
);
});
}
onFromAddressChange = (event, fromAddress) => {
const { onFromAddressChange } = this.props;
onFromAddressChange(fromAddress);
}
onNameChange = (name) => {
const { onNameChange } = this.props;
onNameChange(name);
}
onParamChange = (index, value) => {
const { params, onParamsChange } = this.props;
params[index] = value;
onParamsChange(params);
}
onAbiChange = (abi) => {
const { api } = this.context;
const { onAbiChange, onParamsChange } = this.props;
const { abiError, abiParsed } = validateAbi(abi, api);
if (!abiError) {
const { inputs } = abiParsed.find((method) => method.type === 'constructor') || { inputs: [] };
const params = [];
inputs.forEach((input) => {
switch (input.type) {
case 'string':
params.push('');
break;
default:
params.push('0x');
break;
}
});
onParamsChange(params);
this.setState({ inputs });
} else {
onParamsChange([]);
this.setState({ inputs: [] });
}
onAbiChange(abi);
}
onCodeChange = (code) => {
const { onCodeChange } = this.props;
onCodeChange(code);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './detailsStep';

View File

@@ -0,0 +1,35 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import styles from '../deployContract.css';
export default class ErrorStep extends Component {
static propTypes = {
error: PropTypes.object
}
render () {
const { error } = this.props;
return (
<div className={ styles.center }>
The contract deployment failed: { error.message }
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './errorStep';

View File

@@ -0,0 +1,33 @@
/* 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/>.
*/
.center {
text-align: center;
}
.address {
vertical-align: top;
display: inline-block;
}
.identityicon {
margin: -8px 0.5em;
}
.funcparams {
padding-left: 3em;
}

View File

@@ -0,0 +1,275 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation';
import DetailsStep from './DetailsStep';
import ErrorStep from './ErrorStep';
import styles from './deployContract.css';
const steps = ['contract details', 'deployment', 'completed'];
export default class DeployContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired
}
state = {
abi: '',
abiError: ERRORS.invalidAbi,
code: '',
codeError: ERRORS.invalidCode,
deployState: '',
description: '',
descriptionError: null,
fromAddress: Object.keys(this.props.accounts)[0],
fromAddressError: null,
name: '',
nameError: ERRORS.invalidName,
params: [],
paramsError: [],
step: 0,
deployError: null
}
render () {
const { step, deployError } = this.state;
return (
<Modal
actions={ this.renderDialogActions() }
current={ step }
steps={ deployError ? null : steps }
title={ deployError ? 'deployment failed' : null }
waiting={ [1] }
visible>
{ this.renderStep() }
</Modal>
);
}
renderDialogActions () {
const { deployError, abiError, codeError, nameError, descriptionError, fromAddressError, fromAddress, step } = this.state;
const isValid = !nameError && !fromAddressError && !descriptionError && !abiError && !codeError;
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />
);
if (deployError) {
return cancelBtn;
}
switch (step) {
case 0:
return [
cancelBtn,
<Button
disabled={ !isValid }
icon={ <IdentityIcon button address={ fromAddress } /> }
label='Create'
onClick={ this.onDeployStart } />
];
case 1:
return [
cancelBtn
];
case 2:
return [
<Button
icon={ <ActionDoneAll /> }
label='Close'
onClick={ this.onClose } />
];
}
}
renderStep () {
const { accounts } = this.props;
const { address, deployError, step, deployState, txhash } = this.state;
if (deployError) {
return (
<ErrorStep error={ deployError } />
);
}
switch (step) {
case 0:
return (
<DetailsStep
{ ...this.state }
accounts={ accounts }
onAbiChange={ this.onAbiChange }
onCodeChange={ this.onCodeChange }
onFromAddressChange={ this.onFromAddressChange }
onDescriptionChange={ this.onDescriptionChange }
onNameChange={ this.onNameChange }
onParamsChange={ this.onParamsChange } />
);
case 1:
const body = txhash
? <TxHash hash={ txhash } />
: null;
return (
<BusyStep
title='The deployment is currently in progress'
state={ deployState }>
{ body }
</BusyStep>
);
case 2:
return (
<CompletedStep>
<div>Your contract has been deployed at</div>
<div>
<IdentityIcon address={ address } inline center className={ styles.identityicon } />
<div className={ styles.address }>{ address }</div>
</div>
<TxHash hash={ txhash } />
</CompletedStep>
);
}
}
onDescriptionChange = (description) => {
this.setState({ description, descriptionError: null });
}
onFromAddressChange = (fromAddress) => {
const { api } = this.context;
const fromAddressError = api.util.isAddressValid(fromAddress)
? null
: 'a valid account as the contract owner needs to be selected';
this.setState({ fromAddress, fromAddressError });
}
onNameChange = (name) => {
this.setState(validateName(name));
}
onParamsChange = (params) => {
this.setState({ params });
}
onAbiChange = (abi) => {
const { api } = this.context;
this.setState(validateAbi(abi, api));
}
onCodeChange = (code) => {
const { api } = this.context;
this.setState(validateCode(code, api));
}
onDeployStart = () => {
const { api, store } = this.context;
const { abiParsed, code, description, name, params, fromAddress } = this.state;
const options = {
data: code,
from: fromAddress
};
this.setState({ step: 1 });
api
.newContract(abiParsed)
.deploy(options, params, this.onDeploymentState)
.then((address) => {
return Promise.all([
api.personal.setAccountName(address, name),
api.personal.setAccountMeta(address, {
abi: abiParsed,
contract: true,
deleted: false,
description
})
])
.then(() => {
console.log(`contract deployed at ${address}`);
this.setState({ step: 2, address });
});
})
.catch((error) => {
console.error('error deploying contract', error);
this.setState({ deployError: error });
store.dispatch({ type: 'newError', error });
});
}
onDeploymentState = (error, data) => {
if (error) {
console.error('onDeploymentState', error);
return;
}
console.log('onDeploymentState', data);
switch (data.state) {
case 'estimateGas':
case 'postTransaction':
this.setState({ deployState: 'Preparing transaction for network transmission' });
return;
case 'checkRequest':
this.setState({ deployState: 'Waiting for confirmation of the transaction in the Parity Secure Signer' });
return;
case 'getTransactionReceipt':
this.setState({ deployState: 'Waiting for the contract deployment transaction receipt', txhash: data.txhash });
return;
case 'hasReceipt':
case 'getCode':
this.setState({ deployState: 'Validating the deployed contract code' });
return;
case 'completed':
this.setState({ deployState: 'The contract deployment has been completed' });
return;
default:
console.error('Unknow contract deployment state', data);
return;
}
}
onClose = () => {
this.props.onClose();
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './deployContract';

View File

@@ -0,0 +1,131 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ContentClear from 'material-ui/svg-icons/content/clear';
import ContentSave from 'material-ui/svg-icons/content/save';
import { Button, Form, Input, Modal } from '../../ui';
import { validateName } from '../../util/validation';
export default class EditMeta extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
keys: PropTypes.array.isRequired,
account: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired
}
state = {
meta: this.props.account.meta,
metaErrors: {},
name: this.props.account.name,
nameError: null
}
render () {
const { name, nameError } = this.state;
return (
<Modal
visible
actions={ this.renderActions() }
title='edit metadata'>
<Form>
<Input
label='name'
value={ name }
error={ nameError }
onSubmit={ this.onNameChange } />
{ this.renderMetaFields() }
</Form>
</Modal>
);
}
renderActions () {
const { nameError } = this.state;
return [
<Button
label='Cancel'
icon={ <ContentClear /> }
onClick={ this.props.onClose } />,
<Button
disabled={ !!nameError }
label='Save'
icon={ <ContentSave /> }
onClick={ this.onSave } />
];
}
renderMetaFields () {
const { keys } = this.props;
const { meta } = this.state;
return keys.map((key) => {
const onSubmit = (value) => this.onMetaChange(key, value);
const label = `(optional) ${key}`;
const hint = `the optional ${key} metadata`;
return (
<Input
key={ key }
label={ label }
hint={ hint }
value={ meta[key] || '' }
onSubmit={ onSubmit } />
);
});
}
onNameChange = (name) => {
this.setState(validateName(name));
}
onMetaChange = (key, value) => {
const { meta } = this.state;
this.setState({
meta: Object.assign(meta, { [key]: value })
});
}
onSave = () => {
const { api, store } = this.context;
const { account } = this.props;
const { name, nameError, meta } = this.state;
if (nameError) {
return;
}
Promise
.all([
api.personal.setAccountName(account.address, name),
api.personal.setAccountMeta(account.address, Object.assign({}, account.meta, meta))
])
.then(() => this.props.onClose())
.catch((error) => {
console.error('onSave', error);
store.dispatch({ type: 'newError', error });
});
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './editMeta';

View File

@@ -0,0 +1,167 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { MenuItem } from 'material-ui';
import { AddressSelect, Form, Input, InputAddressSelect, Select } from '../../../ui';
import styles from '../executeContract.css';
export default class DetailsStep extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
contract: PropTypes.object.isRequired,
amount: PropTypes.string,
amountError: PropTypes.string,
onAmountChange: PropTypes.func.isRequired,
fromAddress: PropTypes.string,
fromAddressError: PropTypes.string,
onFromAddressChange: PropTypes.func.isRequired,
func: PropTypes.object,
funcError: PropTypes.string,
onFuncChange: PropTypes.func,
values: PropTypes.array.isRequired,
valuesError: PropTypes.array.isRequired,
onValueChange: PropTypes.func.isRequired
}
render () {
const { accounts, amount, amountError, fromAddress, fromAddressError, onFromAddressChange, onAmountChange } = this.props;
return (
<Form>
<AddressSelect
label='from account'
hint='the account to transact with'
value={ fromAddress }
error={ fromAddressError }
accounts={ accounts }
onChange={ onFromAddressChange } />
{ this.renderFunctionSelect() }
{ this.renderParameters() }
<Input
label='transaction value (in ETH)'
hint='the amount to send to with the transaction'
value={ amount }
error={ amountError }
onSubmit={ onAmountChange } />
</Form>
);
}
renderFunctionSelect () {
const { func, funcError, contract } = this.props;
if (!func) {
return null;
}
const functions = contract.functions
.filter((func) => !func.constant)
.sort((a, b) => a.name.localeCompare(b.name))
.map((func) => {
const params = func.abi.inputs
.map((input, index) => {
return (
<span key={ input.name }>
<span>{ index ? ', ' : '' }</span>
<span className={ styles.paramname }>{ input.name }: </span>
<span>{ input.type }</span>
</span>
);
});
const name = (
<div>
<span>{ func.name }</span>
<span className={ styles.paramname }>(</span>
{ params }
<span className={ styles.paramname }>)</span>
</div>
);
return (
<MenuItem
key={ func.signature }
value={ func.signature }
label={ func.name }>
{ name }
</MenuItem>
);
});
return (
<Select
label='function to execute'
hint='the function to call on the contract'
error={ funcError }
onChange={ this.onFuncChange }
value={ func.signature }>
{ functions }
</Select>
);
}
renderParameters () {
const { accounts, func, values, valuesError, onValueChange } = this.props;
if (!func) {
return null;
}
return func.abi.inputs.map((input, index) => {
const onChange = (event, value) => onValueChange(event, index, value);
const onSubmit = (value) => onValueChange(null, index, value);
const label = `${input.name}: ${input.type}`;
let inputbox;
switch (input.type) {
case 'address':
inputbox = (
<InputAddressSelect
accounts={ accounts }
editing
label={ label }
value={ values[index] }
error={ valuesError[index] }
onChange={ onChange } />
);
break;
default:
inputbox = (
<Input
label={ label }
value={ values[index] }
error={ valuesError[index] }
onSubmit={ onSubmit } />
);
}
return (
<div className={ styles.funcparams } key={ index }>
{ inputbox }
</div>
);
});
}
onFuncChange = (event, index, signature) => {
const { contract, onFuncChange } = this.props;
onFuncChange(event, contract.functions.find((fn) => fn.signature === signature));
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './detailsStep';

View File

@@ -0,0 +1,35 @@
/* 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/>.
*/
.modalbody,
.modalcenter {
}
.modalcenter {
text-align: center;
}
.funcparams {
padding-left: 3em;
}
.paramname {
color: #aaa;
}
.txhash {
word-break: break-all;
}

View File

@@ -0,0 +1,221 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import DetailsStep from './DetailsStep';
export default class ExecuteContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
isTest: PropTypes.bool,
fromAddress: PropTypes.string,
accounts: PropTypes.object,
contract: PropTypes.object,
onClose: PropTypes.func.isRequired,
onFromAddressChange: PropTypes.func.isRequired
}
state = {
amount: '0',
amountError: null,
func: null,
funcError: null,
values: [],
valuesError: [],
step: 0,
sending: false,
busyState: null,
txhash: null
}
componentDidMount () {
const { contract } = this.props;
const functions = contract.functions
.filter((func) => !func.constant)
.sort((a, b) => a.name.localeCompare(b.name));
this.onFuncChange(null, functions[0]);
}
render () {
const { sending } = this.state;
return (
<Modal
actions={ this.renderDialogActions() }
title='execute function'
busy={ sending }
waiting={ [1] }
visible>
{ this.renderStep() }
</Modal>
);
}
renderDialogActions () {
const { onClose, fromAddress } = this.props;
const { sending, step } = this.state;
const cancelBtn = (
<Button
key='cancel'
label='Cancel'
icon={ <ContentClear /> }
onClick={ onClose } />
);
if (step === 0) {
return [
cancelBtn,
<Button
key='postTransaction'
label='post transaction'
disabled={ sending }
icon={ <IdentityIcon address={ fromAddress } button /> }
onClick={ this.postTransaction } />
];
} else if (step === 1) {
return [
cancelBtn
];
}
return [
<Button
key='close'
label='Done'
icon={ <ActionDoneAll /> }
onClick={ onClose } />
];
}
renderStep () {
const { onFromAddressChange } = this.props;
const { step, busyState, txhash } = this.state;
if (step === 0) {
return (
<DetailsStep
{ ...this.props }
{ ...this.state }
onAmountChange={ this.onAmountChange }
onFromAddressChange={ onFromAddressChange }
onFuncChange={ this.onFuncChange }
onValueChange={ this.onValueChange } />
);
} else if (step === 1) {
return (
<BusyStep
title='The function execution is in progress'
state={ busyState } />
);
}
return (
<CompletedStep>
<TxHash hash={ txhash } />
</CompletedStep>
);
}
onAmountChange = (amount) => {
this.setState({ amount });
}
onFuncChange = (event, func) => {
const values = func.inputs.map((input) => {
switch (input.kind.type) {
case 'address':
return '0x';
case 'bytes':
return '0x';
case 'uint':
return '0';
default:
return '';
}
});
this.setState({
func,
values
});
}
onValueChange = (event, index, _value) => {
const { func, values, valuesError } = this.state;
const input = func.inputs.find((input, _index) => index === _index);
let value;
let valueError;
switch (input.kind.type) {
default:
value = _value;
valueError = null;
break;
}
values[index] = value;
valuesError[index] = valueError;
this.setState({
values,
valuesError
});
}
postTransaction = () => {
const { api, store } = this.context;
const { fromAddress } = this.props;
const { amount, func, values } = this.state;
const options = {
from: fromAddress,
value: api.util.toWei(amount || 0)
};
this.setState({ sending: true, step: 1 });
func
.estimateGas(options, values)
.then((gas) => {
options.gas = gas.mul(1.2).toFixed(0);
console.log(`estimateGas: received ${gas.toFormat(0)}, adjusted to ${gas.mul(1.2).toFormat(0)}`);
return func.postTransaction(options, values);
})
.then((requestId) => {
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
return api.pollMethod('eth_checkRequest', requestId);
})
.then((txhash) => {
this.setState({ sending: false, step: 2, txhash, busyState: 'Your transaction has been posted to the network' });
})
.catch((error) => {
console.error('postTransaction', error);
store.dispatch({ type: 'newError', error });
});
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './executeContract';

View File

@@ -0,0 +1,27 @@
// 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/>.
import React, { Component } from 'react';
export default class Completed extends Component {
render () {
return (
<div>
<p>Your node setup has been completed successfully and you are ready to use the application. Next you will receive a walk-through of the available functions and the general application interface to get you up and running in record time.</p>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './completed';

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './welcome';

View File

@@ -0,0 +1,41 @@
// 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/>.
import React, { Component } from 'react';
import imagesEthcore from '../../../../assets/images/ethcore-logo-white-square.png';
const LOGO_STYLE = {
float: 'right',
width: '25%',
height: 'auto'
};
export default class FirstRun extends Component {
render () {
return (
<div>
<img
src={ imagesEthcore }
alt='Ethcore Ltd.'
style={ LOGO_STYLE } />
<p>Welcome to <strong>Parity</strong>, the fastest and simplest way to run your node.</p>
<p>The next few steps will guide you through the process of setting up you Parity instance and the associated account.</p>
<p>Click <strong>Next</strong> to continue your journey.</p>
</div>
);
}
}

View File

@@ -0,0 +1,185 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ActionDone from 'material-ui/svg-icons/action/done';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { Button, Modal } from '../../ui';
import { NewAccount, AccountDetails } from '../CreateAccount';
import Completed from './Completed';
import Welcome from './Welcome';
const STAGE_NAMES = ['welcome', 'new account', 'recovery', 'completed'];
export default class FirstRun extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
visible: PropTypes.bool,
onClose: PropTypes.func.isRequired
}
state = {
stage: 0,
name: '',
address: '',
password: '',
phrase: '',
canCreate: false
}
render () {
const { visible } = this.props;
const { stage } = this.state;
if (!visible) {
return null;
}
return (
<Modal
actions={ this.renderDialogActions() }
current={ stage }
steps={ STAGE_NAMES }
visible>
{ this.renderStage() }
</Modal>
);
}
renderStage () {
const { address, name, phrase, stage } = this.state;
switch (stage) {
case 0:
return (
<Welcome />
);
case 1:
return (
<NewAccount
onChange={ this.onChangeDetails } />
);
case 2:
return (
<AccountDetails
address={ address }
name={ name }
phrase={ phrase } />
);
case 3:
return (
<Completed />
);
}
}
renderDialogActions () {
const { canCreate, stage } = this.state;
switch (stage) {
case 0:
case 2:
return (
<Button
icon={ <NavigationArrowForward /> }
label='Next'
onClick={ this.onNext } />
);
case 1:
return (
<Button
icon={ <ActionDone /> }
label='Create'
disabled={ !canCreate }
onClick={ this.onCreate } />
);
case 3:
return (
<Button
icon={ <ActionDoneAll /> }
label='Close'
onClick={ this.onClose } />
);
}
}
onClose = () => {
const { onClose } = this.props;
this.setState({
stage: 0
}, onClose);
}
onNext = () => {
const { stage } = this.state;
this.setState({
stage: stage + 1
});
}
onChangeDetails = (valid, { name, address, password, phrase }) => {
this.setState({
canCreate: valid,
name: name,
address: address,
password: password,
phrase: phrase
});
}
onCreate = () => {
const { api } = this.context;
const { name, phrase, password } = this.state;
this.setState({
canCreate: false
});
return api.personal
.newAccountFromPhrase(phrase, password)
.then((address) => api.personal.setAccountName(address, name))
.then(() => {
this.onNext();
})
.catch((error) => {
console.error('onCreate', error);
this.setState({
canCreate: true
});
this.newError(error);
});
}
newError = (error) => {
const { store } = this.context;
store.dispatch({ type: 'newError', error });
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './firstRun';

View File

@@ -0,0 +1,67 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import Value from '../Value';
import styles from '../shapeshift.css';
export default class AwaitingDepositStep extends Component {
static propTypes = {
coinSymbol: PropTypes.string.isRequired,
depositAddress: PropTypes.string,
price: PropTypes.shape({
rate: PropTypes.number.isRequired,
minimum: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired
}).isRequired
}
render () {
const { coinSymbol, depositAddress, price } = this.props;
const typeSymbol = (
<div className={ styles.symbol }>
{ coinSymbol }
</div>
);
if (!depositAddress) {
return (
<div className={ styles.center }>
<div className={ styles.busy }>
Awaiting confirmation of the deposit address for your { typeSymbol } funds exchange
</div>
</div>
);
}
return (
<div className={ styles.center }>
<div className={ styles.info }>
<a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> is awaiting a { typeSymbol } deposit. Send the funds from your { typeSymbol } network client to -
</div>
<div className={ styles.hero }>
{ depositAddress }
</div>
<div className={ styles.price }>
<div>
(<Value amount={ price.minimum } symbol={ coinSymbol } /> minimum, <Value amount={ price.limit } symbol={ coinSymbol } /> maximum)
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './awaitingDepositStep';

View File

@@ -0,0 +1,48 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import Value from '../Value';
import styles from '../shapeshift.css';
export default class AwaitingExchangeStep extends Component {
static propTypes = {
depositInfo: PropTypes.shape({
incomingCoin: PropTypes.number.isRequired,
incomingType: PropTypes.string.isRequired
}).isRequired
}
render () {
const { depositInfo } = this.props;
const { incomingCoin, incomingType } = depositInfo;
return (
<div className={ styles.center }>
<div className={ styles.info }>
<a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> has received a deposit of -
</div>
<div className={ styles.hero }>
<Value amount={ incomingCoin } symbol={ incomingType } />
</div>
<div className={ styles.info }>
Awaiting the completion of the funds exchange and transfer of funds to your Parity account.
</div>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './awaitingExchangeStep';

View File

@@ -0,0 +1,54 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import Value from '../Value';
import styles from '../shapeshift.css';
export default class CompletedStep extends Component {
static propTypes = {
depositInfo: PropTypes.shape({
incomingCoin: PropTypes.number.isRequired,
incomingType: PropTypes.string.isRequired
}).isRequired,
exchangeInfo: PropTypes.shape({
outgoingCoin: PropTypes.string.isRequired,
outgoingType: PropTypes.string.isRequired
}).isRequired
}
render () {
const { depositInfo, exchangeInfo } = this.props;
const { incomingCoin, incomingType } = depositInfo;
const { outgoingCoin, outgoingType } = exchangeInfo;
return (
<div className={ styles.center }>
<div className={ styles.info }>
<a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> has completed the funds exchange.
</div>
<div className={ styles.hero }>
<Value amount={ incomingCoin } symbol={ incomingType } /> => <Value amount={ outgoingCoin } symbol={ outgoingType } />
</div>
<div className={ styles.info }>
The change in funds will be reflected in your Parity account shortly.
</div>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './completedStep';

View File

@@ -0,0 +1,43 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import styles from '../shapeshift.css';
export default class ErrorStep extends Component {
static propTypes = {
error: PropTypes.shape({
fatal: PropTypes.bool,
message: PropTypes.string.isRequired
}).isRequired
}
render () {
const { error } = this.props;
return (
<div className={ styles.body }>
<div className={ styles.info }>
The funds shifting via <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a> failed with a fatal error on the exchange. The error message received from the exchange is as follow:
</div>
<div className={ styles.error }>
{ error.message }
</div>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './errorStep';

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './optionsStep';

View File

@@ -0,0 +1,61 @@
/* 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/>.
*/
.body {
}
.accept {
padding: 1.5em 0;
}
.coinselector {
}
.coinselector .coinselect {
margin-top: 11px;
}
.coinselect {
max-height: 36px;
padding: 4px 0 0 0;
line-height: 32px;
}
.coinimage {
display: inline-block;
width: 32px;
height: 32px;
margin-right: 0.5em;
}
.coindetails {
display: inline-block;
vertical-align: top;
}
.coinsymbol {
display: inline-block;
margin-right: 0.5em;
color: #aaa;
}
.coinname {
display: inline-block;
}
.empty {
color: #aaa;
}

View File

@@ -0,0 +1,113 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import { Checkbox, MenuItem } from 'material-ui';
import { Form, Input, Select } from '../../../ui';
import Price from '../Price';
import styles from './optionsStep.css';
export default class OptionsStep extends Component {
static propTypes = {
refundAddress: PropTypes.string.isRequired,
coinSymbol: PropTypes.string.isRequired,
coins: PropTypes.array.isRequired,
price: PropTypes.object,
hasAccepted: PropTypes.bool.isRequired,
onChangeSymbol: PropTypes.func.isRequired,
onChangeRefund: PropTypes.func.isRequired,
onToggleAccept: PropTypes.func.isRequired
};
render () {
const { coinSymbol, coins, refundAddress, hasAccepted, onToggleAccept } = this.props;
const label = `(optional) ${coinSymbol} return address`;
if (!coins.length) {
return (
<div className={ styles.empty }>
There are currently no exchange pairs/coins available to fund with.
</div>
);
}
const items = coins.map(this.renderCoinSelectItem);
return (
<div className={ styles.body }>
<Form>
<Select
className={ styles.coinselector }
label='fund account from'
hint='the type of crypto conversion to do'
value={ coinSymbol }
onChange={ this.onSelectCoin }>
{ items }
</Select>
<Input
label={ label }
hint='the return address for send failures'
value={ refundAddress }
onSubmit={ this.onChangeRefund } />
<Checkbox
className={ styles.accept }
label='I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is completely out of the control of Parity'
checked={ hasAccepted }
onCheck={ onToggleAccept } />
</Form>
<Price { ...this.props } />
</div>
);
}
renderCoinSelectItem = (coin) => {
const { image, name, symbol } = coin;
const item = (
<div className={ styles.coinselect }>
<img className={ styles.coinimage } src={ image } />
<div className={ styles.coindetails }>
<div className={ styles.coinsymbol }>
{ symbol }
</div>
<div className={ styles.coinname }>
{ name }
</div>
</div>
</div>
);
return (
<MenuItem
key={ symbol }
value={ symbol }
label={ item }>
{ item }
</MenuItem>
);
}
onSelectCoin = (event, idx, value) => {
this.props.onChangeSymbol(event, value);
}
onChangeAddress = (event, value) => {
this.props.onChangeRefund(value);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './price';

View File

@@ -0,0 +1,50 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import Value from '../Value';
import styles from '../shapeshift.css';
export default class Price extends Component {
static propTypes = {
coinSymbol: PropTypes.string.isRequired,
price: PropTypes.shape({
rate: PropTypes.number.isRequired,
minimum: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired
})
}
render () {
const { coinSymbol, price } = this.props;
if (!price) {
return null;
}
return (
<div className={ styles.price }>
<div>
<Value amount={ 1 } symbol={ coinSymbol } /> = <Value amount={ price.rate } />
</div>
<div>
(<Value amount={ price.minimum } symbol={ coinSymbol } /> minimum, <Value amount={ price.limit } symbol={ coinSymbol } /> maximum)
</div>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './value';

View File

@@ -0,0 +1,24 @@
/* 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/>.
*/
.body {
display: inline-block;
color: #aaa;
}
.amount {
display: inline-block;
}

View File

@@ -0,0 +1,45 @@
// 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/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import styles from './value.css';
export default class Value extends Component {
static propTypes = {
amount: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string
]),
symbol: PropTypes.string
}
render () {
const { amount, symbol } = this.props;
let value = '';
if (amount) {
value = new BigNumber(amount).toFormat(3);
}
return (
<div className={ styles.body }>
<span>{ value }</span><small>{ symbol || 'ETH' }</small>
</div>
);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './shapeshift';

View File

@@ -0,0 +1,65 @@
/* 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/>.
*/
.body {
}
.shapeshift {
position: absolute;
bottom: 0.5em;
left: 1em;
cursor: pointer;
outline: none;
}
.shapeshift img {
height: 28px;
}
.info, .busy {
}
.center {
}
.center div {
text-align: center;
}
.error {
padding-top: 1.5em;
color: #e44;
}
.hero {
padding-top: 0.75em;
text-align: center;
font-size: 1.75em;
}
.symbol {
display: inline-block;
color: #aaa;
}
.price div {
text-align: center;
font-size: small;
}
.empty {
color: #aaa;
}

View File

@@ -0,0 +1,306 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, IdentityIcon, Modal } from '../../ui';
import initShapeshift from '../../3rdparty/shapeshift';
import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png';
import AwaitingDepositStep from './AwaitingDepositStep';
import AwaitingExchangeStep from './AwaitingExchangeStep';
import CompletedStep from './CompletedStep';
import ErrorStep from './ErrorStep';
import OptionsStep from './OptionsStep';
import styles from './shapeshift.css';
const shapeshift = initShapeshift();
const STAGE_NAMES = ['details', 'awaiting deposit', 'awaiting exchange', 'completed'];
export default class Shapeshift extends Component {
static contextTypes = {
store: PropTypes.object.isRequired
}
static propTypes = {
address: PropTypes.string.isRequired,
onClose: PropTypes.func
}
state = {
stage: 0,
coinSymbol: 'BTC',
coinPair: 'btc_eth',
coins: [],
depositAddress: '',
refundAddress: '',
price: null,
depositInfo: null,
exchangeInfo: null,
error: {},
hasAccepted: false,
shifting: false
}
componentDidMount () {
this.retrieveCoins();
}
render () {
const { error, stage } = this.state;
return (
<Modal
actions={ this.renderDialogActions() }
current={ stage }
steps={ error.fatal ? null : STAGE_NAMES }
title={ error.fatal ? 'exchange failed' : null }
waiting={ [1, 2] }
visible>
{ this.renderPage() }
</Modal>
);
}
renderDialogActions () {
const { address } = this.props;
const { coins, error, stage, hasAccepted, shifting } = this.state;
const logo = (
<a href='http://shapeshift.io' target='_blank' className={ styles.shapeshift }>
<img src={ shapeshiftLogo } />
</a>
);
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />
);
if (error.fatal) {
return [
logo,
cancelBtn
];
}
switch (stage) {
case 0:
return [
logo,
cancelBtn,
<Button
disabled={ !coins.length || !hasAccepted || shifting }
icon={ <IdentityIcon address={ address } button /> }
label='Shift Funds'
onClick={ this.onShift } />
];
case 1:
case 2:
return [
logo,
cancelBtn
];
case 3:
return [
logo,
<Button
icon={ <ActionDoneAll /> }
label='Close'
onClick={ this.onClose } />
];
}
}
renderPage () {
const { error, stage } = this.state;
if (error.fatal) {
return (
<ErrorStep error={ error } />
);
}
switch (stage) {
case 0:
return (
<OptionsStep
{ ...this.state }
onChangeSymbol={ this.onChangeSymbol }
onChangeRefund={ this.onChangeRefund }
onToggleAccept={ this.onToggleAccept } />
);
case 1:
return (
<AwaitingDepositStep { ...this.state } />
);
case 2:
return (
<AwaitingExchangeStep { ...this.state } />
);
case 3:
return (
<CompletedStep { ...this.state } />
);
}
}
setStage (stage) {
this.setState({
stage,
error: {}
});
}
setFatalError (message) {
this.setState({
stage: 0,
error: {
fatal: true,
message
}
});
}
onClose = () => {
this.setStage(0);
this.props.onClose && this.props.onClose();
}
onShift = () => {
const { address } = this.props;
const { coinPair, refundAddress } = this.state;
this.setState({
stage: 1,
shifting: true
});
shapeshift
.shift(address, refundAddress, coinPair)
.then((result) => {
console.log('onShift', result);
const depositAddress = result.deposit;
shapeshift.subscribe(depositAddress, this.onExchangeInfo);
this.setState({ depositAddress });
})
.catch((error) => {
console.error('onShift', error);
const message = `Failed to start exchange: ${error.message}`;
this.newError(new Error(message));
this.setFatalError(message);
});
}
onChangeSymbol = (event, coinSymbol) => {
const coinPair = `${coinSymbol.toLowerCase()}_eth`;
this.setState({
coinPair,
coinSymbol,
price: null
});
this.getPrice(coinPair);
}
onChangeRefund = (event, refundAddress) => {
this.setState({ refundAddress });
}
onToggleAccept = () => {
const { hasAccepted } = this.state;
this.setState({
hasAccepted: !hasAccepted
});
}
onExchangeInfo = (error, result) => {
if (error) {
console.error('onExchangeInfo', error);
if (error.fatal) {
this.setFatalError(error.message);
}
this.newError(error);
return;
}
console.log('onExchangeInfo', result.status, result);
switch (result.status) {
case 'received':
this.setState({ depositInfo: result });
this.setStage(2);
return;
case 'complete':
this.setState({ exchangeInfo: result });
this.setStage(3);
return;
}
}
getPrice (coinPair) {
shapeshift
.getMarketInfo(coinPair)
.then((price) => {
this.setState({ price });
})
.catch((error) => {
console.error('getPrice', error);
});
}
retrieveCoins () {
const { coinPair } = this.state;
shapeshift
.getCoins()
.then((_coins) => {
const coins = Object.values(_coins).filter((coin) => coin.status === 'available');
this.getPrice(coinPair);
this.setState({ coins });
})
.catch((error) => {
console.error('retrieveCoins', error);
const message = `Failed to retrieve available coins from ShapeShift.io: ${error.message}`;
this.newError(new Error(message));
this.setFatalError(message);
});
}
newError (error) {
const { store } = this.context;
store.dispatch({ type: 'newError', error });
}
}

View File

@@ -0,0 +1,193 @@
// 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/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { Checkbox, MenuItem } from 'material-ui';
import Form, { Input, InputAddressSelect, Select } from '../../../ui/Form';
import imageUnknown from '../../../../assets/images/contracts/unknown-64x64.png';
import styles from '../transfer.css';
const CHECK_STYLE = {
position: 'absolute',
top: '38px',
left: '1em'
};
export default class Details extends Component {
static contextTypes = {
api: PropTypes.object
}
static propTypes = {
address: PropTypes.string,
balance: PropTypes.object,
all: PropTypes.bool,
extras: PropTypes.bool,
images: PropTypes.object.isRequired,
recipient: PropTypes.string,
recipientError: PropTypes.string,
tag: PropTypes.string,
total: PropTypes.string,
totalError: PropTypes.string,
value: PropTypes.string,
valueError: PropTypes.string,
onChange: PropTypes.func.isRequired
}
render () {
const { all, extras, tag, total, totalError, value, valueError } = this.props;
const label = `amount to transfer (in ${tag})`;
return (
<Form>
{ this.renderTokenSelect() }
{ this.renderToAddress() }
<div className={ styles.columns }>
<div>
<Input
disabled={ all }
label={ label }
hint='the amount to transfer to the recipient'
value={ value }
error={ valueError }
onChange={ this.onEditValue } />
</div>
<div>
<Checkbox
checked={ all }
label='full account balance'
onCheck={ this.onCheckAll }
style={ CHECK_STYLE } />
</div>
</div>
<div className={ styles.columns }>
<div>
<Input
disabled
label='total transaction amount'
error={ totalError }>
<div className={ styles.inputoverride }>
{ total }<small> ETH</small>
</div>
</Input>
</div>
<div>
<Checkbox
checked={ extras }
label='advanced sending options'
onCheck={ this.onCheckExtras }
style={ CHECK_STYLE } />
</div>
</div>
</Form>
);
}
renderToAddress () {
const { recipient, recipientError } = this.props;
return (
<div className={ styles.address }>
<InputAddressSelect
label='recipient address'
hint='the recipient address'
error={ recipientError }
value={ recipient }
onChange={ this.onEditRecipient } />
</div>
);
}
renderTokenSelect () {
const { api } = this.context;
const { balance, images, tag } = this.props;
const items = balance.tokens
.filter((token) => token.value.gt(0))
.map((balance, idx) => {
const token = balance.token;
const isEth = idx === 0;
const imagesrc = token.image || images[token.address] || imageUnknown;
let value = 0;
if (isEth) {
value = api.util.fromWei(balance.value).toFormat(3);
} else {
value = new BigNumber(balance.value).div(balance.token.format || 1).toFormat(3);
}
const label = (
<div className={ styles.token }>
<img src={ imagesrc } />
<div className={ styles.tokenname }>
{ token.name }
</div>
<div className={ styles.tokenbalance }>
{ value }<small> { token.tag }</small>
</div>
</div>
);
return (
<MenuItem
key={ token.tag }
value={ token.tag }
label={ label }>
{ label }
</MenuItem>
);
});
return (
<Select
label='type of token transfer'
hint='type of token to transfer'
value={ tag }
onChange={ this.onChangeToken }>
{ items }
</Select>
);
}
onChangeToken = (event, idx, tag) => {
this.props.onChange('tag', tag);
}
onEditRecipient = (event, recipient) => {
this.props.onChange('recipient', recipient);
}
onEditValue = (event) => {
this.props.onChange('value', event.target.value);
}
onCheckAll = () => {
this.props.onChange('all', !this.props.all);
}
onCheckExtras = () => {
this.props.onChange('extras', !this.props.extras);
}
onContacts = () => {
this.setState({
showAddresses: true
});
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './details';

View File

@@ -0,0 +1,109 @@
// 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/>.
import React, { Component, PropTypes } from 'react';
import Form, { Input } from '../../../ui/Form';
import styles from '../transfer.css';
export default class Extras extends Component {
static propTypes = {
isEth: PropTypes.bool,
data: PropTypes.string,
dataError: PropTypes.string,
gas: PropTypes.string,
gasEst: PropTypes.string,
gasError: PropTypes.string,
gasPrice: PropTypes.string,
gasPriceDefault: PropTypes.string,
gasPriceError: PropTypes.string,
total: PropTypes.string,
totalError: PropTypes.string,
onChange: PropTypes.func.isRequired
}
render () {
const { gas, gasError, gasEst, gasPrice, gasPriceDefault, gasPriceError, total, totalError } = this.props;
const gasLabel = `gas amount (estimated: ${gasEst})`;
const priceLabel = `gas price (current: ${gasPriceDefault})`;
return (
<Form>
{ this.renderData() }
<div className={ styles.columns }>
<div>
<Input
label={ gasLabel }
hint='the amount of gas to use for the transaction'
error={ gasError }
value={ gas }
onChange={ this.onEditGas } />
</div>
<div>
<Input
label={ priceLabel }
hint='the price of gas to use for the transaction'
error={ gasPriceError }
value={ gasPrice }
onChange={ this.onEditGasPrice } />
</div>
</div>
<div className={ styles.columns }>
<div>
<Input
disabled
label='total transaction amount'
hint='the total amount of the transaction'
error={ totalError }
value={ `${total} ETH` } />
</div>
</div>
</Form>
);
}
renderData () {
const { isEth, data, dataError } = this.props;
if (!isEth) {
return null;
}
return (
<div>
<Input
hint='the data to pass through with the transaction'
label='transaction data'
value={ data }
error={ dataError }
onChange={ this.onEditData } />
</div>
);
}
onEditGas = (event) => {
this.props.onChange('gas', event.target.value);
}
onEditGasPrice = (event) => {
this.props.onChange('gasPrice', event.target.value);
}
onEditData = (event) => {
this.props.onChange('data', event.target.value);
}
}

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './extras';

View File

@@ -0,0 +1,24 @@
// 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/>.
const ERRORS = {
requireRecipient: 'a recipient network address is required for the transaction',
invalidAddress: 'the supplied address is an invalid network address',
invalidAmount: 'the supplied amount should be a valid positive number',
largeAmount: 'the transaction total is higher than the available balance'
};
export default ERRORS;

View File

@@ -0,0 +1,17 @@
// 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/>.
export default from './transfer';

View File

@@ -0,0 +1,130 @@
/* 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/>.
*/
.info {
line-height: 1.618em;
width: 100%;
}
.columns {
display: flex;
flex-wrap: wrap;
position: relative;
}
.columns>div {
flex: 0 1 50%;
width: 50%;
position: relative;
}
.floatbutton {
text-align: right;
float: right;
margin-left: -100%;
margin-top: 28px;
}
.floatbutton>div {
margin-right: 0.5em;
}
.token {
height: 32px;
margin-top: 14px;
}
.token img {
height: 32px;
width: 32px;
margin: 0 16px 0 0;
z-index: 10;
}
.token div {
height: 32px;
line-height: 32px;
display: inline-block;
vertical-align: top;
}
.tokenbalance {
display: inline-block;
color: #aaa;
padding-left: 1em;
}
.tokenname {}
.address {
position: relative;
}
.address input {
padding-left: 48px !important;
}
.from {
padding: 25px 0 0 48px !important;
line-height: 32px;
}
.fromaddress {
text-transform: uppercase;
display: inline-block;
}
.frombalance {
display: inline-block;
color: #aaa;
padding-left: 1em;
}
.icon {
position: absolute;
top: 35px;
}
.grayscale {
-webkit-filter: grayscale(1);
filter: grayscale(1);
opacity: 0;
}
.hdraccount {
padding: 1em 0 0 0;
}
.hdrimage {
display: inline-block;
}
.hdrdetails {
display: inline-block;
}
.hdrname {
text-transform: uppercase;
}
.hdraddress {
color: #aaa;
font-size: 0.75em;
}
.inputoverride {
padding-top: 24px !important;
}

View File

@@ -0,0 +1,577 @@
// 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/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import ContentClear from 'material-ui/svg-icons/content/clear';
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
import Details from './Details';
import Extras from './Extras';
import ERRORS from './errors';
import styles from './transfer.css';
const DEFAULT_GAS = '21000';
const DEFAULT_GASPRICE = '20000000000';
const TITLES = {
transfer: 'transfer details',
sending: 'sending',
complete: 'complete',
extras: 'extra information'
};
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
export default class Transfer extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
static propTypes = {
account: PropTypes.object,
balance: PropTypes.object,
balances: PropTypes.object,
images: PropTypes.object.isRequired,
onClose: PropTypes.func
}
state = {
stage: 0,
data: '',
dataError: null,
extras: false,
gas: DEFAULT_GAS,
gasEst: '0',
gasError: null,
gasPrice: DEFAULT_GASPRICE,
gasPriceError: null,
recipient: '',
recipientError: ERRORS.requireRecipient,
sending: false,
tag: 'ETH',
total: '0.0',
totalError: null,
value: '0.0',
valueAll: false,
valueError: null,
isEth: true,
busyState: null
}
componentDidMount () {
this.getDefaults();
}
render () {
const { stage, extras } = this.state;
return (
<Modal
actions={ this.renderDialogActions() }
current={ stage }
steps={ extras ? STAGES_EXTRA : STAGES_BASIC }
waiting={ extras ? [2] : [1] }
visible>
{ this.renderPage() }
</Modal>
);
}
renderAccount () {
const { account } = this.props;
return (
<div className={ styles.hdraccount }>
<div className={ styles.hdrimage }>
<IdentityIcon
inline center
address={ account.address } />
</div>
<div className={ styles.hdrdetails }>
<div className={ styles.hdrname }>
{ account.name || 'Unnamed' }
</div>
<div className={ styles.hdraddress }>
{ account.address }
</div>
</div>
</div>
);
}
renderPage () {
const { extras, stage } = this.state;
if (stage === 0) {
return this.renderDetailsPage();
} else if (stage === 1 && extras) {
return this.renderExtrasPage();
}
return this.renderCompletePage();
}
renderCompletePage () {
const { sending, txhash, busyState } = this.state;
if (sending) {
return (
<BusyStep
title='The transaction is in progress'
state={ busyState } />
);
}
return (
<CompletedStep>
<TxHash hash={ txhash } />
</CompletedStep>
);
}
renderDetailsPage () {
const { account, balance, images } = this.props;
return (
<Details
address={ account.address }
all={ this.state.valueAll }
balance={ balance }
extras={ this.state.extras }
images={ images }
recipient={ this.state.recipient }
recipientError={ this.state.recipientError }
tag={ this.state.tag }
total={ this.state.total }
totalError={ this.state.totalError }
value={ this.state.value }
valueError={ this.state.valueError }
onChange={ this.onUpdateDetails } />
);
}
renderExtrasPage () {
return (
<Extras
isEth={ this.state.isEth }
data={ this.state.data }
dataError={ this.state.dataError }
gas={ this.state.gas }
gasEst={ this.state.gasEst }
gasError={ this.state.gasError }
gasPrice={ this.state.gasPrice }
gasPriceDefault={ this.state.gasPriceDefault }
gasPriceError={ this.state.gasPriceError }
total={ this.state.total }
totalError={ this.state.totalError }
onChange={ this.onUpdateDetails } />
);
}
renderDialogActions () {
const { account } = this.props;
const { extras, sending, stage } = this.state;
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose } />
);
const nextBtn = (
<Button
disabled={ !this.isValid() }
icon={ <NavigationArrowForward /> }
label='Next'
onClick={ this.onNext } />
);
const prevBtn = (
<Button
icon={ <NavigationArrowBack /> }
label='Back'
onClick={ this.onPrev } />
);
const sendBtn = (
<Button
disabled={ !this.isValid() || sending }
icon={ <IdentityIcon address={ account.address } button /> }
label='Send'
onClick={ this.onSend } />
);
const doneBtn = (
<Button
icon={ <ActionDoneAll /> }
label='Close'
onClick={ this.onClose } />
);
switch (stage) {
case 0:
return extras
? [cancelBtn, nextBtn]
: [cancelBtn, sendBtn];
case 1:
return extras
? [cancelBtn, prevBtn, sendBtn]
: [doneBtn];
default:
return [doneBtn];
}
}
isValid () {
const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError;
const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError;
const verifyValid = !this.state.passwordError;
switch (this.state.stage) {
case 0:
return detailsValid;
case 1:
return this.state.extras ? extrasValid : verifyValid;
case 2:
return verifyValid;
}
}
onNext = () => {
this.setState({
stage: this.state.stage + 1
});
}
onPrev = () => {
this.setState({
stage: this.state.stage - 1
});
}
_onUpdateAll (valueAll) {
this.setState({
valueAll
}, this.recalculateGas);
}
_onUpdateExtras (extras) {
this.setState({
extras
});
}
_onUpdateData (data) {
this.setState({
data
}, this.recalculateGas);
}
validatePositiveNumber (num) {
try {
const v = new BigNumber(num);
if (v.lt(0)) {
return ERRORS.invalidAmount;
}
} catch (e) {
return ERRORS.invalidAmount;
}
return null;
}
_onUpdateGas (gas) {
const gasError = this.validatePositiveNumber(gas);
this.setState({
gas,
gasError
}, this.recalculate);
}
_onUpdateGasPrice (gasPrice) {
const gasPriceError = this.validatePositiveNumber(gasPrice);
this.setState({
gasPrice,
gasPriceError
}, this.recalculate);
}
_onUpdateRecipient (recipient) {
const { api } = this.context;
let recipientError = null;
if (!recipient || !recipient.length) {
recipientError = ERRORS.requireRecipient;
} else if (!api.util.isAddressValid(recipient)) {
recipientError = ERRORS.invalidAddress;
}
this.setState({
recipient,
recipientError
}, this.recalculateGas);
}
_onUpdateTag (tag) {
const { balance } = this.props;
this.setState({
tag,
isEth: tag === balance.tokens[0].token.tag
}, this.recalculateGas);
}
_onUpdateValue (value) {
const valueError = this.validatePositiveNumber(value);
this.setState({
value,
valueError
}, this.recalculateGas);
}
onUpdateDetails = (type, value) => {
switch (type) {
case 'all':
return this._onUpdateAll(value);
case 'extras':
return this._onUpdateExtras(value);
case 'data':
return this._onUpdateData(value);
case 'gas':
return this._onUpdateGas(value);
case 'gasPrice':
return this._onUpdateGasPrice(value);
case 'recipient':
return this._onUpdateRecipient(value);
case 'tag':
return this._onUpdateTag(value);
case 'value':
return this._onUpdateValue(value);
}
}
_sendEth () {
const { api } = this.context;
const { account } = this.props;
const { data, gas, gasPrice, recipient, value } = this.state;
const options = {
from: account.address,
to: recipient,
gas,
gasPrice,
value: api.util.toWei(value || 0)
};
if (data && data.length) {
options.data = data;
}
return api.eth.postTransaction(options);
}
_sendToken () {
const { account, balance } = this.props;
const { recipient, value, tag } = this.state;
const token = balance.tokens.find((balance) => balance.token.tag === tag).token;
return token.contract.instance.transfer
.postTransaction({
from: account.address,
to: token.address
}, [
recipient,
new BigNumber(value).mul(token.format).toString()
]);
}
onSend = () => {
const { api } = this.context;
this.onNext();
this.setState({ sending: true }, () => {
(this.state.isEth
? this._sendEth()
: this._sendToken()
).then((requestId) => {
this.setState({ busyState: 'Waiting for authorization in the Parity Signer' });
return api.pollMethod('eth_checkRequest', requestId);
})
.then((txhash) => {
this.onNext();
this.setState({
sending: false,
txhash,
busyState: 'Your transaction has been posted to the network'
});
})
.catch((error) => {
console.log('send', error);
this.setState({
sending: false
});
this.newError(error);
});
});
}
onClose = () => {
this.setState({ stage: 0 }, () => {
this.props.onClose && this.props.onClose();
});
}
_estimateGasToken () {
const { account, balance } = this.props;
const { recipient, value, tag } = this.state;
const token = balance.tokens.find((balance) => balance.token.tag === tag).token;
return token.contract.instance.transfer
.estimateGas({
from: account.address,
to: token.address
}, [
recipient,
new BigNumber(value || 0).mul(token.format).toString()
]);
}
_estimateGasEth () {
const { api } = this.context;
const { account } = this.props;
const { data, recipient, value } = this.state;
const options = {
from: account.address,
to: recipient,
value: api.util.toWei(value || 0)
};
if (data && data.length) {
options.data = data;
}
return api.eth.estimateGas(options);
}
recalculateGas = () => {
(this.state.isEth
? this._estimateGasEth()
: this._estimateGasToken()
).then((_value) => {
let gas = _value;
if (gas.gt(DEFAULT_GAS)) {
gas = gas.mul(1.2);
}
this.setState({
gas: gas.toFixed(0),
gasEst: _value.toFormat()
}, this.recalculate);
})
.catch((error) => {
console.error('etimateGas', error);
});
}
recalculate = () => {
const { api } = this.context;
const { account, balance } = this.props;
if (!account || !balance) {
return;
}
const { gas, gasPrice, tag, valueAll, isEth } = this.state;
const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0));
const balance_ = balance.tokens.find((b) => tag === b.token.tag);
const availableEth = new BigNumber(balance.tokens[0].value);
const available = new BigNumber(balance_.value);
const format = new BigNumber(balance_.token.format || 1);
let { value, valueError } = this.state;
let totalEth = gasTotal;
let totalError = null;
if (valueAll) {
if (isEth) {
const bn = api.util.fromWei(availableEth.minus(gasTotal));
value = (bn.lt(0) ? new BigNumber(0.0) : bn).toString();
} else {
value = available.div(format).toString();
}
}
if (isEth) {
totalEth = totalEth.plus(api.util.toWei(value || 0));
}
if (new BigNumber(value || 0).gt(available.div(format))) {
valueError = ERRORS.largeAmount;
} else if (valueError === ERRORS.largeAmount) {
valueError = null;
}
if (totalEth.gt(availableEth)) {
totalError = ERRORS.largeAmount;
}
this.setState({
total: api.util.fromWei(totalEth).toString(),
totalError,
value,
valueError
});
}
getDefaults = () => {
const { api } = this.context;
api.eth
.gasPrice()
.then((gasPrice) => {
this.setState({
gasPrice: gasPrice.toString(),
gasPriceDefault: gasPrice.toFormat()
}, this.recalculate);
})
.catch((error) => {
console.error('getDefaults', error);
});
}
newError = (error) => {
const { store } = this.context;
store.dispatch({ type: 'newError', error });
}
}

37
js/src/modals/index.js Normal file
View File

@@ -0,0 +1,37 @@
// 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/>.
import AddAddress from './AddAddress';
import AddContract from './AddContract';
import CreateAccount from './CreateAccount';
import DeployContract from './DeployContract';
import EditMeta from './EditMeta';
import ExecuteContract from './ExecuteContract';
import FirstRun from './FirstRun';
import Shapeshift from './Shapeshift';
import Transfer from './Transfer';
export {
AddAddress,
AddContract,
CreateAccount,
DeployContract,
EditMeta,
ExecuteContract,
FirstRun,
Shapeshift,
Transfer
};