Skip to content

Commit

Permalink
Broadcast Tx (#304)
Browse files Browse the repository at this point in the history
* create MVP of broadcast TX component

* add broadcastTx to routes and tab options

* Update BroadcastTx path to /pushTx

* - add sanitizeHex and padLeftEven functions from V3

* - Move decodeTransaction logic out of ConfirmationModal.
- Add from key to getTransactionFields

* Simplify ConfirmationModal:

1. Move business logic out of component (decodeTransaction).
2. Don't pass node props when Component is already smart. Map from state instead.
3. Pass walletAddress instead of the entire wallet object to component as prop.
4. Handle

* - Don't map node state (child component grabs from state)
- implement and call setWalletAddressOnUpdate

* correct tab to path.

* 1. Integrate Confirmation Modal
2. Validate signedTx input and disable Send Transaction button when invalid

* disable tslint error. EthTx expect a Data type object, but a string is passed. However, tx object is created as expected. Need to investigate

* adjust type definition to match allowed string input. Remove tslint disable

* fix tslint errors

* add textarea valid/invalid stlying based on disabled status

* remove unused imports

* cleanup / address PR comments

* Address PR comments
  • Loading branch information
dternyak authored Oct 23, 2017
1 parent b94bede commit d72b478
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 79 deletions.
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} />
</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) {
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

0 comments on commit d72b478

Please sign in to comment.