Skip to content

Commit

Permalink
Merge pull request #2005 from LiskHQ/1930-implement-success-error-ste…
Browse files Browse the repository at this point in the history
…p-3-of-send-btc

Implement success/error step 3 of send LSK/BTC - closes #1930
  • Loading branch information
Osvaldo Vega Agüero committed May 24, 2019
2 parents b582790 + 5a478ad commit dacc38a
Show file tree
Hide file tree
Showing 27 changed files with 913 additions and 253 deletions.
1 change: 0 additions & 1 deletion i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@
"No search result in given criteria.": "No search result in given criteria.",
"No transactions yet": "No transactions yet",
"No, thanks": "No, thanks",
"Not Yet Implemented. Sorry.": "Not Yet Implemented. Sorry.",
"Not voted": "Not voted",
"Note: After registration completes,": "Note: After registration completes,",
"Nothing to change – already voted/unvoted": "Nothing to change – already voted/unvoted",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"await-to-js": "2.1.1",
"bignumber.js": "8.0.1",
"bip32": "1.0.4",
"bitcoinjs-lib": "5.0.1",
"bitcoinjs-lib": "4.0.5",
"bitcore-mnemonic": "1.7.0",
"body-parser": "1.18.3",
"browser-or-node": "1.1.0",
Expand Down
212 changes: 164 additions & 48 deletions src/actions/transactions.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
/* eslint-disable max-lines */
import i18next from 'i18next';
import to from 'await-to-js';
import actionTypes from '../constants/actions';
import { loadingStarted, loadingFinished } from '../actions/loading';
import { send, getTransactions, getSingleTransaction } from '../utils/api/transactions';
import { getDelegate } from '../utils/api/delegate';
import { loadDelegateCache } from '../utils/delegates';
import { extractAddress } from '../utils/account';
import { passphraseUsed } from './account';
import { getTimeOffset } from '../utils/hacks';
import Fees from '../constants/fees';
import transactionTypes from '../constants/transactionTypes';
import { toRawLsk } from '../utils/lsk';
import { sendWithHW } from '../utils/api/hwWallet';
import { loginType } from '../constants/hwConstants';
import { transactions as transactionsAPI, hardwareWallet as hwAPI } from '../utils/api';
import { tokenMap } from '../constants/tokens';

