diff --git a/CHANGELOG.md b/CHANGELOG.md
index b78f78d2d..e841bbca7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [[v3.0.10](https://github.com/multiversx/mx-sdk-dapp/pull/1305)] - 2024-11-11
+
+- [Added ability to show transaction toast on demand](https://github.com/multiversx/mx-sdk-dapp/pull/1304)
+
## [[v3.0.9](https://github.com/multiversx/mx-sdk-dapp/pull/1299)] - 2024-11-06
- [Fix clear initiated login](https://github.com/multiversx/mx-sdk-dapp/pull/1301)
diff --git a/package.json b/package.json
index 95361f8ac..1acc4a8b0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-dapp",
- "version": "3.0.9",
+ "version": "3.0.10",
"description": "A library to hold the main logic for a dapp on the MultiversX blockchain",
"author": "MultiversX",
"license": "GPL-3.0-or-later",
diff --git a/src/UI/TransactionDetails/TransactionDetails.tsx b/src/UI/TransactionDetails/TransactionDetails.tsx
index e1b82d604..e42050db5 100644
--- a/src/UI/TransactionDetails/TransactionDetails.tsx
+++ b/src/UI/TransactionDetails/TransactionDetails.tsx
@@ -1,6 +1,10 @@
-import React, { useMemo, ReactNode } from 'react';
+import React, { ReactNode, useMemo } from 'react';
import { withStyles, WithStylesImportType } from 'hocs/withStyles';
-import { SignedTransactionType } from 'types/index';
+import { useGetAccount } from 'hooks/account/useGetAccount';
+import {
+ SignedTransactionType,
+ TransactionServerStatusesEnum
+} from 'types/index';
import { isServerTransactionPending } from 'utils/transactions/transactionStateByStatus';
import {
TransactionDetailsBody,
@@ -25,14 +29,20 @@ const TransactionDetailsComponent = ({
return null;
}
+ const { address } = useGetAccount();
+
const processedTransactionsStatus = useMemo(() => {
const processedTransactions = transactions.filter(
- (tx) => !isServerTransactionPending(tx?.status)
+ (tx) =>
+ !isServerTransactionPending(TransactionServerStatusesEnum[tx?.status])
).length;
+
const totalTransactions = transactions.length;
if (totalTransactions === 1 && processedTransactions === 1) {
- return isServerTransactionPending(transactions[0].status)
+ return isServerTransactionPending(
+ TransactionServerStatusesEnum[transactions[0].status]
+ )
? 'Processing transaction'
: 'Transaction processed';
}
@@ -40,17 +50,22 @@ const TransactionDetailsComponent = ({
return `${processedTransactions} / ${totalTransactions} transactions processed`;
}, [transactions]);
+ const hideProcessedTransactionsStatus =
+ transactions.length === 1 && transactions[0].sender !== address;
+
return (
<>
{title &&
{title}
}
- {processedTransactionsStatus}
+ {!hideProcessedTransactionsStatus && (
+ {processedTransactionsStatus}
+ )}
{transactions.map(({ hash, status }) => {
const transactionDetailsBodyProps: TransactionDetailsBodyPropsType = {
className,
hash,
- status,
+ status: TransactionServerStatusesEnum[status],
isTimedOut
};
diff --git a/src/UI/TransactionsToastList/components/CustomToast/CustomToast.tsx b/src/UI/TransactionsToastList/components/CustomToast/CustomToast.tsx
index 941e2b599..52a74dfbe 100644
--- a/src/UI/TransactionsToastList/components/CustomToast/CustomToast.tsx
+++ b/src/UI/TransactionsToastList/components/CustomToast/CustomToast.tsx
@@ -1,4 +1,10 @@
import React from 'react';
+import {
+ ServerTransactionType,
+ SignedTransactionType,
+ TransactionBatchStatusesEnum
+} from 'types';
+import { TransactionToast } from '../TransactionToast';
import { IconToast, SimpleToast, CustomComponentToast } from './components';
import { CustomToastPropsType } from './customToast.types';
import { useRemoveCustomToast } from './helpers';
@@ -11,6 +17,22 @@ export const CustomToast = (props: CustomToastPropsType) => {
return ;
}
+ if (props.transaction) {
+ const serverTransaction = props.transaction as ServerTransactionType;
+ const signedTransaction =
+ props.transaction as unknown as SignedTransactionType;
+
+ const transactionHash = serverTransaction.txHash || signedTransaction.hash;
+
+ return (
+
+ );
+ }
+
if (props.icon) {
return ;
}
diff --git a/src/UI/TransactionsToastList/components/CustomToast/customToast.types.ts b/src/UI/TransactionsToastList/components/CustomToast/customToast.types.ts
index 134d423c6..140b94f26 100644
--- a/src/UI/TransactionsToastList/components/CustomToast/customToast.types.ts
+++ b/src/UI/TransactionsToastList/components/CustomToast/customToast.types.ts
@@ -18,12 +18,16 @@ type SharedCustomToastPropsType = WithClassnameType & {
export type MessageCustomToastPropsType = SharedCustomToastPropsType &
MessageCustomToastType;
+
export type MessageIconToastPropsType = SharedCustomToastPropsType &
MessageIconToastType;
+
export type TransactionIconToastPropsType = SharedCustomToastPropsType &
TransactionIconToastType;
+
export type ComponentIconToastPropsType = SharedCustomToastPropsType &
ComponentIconToastType;
+
export type CustomToastPropsType =
| MessageCustomToastPropsType
| MessageIconToastPropsType
diff --git a/src/UI/TransactionsToastList/components/TransactionToast/TransactionToast.tsx b/src/UI/TransactionsToastList/components/TransactionToast/TransactionToast.tsx
index 027fe89cc..b9b7bf1cb 100644
--- a/src/UI/TransactionsToastList/components/TransactionToast/TransactionToast.tsx
+++ b/src/UI/TransactionsToastList/components/TransactionToast/TransactionToast.tsx
@@ -18,17 +18,17 @@ export interface TransactionToastPropsType
}
const TransactionToastComponent = ({
- toastId,
- title = '',
className = 'dapp-transaction-toast',
- onDelete,
- startTimestamp,
+ customization,
endTimeProgress,
lifetimeAfterSuccess,
+ onDelete,
+ startTimestamp,
status,
- transactions,
- customization,
- styles
+ styles,
+ title = '',
+ toastId,
+ transactions
}: TransactionToastPropsType & WithStylesImportType) => {
const {
isCrossShard,
@@ -62,14 +62,14 @@ const TransactionToastComponent = ({
isCrossShard={isCrossShard}
>
diff --git a/src/UI/TransactionsToastList/components/TransactionToast/hooks/useTransactionToast.ts b/src/UI/TransactionsToastList/components/TransactionToast/hooks/useTransactionToast.ts
index 3c8333738..f68fe87de 100644
--- a/src/UI/TransactionsToastList/components/TransactionToast/hooks/useTransactionToast.ts
+++ b/src/UI/TransactionsToastList/components/TransactionToast/hooks/useTransactionToast.ts
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef } from 'react';
import { AVERAGE_TX_DURATION_MS, CROSS_SHARD_ROUNDS } from 'constants/index';
import { useStyles } from 'hocs/useStyles';
-import { useGetTransactionDisplayInfo } from 'hooks';
+import { useGetAccount, useGetTransactionDisplayInfo } from 'hooks';
import { useSelector } from 'reduxStore/DappProviderContext';
import { shardSelector } from 'reduxStore/selectors';
import { getUnixTimestamp } from 'utils/dateTime/getUnixTimestamp';
@@ -37,6 +37,7 @@ export const useTransactionToast = ({
const transactionDisplayInfo = useGetTransactionDisplayInfo(toastId);
const accountShard = useSelector(shardSelector);
+ const { address } = useGetAccount();
const timeoutRef = useRef();
const areSameShardTransactions = useMemo(
() => getAreTransactionsOnSameShard(transactions, accountShard),
@@ -71,10 +72,12 @@ export const useTransactionToast = ({
const isCompleted = isFailed || isSuccess || isTimedOut;
const toastDataState = getToastDataStateByStatus({
+ address,
+ classes: styles ?? {},
+ sender: transactions?.[0].sender || address,
status,
toastId,
- transactionDisplayInfo,
- classes: styles ?? {}
+ transactionDisplayInfo
});
const handleDeleteToast = () => {
diff --git a/src/UI/TransactionsToastList/components/TransactionToast/transactionToast.type.ts b/src/UI/TransactionsToastList/components/TransactionToast/transactionToast.type.ts
index 122d680b1..2db821fb8 100644
--- a/src/UI/TransactionsToastList/components/TransactionToast/transactionToast.type.ts
+++ b/src/UI/TransactionsToastList/components/TransactionToast/transactionToast.type.ts
@@ -1,14 +1,18 @@
import { FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
-import { SignedTransactionType, TransactionBatchStatusesEnum } from 'types';
+import {
+ SignedTransactionType,
+ TransactionBatchStatusesEnum,
+ TransactionServerStatusesEnum
+} from 'types';
import { ProgressProps } from 'UI/Progress';
import { TransactionDetailsType } from 'UI/TransactionDetails';
import { ComponentTypeWithChildren } from '../types';
-import { TransactionToastContentProps } from './components/TransactionToastContent';
+import { TransactionToastContentProps } from './components';
export interface TransactionToastDefaultProps {
toastId: string;
transactions?: SignedTransactionType[];
- status?: TransactionBatchStatusesEnum;
+ status?: TransactionBatchStatusesEnum | TransactionServerStatusesEnum;
classes?: Record;
lifetimeAfterSuccess?: number;
endTimeProgress?: number;
diff --git a/src/UI/TransactionsToastList/components/TransactionToast/utils/getToastDataStateByStatus.ts b/src/UI/TransactionsToastList/components/TransactionToast/utils/getToastDataStateByStatus.ts
index 650a01eb6..5597dd9f2 100644
--- a/src/UI/TransactionsToastList/components/TransactionToast/utils/getToastDataStateByStatus.ts
+++ b/src/UI/TransactionsToastList/components/TransactionToast/utils/getToastDataStateByStatus.ts
@@ -8,7 +8,8 @@ import {
import {
TransactionBatchStatusesEnum,
TransactionsDefaultTitles,
- TransactionsDisplayInfoType
+ TransactionsDisplayInfoType,
+ TransactionServerStatusesEnum
} from 'types';
export interface ToastDataState {
@@ -21,23 +22,27 @@ export interface ToastDataState {
}
interface GetToastsOptionsDataPropsType {
- status?: TransactionBatchStatusesEnum;
- toastId: string;
+ address: string;
classes?: Record<
'success' | 'warning' | 'danger' | string,
'success' | 'warning' | 'danger' | string
>;
+ sender: string;
+ status?: TransactionBatchStatusesEnum | TransactionServerStatusesEnum;
+ toastId: string;
transactionDisplayInfo: TransactionsDisplayInfoType;
}
export const getToastDataStateByStatus = ({
- status,
- toastId,
+ address,
classes = {
success: 'success',
danger: 'danger',
warning: 'warning'
},
+ sender,
+ status,
+ toastId,
transactionDisplayInfo
}: GetToastsOptionsDataPropsType) => {
const successToastData: ToastDataState = {
@@ -51,6 +56,15 @@ export const getToastDataStateByStatus = ({
iconClassName: classes.success
};
+ const receivedToastData: ToastDataState = {
+ id: toastId,
+ icon: faCheck,
+ expires: 30000,
+ hasCloseButton: true,
+ title: TransactionsDefaultTitles.received,
+ iconClassName: classes.success
+ };
+
const pendingToastData: ToastDataState = {
id: toastId,
expires: false,
@@ -96,7 +110,7 @@ export const getToastDataStateByStatus = ({
case TransactionBatchStatusesEnum.sent:
return pendingToastData;
case TransactionBatchStatusesEnum.success:
- return successToastData;
+ return sender !== address ? receivedToastData : successToastData;
case TransactionBatchStatusesEnum.cancelled:
case TransactionBatchStatusesEnum.fail:
return failToastData;
diff --git a/src/UI/TransactionsToastList/components/TransactionToastGuard.tsx b/src/UI/TransactionsToastList/components/TransactionToastGuard.tsx
index 9aaba94fe..41ea85f68 100644
--- a/src/UI/TransactionsToastList/components/TransactionToastGuard.tsx
+++ b/src/UI/TransactionsToastList/components/TransactionToastGuard.tsx
@@ -29,6 +29,7 @@ export const TransactionToastGuard = ({
const invalidCurrentTx =
currentTx?.transactions == null || currentTx?.status == null;
+
if (invalidCurrentTx) {
return null;
}
diff --git a/src/hooks/login/useLoginService.ts b/src/hooks/login/useLoginService.ts
index 254504ec9..3d29504d8 100644
--- a/src/hooks/login/useLoginService.ts
+++ b/src/hooks/login/useLoginService.ts
@@ -96,6 +96,7 @@ export const useLoginService = (config?: OnProviderLoginType['nativeAuth']) => {
...(apiAddress ? { nativeAuthConfig: configuration } : {})
})
);
+
return nativeAuthToken;
};
@@ -119,6 +120,7 @@ export const useLoginService = (config?: OnProviderLoginType['nativeAuth']) => {
});
tokenRef.current = loginToken;
+
if (!loginToken) {
return;
}
diff --git a/src/hooks/transactions/useSignMultipleTransactions.tsx b/src/hooks/transactions/useSignMultipleTransactions.tsx
index 484c1f743..2a488b3d3 100644
--- a/src/hooks/transactions/useSignMultipleTransactions.tsx
+++ b/src/hooks/transactions/useSignMultipleTransactions.tsx
@@ -183,6 +183,7 @@ export const useSignMultipleTransactions = ({
setWaitingForDevice(isLedger);
let signedTx: Nullable;
+
try {
signedTx = await onSignTransaction(currentTransaction.transaction);
} catch (err) {
@@ -205,6 +206,7 @@ export const useSignMultipleTransactions = ({
const newSignedTransactions = signedTransactions
? { ...signedTransactions, ...newSignedTx }
: newSignedTx;
+
setSignedTransactions(newSignedTransactions);
if (!isLastTransaction) {
@@ -237,6 +239,7 @@ export const useSignMultipleTransactions = ({
if (currentTransaction == null) {
return;
}
+
const signature = currentTransaction.transaction.getSignature();
if (signature.toString('hex') && !isLastTransaction) {
@@ -245,7 +248,8 @@ export const useSignMultipleTransactions = ({
}
await sign();
- } catch {
+ } catch (e) {
+ console.error('Error during signing transaction');
// the only way to check if tx has signature is with try catch
await sign();
}
@@ -274,15 +278,18 @@ export const useSignMultipleTransactions = ({
setCurrentStep((exising) => exising + 1);
return;
}
+
await signTx();
};
const onNext = () => {
setCurrentStep((current) => {
const nextStep = current + 1;
+
if (nextStep > allTransactions?.length) {
return current;
}
+
return nextStep;
});
};
@@ -290,9 +297,11 @@ export const useSignMultipleTransactions = ({
const onPrev = () => {
setCurrentStep((current) => {
const nextStep = current - 1;
+
if (nextStep < 0) {
return current;
}
+
return nextStep;
});
};
diff --git a/src/hooks/transactions/useSignTransactionsWithDevice.tsx b/src/hooks/transactions/useSignTransactionsWithDevice.tsx
index 27c6000f3..80d3cb776 100644
--- a/src/hooks/transactions/useSignTransactionsWithDevice.tsx
+++ b/src/hooks/transactions/useSignTransactionsWithDevice.tsx
@@ -160,7 +160,12 @@ export function useSignTransactionsWithDevice(
if (!transaction) {
return null;
}
- return await connectedProvider.signTransaction(transaction);
+
+ const signedTransaction = await connectedProvider.signTransaction(
+ transaction
+ );
+
+ return signedTransaction;
}
const signMultipleTxReturnValues = useSignMultipleTransactions({
diff --git a/src/services/nativeAuth/nativeAuth.ts b/src/services/nativeAuth/nativeAuth.ts
index 777a94a67..cc376718d 100644
--- a/src/services/nativeAuth/nativeAuth.ts
+++ b/src/services/nativeAuth/nativeAuth.ts
@@ -42,25 +42,36 @@ export const nativeAuth = (config?: NativeAuthConfigType) => {
const getBlockHash = (): Promise =>
nativeAuthClient.getCurrentBlockHash();
- const response =
- initProps?.latestBlockInfo ??
- (await getLatestBlockHash(
- apiAddress,
- blockHashShard,
- getBlockHash,
- initProps?.noCache
- ));
- const { hash, timestamp } = response;
- const encodedExtraInfo = nativeAuthClient.encodeValue(
- JSON.stringify({
- ...(initProps?.extraInfo ?? extraInfoFromConfig),
- ...(timestamp ? { timestamp } : {})
- })
- );
- const encodedOrigin = nativeAuthClient.encodeValue(origin);
+ try {
+ const response =
+ initProps?.latestBlockInfo ??
+ (await getLatestBlockHash(
+ apiAddress,
+ blockHashShard,
+ getBlockHash,
+ initProps?.noCache
+ ));
- return `${encodedOrigin}.${hash}.${expirySeconds}.${encodedExtraInfo}`;
+ if (!response) {
+ return '';
+ }
+
+ const { hash, timestamp } = response;
+ const encodedExtraInfo = nativeAuthClient.encodeValue(
+ JSON.stringify({
+ ...(initProps?.extraInfo ?? extraInfoFromConfig),
+ ...(timestamp ? { timestamp } : {})
+ })
+ );
+
+ const encodedOrigin = nativeAuthClient.encodeValue(origin);
+
+ return `${encodedOrigin}.${hash}.${expirySeconds}.${encodedExtraInfo}`;
+ } catch (err: any) {
+ console.error('Error getting native auth token: ', err.toString());
+ return '';
+ }
};
const getToken = ({
diff --git a/src/services/nativeAuth/tests/nativeAuth.test.ts b/src/services/nativeAuth/tests/nativeAuth.test.ts
index 3abc10136..855fef490 100644
--- a/src/services/nativeAuth/tests/nativeAuth.test.ts
+++ b/src/services/nativeAuth/tests/nativeAuth.test.ts
@@ -98,19 +98,21 @@ describe('Native Auth', () => {
expect(token).toStrictEqual(TOKEN);
});
- it('Internal server error', async () => {
+ it('should return empty string when API call fails', async () => {
server.use(...handlers.serverError);
//this will make sure to expire the cache
jest
.useFakeTimers()
.setSystemTime(new Date().setSeconds(new Date().getSeconds() + 60));
+
const client = nativeAuth({
origin: ORIGIN,
apiAddress: API_URL
});
- await expect(client.initialize()).rejects.toThrow();
+ const nativeAuthToken = await client.initialize();
+ expect(nativeAuthToken).toStrictEqual('');
});
it('Generate Access token', () => {
diff --git a/src/types/enums.types.ts b/src/types/enums.types.ts
index 64476be1f..7612d0d8c 100644
--- a/src/types/enums.types.ts
+++ b/src/types/enums.types.ts
@@ -79,6 +79,7 @@ export enum TransactionTypesEnum {
export enum TransactionsDefaultTitles {
success = 'Transaction successful',
+ received = 'Transaction received',
failed = 'Transaction failed',
pending = 'Processing transaction',
timedOut = 'Transaction timed out',
diff --git a/src/types/toasts.types.ts b/src/types/toasts.types.ts
index ffca78481..5f249e653 100644
--- a/src/types/toasts.types.ts
+++ b/src/types/toasts.types.ts
@@ -1,5 +1,6 @@
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
import { ServerTransactionType } from './serverTransactions.types';
+import { SignedTransactionType } from './transactions.types';
interface SharedCustomToast {
toastId: string;
@@ -60,7 +61,12 @@ export type CustomToastType =
| TransactionIconToastType;
export interface TransactionToastType {
- toastId: string;
+ duration?: number;
+ icon?: IconDefinition;
+ iconClassName?: string;
startTimestamp: number;
+ title?: string;
+ toastId: string;
+ transaction?: SignedTransactionType;
type: string;
}
diff --git a/src/utils/account/signMessage.ts b/src/utils/account/signMessage.ts
index 520b18906..342a5e6ff 100644
--- a/src/utils/account/signMessage.ts
+++ b/src/utils/account/signMessage.ts
@@ -38,7 +38,9 @@ export const signMessage = async ({
);
}
- return await provider.signMessage(signableMessage, {
+ const signedMessage = await provider.signMessage(signableMessage, {
callbackUrl: encodeURIComponent(callbackUrl)
});
+
+ return signedMessage;
};