Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broadcast Tx #304

Merged
merged 20 commits into from
Oct 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8fa842a
create MVP of broadcast TX component
dternyak Oct 18, 2017
c11e450
add broadcastTx to routes and tab options
dternyak Oct 18, 2017
90062e1
Update BroadcastTx path to /pushTx
dternyak Oct 18, 2017
5e904fc
- add sanitizeHex and padLeftEven functions from V3
dternyak Oct 18, 2017
3e09520
- Move decodeTransaction logic out of ConfirmationModal.
dternyak Oct 18, 2017
4bb8bc5
Simplify ConfirmationModal:
dternyak Oct 18, 2017
a7ad257
- Don't map node state (child component grabs from state)
dternyak Oct 18, 2017
296f697
correct tab to path.
dternyak Oct 18, 2017
5ed10c4
1. Integrate Confirmation Modal
dternyak Oct 18, 2017
42c5e43
disable tslint error. EthTx expect a Data type object, but a string i…
dternyak Oct 18, 2017
4b0d936
adjust type definition to match allowed string input. Remove tslint d…
dternyak Oct 18, 2017
c88aa9f
fix tslint errors
dternyak Oct 18, 2017
b902792
Merge branch 'develop' into broadcast-tx
dternyak Oct 18, 2017
d70aed9
Merge branch 'develop' into broadcast-tx
dternyak Oct 18, 2017
c9d5589
add textarea valid/invalid stlying based on disabled status
dternyak Oct 18, 2017
86a1066
Merge branch 'broadcast-tx' of https://github.com/MyEtherWallet/MyEth…
dternyak Oct 18, 2017
661fa10
Merge branch 'develop' of https://github.com/MyEtherWallet/MyEtherWal…
dternyak Oct 19, 2017
904b5c6
remove unused imports
dternyak Oct 19, 2017
8ae85e7
cleanup / address PR comments
dternyak Oct 23, 2017
70fe6cd
Address PR comments
dternyak Oct 23, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Help from 'containers/Tabs/Help';
import SendTransaction from 'containers/Tabs/SendTransaction';
import Swap from 'containers/Tabs/Swap';
import ViewWallet from 'containers/Tabs/ViewWallet';
import BroadcastTx from 'containers/Tabs/BroadcastTx';