/**
* This action is used on logout
Expand Down Expand Up @@ -50,7 +51,7 @@ export const loadTransactions = ({
(dispatch, getState) => {
dispatch(loadingStarted(actionTypes.loadTransactions));
const networkConfig = getState().network;
getTransactions({
transactionsAPI.getTransactions({
networkConfig, address, limit, offset, filters,
})
.then((response) => {
Expand Down Expand Up @@ -87,7 +88,7 @@ export const loadLastTransaction = address => (dispatch, getState) => {
const networkConfig = getState().network;
if (networkConfig) {
dispatch({ type: actionTypes.transactionCleared });
getTransactions({
transactionsAPI.getTransactions({
networkConfig, address, limit: 1, offset: 0,
}).then(response => dispatch({ data: response.data[0], type: actionTypes.transactionLoaded }));
}
Expand All @@ -104,7 +105,7 @@ export const loadSingleTransaction = ({ id }) =>
const networkConfig = getState().network;
dispatch({ type: actionTypes.transactionCleared });
// TODO remove the btc condition
getSingleTransaction(localStorage.getItem('btc') ? { networkConfig, id } : { liskAPIClient, id })
transactionsAPI.getSingleTransaction(localStorage.getItem('btc') ? { networkConfig, id } : { liskAPIClient, id })
.then((response) => { // eslint-disable-line max-statements
let added = [];
let deleted = [];
Expand Down Expand Up @@ -189,7 +190,7 @@ export const updateTransactions = ({
(dispatch, getState) => {
const networkConfig = getState().network;

getTransactions({
transactionsAPI.getTransactions({
networkConfig, address, limit, filters,
})
.then((response) => {
Expand All @@ -205,7 +206,9 @@ export const updateTransactions = ({
});
};

const handleSentError = ({ error, account, dispatch }) => {
const handleSentError = ({
account, dispatch, error, tx,
}) => {
let text;
switch (account.loginType) {
case loginType.normal:
Expand All @@ -217,52 +220,165 @@ const handleSentError = ({ error, account, dispatch }) => {
default:
text = error.message;
}

dispatch({
data: { errorMessage: text },
type: actionTypes.transactionFailed,
data: {
errorMessage: text,
tx,
},
});
};

export const sent = ({
account, recipientId, amount, passphrase, secondPassphrase, data,
}) =>

/**
* Calls transactionAPI.create and transactionAPI.broadcast methods to make a transaction.
* @param {Object} data
* @param {String} data.recipientAddress
* @param {Number} data.amount - In raw format (satoshis, beddows)
* @param {Number} data.fee - In raw format, used for updating the TX List.
* @param {Number} data.dynamicFeePerByte - In raw format, used for creating BTC transaction.
* @param {Number} data.reference - Data field for LSK transactions
* @param {String} data.secondPassphrase - Second passphrase for LSK transactions
*/
// eslint-disable-next-line max-statements
async (dispatch, getState) => {
// account.loginType = 1;
let error;
let callResult;
const liskAPIClient = getState().peers.liskAPIClient;
const timeOffset = getTimeOffset(getState());
switch (account.loginType) {
case loginType.normal:
// eslint-disable-next-line
[error, callResult] = await to(send(liskAPIClient, recipientId, toRawLsk(amount), passphrase, secondPassphrase, data, timeOffset));
break;
case loginType.ledger:
// eslint-disable-next-line
[error, callResult] = await to(sendWithHW(liskAPIClient, account, recipientId, toRawLsk(amount), secondPassphrase, data));
break;
// case 2:
// errorMessage = i18next.t('Not Yet Implemented. Sorry.');
// dispatch({ data: { errorMessage }, type: actionTypes.transactionFailed });
// break;
default:
dispatch({ data: { errorMessage: i18next.t('Login Type not recognized.') }, type: actionTypes.transactionFailed });
}
loadingFinished('sent');
if (error) {
handleSentError({ error, account, dispatch });
export const sent = data => async (dispatch, getState) => {
let broadcastTx;
let tx;
let fail;
const { account, network, settings } = getState();
const timeOffset = getTimeOffset(getState());
const activeToken = localStorage.getItem('btc') // TODO: Refactor after enabling BTC
? settings.token.active
: tokenMap.LSK.key;
const senderId = localStorage.getItem('btc') // TODO: Refactor after enabling BTC
? account.info[activeToken].address
: account.address;

const txData = { ...data, timeOffset };

try {
if (account.loginType === loginType.normal) {
tx = await transactionsAPI.create(activeToken, txData);
broadcastTx = await transactionsAPI.broadcast(activeToken, tx, network);
} else {
dispatch(addPendingTransaction({
id: callResult.id,
senderPublicKey: account.publicKey,
senderId: account.address,
recipientId,
amount: toRawLsk(amount),
fee: Fees.send,
type: transactionTypes.send,
asset: { data },
}));
dispatch(passphraseUsed(passphrase));
[fail, broadcastTx] = await to(sendWithHW(
network,
account,
data.recipientId,
data.amount,
data.secondPassphrase,
data.data,
));

if (fail) throw new Error(fail);
}
};

loadingFinished('sent');
dispatch(addPendingTransaction({
amount: txData.amount,
asset: { reference: txData.data },
fee: Fees.send,
id: broadcastTx.id,
recipientId: txData.recipientId,
senderId,
senderPublicKey: account.publicKey,
type: transactionTypes.send,
}));

dispatch(passphraseUsed(txData.passphrase));
} catch (error) {
loadingFinished('sent');
handleSentError({
error, account, tx, dispatch,
});
}
};


export const transactionCreatedSuccess = data => ({
type: actionTypes.transactionCreatedSuccess,
data,
});

export const transactionCreatedError = data => ({
type: actionTypes.transactionCreatedError,
data,
});

export const resetTransactionResult = () => ({
type: actionTypes.resetTransactionResult,
});

export const broadcastedTransactionError = data => ({
type: actionTypes.broadcastedTransactionError,
data,
});

export const broadcastedTransactionSuccess = data => ({
type: actionTypes.broadcastedTransactionSuccess,
data,
});


/**
* Calls transactionAPI.create for create the tx object that will broadcast
* @param {Object} data
* @param {String} data.recipientAddress
* @param {Number} data.amount - In raw format (satoshis, beddows)
* @param {Number} data.fee - In raw format, used for updating the TX List.
* @param {Number} data.dynamicFeePerByte - In raw format, used for creating BTC transaction.
* @param {Number} data.reference - Data field for LSK transactions
* @param {String} data.secondPassphrase - Second passphrase for LSK transactions
*/
// eslint-disable-next-line max-statements
export const transactionCreated = data => async (dispatch, getState) => {
const { account, settings, ...state } = getState();
const timeOffset = getTimeOffset(state);
const activeToken = localStorage.getItem('btc') // TODO: Refactor after enabling BTC
? settings.token.active
: tokenMap.LSK.key;

const [error, tx] = account.loginType === loginType.normal
? await to(transactionsAPI.create(activeToken, { ...data, timeOffset }))
: await to(hwAPI.create(account, data));

if (error) return dispatch(transactionCreatedError(error));
return dispatch(transactionCreatedSuccess(tx));
};

/**
* Calls transactionAPI.broadcast function for put the tx object (signed) into the network
* @param {Object} transaction
* @param {String} transaction.recipientAddress
* @param {Number} transaction.amount - In raw format (satoshis, beddows)
* @param {Number} transaction.fee - In raw format, used for updating the TX List.
* @param {Number} transaction.dynamicFeePerByte - In raw format, used for creating BTC transaction.
* @param {Number} transaction.reference - Data field for LSK transactions
* @param {String} transaction.secondPassphrase - Second passphrase for LSK transactions
*/
export const transactionBroadcasted = transaction => async (dispatch, getState) => {
const { account, network, settings } = getState();
const activeToken = localStorage.getItem('btc') // TODO: Refactor after enabling BTC
? settings.token.active
: tokenMap.LSK.key;

const [error, tx] = await to(transactionsAPI.broadcast(activeToken, transaction, network));

if (error) return dispatch(broadcastedTransactionError(transaction));

dispatch(broadcastedTransactionSuccess(transaction));

dispatch(addPendingTransaction({
amount: transaction.amount,
asset: { reference: transaction.data },
fee: Fees.send,
id: tx.id,
recipientId: transaction.recipientId,
senderId: account.info[activeToken].address,
senderPublicKey: account.publicKey,
type: transactionTypes.send,
}));

return dispatch(passphraseUsed(transaction.passphrase));
};
32 changes: 26 additions & 6 deletions src/actions/transactions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,22 +173,33 @@ describe('actions: transactions', () => {
amount: 100,
passphrase: 'sample passphrase',
secondPassphrase: null,
dynamicFeePerByte: null, // for BTC
fee: null, // for BTC
account: {
publicKey: 'test_public-key',
address: 'test_address',
loginType: 0,
},
data: 'abc',
};

const actionFunction = sent(data);

beforeEach(() => {
getState = () => ({
peers: { liskAPIClient: {} },
transactions: {
filters: {
direction: txFilters.all,
transactions: { filter: txFilters.all },
network: { liskAPIClient: {} },
settings: {
token: {
active: 'LSK',
},
},
account: {
publicKey: 'test_public-key',
address: 'test_address',
loginType: 0,
},
});
});

Expand All @@ -209,14 +220,20 @@ describe('actions: transactions', () => {
type: 0,
};

transactionsApi.create.mockReturnValue(expectedAction);
transactionsApi.broadcast.mockReturnValue(expectedAction);

await actionFunction(dispatch, getState);
expect(dispatch).toHaveBeenCalledWith({
data: expectedAction, type: actionTypes.addPendingTransaction,
data: data.passphrase, type: actionTypes.passphraseUsed,
});
});

it('should dispatch transactionFailed action if caught', async () => {
transactionsApi.send.mockRejectedValue({ message: 'sample message' });
transactionsApi.create.mockImplementation(() => {
throw new Error('sample message');
});

const expectedAction = {
data: {
errorMessage: 'sample message.',
Expand All @@ -230,7 +247,10 @@ describe('actions: transactions', () => {

it('should dispatch transactionFailed action if caught but no message returned', async () => {
const errorMessage = 'An error occurred while creating the transaction';
transactionsApi.send.mockRejectedValue({ message: errorMessage });
transactionsApi.create.mockImplementation(() => {
throw new Error(errorMessage);
});

const expectedErrorMessage = errorMessage + '.'; // eslint-disable-line
const expectedAction = {
data: {
Expand Down
Loading

0 comments on commit dacc38a

Please sign in to comment.