{token.uris.map((uri, i) => {
- if (uri !== null && uri !== undefined) {
- const link = Buffer.from(String(uri), 'base64').toString();
- const { stringWithLinks, found } = scamFlag(link, token.scamInfo);
+ if (!uri) {
+ return null;
+ }
- return (
- -
-
- {link.startsWith(
- 'https://ipfs.io/ipfs/'
- ) /* && token.isWhitelistedStorage === true */ ? (
-
-
- {found ? stringWithLinks : link}
+ const link = Buffer.from(String(uri), 'base64').toString();
+ const { stringWithLinks, found } = scamFlag(link, token.scamInfo);
+
+ return (
+
-
+
+ {link.startsWith(
+ 'https://ipfs.io/ipfs/'
+ ) /* && token.isWhitelistedStorage === true */ ? (
+
+
+ {found ? stringWithLinks : link}
+
+
+ ) : (
+
+ {found ? (
+
+ {stringWithLinks}
+
+ ) : (
+
+ {link}
-
- ) : (
-
- {found ? (
-
- {stringWithLinks}
-
- ) : (
-
- {link}
-
- )}
-
- )}
-
- );
- } else return null;
+ )}
+
+ )}
+
+ );
})}
{/* {token.isWhitelistedStorage === false && (
diff --git a/src/helpers/processData/index.ts b/src/helpers/processData/index.ts
index 10d2f3b27..bd5bf641a 100644
--- a/src/helpers/processData/index.ts
+++ b/src/helpers/processData/index.ts
@@ -7,6 +7,7 @@ export * from './processGrowthPrice';
export * from './processGrowthSearch';
export * from './processGrowthStaking';
export * from './processGrowthTransactions';
+export * from './processListUpdates';
export * from './processNodesIdentities';
export * from './processNodesOverview';
export * from './processNodesVersions';
diff --git a/src/helpers/processData/processListUpdates.ts b/src/helpers/processData/processListUpdates.ts
new file mode 100644
index 000000000..102e9d479
--- /dev/null
+++ b/src/helpers/processData/processListUpdates.ts
@@ -0,0 +1,42 @@
+import { PAGE_SIZE } from 'appConstants';
+
+interface ProcessListUpdatesProps {
+ existing: any[];
+ incoming: any[];
+ uniqueKey: string;
+ size?: number;
+}
+
+export const processListUpdates = ({
+ existing = [],
+ incoming = [],
+ uniqueKey,
+ size = PAGE_SIZE
+}: ProcessListUpdatesProps) => {
+ const existingSet = new Set(existing.map((entry) => entry[uniqueKey]));
+ const updated = new Map
();
+ const result: any[] = [];
+
+ for (const entry of existing) {
+ updated.set(entry[uniqueKey], entry);
+ }
+ for (const entry of incoming) {
+ updated.set(entry[uniqueKey], { ...entry, isNew: true });
+ }
+
+ for (const entry of incoming) {
+ if (!existingSet.has(entry[uniqueKey])) {
+ result.push(updated.get(entry[uniqueKey])!);
+ }
+ }
+
+ for (const entry of existing) {
+ if (entry[uniqueKey] === undefined) {
+ continue;
+ }
+ result.push(updated.get(entry[uniqueKey])!);
+ }
+
+ // keep the resulting set the same size as the page size prop
+ return result.slice(0, size);
+};
diff --git a/src/hooks/adapter/requests/useTokenRequests.ts b/src/hooks/adapter/requests/useTokenRequests.ts
index c31881c09..1a10bd8c7 100644
--- a/src/hooks/adapter/requests/useTokenRequests.ts
+++ b/src/hooks/adapter/requests/useTokenRequests.ts
@@ -14,8 +14,8 @@ export const useTokenRequests = () => {
return {
/* Tokens */
- getToken: (tokenId: string, { signal, timeout }: AxiosParamsApiType = {}) =>
- provider({ url: `/tokens/${tokenId}`, signal, timeout }),
+ getToken: (token: string, { signal, timeout }: AxiosParamsApiType = {}) =>
+ provider({ url: `/tokens/${token}`, signal, timeout }),
getTokens: ({ signal, timeout, ...params }: GetTokensType) =>
provider({
@@ -34,13 +34,13 @@ export const useTokenRequests = () => {
}),
getTokenTransactions: ({
- tokenId,
+ token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/transactions`,
+ url: `/tokens/${token}/transactions`,
signal,
timeout,
params: getTransactionsParams({
@@ -49,13 +49,13 @@ export const useTokenRequests = () => {
}),
getTokenTransactionsCount: ({
- tokenId,
+ token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/transactions/c`,
+ url: `/tokens/${token}/transactions/c`,
signal,
timeout,
params: getTransactionsParams({
@@ -65,13 +65,13 @@ export const useTokenRequests = () => {
}),
getTokenTransfers: ({
- tokenId,
+ token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/transfers`,
+ url: `/tokens/${token}/transfers`,
signal,
timeout,
params: getTransactionsParams({
@@ -80,13 +80,13 @@ export const useTokenRequests = () => {
}),
getTokenTransfersCount: ({
- tokenId,
+ token,
signal,
timeout,
...params
}: GetTransactionsType & GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/transfers/c`,
+ url: `/tokens/${token}/transfers/c`,
signal,
timeout,
params: getTransactionsParams({
@@ -96,32 +96,28 @@ export const useTokenRequests = () => {
}),
getTokenAccounts: ({
- tokenId,
+ token,
signal,
timeout,
...params
}: GetTokensType & GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/accounts`,
+ url: `/tokens/${token}/accounts`,
signal,
timeout,
params: getTokensParams({ ...params })
}),
- getTokenAccountsCount: ({
- tokenId,
- signal,
- timeout
- }: GetTokenResourceType) =>
+ getTokenAccountsCount: ({ token, signal, timeout }: GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/accounts/count`,
+ url: `/tokens/${token}/accounts/count`,
signal,
timeout
}),
- getTokenSupply: ({ tokenId, signal, timeout }: GetTokenResourceType) =>
+ getTokenSupply: ({ token, signal, timeout }: GetTokenResourceType) =>
provider({
- url: `/tokens/${tokenId}/supply`,
+ url: `/tokens/${token}/supply`,
signal,
timeout
})
diff --git a/src/hooks/fetch/index.ts b/src/hooks/fetch/index.ts
index 7d23bc6ef..50ecd6a7c 100644
--- a/src/hooks/fetch/index.ts
+++ b/src/hooks/fetch/index.ts
@@ -12,5 +12,6 @@ export * from './useFetchNodesVersions';
export * from './useFetchShards';
export * from './useFetchStats';
export * from './useFetchTransactions';
+export * from './useFetchCustomTransfers';
export * from './useFetchTransactionsInPool';
export * from './useFetchWebsocketConfig';
diff --git a/src/hooks/fetch/useFetchApiData.ts b/src/hooks/fetch/useFetchApiData.ts
index c13ee0078..4568f9ce4 100644
--- a/src/hooks/fetch/useFetchApiData.ts
+++ b/src/hooks/fetch/useFetchApiData.ts
@@ -22,7 +22,9 @@ export interface FetchApiDataProps {
event?: WebsocketEventsEnum;
websocketConfig?: Record;
urlParams?: Record;
+ uuid?: string;
isRefreshPaused?: boolean;
+ isCustomUpdate?: boolean;
}
export const useFetchApiData = ({
@@ -35,6 +37,8 @@ export const useFetchApiData = ({
event,
websocketConfig = {},
urlParams = {},
+ uuid = '',
+ isCustomUpdate,
isRefreshPaused = false
}: FetchApiDataProps) => {
const { page, size } = useGetPage();
@@ -61,7 +65,11 @@ export const useFetchApiData = ({
useRegisterWebsocketListener({
subscription,
event,
- config: { from: 0, size: PAGE_SIZE, ...websocketConfig },
+ uuid,
+ config: {
+ ...(isCustomUpdate ? {} : { from: 0, size: PAGE_SIZE }),
+ ...websocketConfig
+ },
onWebsocketEvent,
isPaused
});
diff --git a/src/hooks/fetch/useFetchCustomTransfers.ts b/src/hooks/fetch/useFetchCustomTransfers.ts
new file mode 100644
index 000000000..123165b33
--- /dev/null
+++ b/src/hooks/fetch/useFetchCustomTransfers.ts
@@ -0,0 +1,104 @@
+import { useCallback } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { ELLIPSIS } from 'appConstants';
+import { useGetPage, useGetTransactionFilters } from 'hooks';
+import { customTransfersSelector } from 'redux/selectors';
+import { setCustomTransfers } from 'redux/slices';
+import { TransactionType } from 'types';
+import { FetchApiDataProps, useFetchApiData } from './useFetchApiData';
+
+export interface FetchCustomTransfersProps
+ extends Omit {
+ uuid?: string;
+}
+
+export interface CustomTransfersWebsocketResponseType {
+ transfers: TransactionType[];
+ timestampMs: number;
+}
+
+export const useFetchCustomTransfers = (props: FetchCustomTransfersProps) => {
+ const dispatch = useDispatch();
+ const transactionFilters = useGetTransactionFilters();
+ const { page, size } = useGetPage();
+
+ const { dataCountPromise, filters, websocketConfig } = props;
+
+ const { transactions, transactionsCount, isDataReady, isRefreshPaused } =
+ useSelector(customTransfersSelector);
+
+ const onWebsocketData = useCallback(
+ (event: CustomTransfersWebsocketResponseType) => {
+ if (!event) {
+ return;
+ }
+
+ const { transfers } = event;
+ try {
+ const transfersCount =
+ transactionsCount !== ELLIPSIS
+ ? transactionsCount + transfers.length
+ : ELLIPSIS;
+
+ dispatch(
+ setCustomTransfers({
+ transactions: transfers,
+ transactionsCount: transfersCount,
+ size,
+ uuid: props.uuid,
+ isWebsocket: false, // keep api, only latest updates fetched fron ws
+ isDataReady: true
+ })
+ );
+ } catch {
+ // do nothing
+ }
+ },
+ [size, props.uuid, transactionsCount]
+ );
+
+ const onApiData = useCallback(
+ (response: any[]) => {
+ const [transactionsData, transactionsCountData] = response;
+ dispatch(
+ setCustomTransfers({
+ transactions: transactionsData.data ?? [],
+ transactionsCount: transactionsCountData?.data ?? ELLIPSIS,
+ isWebsocket: false,
+ clearExisting: true,
+ size,
+ uuid: props.uuid,
+ isDataReady:
+ transactionsData.success &&
+ Boolean(!dataCountPromise || transactionsCountData?.success)
+ })
+ );
+ },
+ [size, props.uuid, dataCountPromise]
+ );
+
+ const { fetchData, dataChanged } = useFetchApiData({
+ ...props,
+ filters: {
+ page,
+ size,
+ ...transactionFilters,
+ ...filters
+ },
+ websocketConfig,
+ onWebsocketData,
+ onApiData,
+ urlParams: transactionFilters,
+ isRefreshPaused,
+ isCustomUpdate: true
+ });
+
+ return {
+ transactions,
+ totalTransactions: transactionsCount,
+ isDataReady,
+ fetchTransactions: fetchData,
+ dataChanged
+ };
+};
diff --git a/src/hooks/fetch/useFetchTransactions.ts b/src/hooks/fetch/useFetchTransactions.ts
index 02202cc14..cf8ee858b 100644
--- a/src/hooks/fetch/useFetchTransactions.ts
+++ b/src/hooks/fetch/useFetchTransactions.ts
@@ -10,9 +10,10 @@ import { FetchApiDataProps, useFetchApiData } from './useFetchApiData';
export interface FetchTransactionsProps
extends Omit {
hasMaxTransactionsSize?: boolean;
+ uuid?: string;
}
-interface TransactionsWebsocketResponseType {
+export interface TransactionsWebsocketResponseType {
transactions: TransactionType[];
transactionsCount: number;
}
diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts
index b6a9acd0e..9f6bdde49 100644
--- a/src/hooks/websocket/useRegisterWebsocketListener.ts
+++ b/src/hooks/websocket/useRegisterWebsocketListener.ts
@@ -17,14 +17,16 @@ export interface RegisterWebsocketListenerType {
event?: WebsocketEventsEnum;
config?: Record;
isPaused?: boolean;
+ uuid?: string;
}
export function useRegisterWebsocketListener({
onWebsocketEvent,
- subscription,
+ subscription: subscriptionName,
event,
config,
- isPaused
+ isPaused,
+ uuid = ''
}: RegisterWebsocketListenerType) {
const hasWebsocketUrl = useHasWebsocketUrl();
@@ -32,10 +34,12 @@ export function useRegisterWebsocketListener({
useEffect(() => {
const websocketConfig = config ?? true;
- if (!subscription || !event) {
+ if (!subscriptionName || !event) {
return;
}
+ const subscription = `${subscriptionName}${uuid}`;
+
const websocket = websocketConnection.instance;
const hasSubscription = websocketSubscriptions.has(subscription);
@@ -44,6 +48,13 @@ export function useRegisterWebsocketListener({
const hasActiveSubscription =
websocketActiveSubscriptions.has(subscription);
+ const isStatsEvent = event === WebsocketEventsEnum.statsUpdate;
+ const isCustomEvent = [
+ WebsocketEventsEnum.customTransactionUpdate,
+ WebsocketEventsEnum.customTransferUpdate,
+ WebsocketEventsEnum.customEventUpdate
+ ].includes(event);
+
if (
!websocket ||
!websocket?.active ||
@@ -60,8 +71,13 @@ export function useRegisterWebsocketListener({
}
if (!hasSubscription) {
- websocket.emit(subscription, websocketConfig, (response: any) => {
- console.info(`New Websocket Subscription ${subscription}`);
+ websocket.emit(subscriptionName, websocketConfig, (response: any) => {
+ if (import.meta.env.DEV) {
+ console.info(
+ `New Websocket Subscription ${subscriptionName}`,
+ response
+ );
+ }
if (response?.status !== 'success') {
websocketSubscriptions.delete(subscription);
websocketPendingSubscriptions.delete(subscription);
@@ -74,7 +90,11 @@ export function useRegisterWebsocketListener({
}
websocket.on(event, (response: any) => {
- if (document.hidden) {
+ if (
+ typeof document !== 'undefined' &&
+ document.hidden &&
+ !(isStatsEvent || isCustomEvent)
+ ) {
return;
}
@@ -82,12 +102,27 @@ export function useRegisterWebsocketListener({
websocketPendingSubscriptions.delete(subscription);
websocketActiveSubscriptions.add(subscription);
}
- // console.info(`Client ${event}:`, response);
onWebsocketEvent(response);
});
return () => {
websocket?.off(event);
+ if (!isStatsEvent) {
+ websocket.emit(
+ `un${subscriptionName}`,
+ websocketConfig,
+ (response: any) => {
+ if (import.meta.env.DEV) {
+ console.info(
+ `Unsubscribe Subscription ${subscriptionName}`,
+ response
+ );
+ }
+ }
+ );
+ websocketPendingSubscriptions.delete(subscription);
+ websocketSubscriptions.delete(subscription);
+ }
websocketActiveSubscriptions.delete(subscription);
};
}, [
@@ -98,7 +133,7 @@ export function useRegisterWebsocketListener({
websocketConnection.status,
hasWebsocketUrl,
event,
- subscription,
+ subscriptionName,
isPaused
]);
}
diff --git a/src/layouts/Layout/components/Header/components/Links/links.styles.scss b/src/layouts/Layout/components/Header/components/Links/links.styles.scss
index 5854e0f35..3a3447f02 100644
--- a/src/layouts/Layout/components/Header/components/Links/links.styles.scss
+++ b/src/layouts/Layout/components/Header/components/Links/links.styles.scss
@@ -78,6 +78,12 @@
&.active {
--link-color: var(--primary);
--nav-link-color: var(--primary);
+ }
+ }
+
+ & > .link {
+ &:hover,
+ &.active {
background-color: var(--black);
@include media-breakpoint-up(lg) {
background-color: rgba(black, 0.2);
diff --git a/src/layouts/NftLayout/NftDetailsCard.tsx b/src/layouts/NftLayout/NftDetailsCard.tsx
index 5023c2dad..e26add42e 100644
--- a/src/layouts/NftLayout/NftDetailsCard.tsx
+++ b/src/layouts/NftLayout/NftDetailsCard.tsx
@@ -240,7 +240,7 @@ export const NftDetailsCard = () => {
)
}
: {},
- uris !== undefined && uris[0]
+ uris !== undefined && uris.length > 0
? {
title: 'Assets',
value: (
diff --git a/src/layouts/TokenLayout/TokenHolderDetailsCard.tsx b/src/layouts/TokenLayout/TokenHolderDetailsCard.tsx
index 6f1892e19..4a00ef3da 100644
--- a/src/layouts/TokenLayout/TokenHolderDetailsCard.tsx
+++ b/src/layouts/TokenLayout/TokenHolderDetailsCard.tsx
@@ -26,7 +26,7 @@ export const TokenHolderDetailsCard = () => {
const [accountTokenDetails, setAccountTokenDetails] = useState();
const filterAddress = sender || receiver;
- const address = String(filterAddress);
+ const address = filterAddress ? String(filterAddress) : '';
const usdValue = accountTokenDetails?.valueUsd ?? ZERO;
const fetchAccountTokens = async () => {
diff --git a/src/pages/AccountDetails/AccountTransactions.tsx b/src/pages/AccountDetails/AccountTransactions.tsx
index 73ae869f9..3b4a538c9 100644
--- a/src/pages/AccountDetails/AccountTransactions.tsx
+++ b/src/pages/AccountDetails/AccountTransactions.tsx
@@ -3,9 +3,10 @@ import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom';
import { TransactionsTable } from 'components';
-import { useAdapter, useFetchTransactions } from 'hooks';
+import { useAdapter, useFetchCustomTransfers } from 'hooks';
import { AccountTabs } from 'layouts/AccountLayout/AccountTabs';
import { activeNetworkSelector, accountSelector } from 'redux/selectors';
+import { WebsocketEventsEnum, WebsocketSubcriptionsEnum } from 'types';
export const AccountTransactions = () => {
const [searchParams] = useSearchParams();
@@ -13,7 +14,7 @@ export const AccountTransactions = () => {
const { id: activeNetworkId } = useSelector(activeNetworkSelector);
const { account } = useSelector(accountSelector);
- const { address, txCount, balance } = account;
+ const { address } = account;
const {
fetchTransactions,
@@ -21,18 +22,22 @@ export const AccountTransactions = () => {
totalTransactions,
isDataReady,
dataChanged
- } = useFetchTransactions({
+ } = useFetchCustomTransfers({
+ uuid: address,
dataPromise: getAccountTransfers,
dataCountPromise: getAccountTransfersCount,
+ subscription: WebsocketSubcriptionsEnum.subscribeCustomTransfers,
+ event: WebsocketEventsEnum.customTransferUpdate,
filters: {
address,
withTxsRelayedByAddress: true
- }
+ },
+ websocketConfig: { address }
});
useEffect(() => {
fetchTransactions();
- }, [activeNetworkId, address, txCount, balance]);
+ }, [activeNetworkId, address]);
useEffect(() => {
fetchTransactions(Boolean(searchParams.toString()));
diff --git a/src/pages/Accounts/Accounts.tsx b/src/pages/Accounts/Accounts.tsx
index 0a404e57e..80fe58dde 100644
--- a/src/pages/Accounts/Accounts.tsx
+++ b/src/pages/Accounts/Accounts.tsx
@@ -53,10 +53,10 @@ export const Accounts = () => {
getAccounts({
page,
size,
- name: search,
+ search,
...sort
}),
- getAccountsCount({ name: search })
+ getAccountsCount({ search })
])
.then(([accountsData, accountsCountData]) => {
if (accountsData.success && accountsCountData.success) {
diff --git a/src/pages/Applications/Applications.tsx b/src/pages/Applications/Applications.tsx
index d02c24e7d..40f6dab69 100644
--- a/src/pages/Applications/Applications.tsx
+++ b/src/pages/Applications/Applications.tsx
@@ -82,7 +82,7 @@ export const Applications = () => {
Promise.all([
getAccounts({
page,
- name: search,
+ search,
isSmartContract: true,
withOwnerAssets: true,
withDeployInfo: true,
@@ -90,7 +90,7 @@ export const Applications = () => {
...(is24hCountAvailable ? { size } : { size: minSize }),
...sort
}),
- getAccountsCount({ isSmartContract: true, name: search })
+ getAccountsCount({ isSmartContract: true, search })
])
.then(([applicationsData, applicationsCountData]) => {
if (applicationsData.success && applicationsCountData.success) {
diff --git a/src/pages/TokenDetails/TokenTransactions.tsx b/src/pages/TokenDetails/TokenTransactions.tsx
index ae7799529..3c264b061 100644
--- a/src/pages/TokenDetails/TokenTransactions.tsx
+++ b/src/pages/TokenDetails/TokenTransactions.tsx
@@ -1,19 +1,21 @@
-import { useEffect, useRef } from 'react';
+import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useParams, useSearchParams } from 'react-router-dom';
import { TransactionsTable } from 'components';
-import { useAdapter, useFetchTransactions } from 'hooks';
+import { useAdapter, useFetchCustomTransfers } from 'hooks';
import { TokenTabs } from 'layouts/TokenLayout/TokenTabs';
-import { activeNetworkSelector, tokenSelector } from 'redux/selectors';
-import { TransactionFiltersEnum } from 'types';
+import { activeNetworkSelector } from 'redux/selectors';
+import {
+ TransactionFiltersEnum,
+ WebsocketEventsEnum,
+ WebsocketSubcriptionsEnum
+} from 'types';
export const TokenTransactions = () => {
const [searchParams] = useSearchParams();
const { getTokenTransfers, getTokenTransfersCount } = useAdapter();
const { id: activeNetworkId } = useSelector(activeNetworkSelector);
- const { token } = useSelector(tokenSelector);
- const { transactions: transactionsCount } = token;
const { hash: tokenId } = useParams();
@@ -23,17 +25,21 @@ export const TokenTransactions = () => {
totalTransactions,
isDataReady,
dataChanged
- } = useFetchTransactions({
+ } = useFetchCustomTransfers({
+ uuid: tokenId,
dataPromise: getTokenTransfers,
dataCountPromise: getTokenTransfersCount,
+ subscription: WebsocketSubcriptionsEnum.subscribeCustomTransfers,
+ event: WebsocketEventsEnum.customTransferUpdate,
filters: {
- tokenId
- }
+ token: tokenId
+ },
+ websocketConfig: { token: tokenId }
});
useEffect(() => {
fetchTransactions();
- }, [activeNetworkId, tokenId, transactionsCount]);
+ }, [activeNetworkId, tokenId]);
useEffect(() => {
fetchTransactions(Boolean(searchParams.toString()));
diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts
index 39f95b5af..5379b5369 100644
--- a/src/redux/reducers.ts
+++ b/src/redux/reducers.ts
@@ -35,7 +35,10 @@ import {
eventsReducer,
transactionsReducer,
transactionsInPoolReducer,
- transactionOverviewReducer
+ transactionOverviewReducer,
+ customEventsReducer,
+ customTransactionsReducer,
+ customTransfersReducer
} from './slices/transactions';
import {
@@ -78,6 +81,9 @@ export const customIgnoredSlices = {
accountStaking: accountStakingReducer,
blocks: blocksReducer,
collection: collectionReducer,
+ customEvents: customEventsReducer,
+ customTransactions: customTransactionsReducer,
+ customTransfers: customTransfersReducer,
economics: economicsReducer,
events: eventsReducer,
general: generalReducer,
diff --git a/src/redux/selectors/transactions/customEvents.ts b/src/redux/selectors/transactions/customEvents.ts
new file mode 100644
index 000000000..fe36861a5
--- /dev/null
+++ b/src/redux/selectors/transactions/customEvents.ts
@@ -0,0 +1,11 @@
+import { createSelector } from 'reselect';
+import { RootState } from '../../store';
+
+const stateSelector = (state: RootState) => {
+ return state.customEvents;
+};
+
+export const customEventsSelector = createSelector(
+ stateSelector,
+ (state) => state
+);
diff --git a/src/redux/selectors/transactions/customTransactions.ts b/src/redux/selectors/transactions/customTransactions.ts
new file mode 100644
index 000000000..ed103dab4
--- /dev/null
+++ b/src/redux/selectors/transactions/customTransactions.ts
@@ -0,0 +1,11 @@
+import { createSelector } from 'reselect';
+import { RootState } from '../../store';
+
+const stateSelector = (state: RootState) => {
+ return state.customTransactions;
+};
+
+export const customTransactionsSelector = createSelector(
+ stateSelector,
+ (state) => state
+);
diff --git a/src/redux/selectors/transactions/customTransfers.ts b/src/redux/selectors/transactions/customTransfers.ts
new file mode 100644
index 000000000..ac11d7ffe
--- /dev/null
+++ b/src/redux/selectors/transactions/customTransfers.ts
@@ -0,0 +1,11 @@
+import { createSelector } from 'reselect';
+import { RootState } from '../../store';
+
+const stateSelector = (state: RootState) => {
+ return state.customTransfers;
+};
+
+export const customTransfersSelector = createSelector(
+ stateSelector,
+ (state) => state
+);
diff --git a/src/redux/selectors/transactions/index.ts b/src/redux/selectors/transactions/index.ts
index 9d038a15d..1cbd209d9 100644
--- a/src/redux/selectors/transactions/index.ts
+++ b/src/redux/selectors/transactions/index.ts
@@ -1,3 +1,6 @@
+export * from './customEvents';
+export * from './customTransactions';
+export * from './customTransfers';
export * from './events';
export * from './transactionOverview';
export * from './transactions';
diff --git a/src/redux/slices/transactions/customEvents.ts b/src/redux/slices/transactions/customEvents.ts
new file mode 100644
index 000000000..c2e6815e6
--- /dev/null
+++ b/src/redux/slices/transactions/customEvents.ts
@@ -0,0 +1,62 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { ELLIPSIS } from 'appConstants';
+import { processListUpdates } from 'helpers';
+import { CustomEventsSliceType, EventsSliceType } from 'types';
+import { getInitialEventsState } from './events';
+
+export const getInitialCustomEventsState = (): CustomEventsSliceType => {
+ return {
+ ...getInitialEventsState(),
+ uuid: undefined
+ };
+};
+
+export const customEventsSlice = createSlice({
+ name: 'customEventsSlice',
+ initialState: getInitialCustomEventsState(),
+ reducers: {
+ setCustomEvents: (
+ state: CustomEventsSliceType,
+ action: PayloadAction
+ ) => {
+ if (state.uuid && state.uuid !== action.payload.uuid) {
+ state.events = [];
+ state.eventsCount = ELLIPSIS;
+ }
+
+ const existing = state.events;
+ const incoming = action.payload.events;
+
+ const trimmedEvents = processListUpdates({
+ existing,
+ incoming,
+ uniqueKey: 'txHash',
+ size: action.payload.size
+ });
+
+ state.uuid = action.payload.uuid;
+ state.events = trimmedEvents;
+
+ if (action.payload.eventsCount !== ELLIPSIS) {
+ state.eventsCount = action.payload.eventsCount;
+ }
+
+ state.isDataReady = action.payload.isDataReady;
+ state.isWebsocket = action.payload.isWebsocket;
+ },
+ pauseCustomEventsRefresh: (state: EventsSliceType) => {
+ state.isRefreshPaused = true;
+ },
+ resumeCustomEventsRefresh: (state: EventsSliceType) => {
+ state.isRefreshPaused = false;
+ }
+ }
+});
+
+export const {
+ setCustomEvents,
+ pauseCustomEventsRefresh,
+ resumeCustomEventsRefresh
+} = customEventsSlice.actions;
+
+export const customEventsReducer = customEventsSlice.reducer;
diff --git a/src/redux/slices/transactions/customTransactions.ts b/src/redux/slices/transactions/customTransactions.ts
new file mode 100644
index 000000000..a55ca7fee
--- /dev/null
+++ b/src/redux/slices/transactions/customTransactions.ts
@@ -0,0 +1,63 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { ELLIPSIS } from 'appConstants';
+import { processListUpdates } from 'helpers';
+import { CustomTransactionSliceType, TransactionSliceType } from 'types';
+import { getInitialTransactionsState } from './transactions';
+
+export const getInitialCustomTransactionsState =
+ (): CustomTransactionSliceType => {
+ return {
+ ...getInitialTransactionsState(),
+ uuid: undefined
+ };
+ };
+
+export const customTransactionsSlice = createSlice({
+ name: 'customTransactionsSlice',
+ initialState: getInitialCustomTransactionsState(),
+ reducers: {
+ setCustomTransactions: (
+ state: CustomTransactionSliceType,
+ action: PayloadAction
+ ) => {
+ if (state.uuid && state.uuid !== action.payload.uuid) {
+ state.transactions = [];
+ state.transactionsCount = ELLIPSIS;
+ }
+
+ const existing = state.transactions;
+ const incoming = action.payload.transactions;
+
+ const trimmedTransactions = processListUpdates({
+ existing,
+ incoming,
+ uniqueKey: 'txHash',
+ size: action.payload.size
+ });
+
+ state.uuid = action.payload.uuid;
+ state.transactions = trimmedTransactions;
+
+ if (action.payload.transactionsCount !== ELLIPSIS) {
+ state.transactionsCount = action.payload.transactionsCount;
+ }
+
+ state.isDataReady = action.payload.isDataReady;
+ state.isWebsocket = action.payload.isWebsocket;
+ },
+ pauseCustomTransactionsRefresh: (state: TransactionSliceType) => {
+ state.isRefreshPaused = true;
+ },
+ resumeCustomTransactionsRefresh: (state: TransactionSliceType) => {
+ state.isRefreshPaused = false;
+ }
+ }
+});
+
+export const {
+ setCustomTransactions,
+ pauseCustomTransactionsRefresh,
+ resumeCustomTransactionsRefresh
+} = customTransactionsSlice.actions;
+
+export const customTransactionsReducer = customTransactionsSlice.reducer;
diff --git a/src/redux/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts
new file mode 100644
index 000000000..6092ac72c
--- /dev/null
+++ b/src/redux/slices/transactions/customTransfers.ts
@@ -0,0 +1,66 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { ELLIPSIS } from 'appConstants';
+import { processListUpdates } from 'helpers';
+import { CustomTransactionSliceType, TransactionSliceType } from 'types';
+import { getInitialTransactionsState } from './transactions';
+
+export const getInitialCustomTransfersState =
+ (): CustomTransactionSliceType => {
+ return {
+ ...getInitialTransactionsState(),
+ uuid: undefined
+ };
+ };
+
+export const customTransfersSlice = createSlice({
+ name: 'customTransfersSlice',
+ initialState: getInitialCustomTransfersState(),
+ reducers: {
+ setCustomTransfers: (
+ state: CustomTransactionSliceType,
+ action: PayloadAction
+ ) => {
+ if (
+ action.payload.clearExisting ||
+ (state.uuid && state.uuid !== action.payload.uuid)
+ ) {
+ state.transactions = [];
+ state.transactionsCount = ELLIPSIS;
+ }
+
+ const existing = state.transactions;
+ const incoming = action.payload.transactions;
+
+ const trimmedTransactions = processListUpdates({
+ existing,
+ incoming,
+ uniqueKey: 'txHash',
+ size: action.payload.size
+ });
+
+ state.uuid = action.payload.uuid;
+ state.transactions = trimmedTransactions;
+
+ if (action.payload.transactionsCount !== ELLIPSIS) {
+ state.transactionsCount = action.payload.transactionsCount;
+ }
+
+ state.isDataReady = action.payload.isDataReady;
+ state.isWebsocket = action.payload.isWebsocket;
+ },
+ pauseCustomTransfersRefresh: (state: TransactionSliceType) => {
+ state.isRefreshPaused = true;
+ },
+ resumeCustomTransfersRefresh: (state: TransactionSliceType) => {
+ state.isRefreshPaused = false;
+ }
+ }
+});
+
+export const {
+ setCustomTransfers,
+ pauseCustomTransfersRefresh,
+ resumeCustomTransfersRefresh
+} = customTransfersSlice.actions;
+
+export const customTransfersReducer = customTransfersSlice.reducer;
diff --git a/src/redux/slices/transactions/index.ts b/src/redux/slices/transactions/index.ts
index e54927fdc..16904c7b2 100644
--- a/src/redux/slices/transactions/index.ts
+++ b/src/redux/slices/transactions/index.ts
@@ -1,3 +1,6 @@
+export * from './customEvents';
+export * from './customTransactions';
+export * from './customTransfers';
export * from './events';
export * from './transactions';
export * from './transactionsInPool';
diff --git a/src/types/adapter.types.ts b/src/types/adapter.types.ts
index 26869c137..784f203db 100644
--- a/src/types/adapter.types.ts
+++ b/src/types/adapter.types.ts
@@ -29,7 +29,7 @@ export interface GetAccountResourceType extends AxiosParamsApiType {
}
export interface GetTokenResourceType extends AxiosParamsApiType {
- tokenId: string;
+ token: string;
}
export interface GetNftResourceType extends AxiosParamsApiType {
diff --git a/src/types/events.types.ts b/src/types/events.types.ts
index 6c3bbd302..fe7521a13 100644
--- a/src/types/events.types.ts
+++ b/src/types/events.types.ts
@@ -24,6 +24,10 @@ export interface EventsSliceType extends SliceType {
eventsCount: number | typeof ELLIPSIS;
}
+export interface CustomEventsSliceType extends EventsSliceType {
+ uuid?: string;
+}
+
export enum TransactionEventIdentifiersEnum {
ESDTNFTTransfer = 'ESDTNFTTransfer',
ESDTNFTBurn = 'ESDTNFTBurn',
diff --git a/src/types/transaction.types.ts b/src/types/transaction.types.ts
index 3126ef7f9..1642640ae 100644
--- a/src/types/transaction.types.ts
+++ b/src/types/transaction.types.ts
@@ -280,6 +280,11 @@ export interface TransactionSliceType extends SliceType {
transactionsCount: number | typeof ELLIPSIS;
}
+export interface CustomTransactionSliceType extends TransactionSliceType {
+ uuid?: string;
+ clearExisting?: boolean;
+}
+
export interface TransactionInPoolSliceType extends SliceType {
transactionsInPool: UITransactionInPoolType[];
transactionsInPoolCount: number | typeof ELLIPSIS;
diff --git a/src/types/websocket.types.ts b/src/types/websocket.types.ts
index 763a1df26..dff0bba3e 100644
--- a/src/types/websocket.types.ts
+++ b/src/types/websocket.types.ts
@@ -3,7 +3,10 @@ export enum WebsocketSubcriptionsEnum {
subscribeBlocks = 'subscribeBlocks',
subscribePool = 'subscribePool',
subscribeStats = 'subscribeStats',
- subscribeEvents = 'subscribeEvents'
+ subscribeEvents = 'subscribeEvents',
+ subscribeCustomTransactions = 'subscribeCustomTransactions',
+ subscribeCustomTransfers = 'subscribeCustomTransfers',
+ subscribeCustomEvents = 'subscribeCustomEvents'
}
export enum WebsocketEventsEnum {
@@ -15,5 +18,8 @@ export enum WebsocketEventsEnum {
blocksUpdate = 'blocksUpdate',
poolUpdate = 'poolUpdate',
statsUpdate = 'statsUpdate',
- eventsUpdate = 'eventsUpdate'
+ eventsUpdate = 'eventsUpdate',
+ customTransactionUpdate = 'customTransactionUpdate',
+ customTransferUpdate = 'customTransferUpdate',
+ customEventUpdate = 'customEventUpdate'
}