[beta] UI backports (#4809)

* Update Wallet to new Wallet Code (#4805)

* Update Wallet Version

* Update Wallet Library

* Update Wallets Bytecodes

* Typo

* Separate Deploy in Contract API

* Use the new Wallet ABI // Update wallet code

* WIP .// Deploy from Wallet

* Update Wallet contract

* Contract Deployment for Wallet

* Working deployments for Single Owned Wallet contracts

* Linting

* Create a Wallet from a Wallet

* Linting

* Fix Signer transactions // Add Gas Used for transactions

* Deploy wallet contract fix

* Fix too high gas estimate for Wallet Contract Deploys

* Final piece ; deploying from Wallet owned by wallet

* Update Wallet Code

* Updated the Wallet Codes

* Fixing Wallet Deployments

* Add Support for older wallets

* Linting

* SMS Faucet (#4774)

* Faucet

* Remove flakey button-index testing

* Only display faucet when sms verified (mainnet)

* simplify availability checks

* WIP

* Resuest from verified -> verified

* Update endpoint, display response text

* Error icon on errors

* Parse hash text response

* Use /api/:address endpoint

* hash -> data

* Adjust sms-certified message

* Fix SectionList hovering issue (#4749)

* Fix SectionList Items hover when <3 items

* Even easier...

* lint (new)
This commit is contained in:
Jaco Greeff
2017-03-08 10:43:59 +01:00
committed by Arkadiy Paronyan
parent 3b56e8eded
commit c4196a5de3
32 changed files with 2656 additions and 993 deletions

View File

@@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { omitBy } from 'lodash';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
@@ -106,9 +105,6 @@ export default class WalletDetails extends Component {
renderMultisigDetails () {
const { accounts, wallet, errors } = this.props;
// Wallets cannot create contracts
const _accounts = omitBy(accounts, (a) => a.wallet);
return (
<Form>
<Input
@@ -148,7 +144,7 @@ export default class WalletDetails extends Component {
/>
<AddressSelect
accounts={ _accounts }
accounts={ accounts }
error={ errors.account }
hint={
<FormattedMessage

View File

@@ -22,10 +22,11 @@ import Contract from '~/api/contract';
import { ERROR_CODES } from '~/api/transport/error';
import Contracts from '~/contracts';
import { wallet as walletAbi } from '~/contracts/abi';
import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
import { wallet as walletCode, walletLibrary as walletLibraryCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
import { validateUint, validateAddress, validateName } from '~/util/validation';
import { toWei } from '~/api/util/wei';
import { deploy } from '~/util/tx';
import WalletsUtils from '~/util/wallets';
const STEPS = {
@@ -179,6 +180,8 @@ export default class CreateWalletStore {
this.wallet.owners = owners;
this.wallet.required = require.toNumber();
this.wallet.dailylimit = dailylimit.limit;
this.wallet = this.getWalletWithMeta(this.wallet);
});
return this.addWallet(this.wallet);
@@ -202,21 +205,51 @@ export default class CreateWalletStore {
return null; // exception when registry is not available
})
.then((address) => {
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase();
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress)
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress)
: fullWalletCode;
if (!address || /^(0x)?0*$/.test(address)) {
return null;
}
// Check that it's actually the expected code
return this.api.eth
.getCode(address)
.then((code) => {
const strippedCode = code.replace(/^0x/, '');
// The actual deployed code is included in the wallet
// library code (which might have some more data)
if (walletLibraryCode.indexOf(strippedCode) >= 0) {
return address;
}
return null;
});
})
.then((address) => {
let code = fullWalletCode;
if (address) {
const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase();
code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress);
} else {
console.warn('wallet library has not been found in the registry');
}
const options = {
data: code,
from: account
};
return this.api
.newContract(walletAbi)
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState);
const contract = this.api.newContract(walletAbi);
this.wallet = this.getWalletWithMeta(this.wallet);
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
})
.then((address) => {
if (!address || /^(0x)?0*$/.test(address)) {
return false;
}
this.deployed = true;
this.wallet.address = address;
return this.addWallet(this.wallet);
@@ -233,26 +266,37 @@ export default class CreateWalletStore {
}
@action addWallet = (wallet) => {
const { address, name, description } = wallet;
const { address, name, metadata } = wallet;
return Promise
.all([
this.api.parity.setAccountName(address, name),
this.api.parity.setAccountMeta(address, {
abi: walletAbi,
wallet: true,
timestamp: Date.now(),
deleted: false,
description,
name,
tags: ['wallet']
})
this.api.parity.setAccountMeta(address, metadata)
])
.then(() => {
this.step = 'INFO';
});
}
getWalletWithMeta = (wallet) => {
const { name, description } = wallet;
const metadata = {
abi: walletAbi,
wallet: true,
timestamp: Date.now(),
deleted: false,
tags: [ 'wallet' ],
description,
name
};
return {
...wallet,
metadata
};
}
onDeploymentState = (error, data) => {
if (error) {
return console.error('createWallet::onDeploymentState', error);
@@ -298,6 +342,15 @@ export default class CreateWalletStore {
);
return;
case 'confirmationNeeded':
this.deployState = (
<FormattedMessage
id='createWallet.states.confirmationNeeded'
defaultMessage='The contract deployment needs confirmations from other owners of the Wallet'
/>
);
return;
case 'completed':
this.deployState = (
<FormattedMessage

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { pick, omitBy } from 'lodash';
import { pick } from 'lodash';
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
@@ -23,6 +23,7 @@ import { connect } from 'react-redux';
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
import { CancelIcon, DoneIcon } from '~/ui/Icons';
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
import { deploy, deployEstimateGas } from '~/util/tx';
import DetailsStep from './DetailsStep';
import ParametersStep from './ParametersStep';
@@ -73,7 +74,7 @@ class DeployContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
store: PropTypes.object.isRequired
}
};
static propTypes = {
accounts: PropTypes.object.isRequired,
@@ -422,9 +423,9 @@ class DeployContract extends Component {
from: fromAddress
};
api
.newContract(abiParsed)
.deployEstimateGas(options, params)
const contract = api.newContract(abiParsed);
deployEstimateGas(contract, options, params)
.then(([gasEst, gas]) => {
this.gasStore.setEstimated(gasEst.toFixed(0));
this.gasStore.setGas(gas.toFixed(0));
@@ -490,6 +491,17 @@ class DeployContract extends Component {
const { api, store } = this.context;
const { source } = this.props;
const { abiParsed, code, description, name, params, fromAddress } = this.state;
const metadata = {
abi: abiParsed,
contract: true,
deleted: false,
timestamp: Date.now(),
name,
description,
source
};
const options = {
data: code,
from: fromAddress
@@ -499,28 +511,25 @@ class DeployContract extends Component {
const contract = api.newContract(abiParsed);
contract
.deploy(options, params, this.onDeploymentState)
deploy(contract, options, params, metadata, this.onDeploymentState)
.then((address) => {
const blockNumber = contract._receipt
// No contract address given, might need some confirmations
// from the wallet owners...
if (!address || /^(0x)?0*$/.test(address)) {
return false;
}
metadata.blockNumber = contract._receipt
? contract.receipt.blockNumber.toNumber()
: null;
return Promise.all([
api.parity.setAccountName(address, name),
api.parity.setAccountMeta(address, {
abi: abiParsed,
contract: true,
timestamp: Date.now(),
deleted: false,
blockNumber,
description,
source
})
api.parity.setAccountMeta(address, metadata)
])
.then(() => {
console.log(`contract deployed at ${address}`);
this.setState({ step: 'DEPLOYMENT', address });
this.setState({ step: 'COMPLETED', address });
});
})
.catch((error) => {
@@ -589,6 +598,17 @@ class DeployContract extends Component {
});
return;
case 'confirmationNeeded':
this.setState({
deployState: (
<FormattedMessage
id='deployContract.state.confirmationNeeded'
defaultMessage='The operation needs confirmations from the other owners of the contract'
/>
)
});
return;
case 'completed':
this.setState({
deployState: (
@@ -614,17 +634,14 @@ class DeployContract extends Component {
function mapStateToProps (initState, initProps) {
const { accounts } = initProps;
// Skip Wallet accounts : they can't create Contracts
const _accounts = omitBy(accounts, (a) => a.wallet);
const fromAddresses = Object.keys(_accounts);
const fromAddresses = Object.keys(accounts);
return (state) => {
const balances = pick(state.balances.balances, fromAddresses);
const { gasLimit } = state.nodeStatus;
return {
accounts: _accounts,
accounts,
balances,
gasLimit
};

View File

@@ -0,0 +1,162 @@
// Copyright 2015-2017 Parity Technologies (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 { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { txLink } from '~/3rdparty/etherscan/links';
import { Button, ModalBox, Portal, ShortenedHash } from '~/ui';
import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons';
import Store from './store';
@observer
export default class Faucet extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
netVersion: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired
}
store = new Store(this.props.netVersion, this.props.address);
render () {
const { error, isBusy, isCompleted } = this.store;
let icon = <DialIcon />;
if (isCompleted) {
icon = error
? <ErrorIcon />
: <DoneIcon />;
}
return (
<Portal
buttons={ this.renderActions() }
busy={ isBusy }
isSmallModal
onClose={ this.onClose }
open
title={
<FormattedMessage
id='faucet.title'
defaultMessage='Kovan ETH Faucet'
/>
}
>
<ModalBox
icon={ icon }
summary={
isCompleted
? this.renderSummaryDone()
: this.renderSummaryRequest()
}
/>
</Portal>
);
}
renderActions = () => {
const { canTransact, isBusy, isCompleted } = this.store;
return isCompleted || isBusy
? (
<Button
disabled={ isBusy }
icon={ <DoneIcon /> }
key='done'
label={
<FormattedMessage
id='faucet.buttons.done'
defaultMessage='close'
/>
}
onClick={ this.onClose }
/>
)
: [
<Button
icon={ <CloseIcon /> }
key='close'
label={
<FormattedMessage
id='faucet.buttons.close'
defaultMessage='close'
/>
}
onClick={ this.onClose }
/>,
<Button
disabled={ !canTransact }
icon={ <SendIcon /> }
key='request'
label={
<FormattedMessage
id='faucet.buttons.request'
defaultMessage='request'
/>
}
onClick={ this.onExecute }
/>
];
}
renderSummaryDone () {
const { error, responseText, responseTxHash } = this.store;
return (
<div>
<FormattedMessage
id='faucet.summary.done'
defaultMessage='Your Kovan ETH has been requested from the faucet which responded with -'
/>
{
error
? (
<p>{ error }</p>
)
: (
<p>
<span>{ responseText }&nbsp;</span>
<a href={ txLink(responseTxHash, false, '42') } target='_blank'>
<ShortenedHash data={ responseTxHash } />
</a>
</p>
)
}
</div>
);
}
renderSummaryRequest () {
return (
<FormattedMessage
id='faucet.summary.info'
defaultMessage='To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet. Once executed the faucet will deposit Kovan ETH into the current account.'
/>
);
}
onClose = () => {
this.props.onClose();
}
onExecute = () => {
return this.store.makeItRain();
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (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 './faucet';

View File

@@ -0,0 +1,126 @@
// Copyright 2015-2017 Parity Technologies (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 { action, computed, observable, transaction } from 'mobx';
import apiutil from '~/api/util';
const ENDPOINT = 'http://faucet.kovan.network/api/';
export default class Store {
@observable addressReceive = null;
@observable addressVerified = null;
@observable error = null;
@observable responseText = null;
@observable responseTxHash = null;
@observable isBusy = false;
@observable isCompleted = false;
@observable isDestination = false;
@observable isDone = false;
constructor (netVersion, address) {
transaction(() => {
this.setDestination(netVersion === '42');
this.setAddressReceive(address);
this.setAddressVerified(address);
});
}
@computed get canTransact () {
return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
}
@computed get addressReceiveValid () {
return apiutil.isAddressValid(this.addressReceive);
}
@computed get addressVerifiedValid () {
return apiutil.isAddressValid(this.addressVerified);
}
@action setAddressReceive = (address) => {
this.addressReceive = address;
}
@action setAddressVerified = (address) => {
this.addressVerified = address;
}
@action setBusy = (isBusy) => {
this.isBusy = isBusy;
}
@action setCompleted = (isCompleted) => {
transaction(() => {
this.setBusy(false);
this.isCompleted = isCompleted;
});
}
@action setDestination = (isDestination) => {
this.isDestination = isDestination;
}
@action setError = (error) => {
if (error.indexOf('not certified') !== -1) {
this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
} else {
this.error = error;
}
}
@action setResponse = (response) => {
this.responseText = response.result;
this.responseTxHash = response.tx;
}
makeItRain = () => {
this.setBusy(true);
const options = {
method: 'GET',
mode: 'cors'
};
const url = `${ENDPOINT}${this.addressVerified}`;
return fetch(url, options)
.then((response) => {
if (!response.ok) {
return null;
}
return response.json();
})
.catch(() => {
return null;
})
.then((response) => {
transaction(() => {
if (!response || response.error) {
this.setError(
response
? response.error
: 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
);
} else {
this.setResponse(response);
}
this.setCompleted(true);
});
});
}
}

View File

@@ -401,6 +401,7 @@ class WalletSettings extends Component {
const cancelBtn = (
<Button
icon={ <CancelIcon /> }
key='cancelBtn'
label={
<FormattedMessage
id='walletSettings.buttons.cancel'
@@ -414,6 +415,7 @@ class WalletSettings extends Component {
const closeBtn = (
<Button
icon={ <CancelIcon /> }
key='closeBtn'
label={
<FormattedMessage
id='walletSettings.buttons.close'
@@ -427,6 +429,7 @@ class WalletSettings extends Component {
const sendingBtn = (
<Button
icon={ <DoneIcon /> }
key='sendingBtn'
label={
<FormattedMessage
id='walletSettings.buttons.sending'
@@ -440,6 +443,7 @@ class WalletSettings extends Component {
const nextBtn = (
<Button
icon={ <NextIcon /> }
key='nextBtn'
label={
<FormattedMessage
id='walletSettings.buttons.next'
@@ -454,6 +458,7 @@ class WalletSettings extends Component {
const sendBtn = (
<Button
icon={ <NextIcon /> }
key='sendBtn'
label={
<FormattedMessage
id='walletSettings.buttons.send'

View File

@@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
export DeployContract from './DeployContract';
export EditMeta from './EditMeta';
export ExecuteContract from './ExecuteContract';
export Faucet from './Faucet';
export FirstRun from './FirstRun';
export LoadContract from './LoadContract';
export PasswordManager from './PasswordManager';