// TODO: fix this
interface Props {
Expand All @@ -31,6 +32,7 @@ export default class Root extends Component<Props, {}> {
<Route path="/send-transaction" component={SendTransaction} />
<Route path="/contracts" component={Contracts} />
<Route path="/ens" component={ENS} />
<Route path="/pushTx" component={BroadcastTx} />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason for naming this pushTx instead of the format of broadcast-tx like the send-transaction route?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, to match etherscan.

</div>
</Router>
</Provider>
Expand Down
4 changes: 4 additions & 0 deletions common/components/Header/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const tabs = [
name: 'NAV_ENS',
to: 'ens'
},
{
name: 'Broadcast Transaction',
to: 'pushTx'
},
{
name: 'NAV_Help',
to: 'https://myetherwallet.groovehq.com/help_center',
Expand Down
7 changes: 7 additions & 0 deletions common/containers/Tabs/BroadcastTx/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@import "common/sass/variables";

.BroadcastTx {
&-title {
margin: $space auto $space * 2.5;
}
}
140 changes: 140 additions & 0 deletions common/containers/Tabs/BroadcastTx/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import TabSection from 'containers/TabSection';
import { translateRaw } from 'translations';
import { broadcastTx as dBroadcastTx, TBroadcastTx } from 'actions/wallet';
import { QRCode } from 'components/ui';
import './index.scss';
import {
BroadcastTransactionStatus,
getTransactionFields
} from 'libs/transaction';
import EthTx from 'ethereumjs-tx';
import { ConfirmationModal } from 'containers/Tabs/SendTransaction/components';
import classnames from 'classnames';

interface Props {
broadcastTx: TBroadcastTx;
transactions: BroadcastTransactionStatus[];
}

interface State {
signedTx: string;
showConfirmationModal: boolean;
disabled: boolean;
}

const initialState: State = {
showConfirmationModal: false,
signedTx: '',
disabled: true
};

class BroadcastTx extends Component<Props, State> {
public state = initialState;

public ensureValidSignedTxInputOnUpdate() {
try {
const tx = new EthTx(this.state.signedTx);
getTransactionFields(tx);
if (this.state.disabled) {
this.setState({ disabled: false });
}
} catch (e) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS lets you do } catch { ..rest of code here if you don't do anything with the error param

if (!this.state.disabled) {
this.setState({ disabled: true });
}
}
}

public componentDidUpdate() {
this.ensureValidSignedTxInputOnUpdate();
}

public render() {
const { signedTx, disabled, showConfirmationModal } = this.state;

const inputClasses = classnames({
'form-control': true,
'is-valid': !disabled,
'is-invalid': disabled
});

return (
<TabSection>
<div className="Tab-content-pane row block text-center">
<div className="col-md-6">
<div className="col-md-12 BroadcastTx-title">
<h2>Broadcast Signed Transaction</h2>
</div>
<p>
Paste a signed transaction and press the "SEND TRANSACTION"
button.
</p>
<label>{translateRaw('SEND_signed')}</label>
<textarea
className={inputClasses}
rows={7}
value={signedTx}
onChange={this.handleChange}
/>
<button
className="btn btn-primary"
disabled={disabled || signedTx === ''}
onClick={this.handleBroadcastTx}
>
{translateRaw('SEND_trans')}
</button>
</div>

<div className="col-md-6" style={{ marginTop: '70px' }}>
<div
className="qr-code text-center"
style={{
maxWidth: '15rem',
margin: '1rem auto',
width: '100%'
}}
>
{signedTx && <QRCode data={signedTx} />}
</div>
</div>
</div>
{showConfirmationModal && (
<ConfirmationModal
signedTx={signedTx}
onClose={this.handleClose}
onConfirm={this.handleConfirm}
/>
)}
</TabSection>
);
}

public handleClose = () => {
this.setState({ showConfirmationModal: false });
};

public handleBroadcastTx = () => {
this.setState({ showConfirmationModal: true });
};

public handleConfirm = () => {
this.props.broadcastTx(this.state.signedTx);
};

protected handleChange = event => {
this.setState({ signedTx: event.target.value });
};
}

function mapStateToProps(state: AppState) {
return {
transactions: state.wallet.transactions
};
}

export default connect(mapStateToProps, { broadcastTx: dBroadcastTx })(
BroadcastTx
);
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import Big from 'bignumber.js';
import Identicon from 'components/ui/Identicon';
import Modal, { IButton } from 'components/ui/Modal';
import Spinner from 'components/ui/Spinner';
import { NetworkConfig, NodeConfig } from 'config/data';
import EthTx from 'ethereumjs-tx';
import ERC20 from 'libs/erc20';
import {
BroadcastTransactionStatus,
getTransactionFields
getTransactionFields,
decodeTransaction
} from 'libs/transaction';
import { toTokenDisplay, toUnit } from 'libs/units';
import { IWallet } from 'libs/wallet/IWallet';
import React from 'react';
import { connect } from 'react-redux';
import { getLanguageSelection, getNetworkConfig } from 'selectors/config';
import {
getLanguageSelection,
getNetworkConfig,
getNodeConfig
} from 'selectors/config';
import { getTokens, getTxFromState, MergedToken } from 'selectors/wallet';
import translate, { translateRaw } from 'translations';
import './ConfirmationModal.scss';

interface Props {
signedTx: string;
transaction: EthTx;
wallet: IWallet;
node: NodeConfig;
token: MergedToken | undefined;
token: MergedToken;
network: NetworkConfig;
lang: string;
broadCastTxStatus: BroadcastTransactionStatus;
Expand All @@ -32,30 +32,22 @@ interface Props {
}

interface State {
fromAddress: string;
timeToRead: number;
hasBroadCasted: boolean;
}

class ConfirmationModal extends React.Component<Props, State> {
public state = {
fromAddress: '',
timeToRead: 5,
hasBroadCasted: false
};

private readTimer = 0;

public componentWillReceiveProps(newProps: Props) {
// Reload address if the wallet changes
if (newProps.wallet !== this.props.wallet) {
this.setWalletAddress(this.props.wallet);
}
}

public componentDidUpdate() {
if (
this.state.hasBroadCasted &&
this.props.broadCastTxStatus &&
!this.props.broadCastTxStatus.isBroadcasting
) {
this.props.onClose();
Expand All @@ -71,20 +63,22 @@ class ConfirmationModal extends React.Component<Props, State> {
window.clearInterval(this.readTimer);
}
}, 1000);

this.setWalletAddress(this.props.wallet);
}

public render() {
const { node, token, network, onClose, broadCastTxStatus } = this.props;
const { fromAddress, timeToRead } = this.state;
const {
toAddress,
value,
gasPrice,
data,
nonce
} = this.decodeTransaction();
node,
token,
network,
onClose,
broadCastTxStatus,
transaction
} = this.props;
const { timeToRead } = this.state;
const { toAddress, value, gasPrice, data, from, nonce } = decodeTransaction(
transaction,
token
);

const buttonPrefix = timeToRead > 0 ? `(${timeToRead}) ` : '';
const buttons: IButton[] = [
Expand Down Expand Up @@ -124,7 +118,7 @@ class ConfirmationModal extends React.Component<Props, State> {
<div>
<div className="ConfModal-summary">
<div className="ConfModal-summary-icon ConfModal-summary-icon--from">
<Identicon size="100%" address={fromAddress} />
<Identicon size="100%" address={from} />
</div>
<div className="ConfModal-summary-amount">
<div className="ConfModal-summary-amount-arrow" />
Expand All @@ -139,7 +133,7 @@ class ConfirmationModal extends React.Component<Props, State> {

<ul className="ConfModal-details">
<li className="ConfModal-details-detail">
You are sending from <code>{fromAddress}</code>
You are sending from <code>{from}</code>
</li>
<li className="ConfModal-details-detail">
You are sending to <code>{toAddress}</code>
Expand Down Expand Up @@ -192,38 +186,6 @@ class ConfirmationModal extends React.Component<Props, State> {
window.clearInterval(this.readTimer);
}

private async setWalletAddress(wallet: IWallet) {
// TODO move getAddress to saga
const fromAddress = await wallet.getAddress();
this.setState({ fromAddress });
}

private decodeTransaction() {
const { transaction, token } = this.props;
const { to, value, data, gasPrice, nonce } = getTransactionFields(
transaction
);
let fixedValue;
let toAddress;

if (token) {
const tokenData = ERC20.$transfer(data);
fixedValue = toTokenDisplay(new Big(tokenData.value), token).toString();
toAddress = tokenData.to;
} else {
fixedValue = toUnit(new Big(value, 16), 'wei', 'ether').toString();
toAddress = to;
}

return {
value: fixedValue,
gasPrice: toUnit(new Big(gasPrice, 16), 'wei', 'gwei').toString(),
data,
toAddress,
nonce
};
}

private confirm = () => {
if (this.state.timeToRead < 1) {
this.props.onConfirm(this.props.signedTx);
Expand All @@ -239,6 +201,8 @@ function mapStateToProps(state, props) {
// Network config for defaults
const network = getNetworkConfig(state);

const node = getNodeConfig(state);

const lang = getLanguageSelection(state);

const broadCastTxStatus = getTxFromState(state, props.signedTx);
Expand All @@ -249,6 +213,7 @@ function mapStateToProps(state, props) {
const token = data && tokens.find(t => t.address === to);

return {
node,
broadCastTxStatus,
transaction,
token,
Expand Down
Loading