Several fixes to the Wallet in general (#4504)
* Fix address non-ellipsis on Wallet view * Add warning if daily limit has been reached * Add OOG warnings to TxHash * Fixes to the Warning * Added stringified version of the changes * Add wallet link for notifications * Fix tests * Use Formatted Messages * React Intl * s/ui.walletSettings/walletSettings in React Intl
This commit is contained in:
parent
da2e28dad1
commit
ace5c27211
@ -20,6 +20,7 @@ import { uniq } from 'lodash';
|
|||||||
|
|
||||||
import { wallet as walletAbi } from '~/contracts/abi';
|
import { wallet as walletAbi } from '~/contracts/abi';
|
||||||
import { bytesToHex } from '~/api/util/format';
|
import { bytesToHex } from '~/api/util/format';
|
||||||
|
import { fromWei } from '~/api/util/wei';
|
||||||
import Contract from '~/api/contract';
|
import Contract from '~/api/contract';
|
||||||
import ERRORS from './errors';
|
import ERRORS from './errors';
|
||||||
import { ERROR_CODES } from '~/api/transport/error';
|
import { ERROR_CODES } from '~/api/transport/error';
|
||||||
@ -39,6 +40,8 @@ const TITLES = {
|
|||||||
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
||||||
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
|
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
|
||||||
|
|
||||||
|
export const WALLET_WARNING_SPENT_TODAY_LIMIT = 'WALLET_WARNING_SPENT_TODAY_LIMIT';
|
||||||
|
|
||||||
export default class TransferStore {
|
export default class TransferStore {
|
||||||
@observable stage = 0;
|
@observable stage = 0;
|
||||||
@observable extras = false;
|
@observable extras = false;
|
||||||
@ -65,6 +68,8 @@ export default class TransferStore {
|
|||||||
@observable value = '0.0';
|
@observable value = '0.0';
|
||||||
@observable valueError = null;
|
@observable valueError = null;
|
||||||
|
|
||||||
|
@observable walletWarning = null;
|
||||||
|
|
||||||
account = null;
|
account = null;
|
||||||
balance = null;
|
balance = null;
|
||||||
onClose = null;
|
onClose = null;
|
||||||
@ -332,6 +337,21 @@ export default class TransferStore {
|
|||||||
valueError = this._validateDecimals(value);
|
valueError = this._validateDecimals(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isWallet && !valueError) {
|
||||||
|
const { last, limit, spent } = this.wallet.dailylimit;
|
||||||
|
const remains = fromWei(limit.minus(spent));
|
||||||
|
const today = Math.round(Date.now() / (24 * 3600 * 1000));
|
||||||
|
const isResetable = last.lt(today);
|
||||||
|
|
||||||
|
if ((!isResetable && remains.lt(value)) || fromWei(limit).lt(value)) {
|
||||||
|
// already spent too much today
|
||||||
|
this.walletWarning = WALLET_WARNING_SPENT_TODAY_LIMIT;
|
||||||
|
} else if (this.walletWarning) {
|
||||||
|
// all ok
|
||||||
|
this.walletWarning = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.valueError = valueError;
|
this.valueError = valueError;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
@ -28,7 +29,7 @@ import { nullableProptype } from '~/util/proptypes';
|
|||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import Extras from './Extras';
|
import Extras from './Extras';
|
||||||
|
|
||||||
import TransferStore from './store';
|
import TransferStore, { WALLET_WARNING_SPENT_TODAY_LIMIT } from './store';
|
||||||
import styles from './transfer.css';
|
import styles from './transfer.css';
|
||||||
|
|
||||||
const STEP_DETAILS = 0;
|
const STEP_DETAILS = 0;
|
||||||
@ -71,6 +72,7 @@ class Transfer extends Component {
|
|||||||
visible
|
visible
|
||||||
>
|
>
|
||||||
{ this.renderExceptionWarning() }
|
{ this.renderExceptionWarning() }
|
||||||
|
{ this.renderWalletWarning() }
|
||||||
{ this.renderPage() }
|
{ this.renderPage() }
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@ -89,6 +91,29 @@ class Transfer extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderWalletWarning () {
|
||||||
|
const { walletWarning } = this.store;
|
||||||
|
|
||||||
|
if (!walletWarning) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walletWarning === WALLET_WARNING_SPENT_TODAY_LIMIT) {
|
||||||
|
const warning = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='transfer.warning.wallet_spent_limit'
|
||||||
|
defaultMessage='This transaction value is above the remaining daily limit. It will need to be confirmed by other owners.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Warning warning={ warning } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
renderAccount () {
|
renderAccount () {
|
||||||
const { account } = this.props;
|
const { account } = this.props;
|
||||||
|
|
||||||
|
@ -61,3 +61,8 @@
|
|||||||
margin-left: 0.125em;
|
margin-left: 0.125em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modifications {
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
@ -23,7 +24,7 @@ import ActionDone from 'material-ui/svg-icons/action/done';
|
|||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
|
|
||||||
import { Button, Modal, TxHash, BusyStep, Form, TypedInput, InputAddress, AddressSelect } from '~/ui';
|
import { Button, Modal, TxHash, BusyStep, Form, TypedInput, Input, InputAddress, AddressSelect } from '~/ui';
|
||||||
import { fromWei } from '~/api/util/wei';
|
import { fromWei } from '~/api/util/wei';
|
||||||
|
|
||||||
import WalletSettingsStore from './walletSettingsStore.js';
|
import WalletSettingsStore from './walletSettingsStore.js';
|
||||||
@ -51,12 +52,27 @@ class WalletSettings extends Component {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible
|
visible
|
||||||
title='rejected'
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.rejected.title'
|
||||||
|
defaultMessage='rejected'
|
||||||
|
/>
|
||||||
|
}
|
||||||
actions={ this.renderDialogActions() }
|
actions={ this.renderDialogActions() }
|
||||||
>
|
>
|
||||||
<BusyStep
|
<BusyStep
|
||||||
title='The modifications have been rejected'
|
title={
|
||||||
state='The wallet settings will not be modified. You can safely close this window.'
|
<FormattedMessage
|
||||||
|
id='walletSettings.rejected.busyStep.title'
|
||||||
|
defaultMessage='The modifications have been rejected'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
state={
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.rejected.busyStep.state'
|
||||||
|
defaultMessage='The wallet settings will not be modified. You can safely close this window.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@ -112,57 +128,124 @@ class WalletSettings extends Component {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
case 'EDIT':
|
case 'EDIT':
|
||||||
const { wallet, errors } = this.store;
|
const { errors, fromString, wallet } = this.store;
|
||||||
const { accountsInfo, senders } = this.props;
|
const { accountsInfo, senders } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<p>
|
<p>
|
||||||
In order to edit this contract's settings, at
|
<FormattedMessage
|
||||||
least { this.store.initialWallet.require.toNumber() } owners have to
|
id='walletSettings.edit.message'
|
||||||
send the very same modifications.
|
defaultMessage={
|
||||||
Otherwise, no modification will be taken into account...
|
`In order to edit this contract's settings, at
|
||||||
|
least { owners, number } { owners, plural,
|
||||||
|
one { owner }
|
||||||
|
other { owners }
|
||||||
|
} have to
|
||||||
|
send the very same modifications. You can paste a stringified version
|
||||||
|
of the modifications here.`
|
||||||
|
}
|
||||||
|
values={ {
|
||||||
|
owners: this.store.initialWallet.require.toNumber()
|
||||||
|
} }
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
hint='[ ... ]'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.modifications.fromString.label'
|
||||||
|
defaultMessage='modifications'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onChange={ this.store.onModificationsStringChange }
|
||||||
|
/>
|
||||||
|
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
label='from account (wallet owner)'
|
label={
|
||||||
hint='send modifications as this owner'
|
<FormattedMessage
|
||||||
|
id='walletSettings.modifications.sender.label'
|
||||||
|
defaultMessage='from account (wallet owner)'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.modifications.sender.hint'
|
||||||
|
defaultMessage='send modifications as this owner'
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={ wallet.sender }
|
value={ wallet.sender }
|
||||||
error={ errors.sender }
|
error={ errors.sender }
|
||||||
onChange={ this.store.onSenderChange }
|
onChange={ this.store.onSenderChange }
|
||||||
accounts={ senders }
|
accounts={ senders }
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TypedInput
|
<br />
|
||||||
label='other wallet owners'
|
|
||||||
value={ wallet.owners.slice() }
|
|
||||||
onChange={ this.store.onOwnersChange }
|
|
||||||
accounts={ accountsInfo }
|
|
||||||
param='address[]'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={ styles.splitInput }>
|
{
|
||||||
<TypedInput
|
fromString
|
||||||
label='required owners'
|
? null
|
||||||
hint='number of required owners to accept a transaction'
|
: (
|
||||||
error={ errors.require }
|
<div>
|
||||||
min={ 1 }
|
<TypedInput
|
||||||
onChange={ this.store.onRequireChange }
|
label={
|
||||||
max={ wallet.owners.length }
|
<FormattedMessage
|
||||||
param='uint'
|
id='walletSettings.modifications.owners.label'
|
||||||
value={ wallet.require }
|
defaultMessage='other wallet owners'
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
value={ wallet.owners.slice() }
|
||||||
|
onChange={ this.store.onOwnersChange }
|
||||||
|
accounts={ accountsInfo }
|
||||||
|
param='address[]'
|
||||||
|
/>
|
||||||
|
|
||||||
<TypedInput
|
<div className={ styles.splitInput }>
|
||||||
label='wallet day limit'
|
<TypedInput
|
||||||
hint='amount of ETH spendable without confirmations'
|
label={
|
||||||
value={ wallet.dailylimit }
|
<FormattedMessage
|
||||||
error={ errors.dailylimit }
|
id='walletSettings.modifications.required.label'
|
||||||
onChange={ this.store.onDailylimitChange }
|
defaultMessage='required owners'
|
||||||
param='uint'
|
/>
|
||||||
isEth
|
}
|
||||||
/>
|
hint={
|
||||||
</div>
|
<FormattedMessage
|
||||||
|
id='walletSettings.modifications.required.hint'
|
||||||
|
defaultMessage='number of required owners to accept a transaction'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
error={ errors.require }
|
||||||
|
min={ 1 }
|
||||||
|
onChange={ this.store.onRequireChange }
|
||||||
|
max={ wallet.owners.length }
|
||||||
|
param='uint'
|
||||||
|
value={ wallet.require }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TypedInput
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.modifications.daylimit.label'
|
||||||
|
defaultMessage='wallet day limit'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.modifications.daylimit.hint'
|
||||||
|
defaultMessage='amount of ETH spendable without confirmations'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={ wallet.dailylimit }
|
||||||
|
error={ errors.dailylimit }
|
||||||
|
onChange={ this.store.onDailylimitChange }
|
||||||
|
param='uint'
|
||||||
|
isEth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -171,7 +254,12 @@ class WalletSettings extends Component {
|
|||||||
renderChanges (changes) {
|
renderChanges (changes) {
|
||||||
if (changes.length === 0) {
|
if (changes.length === 0) {
|
||||||
return (
|
return (
|
||||||
<p>No modifications have been made to the Wallet settings.</p>
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.changes.none'
|
||||||
|
defaultMessage='No modifications have been made to the Wallet settings.'
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +271,31 @@ class WalletSettings extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>You are about to make the following modifications</p>
|
<p className={ styles.modifications }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.changes.modificationString'
|
||||||
|
defaultMessage={
|
||||||
|
`For your modifications to be taken into account,
|
||||||
|
other owners have to send the same modifications. They can paste
|
||||||
|
this string to make it easier:`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
allowCopy
|
||||||
|
label='modifications'
|
||||||
|
readOnly
|
||||||
|
value={ this.store.changesToString() }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id='walletSettings.changes.overview'
|
||||||
|
defaultMessage={
|
||||||
|
`You are about to make the following modifications`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
{ modifications }
|
{ modifications }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -30,10 +30,11 @@ const STEPS = {
|
|||||||
export default class WalletSettingsStore {
|
export default class WalletSettingsStore {
|
||||||
accounts = {};
|
accounts = {};
|
||||||
|
|
||||||
@observable step = null;
|
|
||||||
@observable requests = [];
|
|
||||||
@observable deployState = '';
|
@observable deployState = '';
|
||||||
@observable done = false;
|
@observable done = false;
|
||||||
|
@observable fromString = false;
|
||||||
|
@observable requests = [];
|
||||||
|
@observable step = null;
|
||||||
|
|
||||||
@observable wallet = {
|
@observable wallet = {
|
||||||
owners: null,
|
owners: null,
|
||||||
@ -79,6 +80,51 @@ export default class WalletSettingsStore {
|
|||||||
.map((s) => s.idx);
|
.map((s) => s.idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
changesFromString (json) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(json);
|
||||||
|
const changes = data.map((datum) => {
|
||||||
|
const [ type, valueStr ] = datum.split(';');
|
||||||
|
|
||||||
|
let value = valueStr;
|
||||||
|
|
||||||
|
// Only addresses start with `0x`, the others
|
||||||
|
// are BigNumbers
|
||||||
|
if (!/^0x/.test(valueStr)) {
|
||||||
|
value = new BigNumber(valueStr, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type, value };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.changes = changes;
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof SyntaxError)) {
|
||||||
|
console.error('changes from string', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changesToString () {
|
||||||
|
const changes = this.changes.map((change) => {
|
||||||
|
const { type, value } = change;
|
||||||
|
|
||||||
|
const valueStr = (value && typeof value.plus === 'function')
|
||||||
|
? value.toString(16)
|
||||||
|
: value;
|
||||||
|
|
||||||
|
return [
|
||||||
|
type,
|
||||||
|
valueStr
|
||||||
|
].join(';');
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(changes);
|
||||||
|
}
|
||||||
|
|
||||||
get changes () {
|
get changes () {
|
||||||
const changes = [];
|
const changes = [];
|
||||||
|
|
||||||
@ -127,6 +173,36 @@ export default class WalletSettingsStore {
|
|||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set changes (changes) {
|
||||||
|
transaction(() => {
|
||||||
|
this.wallet.dailylimit = this.initialWallet.dailylimit;
|
||||||
|
this.wallet.require = this.initialWallet.require;
|
||||||
|
this.wallet.owners = this.initialWallet.owners.slice();
|
||||||
|
|
||||||
|
changes.forEach((change) => {
|
||||||
|
const { type, value } = change;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'dailylimit':
|
||||||
|
this.wallet.dailylimit = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'require':
|
||||||
|
this.wallet.require = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'remove_owner':
|
||||||
|
this.wallet.owners = this.wallet.owners.filter((owner) => owner !== value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'add_owner':
|
||||||
|
this.wallet.owners.push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor (api, wallet) {
|
constructor (api, wallet) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.step = this.stepsKeys[0];
|
this.step = this.stepsKeys[0];
|
||||||
@ -177,6 +253,16 @@ export default class WalletSettingsStore {
|
|||||||
this.onChange({ dailylimit });
|
this.onChange({ dailylimit });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action onModificationsStringChange = (event, value) => {
|
||||||
|
this.changesFromString(value);
|
||||||
|
|
||||||
|
if (this.changes && this.changes.length > 0) {
|
||||||
|
this.fromString = true;
|
||||||
|
} else {
|
||||||
|
this.fromString = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@action send = () => {
|
@action send = () => {
|
||||||
const changes = this.changes;
|
const changes = this.changes;
|
||||||
const walletInstance = this.walletInstance;
|
const walletInstance = this.walletInstance;
|
||||||
|
@ -90,7 +90,11 @@ function setBalances (_balances, skipNotifications = false) {
|
|||||||
const txValue = value.minus(prevValue);
|
const txValue = value.minus(prevValue);
|
||||||
|
|
||||||
const redirectToAccount = () => {
|
const redirectToAccount = () => {
|
||||||
const route = `/accounts/${account.address}`;
|
const basePath = account.wallet
|
||||||
|
? 'wallet'
|
||||||
|
: 'accounts';
|
||||||
|
|
||||||
|
const route = `/${basePath}/${account.address}`;
|
||||||
|
|
||||||
dispatch(push(route));
|
dispatch(push(route));
|
||||||
};
|
};
|
||||||
|
@ -21,8 +21,9 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { txLink } from '~/3rdparty/etherscan/links';
|
import { txLink } from '~/3rdparty/etherscan/links';
|
||||||
import ShortenedHash from '../ShortenedHash';
|
import Warning from '~/ui/Warning';
|
||||||
|
|
||||||
|
import ShortenedHash from '../ShortenedHash';
|
||||||
import styles from './txHash.css';
|
import styles from './txHash.css';
|
||||||
|
|
||||||
class TxHash extends Component {
|
class TxHash extends Component {
|
||||||
@ -43,10 +44,44 @@ class TxHash extends Component {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
blockNumber: new BigNumber(0),
|
blockNumber: new BigNumber(0),
|
||||||
|
gas: {},
|
||||||
subscriptionId: null,
|
subscriptionId: null,
|
||||||
transaction: null
|
transaction: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.fetchTransactionGas();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
const prevHash = this.props.hash;
|
||||||
|
const nextHash = nextProps.hash;
|
||||||
|
|
||||||
|
if (prevHash !== nextHash) {
|
||||||
|
this.fetchTransactionGas(nextProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the gas send for the current transaction
|
||||||
|
* and save the value in the state
|
||||||
|
*/
|
||||||
|
fetchTransactionGas (props = this.props) {
|
||||||
|
const { hash } = props;
|
||||||
|
|
||||||
|
if (!hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.context.api.eth
|
||||||
|
.getTransactionByHash(hash)
|
||||||
|
.then((transaction = {}) => {
|
||||||
|
const { gas = new BigNumber(0) } = transaction;
|
||||||
|
|
||||||
|
this.setState({ gas: { hash, value: gas } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
|
|
||||||
@ -73,6 +108,7 @@ class TxHash extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{ this.renderWarning() }
|
||||||
<p>{
|
<p>{
|
||||||
summary
|
summary
|
||||||
? hashLink
|
? hashLink
|
||||||
@ -87,6 +123,32 @@ class TxHash extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderWarning () {
|
||||||
|
const { gas, transaction } = this.state;
|
||||||
|
|
||||||
|
if (!(transaction && transaction.blockNumber && transaction.blockNumber.gt(0))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { gasUsed = new BigNumber(0) } = transaction;
|
||||||
|
const isOog = transaction.transactionHash === gas.hash && gasUsed.gte(gas.value);
|
||||||
|
|
||||||
|
if (!isOog) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Warning
|
||||||
|
warning={
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txHash.oog'
|
||||||
|
defaultMessage='The transaction might have gone out of gas. Try again with more gas.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderConfirmations () {
|
renderConfirmations () {
|
||||||
const { maxConfirmations } = this.props;
|
const { maxConfirmations } = this.props;
|
||||||
const { blockNumber, transaction } = this.state;
|
const { blockNumber, transaction } = this.state;
|
||||||
|
@ -33,10 +33,17 @@ function createApi () {
|
|||||||
blockNumber = new BigNumber(100);
|
blockNumber = new BigNumber(100);
|
||||||
api = {
|
api = {
|
||||||
eth: {
|
eth: {
|
||||||
|
getTransactionByHash: (hash) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
blockNumber: new BigNumber(100),
|
||||||
|
gas: new BigNumber(42000)
|
||||||
|
});
|
||||||
|
},
|
||||||
getTransactionReceipt: (hash) => {
|
getTransactionReceipt: (hash) => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
blockNumber: new BigNumber(100),
|
blockNumber: new BigNumber(100),
|
||||||
hash
|
transactionHash: hash,
|
||||||
|
gasUsed: new BigNumber(42000)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -129,6 +136,14 @@ describe('ui/TxHash', () => {
|
|||||||
it('renders confirmation text', () => {
|
it('renders confirmation text', () => {
|
||||||
expect(child.find('FormattedMessage').props().id).to.equal('ui.txHash.confirmations');
|
expect(child.find('FormattedMessage').props().id).to.equal('ui.txHash.confirmations');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders with warnings', () => {
|
||||||
|
expect(component.find('Warning')).to.have.length.gte(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with oog warning', () => {
|
||||||
|
expect(component.find('Warning').shallow().find('FormattedMessage').prop('id')).to.match(/oog/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -59,9 +59,15 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addressline {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.address {
|
.address {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: .5em;
|
margin-left: 0.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
.header {
|
.header {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
|
Loading…
Reference in New Issue
Block a user