mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-01-11 03:26:14 +00:00
Refactor Send (#164)
* refactor SendTransaction component and transaction helper lib * move transaction generation out of component; refactor tx types and simply tx generation * remove commented out try/catch * address todo; rename function/types * fix imports and address comments
This commit is contained in:
parent
0989424d73
commit
e3d3b2c8e8
@ -17,7 +17,7 @@ import type { Token, NetworkConfig } from 'config/data';
|
||||
import Modal from 'components/ui/Modal';
|
||||
import Identicon from 'components/ui/Identicon';
|
||||
import Spinner from 'components/ui/Spinner';
|
||||
import type { BroadcastStatusTransaction } from 'libs/transaction';
|
||||
import type { BroadcastTransactionStatus } from 'libs/transaction';
|
||||
|
||||
type Props = {
|
||||
signedTx: string,
|
||||
@ -29,7 +29,7 @@ type Props = {
|
||||
onConfirm: (string, EthTx) => void,
|
||||
onClose: () => void,
|
||||
lang: string,
|
||||
broadCastStatusTx: BroadcastStatusTransaction
|
||||
broadCastTxStatus: BroadcastTransactionStatus
|
||||
};
|
||||
|
||||
type State = {
|
||||
@ -62,7 +62,7 @@ class ConfirmationModal extends React.Component {
|
||||
componentDidUpdate() {
|
||||
if (
|
||||
this.state.hasBroadCasted &&
|
||||
!this.props.broadCastStatusTx.isBroadcasting
|
||||
!this.props.broadCastTxStatus.isBroadcasting
|
||||
) {
|
||||
this.props.onClose();
|
||||
}
|
||||
@ -124,7 +124,7 @@ class ConfirmationModal extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { node, token, network, onClose, broadCastStatusTx } = this.props;
|
||||
const { node, token, network, onClose, broadCastTxStatus } = this.props;
|
||||
const { fromAddress, timeToRead } = this.state;
|
||||
const { toAddress, value, gasPrice, data } = this._decodeTransaction();
|
||||
|
||||
@ -146,7 +146,7 @@ class ConfirmationModal extends React.Component {
|
||||
const symbol = token ? token.symbol : network.unit;
|
||||
|
||||
const isBroadcasting =
|
||||
broadCastStatusTx && broadCastStatusTx.isBroadcasting;
|
||||
broadCastTxStatus && broadCastTxStatus.isBroadcasting;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -233,7 +233,7 @@ function mapStateToProps(state, props) {
|
||||
|
||||
const lang = getLanguageSelection(state);
|
||||
|
||||
const broadCastStatusTx = getTxFromState(state, props.signedTx);
|
||||
const broadCastTxStatus = getTxFromState(state, props.signedTx);
|
||||
|
||||
// Determine if we're sending to a token from the transaction to address
|
||||
const { to, data } = getTransactionFields(transaction);
|
||||
@ -241,7 +241,7 @@ function mapStateToProps(state, props) {
|
||||
const token = data && tokens.find(t => t.address === to);
|
||||
|
||||
return {
|
||||
broadCastStatusTx,
|
||||
broadCastTxStatus,
|
||||
transaction,
|
||||
token,
|
||||
network,
|
||||
|
@ -1,7 +1,23 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
// UTILS
|
||||
import { formatGasLimit } from 'utils/formatters';
|
||||
import translate from 'translations';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
// SELECTORS
|
||||
import { getNodeConfig } from 'selectors/config';
|
||||
import {
|
||||
getNodeLib,
|
||||
getNetworkConfig,
|
||||
getGasPriceGwei
|
||||
} from 'selectors/config';
|
||||
import {
|
||||
getTokenBalances,
|
||||
getTxFromBroadcastTransactionStatus
|
||||
} from 'selectors/wallet';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
import type { TokenBalance } from 'selectors/wallet';
|
||||
// COMPONENTS
|
||||
import { UnlockHeader } from 'components/ui';
|
||||
import {
|
||||
Donate,
|
||||
@ -13,45 +29,37 @@ import {
|
||||
ConfirmationModal
|
||||
} from './components';
|
||||
import { BalanceSidebar } from 'components';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import type { State as AppState } from 'reducers';
|
||||
import { connect } from 'react-redux';
|
||||
import BaseWallet from 'libs/wallet/base';
|
||||
// import type { Transaction } from './types';
|
||||
import customMessages from './messages';
|
||||
import { donationAddressMap } from 'config/data';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import {
|
||||
getNodeLib,
|
||||
getNetworkConfig,
|
||||
getGasPriceGwei
|
||||
} from 'selectors/config';
|
||||
import { getTokens } from 'selectors/wallet';
|
||||
// CONFIG
|
||||
import type { NodeConfig } from 'config/data';
|
||||
import type { Token, NetworkConfig } from 'config/data';
|
||||
import Big from 'bignumber.js';
|
||||
import { valueToHex } from 'libs/values';
|
||||
import ERC20 from 'libs/erc20';
|
||||
import type { TokenBalance } from 'selectors/wallet';
|
||||
import {
|
||||
getTokenBalances,
|
||||
getTxFromBroadcastStatusTransactions
|
||||
} from 'selectors/wallet';
|
||||
import type { RPCNode } from 'libs/nodes';
|
||||
import { donationAddressMap } from 'config/data';
|
||||
// REDUX
|
||||
import { connect } from 'react-redux';
|
||||
import type { State as AppState } from 'reducers';
|
||||
import { broadcastTx } from 'actions/wallet';
|
||||
import type { BroadcastTxRequestedAction } from 'actions/wallet';
|
||||
import type { BroadcastStatusTransaction } from 'libs/transaction';
|
||||
import type {
|
||||
TransactionWithoutGas,
|
||||
BroadcastTransaction
|
||||
} from 'libs/transaction';
|
||||
import type { UNIT } from 'libs/units';
|
||||
import { toWei, toTokenUnit } from 'libs/units';
|
||||
import { formatGasLimit } from 'utils/formatters';
|
||||
import { showNotification } from 'actions/notifications';
|
||||
import type { ShowNotificationAction } from 'actions/notifications';
|
||||
import type { NodeConfig } from 'config/data';
|
||||
import { getNodeConfig } from 'selectors/config';
|
||||
import { generateTransaction, getBalanceMinusGasCosts } from 'libs/transaction';
|
||||
// LIBS
|
||||
import BaseWallet from 'libs/wallet/base';
|
||||
import { isValidETHAddress } from 'libs/validators';
|
||||
import type { RPCNode } from 'libs/nodes';
|
||||
import type {
|
||||
BroadcastTransactionStatus,
|
||||
TransactionInput,
|
||||
CompleteTransaction
|
||||
} from 'libs/transaction';
|
||||
import type { TransactionWithoutGas } from 'libs/messages';
|
||||
import type { UNIT } from 'libs/units';
|
||||
import { toWei } from 'libs/units';
|
||||
import {
|
||||
generateCompleteTransaction,
|
||||
getBalanceMinusGasCosts,
|
||||
formatTxInput
|
||||
} from 'libs/transaction';
|
||||
// MISC
|
||||
import customMessages from './messages';
|
||||
import Big from 'bignumber.js';
|
||||
|
||||
type State = {
|
||||
hasQueryString: boolean,
|
||||
@ -65,7 +73,7 @@ type State = {
|
||||
gasLimit: string,
|
||||
data: string,
|
||||
gasChanged: boolean,
|
||||
transaction: ?BroadcastTransaction,
|
||||
transaction: ?CompleteTransaction,
|
||||
showTxConfirm: boolean,
|
||||
generateDisabled: boolean
|
||||
};
|
||||
@ -76,13 +84,9 @@ function getParam(query: { [string]: string }, key: string) {
|
||||
if (index === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return query[keys[index]];
|
||||
}
|
||||
|
||||
// TODO query string
|
||||
// TODO how to handle DATA?
|
||||
|
||||
type Props = {
|
||||
location: {
|
||||
query: {
|
||||
@ -96,14 +100,14 @@ type Props = {
|
||||
network: NetworkConfig,
|
||||
tokens: Token[],
|
||||
tokenBalances: TokenBalance[],
|
||||
gasPrice: number,
|
||||
gasPrice: string,
|
||||
broadcastTx: (signedTx: string) => BroadcastTxRequestedAction,
|
||||
showNotification: (
|
||||
level: string,
|
||||
msg: string,
|
||||
duration?: number
|
||||
) => ShowNotificationAction,
|
||||
transactions: Array<BroadcastStatusTransaction>
|
||||
transactions: Array<BroadcastTransactionStatus>
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
@ -150,22 +154,21 @@ export class SendTransaction extends React.Component {
|
||||
this.estimateGas();
|
||||
}
|
||||
}
|
||||
if (this.state.generateDisabled !== !this.isValid()) {
|
||||
if (this.state.generateDisabled === this.isValid()) {
|
||||
this.setState({ generateDisabled: !this.isValid() });
|
||||
}
|
||||
|
||||
const componentStateTransaction = this.state.transaction;
|
||||
if (componentStateTransaction) {
|
||||
// lives in redux state
|
||||
const currentTxAsBroadcastTransaction = getTxFromBroadcastStatusTransactions(
|
||||
const currentTxAsSignedTransaction = getTxFromBroadcastTransactionStatus(
|
||||
this.props.transactions,
|
||||
componentStateTransaction.signedTx
|
||||
);
|
||||
// if there is a matching tx in redux state
|
||||
if (currentTxAsBroadcastTransaction) {
|
||||
if (currentTxAsSignedTransaction) {
|
||||
// if the broad-casted transaction attempt is successful, clear the form
|
||||
if (currentTxAsBroadcastTransaction.successfullyBroadcast) {
|
||||
this.resetTransaction();
|
||||
if (currentTxAsSignedTransaction.successfullyBroadcast) {
|
||||
this.resetTx();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,7 +235,7 @@ export class SendTransaction extends React.Component {
|
||||
<button
|
||||
disabled={this.state.generateDisabled}
|
||||
className="btn btn-info btn-block"
|
||||
onClick={this.generateTx}
|
||||
onClick={this.generateTxFromState}
|
||||
>
|
||||
{translate('SEND_generate')}
|
||||
</button>
|
||||
@ -269,6 +272,7 @@ export class SendTransaction extends React.Component {
|
||||
<div className="form-group">
|
||||
<button
|
||||
className="btn btn-primary btn-block col-sm-11"
|
||||
disabled={!this.state.transaction}
|
||||
onClick={this.openTxModal}
|
||||
>
|
||||
{translate('SEND_trans')}
|
||||
@ -332,49 +336,37 @@ export class SendTransaction extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
async getTransactionInfoFromState(): Promise<TransactionWithoutGas> {
|
||||
async getFormattedTxFromState(): Promise<TransactionWithoutGas> {
|
||||
const { wallet } = this.props;
|
||||
const { token, unit, value, to, data } = this.state;
|
||||
|
||||
if (unit === 'ether') {
|
||||
return {
|
||||
to,
|
||||
from: await wallet.getAddress(),
|
||||
value: valueToHex(value),
|
||||
data
|
||||
};
|
||||
} else {
|
||||
if (!token) {
|
||||
throw new Error('No matching token');
|
||||
}
|
||||
|
||||
const bigAmount = new Big(value);
|
||||
|
||||
return {
|
||||
to: token.address,
|
||||
from: await wallet.getAddress(),
|
||||
value: '0x0',
|
||||
data: ERC20.transfer(to, toTokenUnit(bigAmount, token))
|
||||
};
|
||||
}
|
||||
const transactionInput: TransactionInput = {
|
||||
token,
|
||||
unit,
|
||||
value,
|
||||
to,
|
||||
data
|
||||
};
|
||||
return await formatTxInput(wallet, transactionInput);
|
||||
}
|
||||
|
||||
async estimateGas() {
|
||||
if (!isNaN(parseInt(this.state.value))) {
|
||||
try {
|
||||
const transaction = await this.getTransactionInfoFromState();
|
||||
// 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;
|
||||
const gasLimit = await this.props.nodeLib.estimateGas(transaction);
|
||||
if (this.state === state) {
|
||||
this.setState({ gasLimit: formatGasLimit(gasLimit, state.unit) });
|
||||
} else {
|
||||
this.estimateGas();
|
||||
}
|
||||
} catch (error) {
|
||||
this.props.showNotification('danger', error.message, 5000);
|
||||
if (isNaN(parseInt(this.state.value))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const cachedFormattedTx = await this.getFormattedTxFromState();
|
||||
// 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;
|
||||
const gasLimit = await this.props.nodeLib.estimateGas(cachedFormattedTx);
|
||||
if (this.state === state) {
|
||||
this.setState({ gasLimit: formatGasLimit(gasLimit, state.unit) });
|
||||
} else {
|
||||
// state has changed, so try again from the start (with the hope that state won't change by the next time)
|
||||
this.estimateGas();
|
||||
}
|
||||
} catch (error) {
|
||||
this.props.showNotification('danger', error.message, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,13 +395,9 @@ export class SendTransaction extends React.Component {
|
||||
};
|
||||
|
||||
onDataChange = (value: string) => {
|
||||
if (this.state.unit !== 'ether') {
|
||||
return;
|
||||
if (this.state.unit === 'ether') {
|
||||
this.setState({ data: value });
|
||||
}
|
||||
this.setState({
|
||||
...this.state,
|
||||
data: value
|
||||
});
|
||||
};
|
||||
|
||||
onGasChange = (value: string) => {
|
||||
@ -437,9 +425,7 @@ export class SendTransaction extends React.Component {
|
||||
value = tokenBalance.balance.toString();
|
||||
}
|
||||
}
|
||||
|
||||
let token = this.props.tokens.find(x => x.symbol === unit);
|
||||
|
||||
this.setState({
|
||||
value,
|
||||
unit,
|
||||
@ -447,40 +433,41 @@ export class SendTransaction extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
generateTx = async () => {
|
||||
const { nodeLib, wallet } = this.props;
|
||||
const { token } = this.state;
|
||||
const stateTxInfo = await this.getTransactionInfoFromState();
|
||||
|
||||
generateTxFromState = async () => {
|
||||
const { nodeLib, wallet, gasPrice, network } = this.props;
|
||||
const { token, unit, value, to, data, gasLimit } = this.state;
|
||||
const chainId = network.chainId;
|
||||
const transactionInput = {
|
||||
token,
|
||||
unit,
|
||||
value,
|
||||
to,
|
||||
data
|
||||
};
|
||||
try {
|
||||
const transaction = await generateTransaction(
|
||||
nodeLib,
|
||||
{
|
||||
...stateTxInfo,
|
||||
gasLimit: this.state.gasLimit,
|
||||
gasPrice: this.props.gasPrice,
|
||||
chainId: this.props.network.chainId
|
||||
},
|
||||
const signedTx = await generateCompleteTransaction(
|
||||
wallet,
|
||||
token
|
||||
nodeLib,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
chainId,
|
||||
transactionInput
|
||||
);
|
||||
this.setState({ transaction });
|
||||
this.setState({ transaction: signedTx });
|
||||
} catch (err) {
|
||||
this.props.showNotification('danger', err.message, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
openTxModal = () => {
|
||||
if (this.state.transaction) {
|
||||
this.setState({ showTxConfirm: true });
|
||||
}
|
||||
this.setState({ showTxConfirm: true });
|
||||
};
|
||||
|
||||
hideConfirmTx = () => {
|
||||
this.setState({ showTxConfirm: false });
|
||||
};
|
||||
|
||||
resetTransaction = () => {
|
||||
resetTx = () => {
|
||||
this.setState({
|
||||
to: '',
|
||||
value: '',
|
||||
@ -502,7 +489,7 @@ function mapStateToProps(state: AppState) {
|
||||
nodeLib: getNodeLib(state),
|
||||
network: getNetworkConfig(state),
|
||||
tokens: getTokens(state),
|
||||
gasPrice: toWei(new Big(getGasPriceGwei(state)), 'gwei'),
|
||||
gasPrice: toWei(new Big(getGasPriceGwei(state)), 'gwei').toString(),
|
||||
transactions: state.wallet.transactions
|
||||
};
|
||||
}
|
||||
|
7
common/libs/messages.js
Normal file
7
common/libs/messages.js
Normal file
@ -0,0 +1,7 @@
|
||||
// TODO - move this out of transaction; it's only for estimating gas costs
|
||||
export type TransactionWithoutGas = {|
|
||||
to: string,
|
||||
value: string | number,
|
||||
data: string,
|
||||
from: string
|
||||
|};
|
@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import Big from 'bignumber.js';
|
||||
import type { TransactionWithoutGas } from 'libs/transaction';
|
||||
import type { TransactionWithoutGas } from 'libs/messages';
|
||||
import type { Token } from 'config/data';
|
||||
|
||||
export interface INode {
|
||||
|
@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import Big from 'bignumber.js';
|
||||
import type { INode } from '../INode';
|
||||
import type { TransactionWithoutGas } from 'libs/transaction';
|
||||
import type { TransactionWithoutGas } from 'libs/messages';
|
||||
import RPCClient, {
|
||||
getBalance,
|
||||
estimateGas,
|
||||
@ -39,7 +39,7 @@ export default class RpcNode implements INode {
|
||||
return this.client.call(getTokenBalance(address, token)).then(response => {
|
||||
if (response.error) {
|
||||
// TODO - Error handling
|
||||
return Big(0);
|
||||
return new Big(0);
|
||||
}
|
||||
return new Big(String(response.result)).div(
|
||||
new Big(10).pow(token.decimal)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// don't use flow temporarily
|
||||
import type { TransactionWithoutGas } from 'libs/transaction';
|
||||
import type { TransactionWithoutGas } from 'libs/messages';
|
||||
|
||||
type DATA = string;
|
||||
type QUANTITY = string;
|
||||
|
@ -11,39 +11,46 @@ import type { BaseWallet } from 'libs/wallet';
|
||||
import type { Token } from 'config/data';
|
||||
import type EthTx from 'ethereumjs-tx';
|
||||
import { toUnit } from 'libs/units';
|
||||
import { valueToHex } from 'libs/values';
|
||||
import type { UNIT } from 'libs/units';
|
||||
import { RPCNode } from 'libs/nodes';
|
||||
import { TransactionWithoutGas } from 'libs/messages';
|
||||
|
||||
export type BroadcastStatusTransaction = {
|
||||
export type TransactionInput = {
|
||||
token: ?Token,
|
||||
unit: UNIT,
|
||||
value: string,
|
||||
to: string,
|
||||
data: string
|
||||
};
|
||||
|
||||
export type BroadcastTransactionStatus = {
|
||||
isBroadcasting: boolean,
|
||||
signedTx: string,
|
||||
successfullyBroadcast: boolean
|
||||
};
|
||||
|
||||
// TODO: Enforce more bigs, or find better way to avoid ether vs wei for value
|
||||
export type TransactionWithoutGas = {|
|
||||
from: string,
|
||||
to: string,
|
||||
gasLimit?: string | number,
|
||||
value: string | number,
|
||||
data?: string,
|
||||
chainId?: number
|
||||
|};
|
||||
|
||||
export type Transaction = {|
|
||||
...TransactionWithoutGas,
|
||||
gasPrice: string | number
|
||||
|};
|
||||
|
||||
export type RawTransaction = {|
|
||||
nonce: string,
|
||||
gasPrice: string,
|
||||
gasLimit: string,
|
||||
export type BaseTransaction = {|
|
||||
to: string,
|
||||
value: string,
|
||||
data: string,
|
||||
gasLimit: string,
|
||||
gasPrice: string,
|
||||
chainId: number
|
||||
|};
|
||||
|
||||
export type BroadcastTransaction = {|
|
||||
export type RawTransaction = {|
|
||||
...BaseTransaction,
|
||||
nonce: string
|
||||
|};
|
||||
|
||||
export type ExtendedRawTransaction = {|
|
||||
...RawTransaction,
|
||||
// non-standard, legacy
|
||||
from: string
|
||||
|};
|
||||
|
||||
export type CompleteTransaction = {|
|
||||
...RawTransaction,
|
||||
rawTx: string,
|
||||
signedTx: string
|
||||
@ -71,12 +78,12 @@ export function getTransactionFields(tx: EthTx) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateTransaction(
|
||||
export async function generateCompleteTransactionFromRawTransaction(
|
||||
node: INode,
|
||||
tx: Transaction,
|
||||
tx: ExtendedRawTransaction,
|
||||
wallet: BaseWallet,
|
||||
token: ?Token
|
||||
): Promise<BroadcastTransaction> {
|
||||
): Promise<CompleteTransaction> {
|
||||
// Reject bad addresses
|
||||
if (!isValidETHAddress(tx.to)) {
|
||||
throw new Error(translate('ERROR_5'));
|
||||
@ -115,7 +122,6 @@ export async function generateTransaction(
|
||||
let balance;
|
||||
|
||||
if (token) {
|
||||
// $FlowFixMe - We reject above if tx has no data for token
|
||||
value = new Big(ERC20.$transfer(tx.data).value);
|
||||
balance = toTokenUnit(await node.getTokenBalance(tx.from, token), token);
|
||||
} else {
|
||||
@ -131,10 +137,8 @@ export async function generateTransaction(
|
||||
// prefix'd hex value.
|
||||
const cleanHex = hex => addHexPrefix(padToEven(stripHex(hex)));
|
||||
|
||||
// Generate the raw transaction
|
||||
const txCount = await node.getTransactionCount(tx.from);
|
||||
const rawTx = {
|
||||
nonce: cleanHex(txCount),
|
||||
const cleanedRawTx = {
|
||||
nonce: cleanHex(tx.nonce),
|
||||
gasPrice: cleanHex(new Big(tx.gasPrice).toString(16)),
|
||||
gasLimit: cleanHex(new Big(tx.gasLimit).toString(16)),
|
||||
to: cleanHex(tx.to),
|
||||
@ -144,24 +148,82 @@ export async function generateTransaction(
|
||||
};
|
||||
|
||||
// Sign the transaction
|
||||
const rawTxJson = JSON.stringify(rawTx);
|
||||
const signedTx = await wallet.signRawTransaction(rawTx);
|
||||
const rawTxJson = JSON.stringify(cleanedRawTx);
|
||||
const signedTx = await wallet.signRawTransaction(cleanedRawTx);
|
||||
|
||||
// Repeat all of this shit for Flow typechecking. Sealed objects don't
|
||||
// like spreads, so we have to be explicit.
|
||||
return {
|
||||
nonce: rawTx.nonce,
|
||||
gasPrice: rawTx.gasPrice,
|
||||
gasLimit: rawTx.gasLimit,
|
||||
to: rawTx.to,
|
||||
value: rawTx.value,
|
||||
data: rawTx.data,
|
||||
chainId: rawTx.chainId,
|
||||
nonce: cleanedRawTx.nonce,
|
||||
gasPrice: cleanedRawTx.gasPrice,
|
||||
gasLimit: cleanedRawTx.gasLimit,
|
||||
to: cleanedRawTx.to,
|
||||
value: cleanedRawTx.value,
|
||||
data: cleanedRawTx.data,
|
||||
chainId: cleanedRawTx.chainId,
|
||||
rawTx: rawTxJson,
|
||||
signedTx: signedTx
|
||||
};
|
||||
}
|
||||
|
||||
export async function formatTxInput(
|
||||
wallet: BaseWallet,
|
||||
{ token, unit, value, to, data }: TransactionInput
|
||||
): Promise<TransactionWithoutGas> {
|
||||
if (unit === 'ether') {
|
||||
return {
|
||||
to,
|
||||
from: await wallet.getAddress(),
|
||||
value: valueToHex(value),
|
||||
data
|
||||
};
|
||||
} else {
|
||||
if (!token) {
|
||||
throw new Error('No matching token');
|
||||
}
|
||||
const bigAmount = new Big(value);
|
||||
return {
|
||||
to: token.address,
|
||||
from: await wallet.getAddress(),
|
||||
value: '0x0',
|
||||
data: ERC20.transfer(to, toTokenUnit(bigAmount, token))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateCompleteTransaction(
|
||||
wallet: BaseWallet,
|
||||
nodeLib: RPCNode,
|
||||
gasPrice: string,
|
||||
gasLimit: string,
|
||||
chainId: number,
|
||||
transactionInput: TransactionInput
|
||||
): Promise<CompleteTransaction> {
|
||||
const { token } = transactionInput;
|
||||
|
||||
const formattedTx = await formatTxInput(wallet, transactionInput);
|
||||
|
||||
const from = await wallet.getAddress();
|
||||
|
||||
const transaction: ExtendedRawTransaction = {
|
||||
nonce: await nodeLib.getTransactionCount(from),
|
||||
from,
|
||||
to: formattedTx.to,
|
||||
gasLimit,
|
||||
value: formattedTx.value,
|
||||
data: formattedTx.data,
|
||||
chainId,
|
||||
gasPrice
|
||||
};
|
||||
|
||||
return await generateCompleteTransactionFromRawTransaction(
|
||||
nodeLib,
|
||||
transaction,
|
||||
wallet,
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
// TODO determine best place for helper function
|
||||
export function getBalanceMinusGasCosts(
|
||||
weiGasLimit: Big,
|
||||
|
@ -8,8 +8,8 @@ import type {
|
||||
import { BaseWallet } from 'libs/wallet';
|
||||
import { toUnit } from 'libs/units';
|
||||
import Big from 'bignumber.js';
|
||||
import { getTxFromBroadcastStatusTransactions } from 'selectors/wallet';
|
||||
import type { BroadcastStatusTransaction } from 'libs/transaction';
|
||||
import { getTxFromBroadcastTransactionStatus } from 'selectors/wallet';
|
||||
import type { BroadcastTransactionStatus } from 'libs/transaction';
|
||||
export type State = {
|
||||
inst: ?BaseWallet,
|
||||
// in ETH
|
||||
@ -17,7 +17,7 @@ export type State = {
|
||||
tokens: {
|
||||
[string]: Big
|
||||
},
|
||||
transactions: Array<BroadcastStatusTransaction>
|
||||
transactions: Array<BroadcastTransactionStatus>
|
||||
};
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
@ -42,11 +42,11 @@ function setTokenBalances(state: State, action: SetTokenBalancesAction): State {
|
||||
}
|
||||
|
||||
function handleUpdateTxArray(
|
||||
transactions: Array<BroadcastStatusTransaction>,
|
||||
broadcastStatusTx: BroadcastStatusTransaction,
|
||||
transactions: Array<BroadcastTransactionStatus>,
|
||||
broadcastStatusTx: BroadcastTransactionStatus,
|
||||
isBroadcasting: boolean,
|
||||
successfullyBroadcast: boolean
|
||||
): Array<BroadcastStatusTransaction> {
|
||||
): Array<BroadcastTransactionStatus> {
|
||||
return transactions.map(item => {
|
||||
if (item === broadcastStatusTx) {
|
||||
return { ...item, isBroadcasting, successfullyBroadcast };
|
||||
@ -60,9 +60,8 @@ function handleTxBroadcastCompleted(
|
||||
state: State,
|
||||
signedTx: string,
|
||||
successfullyBroadcast: boolean
|
||||
// TODO How to handle null case for existing Tx?. Should use Array<BroadcastStatusTransaction> but can't.
|
||||
): Array<any> {
|
||||
const existingTx = getTxFromBroadcastStatusTransactions(
|
||||
): Array<BroadcastTransactionStatus> {
|
||||
const existingTx = getTxFromBroadcastTransactionStatus(
|
||||
state.transactions,
|
||||
signedTx
|
||||
);
|
||||
@ -75,12 +74,12 @@ function handleTxBroadcastCompleted(
|
||||
successfullyBroadcast
|
||||
);
|
||||
} else {
|
||||
return [];
|
||||
return state.transactions;
|
||||
}
|
||||
}
|
||||
|
||||
function handleBroadcastTxRequested(state: State, signedTx: string) {
|
||||
const existingTx = getTxFromBroadcastStatusTransactions(
|
||||
const existingTx = getTxFromBroadcastTransactionStatus(
|
||||
state.transactions,
|
||||
signedTx
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { BaseWallet } from 'libs/wallet';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import Big from 'bignumber.js';
|
||||
import type { Token } from 'config/data';
|
||||
import type { BroadcastStatusTransaction } from 'libs/transaction';
|
||||
import type { BroadcastTransactionStatus } from 'libs/transaction';
|
||||
|
||||
export function getWalletInst(state: State): ?BaseWallet {
|
||||
return state.wallet.inst;
|
||||
@ -44,15 +44,15 @@ export function getTokenBalances(state: State): TokenBalance[] {
|
||||
export function getTxFromState(
|
||||
state: State,
|
||||
signedTx: string
|
||||
): ?BroadcastStatusTransaction {
|
||||
): ?BroadcastTransactionStatus {
|
||||
const transactions = state.wallet.transactions;
|
||||
return getTxFromBroadcastStatusTransactions(transactions, signedTx);
|
||||
return getTxFromBroadcastTransactionStatus(transactions, signedTx);
|
||||
}
|
||||
|
||||
export function getTxFromBroadcastStatusTransactions(
|
||||
transactions: Array<BroadcastStatusTransaction>,
|
||||
export function getTxFromBroadcastTransactionStatus(
|
||||
transactions: Array<BroadcastTransactionStatus>,
|
||||
signedTx: string
|
||||
): ?BroadcastStatusTransaction {
|
||||
): ?BroadcastTransactionStatus {
|
||||
return transactions.find(transaction => {
|
||||
return transaction.signedTx === signedTx;
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user