2017-06-26 22:27:55 +00:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
import translate from 'translations';
|
|
|
|
import { UnlockHeader } from 'components/ui';
|
|
|
|
import {
|
2017-07-02 05:49:06 +00:00
|
|
|
Donate,
|
|
|
|
DataField,
|
|
|
|
CustomMessage,
|
|
|
|
GasField,
|
|
|
|
AmountField,
|
|
|
|
AddressField
|
2017-06-26 22:27:55 +00:00
|
|
|
} from './components';
|
2017-07-13 21:02:39 +00:00
|
|
|
import { BalanceSidebar } from 'components';
|
2017-06-26 22:27:55 +00:00
|
|
|
import pickBy from 'lodash/pickBy';
|
2017-06-29 23:03:11 +00:00
|
|
|
import type { State as AppState } from 'reducers';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import BaseWallet from 'libs/wallet/base';
|
2017-06-26 22:27:55 +00:00
|
|
|
// import type { Transaction } from './types';
|
|
|
|
import customMessages from './messages';
|
2017-07-04 00:20:47 +00:00
|
|
|
import { donationAddressMap } from 'config/data';
|
2017-08-08 03:45:08 +00:00
|
|
|
import { isValidETHAddress } from 'libs/validators';
|
2017-08-11 21:54:10 +00:00
|
|
|
import {
|
|
|
|
getNodeLib,
|
|
|
|
getNetworkConfig,
|
|
|
|
getGasPriceGwei
|
|
|
|
} from 'selectors/config';
|
2017-08-08 03:45:08 +00:00
|
|
|
import { getTokens } from 'selectors/wallet';
|
2017-08-11 21:54:10 +00:00
|
|
|
import type { Token, NetworkConfig } from 'config/data';
|
2017-08-08 03:45:08 +00:00
|
|
|
import Big from 'bignumber.js';
|
|
|
|
import { valueToHex } from 'libs/values';
|
|
|
|
import ERC20 from 'libs/erc20';
|
2017-07-15 23:05:57 +00:00
|
|
|
import type { TokenBalance } from 'selectors/wallet';
|
|
|
|
import { getTokenBalances } from 'selectors/wallet';
|
2017-08-11 21:54:10 +00:00
|
|
|
import type { RPCNode } from 'libs/nodes';
|
|
|
|
import type {
|
|
|
|
TransactionWithoutGas,
|
|
|
|
BroadcastTransaction
|
|
|
|
} from 'libs/transaction';
|
|
|
|
import type { UNIT } from 'libs/units';
|
|
|
|
import { toWei } from 'libs/units';
|
2017-08-08 03:45:08 +00:00
|
|
|
import { formatGasLimit } from 'utils/formatters';
|
2017-08-11 21:54:10 +00:00
|
|
|
import { showNotification } from 'actions/notifications';
|
|
|
|
import type { ShowNotificationAction } from 'actions/notifications';
|
2017-06-26 22:27:55 +00:00
|
|
|
|
|
|
|
type State = {
|
2017-07-02 05:49:06 +00:00
|
|
|
hasQueryString: boolean,
|
|
|
|
readOnly: boolean,
|
|
|
|
to: string,
|
|
|
|
value: string,
|
2017-08-11 21:54:10 +00:00
|
|
|
// $FlowFixMe - Comes from getParam not validating unit
|
|
|
|
unit: UNIT,
|
2017-07-02 05:49:06 +00:00
|
|
|
gasLimit: string,
|
|
|
|
data: string,
|
2017-08-11 21:54:10 +00:00
|
|
|
gasChanged: boolean,
|
|
|
|
transaction: ?BroadcastTransaction
|
2017-06-26 22:27:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function getParam(query: { [string]: string }, key: string) {
|
2017-07-02 05:49:06 +00:00
|
|
|
const keys = Object.keys(query);
|
|
|
|
const index = keys.findIndex(k => k.toLowerCase() === key.toLowerCase());
|
|
|
|
if (index === -1) {
|
|
|
|
return null;
|
|
|
|
}
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
return query[keys[index]];
|
2017-06-26 22:27:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO query string
|
|
|
|
// TODO how to handle DATA?
|
|
|
|
|
2017-07-15 23:05:57 +00:00
|
|
|
type Props = {
|
|
|
|
location: {
|
|
|
|
query: {
|
|
|
|
[string]: string
|
|
|
|
}
|
|
|
|
},
|
|
|
|
wallet: BaseWallet,
|
|
|
|
balance: Big,
|
2017-08-11 21:54:10 +00:00
|
|
|
nodeLib: RPCNode,
|
|
|
|
network: NetworkConfig,
|
2017-08-08 03:45:08 +00:00
|
|
|
tokens: Token[],
|
2017-08-11 21:54:10 +00:00
|
|
|
tokenBalances: TokenBalance[],
|
|
|
|
gasPrice: number,
|
|
|
|
showNotification: (
|
|
|
|
level: string,
|
|
|
|
msg: string,
|
|
|
|
duration?: number
|
|
|
|
) => ShowNotificationAction
|
2017-07-15 23:05:57 +00:00
|
|
|
};
|
|
|
|
|
2017-06-26 22:27:55 +00:00
|
|
|
export class SendTransaction extends React.Component {
|
2017-07-15 23:05:57 +00:00
|
|
|
props: Props;
|
2017-07-02 05:49:06 +00:00
|
|
|
state: State = {
|
|
|
|
hasQueryString: false,
|
|
|
|
readOnly: false,
|
|
|
|
// FIXME use correct defaults
|
|
|
|
to: '',
|
2017-07-15 23:05:57 +00:00
|
|
|
value: '',
|
2017-07-02 05:49:06 +00:00
|
|
|
unit: 'ether',
|
|
|
|
gasLimit: '21000',
|
|
|
|
data: '',
|
2017-08-11 21:54:10 +00:00
|
|
|
gasChanged: false,
|
|
|
|
transaction: null
|
2017-07-02 05:49:06 +00:00
|
|
|
};
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
componentDidMount() {
|
|
|
|
const queryPresets = pickBy(this.parseQuery());
|
|
|
|
if (Object.keys(queryPresets).length) {
|
|
|
|
this.setState({ ...queryPresets, hasQueryString: true });
|
2017-06-26 22:27:55 +00:00
|
|
|
}
|
2017-07-02 05:49:06 +00:00
|
|
|
}
|
|
|
|
|
2017-08-08 03:45:08 +00:00
|
|
|
componentDidUpdate(_prevProps: Props, prevState: State) {
|
|
|
|
// if gas is not changed
|
|
|
|
// and we have valid tx
|
|
|
|
// and relevant fields changed
|
|
|
|
// estimate gas
|
|
|
|
// TODO we might want to listen to gas price changes here
|
|
|
|
// TODO debunce the call
|
|
|
|
if (
|
|
|
|
!this.state.gasChanged &&
|
|
|
|
this.isValid() &&
|
|
|
|
(this.state.to !== prevState.to ||
|
|
|
|
this.state.value !== prevState.value ||
|
|
|
|
this.state.unit !== prevState.unit ||
|
|
|
|
this.state.data !== prevState.data)
|
|
|
|
) {
|
|
|
|
this.estimateGas();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
render() {
|
2017-07-04 03:21:19 +00:00
|
|
|
const unlocked = !!this.props.wallet;
|
2017-07-02 05:49:06 +00:00
|
|
|
const hasEnoughBalance = false;
|
2017-07-16 21:02:13 +00:00
|
|
|
const {
|
|
|
|
to,
|
|
|
|
value,
|
|
|
|
unit,
|
|
|
|
gasLimit,
|
|
|
|
data,
|
|
|
|
readOnly,
|
2017-08-11 21:54:10 +00:00
|
|
|
hasQueryString,
|
|
|
|
transaction
|
2017-07-16 21:02:13 +00:00
|
|
|
} = this.state;
|
2017-07-02 05:49:06 +00:00
|
|
|
const customMessage = customMessages.find(m => m.to === to);
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
// tokens
|
|
|
|
// ng-show="token.balance!=0 && token.balance!='loading' || token.type!=='default' || tokenVisibility=='shown'"
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
return (
|
|
|
|
<section className="container" style={{ minHeight: '50%' }}>
|
|
|
|
<div className="tab-content">
|
2017-07-06 00:36:11 +00:00
|
|
|
<main className="tab-pane active">
|
2017-07-02 05:49:06 +00:00
|
|
|
{hasQueryString &&
|
|
|
|
<div className="alert alert-info">
|
|
|
|
<p>
|
|
|
|
{translate('WARN_Send_Link')}
|
|
|
|
</p>
|
|
|
|
</div>}
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
<UnlockHeader title={'NAV_SendEther'} />
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
{unlocked &&
|
|
|
|
<article className="row">
|
|
|
|
{'' /* <!-- Sidebar --> */}
|
|
|
|
<section className="col-sm-4">
|
|
|
|
<div style={{ maxWidth: 350 }}>
|
2017-07-13 21:02:39 +00:00
|
|
|
<BalanceSidebar />
|
2017-07-02 05:49:06 +00:00
|
|
|
<hr />
|
|
|
|
<Donate onDonate={this.onNewTx} />
|
|
|
|
</div>
|
|
|
|
</section>
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
<section className="col-sm-8">
|
|
|
|
{readOnly &&
|
|
|
|
!hasEnoughBalance &&
|
|
|
|
<div className="row form-group">
|
|
|
|
<div className="alert alert-danger col-xs-12 clearfix">
|
|
|
|
<strong>
|
2017-07-16 21:02:13 +00:00
|
|
|
Warning! You do not have enough funds to complete this
|
|
|
|
swap.
|
2017-07-13 21:02:39 +00:00
|
|
|
</strong>
|
2017-07-02 05:49:06 +00:00
|
|
|
<br />
|
|
|
|
Please add more funds or access a different wallet.
|
|
|
|
</div>
|
|
|
|
</div>}
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
<div className="row form-group">
|
|
|
|
<h4 className="col-xs-12">
|
|
|
|
{translate('SEND_trans')}
|
|
|
|
</h4>
|
|
|
|
</div>
|
|
|
|
<AddressField
|
2017-07-04 00:20:47 +00:00
|
|
|
placeholder={donationAddressMap.ETH}
|
2017-07-02 05:49:06 +00:00
|
|
|
value={this.state.to}
|
|
|
|
onChange={readOnly ? null : this.onAddressChange}
|
|
|
|
/>
|
|
|
|
<AmountField
|
|
|
|
value={value}
|
|
|
|
unit={unit}
|
2017-07-15 23:05:57 +00:00
|
|
|
tokens={this.props.tokenBalances
|
|
|
|
.filter(token => !token.balance.eq(0))
|
|
|
|
.map(token => token.symbol)
|
|
|
|
.sort()}
|
2017-07-02 05:49:06 +00:00
|
|
|
onChange={readOnly ? void 0 : this.onAmountChange}
|
|
|
|
/>
|
2017-07-16 21:02:13 +00:00
|
|
|
<GasField
|
|
|
|
value={gasLimit}
|
|
|
|
onChange={readOnly ? void 0 : this.onGasChange}
|
|
|
|
/>
|
2017-07-02 05:49:06 +00:00
|
|
|
{unit === 'ether' &&
|
2017-07-16 21:02:13 +00:00
|
|
|
<DataField
|
|
|
|
value={data}
|
|
|
|
onChange={readOnly ? void 0 : this.onDataChange}
|
|
|
|
/>}
|
2017-07-02 05:49:06 +00:00
|
|
|
<CustomMessage message={customMessage} />
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
<div className="row form-group">
|
|
|
|
<div className="col-xs-12 clearfix">
|
2017-07-16 21:02:13 +00:00
|
|
|
<a
|
|
|
|
className="btn btn-info btn-block"
|
|
|
|
onClick={this.generateTx}
|
|
|
|
>
|
2017-07-02 05:49:06 +00:00
|
|
|
{translate('SEND_generate')}
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-06 00:36:11 +00:00
|
|
|
<div className="row form-group">
|
2017-07-02 05:49:06 +00:00
|
|
|
<div className="col-sm-6">
|
2017-07-13 21:02:39 +00:00
|
|
|
<label>
|
|
|
|
{translate('SEND_raw')}
|
|
|
|
</label>
|
2017-08-11 21:54:10 +00:00
|
|
|
<textarea
|
|
|
|
className="form-control"
|
|
|
|
value={transaction ? transaction.rawTx : ''}
|
|
|
|
rows="4"
|
|
|
|
readOnly
|
|
|
|
/>
|
2017-07-02 05:49:06 +00:00
|
|
|
</div>
|
|
|
|
<div className="col-sm-6">
|
2017-07-06 00:36:11 +00:00
|
|
|
<label>
|
|
|
|
{translate('SEND_signed')}
|
2017-07-02 05:49:06 +00:00
|
|
|
</label>
|
2017-08-11 21:54:10 +00:00
|
|
|
<textarea
|
|
|
|
className="form-control"
|
|
|
|
value={transaction ? transaction.signedTx : ''}
|
|
|
|
rows="4"
|
|
|
|
readOnly
|
|
|
|
/>
|
2017-07-02 05:49:06 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-06 00:36:11 +00:00
|
|
|
<div className="form-group">
|
2017-07-02 05:49:06 +00:00
|
|
|
<a
|
|
|
|
className="btn btn-primary btn-block col-sm-11"
|
|
|
|
data-toggle="modal"
|
|
|
|
data-target="#sendTransaction"
|
|
|
|
>
|
2017-07-06 00:36:11 +00:00
|
|
|
{translate('SEND_trans')}
|
2017-07-02 05:49:06 +00:00
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
{'' /* <!-- / Content --> */}
|
|
|
|
{
|
|
|
|
'' /* @@if (site === 'mew' ) { @@include( './sendTx-content.tpl', { "site": "mew" } ) }
|
2017-06-26 22:27:55 +00:00
|
|
|
@@if (site === 'cx' ) { @@include( './sendTx-content.tpl', { "site": "cx" } ) }
|
|
|
|
|
|
|
|
@@if (site === 'mew' ) { @@include( './sendTx-modal.tpl', { "site": "mew" } ) }
|
|
|
|
@@if (site === 'cx' ) { @@include( './sendTx-modal.tpl', { "site": "cx" } ) } */
|
2017-07-02 05:49:06 +00:00
|
|
|
}
|
|
|
|
</article>}
|
|
|
|
</main>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
);
|
|
|
|
}
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
parseQuery() {
|
|
|
|
const query = this.props.location.query;
|
|
|
|
const to = getParam(query, 'to');
|
|
|
|
const data = getParam(query, 'data');
|
|
|
|
// FIXME validate token against presets
|
|
|
|
const unit = getParam(query, 'tokenSymbol');
|
|
|
|
const value = getParam(query, 'value');
|
|
|
|
let gasLimit = getParam(query, 'gas');
|
|
|
|
if (gasLimit === null) {
|
|
|
|
gasLimit = getParam(query, 'limit');
|
2017-06-26 22:27:55 +00:00
|
|
|
}
|
2017-07-02 05:49:06 +00:00
|
|
|
const readOnly = getParam(query, 'readOnly') == null ? false : true;
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
return { to, data, value, unit, gasLimit, readOnly };
|
|
|
|
}
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-08-08 03:45:08 +00:00
|
|
|
isValid() {
|
|
|
|
const { to, value } = this.state;
|
|
|
|
return (
|
|
|
|
isValidETHAddress(to) &&
|
|
|
|
value &&
|
|
|
|
Number(value) > 0 &&
|
|
|
|
!isNaN(Number(value)) &&
|
|
|
|
isFinite(Number(value))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-08-11 21:54:10 +00:00
|
|
|
async getTransactionFromState(): Promise<TransactionWithoutGas> {
|
|
|
|
const { wallet } = this.props;
|
|
|
|
|
2017-08-08 03:45:08 +00:00
|
|
|
if (this.state.unit === 'ether') {
|
|
|
|
return {
|
|
|
|
to: this.state.to,
|
2017-08-11 21:54:10 +00:00
|
|
|
from: await wallet.getAddress(),
|
2017-08-08 03:45:08 +00:00
|
|
|
value: valueToHex(this.state.value)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
const token = this.props.tokens.find(x => x.symbol === this.state.unit);
|
|
|
|
if (!token) {
|
2017-08-11 21:54:10 +00:00
|
|
|
throw new Error('No matching token');
|
2017-08-08 03:45:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
to: token.address,
|
2017-08-11 21:54:10 +00:00
|
|
|
from: await wallet.getAddress(),
|
2017-08-08 03:45:08 +00:00
|
|
|
value: '0x0',
|
|
|
|
data: ERC20.transfer(
|
|
|
|
this.state.to,
|
|
|
|
new Big(this.state.value).times(new Big(10).pow(token.decimal))
|
|
|
|
)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-08-11 21:54:10 +00:00
|
|
|
async estimateGas() {
|
|
|
|
const trans = await this.getTransactionFromState();
|
2017-08-08 03:45:08 +00:00
|
|
|
if (!trans) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Grab a reference to state. If it has changed by the time the estimateGas
|
|
|
|
// call comes back, we don't want to replace the gasLimit in state.
|
|
|
|
const state = this.state;
|
|
|
|
|
2017-08-11 21:54:10 +00:00
|
|
|
this.props.nodeLib.estimateGas(trans).then(gasLimit => {
|
2017-08-08 03:45:08 +00:00
|
|
|
if (this.state === state) {
|
|
|
|
this.setState({ gasLimit: formatGasLimit(gasLimit, state.unit) });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
// FIXME use mkTx instead or something that could take care of default gas/data and whatnot,
|
|
|
|
onNewTx = (
|
|
|
|
address: string,
|
|
|
|
amount: string,
|
|
|
|
unit: string,
|
|
|
|
data: string = '',
|
|
|
|
gasLimit: string = '21000'
|
|
|
|
) => {
|
|
|
|
this.setState({
|
|
|
|
to: address,
|
|
|
|
value: amount,
|
|
|
|
unit,
|
|
|
|
data,
|
|
|
|
gasLimit,
|
|
|
|
gasChanged: false
|
|
|
|
});
|
|
|
|
};
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
onAddressChange = (value: string) => {
|
|
|
|
this.setState({
|
|
|
|
to: value
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
onDataChange = (value: string) => {
|
|
|
|
if (this.state.unit !== 'ether') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setState({
|
|
|
|
...this.state,
|
|
|
|
data: value
|
|
|
|
});
|
|
|
|
};
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
onGasChange = (value: string) => {
|
|
|
|
this.setState({ gasLimit: value, gasChanged: true });
|
|
|
|
};
|
2017-06-26 22:27:55 +00:00
|
|
|
|
2017-07-02 05:49:06 +00:00
|
|
|
onAmountChange = (value: string, unit: string) => {
|
2017-07-15 23:05:57 +00:00
|
|
|
// TODO sub gas for eth
|
2017-07-13 21:02:39 +00:00
|
|
|
if (value === 'everything') {
|
2017-07-15 23:05:57 +00:00
|
|
|
if (unit === 'ether') {
|
|
|
|
value = this.props.balance.toString();
|
|
|
|
}
|
2017-07-16 21:02:13 +00:00
|
|
|
const token = this.props.tokenBalances.find(
|
|
|
|
token => token.symbol === unit
|
|
|
|
);
|
2017-07-15 23:05:57 +00:00
|
|
|
if (!token) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
value = token.balance.toString();
|
2017-07-13 21:02:39 +00:00
|
|
|
}
|
2017-07-02 05:49:06 +00:00
|
|
|
this.setState({
|
|
|
|
value,
|
|
|
|
unit
|
|
|
|
});
|
|
|
|
};
|
2017-08-11 21:54:10 +00:00
|
|
|
|
|
|
|
generateTx = async () => {
|
|
|
|
const { nodeLib, wallet } = this.props;
|
|
|
|
const address = await wallet.getAddress();
|
|
|
|
|
|
|
|
try {
|
|
|
|
const transaction = await nodeLib.generateTransaction(
|
|
|
|
{
|
|
|
|
to: this.state.to,
|
|
|
|
from: address,
|
|
|
|
value: this.state.value,
|
|
|
|
gasLimit: this.state.gasLimit,
|
|
|
|
gasPrice: this.props.gasPrice,
|
|
|
|
data: this.state.data,
|
|
|
|
chainId: this.props.network.chainId
|
|
|
|
},
|
|
|
|
wallet
|
|
|
|
);
|
|
|
|
|
|
|
|
this.setState({ transaction });
|
|
|
|
} catch (err) {
|
|
|
|
this.props.showNotification('danger', err.message, 5000);
|
|
|
|
}
|
|
|
|
};
|
2017-06-26 22:27:55 +00:00
|
|
|
}
|
2017-06-29 23:03:11 +00:00
|
|
|
|
|
|
|
function mapStateToProps(state: AppState) {
|
2017-07-04 03:21:19 +00:00
|
|
|
return {
|
2017-07-13 21:02:39 +00:00
|
|
|
wallet: state.wallet.inst,
|
2017-07-15 23:05:57 +00:00
|
|
|
balance: state.wallet.balance,
|
2017-08-11 21:54:10 +00:00
|
|
|
tokenBalances: getTokenBalances(state),
|
|
|
|
nodeLib: getNodeLib(state),
|
|
|
|
network: getNetworkConfig(state),
|
2017-08-08 03:45:08 +00:00
|
|
|
tokens: getTokens(state),
|
2017-08-11 21:54:10 +00:00
|
|
|
gasPrice: toWei(new Big(getGasPriceGwei(state)), 'gwei')
|
2017-07-04 03:21:19 +00:00
|
|
|
};
|
2017-06-29 23:03:11 +00:00
|
|
|
}
|
|
|
|
|
2017-08-11 21:54:10 +00:00
|
|
|
export default connect(mapStateToProps, { showNotification })(SendTransaction);
|