2018-02-16 16:57:23 +00:00
|
|
|
import { SagaIterator } from 'redux-saga';
|
2018-03-14 20:10:14 +00:00
|
|
|
import { put, select, apply, call, take, takeEvery } from 'redux-saga/effects';
|
|
|
|
import EthTx from 'ethereumjs-tx';
|
2018-06-18 01:53:00 +00:00
|
|
|
|
2018-03-14 20:10:14 +00:00
|
|
|
import { INode } from 'libs/nodes';
|
|
|
|
import { hexEncodeData } from 'libs/nodes/rpc/utils';
|
|
|
|
import { getTransactionFields } from 'libs/transaction';
|
|
|
|
import { NetworkConfig } from 'types/network';
|
2018-06-18 01:53:00 +00:00
|
|
|
import { TransactionData, TransactionReceipt, SavedTransaction } from 'types/transactions';
|
|
|
|
import { AppState } from 'features/reducers';
|
|
|
|
import * as configNodesSelectors from 'features/config/nodes/selectors';
|
|
|
|
import * as configSelectors from 'features/config/selectors';
|
|
|
|
import { getChecksumAddressFn } from 'features/config';
|
|
|
|
import { walletSelectors } from 'features/wallet';
|
|
|
|
import { transactionBroadcastTypes } from 'features/transaction';
|
|
|
|
import * as types from './types';
|
|
|
|
import * as actions from './actions';
|
2018-02-16 16:57:23 +00:00
|
|
|
|
2018-06-18 01:53:00 +00:00
|
|
|
export function* fetchTxData(action: types.FetchTransactionDataAction): SagaIterator {
|
2018-02-16 16:57:23 +00:00
|
|
|
const txhash = action.payload;
|
|
|
|
let data: TransactionData | null = null;
|
|
|
|
let receipt: TransactionReceipt | null = null;
|
|
|
|
let error: string | null = null;
|
|
|
|
|
2018-06-18 01:53:00 +00:00
|
|
|
const node: INode = yield select(configNodesSelectors.getNodeLib);
|
2018-02-16 16:57:23 +00:00
|
|
|
|
|
|
|
// Fetch data and receipt separately, not in parallel. Receipt should only be
|
|
|
|
// fetched if the tx is mined, and throws if it's not, but that's not really
|
|
|
|
// an "error", since we'd still want to show the unmined tx data.
|
|
|
|
try {
|
|
|
|
data = yield apply(node, node.getTransactionByHash, [txhash]);
|
|
|
|
} catch (err) {
|
|
|
|
console.warn('Failed to fetch transaction data', err);
|
|
|
|
error = err.message;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data && data.blockHash) {
|
|
|
|
try {
|
|
|
|
receipt = yield apply(node, node.getTransactionReceipt, [txhash]);
|
|
|
|
} catch (err) {
|
|
|
|
console.warn('Failed to fetch transaction receipt', err);
|
|
|
|
receipt = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-18 01:53:00 +00:00
|
|
|
yield put(actions.setTransactionData({ txhash, data, receipt, error }));
|
2018-02-16 16:57:23 +00:00
|
|
|
}
|
|
|
|
|
2018-06-18 01:53:00 +00:00
|
|
|
export function* saveBroadcastedTx(
|
|
|
|
action: transactionBroadcastTypes.BroadcastTransactionQueuedAction
|
|
|
|
) {
|
2018-03-14 20:10:14 +00:00
|
|
|
const { serializedTransaction: txBuffer, indexingHash: txIdx } = action.payload;
|
|
|
|
|
2018-06-18 01:53:00 +00:00
|
|
|
const res:
|
|
|
|
| transactionBroadcastTypes.BroadcastTransactionSucceededAction
|
|
|
|
| transactionBroadcastTypes.BroadcastTransactionFailedAction = yield take([
|
|
|
|
transactionBroadcastTypes.TransactionBroadcastActions.TRANSACTION_SUCCEEDED,
|
|
|
|
transactionBroadcastTypes.TransactionBroadcastActions.TRANSACTION_FAILED
|
2018-03-14 20:10:14 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
// If our TX succeeded, save it and update the store.
|
|
|
|
if (
|
2018-06-18 01:53:00 +00:00
|
|
|
res.type === transactionBroadcastTypes.TransactionBroadcastActions.TRANSACTION_SUCCEEDED &&
|
2018-03-14 20:10:14 +00:00
|
|
|
res.payload.indexingHash === txIdx
|
|
|
|
) {
|
|
|
|
const tx = new EthTx(txBuffer);
|
|
|
|
const savableTx: SavedTransaction = yield call(
|
|
|
|
getSaveableTransaction,
|
|
|
|
tx,
|
|
|
|
res.payload.broadcastedHash
|
|
|
|
);
|
2018-06-18 01:53:00 +00:00
|
|
|
yield put(actions.addRecentTransaction(savableTx));
|
2018-03-14 20:10:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Given a serialized transaction, return a transaction we could save in LS
|
|
|
|
export function* getSaveableTransaction(tx: EthTx, hash: string): SagaIterator {
|
|
|
|
const fields = getTransactionFields(tx);
|
|
|
|
let from: string = '';
|
|
|
|
let chainId: number = 0;
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Signed transactions have these fields
|
|
|
|
from = hexEncodeData(tx.getSenderAddress());
|
|
|
|
chainId = fields.chainId;
|
|
|
|
} catch (err) {
|
|
|
|
// Unsigned transactions (e.g. web3) don't, so grab them from current state
|
2018-06-18 01:53:00 +00:00
|
|
|
const wallet: AppState['wallet']['inst'] = yield select(walletSelectors.getWalletInst);
|
|
|
|
const network: NetworkConfig = yield select(configSelectors.getNetworkConfig);
|
2018-03-14 20:10:14 +00:00
|
|
|
|
|
|
|
chainId = network.chainId;
|
|
|
|
if (wallet) {
|
|
|
|
from = wallet.getAddressString();
|
|
|
|
}
|
|
|
|
}
|
2018-06-15 23:28:42 +00:00
|
|
|
const toChecksumAddress = yield select(getChecksumAddressFn);
|
2018-03-14 20:10:14 +00:00
|
|
|
const savableTx: SavedTransaction = {
|
|
|
|
hash,
|
|
|
|
from,
|
|
|
|
chainId,
|
|
|
|
to: toChecksumAddress(fields.to),
|
|
|
|
value: fields.value,
|
|
|
|
time: Date.now()
|
|
|
|
};
|
|
|
|
return savableTx;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function* resetTxData() {
|
2018-06-18 01:53:00 +00:00
|
|
|
yield put(actions.resetTransactionData());
|
2018-03-14 20:10:14 +00:00
|
|
|
}
|
|
|
|
|
2018-06-18 01:53:00 +00:00
|
|
|
export function* transactionsSaga(): SagaIterator {
|
|
|
|
yield takeEvery(types.TransactionsActions.FETCH_TRANSACTION_DATA, fetchTxData);
|
|
|
|
yield takeEvery(
|
|
|
|
transactionBroadcastTypes.TransactionBroadcastActions.TRANSACTION_SUCCEEDED,
|
|
|
|
saveBroadcastedTx
|
|
|
|
);
|
|
|
|
yield takeEvery(types.TransactionsActions.RESET_TRANSACTION_DATA, resetTxData);
|
2018-02-16 16:57:23 +00:00
|
|
|
}
|