Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added transaction tracking #51

Merged
merged 4 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Added transaction tracking](https://github.com/multiversx/mx-sdk-dapp-core/pull/51)
- [Added provider constants and getTransactions API call](https://github.com/multiversx/mx-sdk-dapp-core/pull/50)
- [Added pending transactions](https://github.com/multiversx/mx-sdk-dapp-core/pull/48)
- [Added transaction manager](https://github.com/multiversx/mx-sdk-dapp-core/pull/41)
Expand Down
19 changes: 17 additions & 2 deletions src/core/managers/TransactionManager/TransactionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { Transaction } from '@multiversx/sdk-core/out';
import axios, { AxiosError } from 'axios';
import { BATCH_TRANSACTIONS_ID_SEPARATOR } from 'constants/transactions.constants';
import { getAccount } from 'core/methods/account/getAccount';
import { createTrackedTransactionsSession } from 'store/actions/trackedTransactions/trackedTransactionsActions';
import { networkSelector } from 'store/selectors';
import { getState } from 'store/store';
import { GuardianActionsEnum } from 'types';
import { GuardianActionsEnum, TransactionServerStatusesEnum } from 'types';
import { BatchTransactionsResponseType } from 'types/serverTransactions.types';
import { SignedTransactionType } from 'types/transactions.types';

Expand Down Expand Up @@ -55,6 +56,19 @@ export class TransactionManager {
}
};

public track = async (
signedTransactions: Transaction[],
options?: { enableToasts: boolean }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disableToasts boolean default undefined ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i change it to default {enableToasts:true}

) => {
const parsedTransactions = signedTransactions.map((transaction) =>
this.parseSignedTransaction(transaction)
);
createTrackedTransactionsSession({
transactions: parsedTransactions,
enableToasts: options?.enableToasts ?? true
});
};

private sendSignedTransactions = async (
signedTransactions: Transaction[]
): Promise<string[]> => {
Expand Down Expand Up @@ -132,7 +146,8 @@ export class TransactionManager {
private parseSignedTransaction = (signedTransaction: Transaction) => {
const parsedTransaction = {
...signedTransaction.toPlainObject(),
hash: signedTransaction.getHash().hex()
hash: signedTransaction.getHash().hex(),
status: TransactionServerStatusesEnum.pending
};

// TODO: Remove when the protocol supports usernames for guardian transactions
Expand Down
7 changes: 1 addition & 6 deletions src/core/methods/initApp/initApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ export async function initApp({
}: InitAppType) {
initStore(storage.getStorageCallback);

const shouldEnableTransactionTracker =
dAppConfig.enableTansactionTracker !== false;

const { apiAddress } = await initializeNetwork({
customNetworkConfig: dAppConfig.network,
environment: dAppConfig.environment
Expand All @@ -66,9 +63,7 @@ export async function initApp({
setCrossWindowConfig(dAppConfig.providers.crossWindow);
}

if (shouldEnableTransactionTracker) {
trackTransactions();
}
trackTransactions();

const isLoggedIn = getIsLoggedIn();

Expand Down
4 changes: 0 additions & 4 deletions src/core/methods/initApp/initApp.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ type BaseDappConfigType = {
* If set to `NativeAuthConfigType`, will set the native auth configuration.
*/
nativeAuth?: boolean | NativeAuthConfigType;
/**
* default: `true`
*/
enableTansactionTracker?: boolean;
providers?: {
crossWindow?: CrossWindowConfig;
walletConnect?: WalletConnectConfig;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getTransactionsByHashes } from 'apiCalls/transactions/getTransactionsByHashes';
import {
updateSignedTransactionStatus,
updateTransactionsSession
} from 'store/actions/transactions/transactionsActions';
updateTrackedTransactionStatus,
updateTrackedTransactionsSession
} from 'store/actions/trackedTransactions/trackedTransactionsActions';
import { getIsTransactionFailed } from 'store/actions/transactions/transactionStateByStatus';
import {
TransactionBatchStatusesEnum,
Expand Down Expand Up @@ -63,7 +63,7 @@ function manageTransaction({
const retriesForThisHash = retries[hash];
if (retriesForThisHash > 30) {
// consider transaction as stuck after 1 minute
updateTransactionsSession({
updateTrackedTransactionsSession({
sessionId,
status: TransactionBatchStatusesEnum.timedOut
});
Expand All @@ -81,7 +81,7 @@ function manageTransaction({
// The tx is from a sequential batch.
// If the transactions before this are not successful then it means that no other tx will be processed
if (isSequential && !status) {
updateSignedTransactionStatus({
updateTrackedTransactionStatus({
sessionId,
status,
transactionHash: hash,
Expand All @@ -92,7 +92,7 @@ function manageTransaction({
}

if (hasStatusChanged) {
updateSignedTransactionStatus({
updateTrackedTransactionStatus({
sessionId,
status,
transactionHash: hash,
Expand All @@ -111,7 +111,7 @@ function manageTransaction({
}
} catch (error) {
console.error(error);
updateTransactionsSession({
updateTrackedTransactionsSession({
sessionId,
status: TransactionBatchStatusesEnum.timedOut
});
Expand Down Expand Up @@ -159,7 +159,7 @@ export async function checkBatch({
);

if (isSuccessful) {
updateTransactionsSession({
updateTrackedTransactionsSession({
sessionId,
status: TransactionBatchStatusesEnum.success
});
Expand All @@ -171,7 +171,7 @@ export async function checkBatch({
);

if (isFailed) {
updateTransactionsSession({
updateTrackedTransactionsSession({
sessionId,
status: TransactionBatchStatusesEnum.fail
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { refreshAccount } from 'utils/account';
import { checkBatch } from './checkBatch';
import { TransactionsTrackerType } from '../../trackTransactions.types';
import { getPendingStoreTransactions } from '../getPendingStoreTransactions';
import { getPendingStoreTrackedTransactions } from '../getPendingStoreTrackedTransactions';

export async function checkTransactionStatus(
props: TransactionsTrackerType & {
shouldRefreshBalance?: boolean;
}
) {
const { pendingSessions } = getPendingStoreTransactions();
const { pendingTrackedSessions: pendingSessions } =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not keep sessions, since we don't have any other sessions

getPendingStoreTrackedTransactions();
if (Object.keys(pendingSessions).length > 0) {
for (const [sessionId, { transactions }] of Object.entries(
pendingSessions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
updateSignedTransactionStatus,
updateTransactionsSession
} from 'store/actions/transactions/transactionsActions';
updateTrackedTransactionStatus,
updateTrackedTransactionsSession
} from 'store/actions/trackedTransactions/trackedTransactionsActions';
import {
TransactionBatchStatusesEnum,
TransactionServerStatusesEnum
Expand All @@ -22,7 +22,7 @@ export function manageFailedTransactions({
(scResult) => scResult?.returnMessage !== ''
);

updateSignedTransactionStatus({
updateTrackedTransactionStatus({
transactionHash: hash,
sessionId,
status: TransactionServerStatusesEnum.fail,
Expand All @@ -31,7 +31,7 @@ export function manageFailedTransactions({
serverTransaction: resultWithError as unknown as ServerTransactionType
});

updateTransactionsSession({
updateTrackedTransactionsSession({
sessionId,
status: TransactionBatchStatusesEnum.fail,
errorMessage: resultWithError?.returnMessage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
pendingTrackedSessionsSelector,
pendingTrackedTransactionsSelector
} from 'store/selectors/trackedTransactionsSelector';
import { TrackedTransactionsSliceType } from 'store/slices/trackedTransactions/trackedTransactionsSlice.types';
import { getState } from 'store/store';
import { SignedTransactionType } from 'types/transactions.types';

export interface UseGetPendingTrackedTransactionsReturnType {
pendingTrackedTransactions: SignedTransactionType[];
pendingTrackedSessions: TrackedTransactionsSliceType;
hasPendingTrackedTransactions: boolean;
}

export function getPendingStoreTrackedTransactions(): UseGetPendingTrackedTransactionsReturnType {
const pendingTrackedTransactions =
pendingTrackedTransactionsSelector(getState());
const pendingTrackedSessions = pendingTrackedSessionsSelector(getState());
const hasPendingTrackedTransactions = pendingTrackedTransactions.length > 0;

return {
pendingTrackedTransactions,
pendingTrackedSessions,
hasPendingTrackedTransactions
};
}

This file was deleted.

17 changes: 3 additions & 14 deletions src/core/methods/trackTransactions/trackTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,34 @@
import { getTransactionsByHashes as defaultGetTxByHash } from 'apiCalls/transactions/getTransactionsByHashes';
import { getTransactionsByHashes } from 'apiCalls/transactions/getTransactionsByHashes';
import { websocketEventSelector } from 'store/selectors/accountSelectors';
import { getStore } from 'store/store';
import { checkTransactionStatus } from './helpers/checkTransactionStatus';
import { getPollingInterval } from './helpers/getPollingInterval';
import { TransactionsTrackerType } from './trackTransactions.types';
import {
websocketConnection,
WebsocketConnectionStatusEnum
} from '../initApp/websocket/websocket.constants';

/**
* Tracks transactions using websocket or polling
* @param props - optional object with additional websocket parameters
* @returns cleanup function
*/
export async function trackTransactions(props?: TransactionsTrackerType) {
export async function trackTransactions() {
const store = getStore();
const pollingInterval = getPollingInterval();
// eslint-disable-next-line no-undef
let pollingIntervalTimer: NodeJS.Timeout | null = null;
let timestamp = websocketEventSelector(store.getState())?.timestamp;

// Check if websocket is completed
const isWebsocketCompleted =
websocketConnection.status === WebsocketConnectionStatusEnum.COMPLETED;

// Assign getTransactionsByHash based on props or use default
const getTransactionsByHash =
props?.getTransactionsByHash ?? defaultGetTxByHash;

// Function that handles message (checking transaction status)
const recheckStatus = () => {
checkTransactionStatus({
shouldRefreshBalance: isWebsocketCompleted,
getTransactionsByHash,
...props
getTransactionsByHash: getTransactionsByHashes
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strange name difference. & why not use it as a default in checkTransactionStatus and allow optional override

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed it and used as default in checkTransactionStatus

};

// recheck on page initial page load
recheckStatus();

if (isWebsocketCompleted) {
Expand All @@ -60,7 +50,6 @@ export async function trackTransactions(props?: TransactionsTrackerType) {
}
}

// Return cleanup function for clearing the interval
function cleanup() {
if (pollingIntervalTimer) {
clearInterval(pollingIntervalTimer);
Expand Down
63 changes: 63 additions & 0 deletions src/store/actions/toasts/toastsActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
CustomToastType,
ToastsEnum
} from 'store/slices/toast/toastSlice.types';
import { getStore } from 'store/store';
import { getUnixTimestamp } from 'utils';

export const addCustomToast = (
customToasts: CustomToastType,
currentToastId?: string
) => {
getStore().setState(({ toasts: state }) => {
const toastId =
currentToastId || `custom-toast-${state.customToasts.length + 1}`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add toast 0 --> custom-toast-1
add toast 1 --> custom-toast-2
delete toast 0
add toast --> custom-toast-2 => you will have duplicates of custom-toast-2
solution state.customToasts --> get last toast index and increase it by one

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


const existingToastIndex = state.customToasts.findIndex(
(toast) => toast.toastId === toastId
);

if (existingToastIndex !== -1) {
state.customToasts[existingToastIndex] = {
...state.customToasts[existingToastIndex],
...customToasts,
type: ToastsEnum.custom,
toastId
} as CustomToastType;
return;
}

state.customToasts.push({
...customToasts,
type: ToastsEnum.custom,
toastId
});
});
};

export const removeCustomToast = (toastId: string) => {
getStore().setState(({ toasts: state }) => {
state.customToasts = state.customToasts.filter(
(toast) => toast.toastId !== toastId
);
});
};

export const addTransactionToast = (toastId: string) => {
getStore().setState(({ toasts: state }) => {
state.transactionToasts.push({
type: ToastsEnum.transaction,
startTimestamp: getUnixTimestamp(),
toastId:
toastId || `transaction-toast-${state.transactionToasts.length + 1}`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check vulenrability here if there is one

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did the same thing as in custom case

});
});
};

export const removeTransactionToast = (toastId: string) => {
getStore().setState(({ toasts: state }) => {
state.transactionToasts = state.transactionToasts.filter((toast) => {
return toast.toastId !== toastId;
});
});
};
Loading
Loading