From 1fc663d09555835b09f71381fda2ed90f78bd30c Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Fri, 19 Dec 2025 14:08:23 +0200 Subject: [PATCH 01/21] update menu styling --- src/assets/scss/common/variables/_bootstrap-variables.scss | 2 +- .../components/Header/components/Links/links.styles.scss | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/assets/scss/common/variables/_bootstrap-variables.scss b/src/assets/scss/common/variables/_bootstrap-variables.scss index 476543d93..22f8f1226 100644 --- a/src/assets/scss/common/variables/_bootstrap-variables.scss +++ b/src/assets/scss/common/variables/_bootstrap-variables.scss @@ -393,7 +393,7 @@ $dropdown-link-color: $neutral-400; $dropdown-link-hover-color: $body-color; $dropdown-link-hover-bg: $neutral-700; $dropdown-link-active-color: $body-color; -$dropdown-link-active-bg: $neutral-900; +$dropdown-link-active-bg: $neutral-980; $dropdown-link-disabled-color: $neutral-700; $dropdown-header-color: $neutral-700; // scss-docs-end dropdown-variables 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); From eab46def586427c3b0fd5eb6c1f85c48f2987770 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Mon, 5 Jan 2026 15:38:45 +0200 Subject: [PATCH 02/21] proper display for nft uris --- src/components/NftPreview/NftPreview.tsx | 86 ++++++++++++------------ src/layouts/NftLayout/NftDetailsCard.tsx | 2 +- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/components/NftPreview/NftPreview.tsx b/src/components/NftPreview/NftPreview.tsx index 542d3b5c1..86e0b1b2d 100644 --- a/src/components/NftPreview/NftPreview.tsx +++ b/src/components/NftPreview/NftPreview.tsx @@ -83,50 +83,52 @@ export const NftPreview = ({ token }: { token: NftType }) => {
    {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/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: ( From e0f43a29479d837382643f5ef6cf6a129a1aadfc Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Tue, 13 Jan 2026 17:11:52 +0200 Subject: [PATCH 03/21] update websocket types with custom, filtered ones --- src/types/websocket.types.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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' } From d94391e0583b050c2f9e319ad6154450742e62e6 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Tue, 13 Jan 2026 17:12:22 +0200 Subject: [PATCH 04/21] use token instead of tokenId --- .../adapter/requests/useTokenRequests.ts | 36 +++++++++---------- src/hooks/fetch/useFetchTransactions.ts | 2 +- src/types/adapter.types.ts | 2 +- 3 files changed, 18 insertions(+), 22 deletions(-) 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/useFetchTransactions.ts b/src/hooks/fetch/useFetchTransactions.ts index 02202cc14..de03cfa5b 100644 --- a/src/hooks/fetch/useFetchTransactions.ts +++ b/src/hooks/fetch/useFetchTransactions.ts @@ -12,7 +12,7 @@ export interface FetchTransactionsProps hasMaxTransactionsSize?: boolean; } -interface TransactionsWebsocketResponseType { +export interface TransactionsWebsocketResponseType { transactions: TransactionType[]; transactionsCount: number; } 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 { From af2742749ee6548ddb69d08c30da4f7f1fa16930 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Tue, 13 Jan 2026 17:12:52 +0200 Subject: [PATCH 05/21] updated states to handle customTransactions, customTransfers and customEvents --- src/redux/reducers.ts | 8 +++- .../selectors/transactions/customEvents.ts | 11 +++++ .../transactions/customTransactions.ts | 11 +++++ .../selectors/transactions/customTransfers.ts | 11 +++++ src/redux/selectors/transactions/index.ts | 3 ++ src/redux/slices/transactions/customEvents.ts | 46 +++++++++++++++++++ .../slices/transactions/customTransactions.ts | 46 +++++++++++++++++++ .../slices/transactions/customTransfers.ts | 46 +++++++++++++++++++ src/redux/slices/transactions/index.ts | 3 ++ 9 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/redux/selectors/transactions/customEvents.ts create mode 100644 src/redux/selectors/transactions/customTransactions.ts create mode 100644 src/redux/selectors/transactions/customTransfers.ts create mode 100644 src/redux/slices/transactions/customEvents.ts create mode 100644 src/redux/slices/transactions/customTransactions.ts create mode 100644 src/redux/slices/transactions/customTransfers.ts 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..a0adb1572 --- /dev/null +++ b/src/redux/slices/transactions/customEvents.ts @@ -0,0 +1,46 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ELLIPSIS } from 'appConstants'; +import { EventsSliceType, UIEventType } from 'types'; +import { getInitialEventsState } from './events'; + +export const customEventsSlice = createSlice({ + name: 'customEventsSlice', + initialState: getInitialEventsState(), + reducers: { + setCustomEvents: ( + state: EventsSliceType, + action: PayloadAction + ) => { + const existingHashes = state.events.map( + (event: UIEventType) => event.txHash + ); + const newEvents = action.payload.events.map((event: UIEventType) => ({ + ...event, + isNew: !existingHashes.includes(event.txHash) + })); + + state.events = newEvents; + + 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..0f33f1fc9 --- /dev/null +++ b/src/redux/slices/transactions/customTransactions.ts @@ -0,0 +1,46 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ELLIPSIS } from 'appConstants'; +import { TransactionSliceType } from 'types'; +import { getInitialTransactionsState } from './transactions'; + +export const customTransactionsSlice = createSlice({ + name: 'customTransactionsSlice', + initialState: getInitialTransactionsState(), + reducers: { + setCustomTransactions: ( + state: TransactionSliceType, + action: PayloadAction + ) => { + const existingHashes = state.transactions.map((b) => b.txHash); + const newCustomTransactions = action.payload.transactions.map( + (transaction) => ({ + ...transaction, + isNew: !existingHashes.includes(transaction.txHash) + }) + ); + + state.transactions = newCustomTransactions; + + if (action.payload.transactionsCount !== ELLIPSIS) { + state.transactionsCount = action.payload.transactionsCount; + } + + state.isDataReady = action.payload.isDataReady; + state.isWebsocket = action.payload.isWebsocket; + }, + pauseCustomTxRefresh: (state: TransactionSliceType) => { + state.isRefreshPaused = true; + }, + resumeCustomTxRefresh: (state: TransactionSliceType) => { + state.isRefreshPaused = false; + } + } +}); + +export const { + setCustomTransactions, + pauseCustomTxRefresh, + resumeCustomTxRefresh +} = 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..3a9184697 --- /dev/null +++ b/src/redux/slices/transactions/customTransfers.ts @@ -0,0 +1,46 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ELLIPSIS } from 'appConstants'; +import { TransactionSliceType } from 'types'; +import { getInitialTransactionsState } from './transactions'; + +export const customTransfersSlice = createSlice({ + name: 'customTransfersSlice', + initialState: getInitialTransactionsState(), + reducers: { + setCustomTransfers: ( + state: TransactionSliceType, + action: PayloadAction + ) => { + const existingHashes = state.transactions.map((b) => b.txHash); + const newCustomTransfers = action.payload.transactions.map( + (transfer) => ({ + ...transfer, + isNew: !existingHashes.includes(transfer.txHash) + }) + ); + + state.transactions = newCustomTransfers; + + if (action.payload.transactionsCount !== ELLIPSIS) { + state.transactionsCount = action.payload.transactionsCount; + } + + state.isDataReady = action.payload.isDataReady; + state.isWebsocket = action.payload.isWebsocket; + }, + pauseCustomTransferRefresh: (state: TransactionSliceType) => { + state.isRefreshPaused = true; + }, + resumeCustomTtransferRefresh: (state: TransactionSliceType) => { + state.isRefreshPaused = false; + } + } +}); + +export const { + setCustomTransfers, + pauseCustomTransferRefresh, + resumeCustomTtransferRefresh +} = 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'; From f78158b7a792b2cfde316cb139acc872c82dc20b Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Tue, 13 Jan 2026 17:13:17 +0200 Subject: [PATCH 06/21] proper filtering --- src/layouts/TokenLayout/TokenHolderDetailsCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 () => { From a7cd6f47920e84699dcd056649b5277c853224b9 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 14 Jan 2026 15:14:05 +0200 Subject: [PATCH 07/21] added websocke tupdates on AccountTransactions an TokenTransactions --- src/hooks/fetch/index.ts | 1 + src/hooks/fetch/useFetchApiData.ts | 10 ++- src/hooks/fetch/useFetchCustomTransfers.ts | 87 +++++++++++++++++++ src/hooks/fetch/useFetchTransactions.ts | 1 + .../websocket/useRegisterWebsocketListener.ts | 32 +++++-- .../AccountDetails/AccountTransactions.tsx | 18 ++-- src/pages/TokenDetails/TokenTransactions.tsx | 26 +++--- .../slices/transactions/customTransfers.ts | 49 +++++++++-- 8 files changed, 190 insertions(+), 34 deletions(-) create mode 100644 src/hooks/fetch/useFetchCustomTransfers.ts 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..dcd324bf0 --- /dev/null +++ b/src/hooks/fetch/useFetchCustomTransfers.ts @@ -0,0 +1,87 @@ +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 = (event: CustomTransfersWebsocketResponseType) => { + if (!event) { + return; + } + + console.log('-------event', event); + + const { transfers } = event; + dispatch( + setCustomTransfers({ + transactions: transfers, + transactionsCount: ELLIPSIS, + size, + isWebsocket: false, // keep api, only latest updates fetched fron ws + isDataReady: true + }) + ); + }; + + const onApiData = (response: any[]) => { + const [transactionsData, transactionsCountData] = response; + dispatch( + setCustomTransfers({ + transactions: transactionsData.data ?? [], + transactionsCount: transactionsCountData?.data ?? ELLIPSIS, + isWebsocket: false, + size, + isDataReady: + transactionsData.success && + Boolean(!dataCountPromise || transactionsCountData?.success) + }) + ); + }; + + 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 de03cfa5b..cf8ee858b 100644 --- a/src/hooks/fetch/useFetchTransactions.ts +++ b/src/hooks/fetch/useFetchTransactions.ts @@ -10,6 +10,7 @@ import { FetchApiDataProps, useFetchApiData } from './useFetchApiData'; export interface FetchTransactionsProps extends Omit { hasMaxTransactionsSize?: boolean; + uuid?: string; } export interface TransactionsWebsocketResponseType { diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index b6a9acd0e..2d5d27819 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); @@ -60,8 +64,8 @@ export function useRegisterWebsocketListener({ } if (!hasSubscription) { - websocket.emit(subscription, websocketConfig, (response: any) => { - console.info(`New Websocket Subscription ${subscription}`); + websocket.emit(subscriptionName, websocketConfig, (response: any) => { + console.info(`New Websocket Subscription ${subscriptionName}`); if (response?.status !== 'success') { websocketSubscriptions.delete(subscription); websocketPendingSubscriptions.delete(subscription); @@ -82,12 +86,26 @@ export function useRegisterWebsocketListener({ websocketPendingSubscriptions.delete(subscription); websocketActiveSubscriptions.add(subscription); } - // console.info(`Client ${event}:`, response); + console.info(`Client ${event}:`, response); onWebsocketEvent(response); }); return () => { websocket?.off(event); + if (uuid) { + websocket.emit( + `un${subscriptionName}`, + websocketConfig, + (response: any) => { + console.info( + `Unsubscribe Subscription ${subscriptionName}`, + response + ); + } + ); + websocketPendingSubscriptions.delete(subscription); + websocketSubscriptions.delete(subscription); + } websocketActiveSubscriptions.delete(subscription); }; }, [ @@ -98,7 +116,7 @@ export function useRegisterWebsocketListener({ websocketConnection.status, hasWebsocketUrl, event, - subscription, + subscriptionName, isPaused ]); } diff --git a/src/pages/AccountDetails/AccountTransactions.tsx b/src/pages/AccountDetails/AccountTransactions.tsx index 73ae869f9..2df22cfbc 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,21 @@ export const AccountTransactions = () => { totalTransactions, isDataReady, dataChanged - } = useFetchTransactions({ + } = useFetchCustomTransfers({ + uuid: address, dataPromise: getAccountTransfers, dataCountPromise: getAccountTransfersCount, + subscription: WebsocketSubcriptionsEnum.subscribeCustomTransfers, + event: WebsocketEventsEnum.customTransferUpdate, filters: { - address, - withTxsRelayedByAddress: true - } + address + }, + websocketConfig: { address } }); useEffect(() => { fetchTransactions(); - }, [activeNetworkId, address, txCount, balance]); + }, [activeNetworkId, address]); useEffect(() => { fetchTransactions(Boolean(searchParams.toString())); 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/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts index 3a9184697..453ef4e08 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -1,6 +1,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { ELLIPSIS } from 'appConstants'; -import { TransactionSliceType } from 'types'; +import { + TransactionSliceType, + TransactionType, + UITransactionType +} from 'types'; import { getInitialTransactionsState } from './transactions'; export const customTransfersSlice = createSlice({ @@ -9,17 +13,44 @@ export const customTransfersSlice = createSlice({ reducers: { setCustomTransfers: ( state: TransactionSliceType, - action: PayloadAction + action: PayloadAction ) => { - const existingHashes = state.transactions.map((b) => b.txHash); - const newCustomTransfers = action.payload.transactions.map( - (transfer) => ({ - ...transfer, - isNew: !existingHashes.includes(transfer.txHash) - }) + const existing = state.transactions; + const incoming = action.payload.transactions; + + const existingSet = new Set(existing.map((tx) => tx.txHash)); + const updated = new Map(); + const result: UITransactionType[] = []; + + console.log( + '------existing', + existing.map((ex) => ex.txHash) + ); + + for (const tx of existing) updated.set(tx.txHash, tx); + for (const tx of incoming) updated.set(tx.txHash, { ...tx, isNew: true }); + + console.log( + '------incoming', + incoming.map((ex) => ex.txHash) + ); + + for (const tx of incoming) { + if (!existingSet.has(tx.txHash)) { + result.push(updated.get(tx.txHash)!); + } + } + + for (const tx of existing) { + result.push(updated.get(tx.txHash)!); + } + + console.log( + '------result', + result.map((ex) => ex.txHash) ); - state.transactions = newCustomTransfers; + state.transactions = result.slice(0, action.payload.size); if (action.payload.transactionsCount !== ELLIPSIS) { state.transactionsCount = action.payload.transactionsCount; From 374d12e4bc4711249e458cc902569bca4047a0da Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 14 Jan 2026 16:32:22 +0200 Subject: [PATCH 08/21] avoid recharts responsive container warnings --- src/components/Chart/ChartLine.tsx | 39 ++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/components/Chart/ChartLine.tsx b/src/components/Chart/ChartLine.tsx index 56b8333f5..d9ba82dee 100644 --- a/src/components/Chart/ChartLine.tsx +++ b/src/components/Chart/ChartLine.tsx @@ -1,3 +1,4 @@ +import { JSXElementConstructor, memo, ReactElement } from 'react'; import BigNumber from 'bignumber.js'; import classNames from 'classnames'; import moment from 'moment'; @@ -16,7 +17,7 @@ import { ChartTooltip } from './ChartTooltip'; import { formatYAxis } from './helpers/formatYAxis'; import { getChartMergedData } from './helpers/getChartMergedData'; import { StartEndTick } from './helpers/StartEndTick'; -import { ChartProps } from './helpers/types'; +import { ChartProps, MergedChartDataType } from './helpers/types'; export const ChartLine = ({ config, @@ -48,6 +49,34 @@ export const ChartLine = ({ 'primary' ]); + const ChartContainer = memo( + ({ + children, + data, + width, + height + }: { + children: ReactElement>; + data: MergedChartDataType[]; + width?: string | number; + height?: string | number; + }) => { + if (width && height) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); + } + ); + const gradientOffset = () => { const fistValue = new BigNumber(chartData?.[0]?.value ?? '0'); const apendedData = chartData.map((i: any) => @@ -82,8 +111,8 @@ export const ChartLine = ({ 'has-only-start-end-tick': hasOnlyStartEndTick })} > - - + + <> @@ -190,8 +219,8 @@ export const ChartLine = ({ } /> )} - - + +
); }; From dbc0edd80a64ca29eab895cb65d76eac0ed0eeb3 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 14 Jan 2026 16:42:26 +0200 Subject: [PATCH 09/21] handle uuid changes --- src/hooks/fetch/useFetchCustomTransfers.ts | 69 ++++++++++--------- .../websocket/useRegisterWebsocketListener.ts | 13 +++- .../slices/transactions/customTransfers.ts | 35 +++++++--- src/types/transaction.types.ts | 5 ++ 4 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/hooks/fetch/useFetchCustomTransfers.ts b/src/hooks/fetch/useFetchCustomTransfers.ts index dcd324bf0..58812be35 100644 --- a/src/hooks/fetch/useFetchCustomTransfers.ts +++ b/src/hooks/fetch/useFetchCustomTransfers.ts @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { ELLIPSIS } from 'appConstants'; @@ -27,39 +28,45 @@ export const useFetchCustomTransfers = (props: FetchCustomTransfersProps) => { const { transactions, transactionsCount, isDataReady, isRefreshPaused } = useSelector(customTransfersSelector); - const onWebsocketData = (event: CustomTransfersWebsocketResponseType) => { - if (!event) { - return; - } + const onWebsocketData = useCallback( + (event: CustomTransfersWebsocketResponseType) => { + if (!event) { + return; + } - console.log('-------event', event); - - const { transfers } = event; - dispatch( - setCustomTransfers({ - transactions: transfers, - transactionsCount: ELLIPSIS, - size, - isWebsocket: false, // keep api, only latest updates fetched fron ws - isDataReady: true - }) - ); - }; + const { transfers } = event; + dispatch( + setCustomTransfers({ + transactions: transfers, + transactionsCount: ELLIPSIS, + size, + uuid: props.uuid, + isWebsocket: false, // keep api, only latest updates fetched fron ws + isDataReady: true + }) + ); + }, + [props] + ); - const onApiData = (response: any[]) => { - const [transactionsData, transactionsCountData] = response; - dispatch( - setCustomTransfers({ - transactions: transactionsData.data ?? [], - transactionsCount: transactionsCountData?.data ?? ELLIPSIS, - isWebsocket: false, - size, - isDataReady: - transactionsData.success && - Boolean(!dataCountPromise || transactionsCountData?.success) - }) - ); - }; + const onApiData = useCallback( + (response: any[]) => { + const [transactionsData, transactionsCountData] = response; + dispatch( + setCustomTransfers({ + transactions: transactionsData.data ?? [], + transactionsCount: transactionsCountData?.data ?? ELLIPSIS, + isWebsocket: false, + size, + uuid: props.uuid, + isDataReady: + transactionsData.success && + Boolean(!dataCountPromise || transactionsCountData?.success) + }) + ); + }, + [props] + ); const { fetchData, dataChanged } = useFetchApiData({ ...props, diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index 2d5d27819..13b500df3 100644 --- a/src/hooks/websocket/useRegisterWebsocketListener.ts +++ b/src/hooks/websocket/useRegisterWebsocketListener.ts @@ -86,7 +86,18 @@ export function useRegisterWebsocketListener({ websocketPendingSubscriptions.delete(subscription); websocketActiveSubscriptions.add(subscription); } - console.info(`Client ${event}:`, response); + if ( + // TODO - temp + ![ + 'transactionUpdate', + 'blocksUpdate', + 'poolUpdate', + 'statsUpdate', + 'eventsUpdate' + ].includes(event) + ) { + console.info(`Client ${event}:`, response); + } onWebsocketEvent(response); }); diff --git a/src/redux/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts index 453ef4e08..7aba2cdc9 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -1,20 +1,33 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ELLIPSIS } from 'appConstants'; +import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; import { + CustomTransfersSliceType, TransactionSliceType, - TransactionType, UITransactionType } from 'types'; import { getInitialTransactionsState } from './transactions'; +export const getInitialCustomTransfersState = (): CustomTransfersSliceType => { + return { + ...getInitialTransactionsState(), + uuid: undefined, + size: PAGE_SIZE + }; +}; + export const customTransfersSlice = createSlice({ name: 'customTransfersSlice', - initialState: getInitialTransactionsState(), + initialState: getInitialCustomTransfersState(), reducers: { setCustomTransfers: ( - state: TransactionSliceType, - action: PayloadAction + state: CustomTransfersSliceType, + action: PayloadAction ) => { + if (state.uuid && state.uuid !== action.payload.uuid) { + state.transactions = []; + state.transactionsCount = ELLIPSIS; + } + const existing = state.transactions; const incoming = action.payload.transactions; @@ -22,7 +35,7 @@ export const customTransfersSlice = createSlice({ const updated = new Map(); const result: UITransactionType[] = []; - console.log( + console.info( '------existing', existing.map((ex) => ex.txHash) ); @@ -30,7 +43,7 @@ export const customTransfersSlice = createSlice({ for (const tx of existing) updated.set(tx.txHash, tx); for (const tx of incoming) updated.set(tx.txHash, { ...tx, isNew: true }); - console.log( + console.info( '------incoming', incoming.map((ex) => ex.txHash) ); @@ -45,12 +58,13 @@ export const customTransfersSlice = createSlice({ result.push(updated.get(tx.txHash)!); } - console.log( + const trimmedTransactions = result.slice(0, action.payload.size); + console.info( '------result', - result.map((ex) => ex.txHash) + trimmedTransactions.map((ex) => ex.txHash) ); - state.transactions = result.slice(0, action.payload.size); + state.transactions = trimmedTransactions; if (action.payload.transactionsCount !== ELLIPSIS) { state.transactionsCount = action.payload.transactionsCount; @@ -58,6 +72,7 @@ export const customTransfersSlice = createSlice({ state.isDataReady = action.payload.isDataReady; state.isWebsocket = action.payload.isWebsocket; + state.uuid = action.payload.uuid; }, pauseCustomTransferRefresh: (state: TransactionSliceType) => { state.isRefreshPaused = true; diff --git a/src/types/transaction.types.ts b/src/types/transaction.types.ts index 3126ef7f9..3ae3a4859 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 CustomTransfersSliceType extends TransactionSliceType { + size: number; + uuid?: string; +} + export interface TransactionInPoolSliceType extends SliceType { transactionsInPool: UITransactionInPoolType[]; transactionsInPoolCount: number | typeof ELLIPSIS; From e6845b77374da4b4b954b42c20b23ee67c6cce82 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 14 Jan 2026 17:15:07 +0200 Subject: [PATCH 10/21] avoid recharts responsive container warnings --- src/components/Chart/ChartLine.tsx | 46 +++------------------ src/components/Chart/ChartLineContainer.tsx | 28 +++++++++++++ 2 files changed, 33 insertions(+), 41 deletions(-) create mode 100644 src/components/Chart/ChartLineContainer.tsx diff --git a/src/components/Chart/ChartLine.tsx b/src/components/Chart/ChartLine.tsx index d9ba82dee..c9e06035f 100644 --- a/src/components/Chart/ChartLine.tsx +++ b/src/components/Chart/ChartLine.tsx @@ -1,23 +1,15 @@ -import { JSXElementConstructor, memo, ReactElement } from 'react'; import BigNumber from 'bignumber.js'; import classNames from 'classnames'; import moment from 'moment'; -import { - ResponsiveContainer, - XAxis, - YAxis, - Line, - LineChart, - Tooltip, - CartesianGrid -} from 'recharts'; +import { XAxis, YAxis, Line, Tooltip, CartesianGrid } from 'recharts'; import { getColors } from 'helpers'; +import { ChartLineContainer } from './ChartLineContainer'; import { ChartTooltip } from './ChartTooltip'; import { formatYAxis } from './helpers/formatYAxis'; import { getChartMergedData } from './helpers/getChartMergedData'; import { StartEndTick } from './helpers/StartEndTick'; -import { ChartProps, MergedChartDataType } from './helpers/types'; +import { ChartProps } from './helpers/types'; export const ChartLine = ({ config, @@ -49,34 +41,6 @@ export const ChartLine = ({ 'primary' ]); - const ChartContainer = memo( - ({ - children, - data, - width, - height - }: { - children: ReactElement>; - data: MergedChartDataType[]; - width?: string | number; - height?: string | number; - }) => { - if (width && height) { - return ( - - {children} - - ); - } - - return ( - - {children} - - ); - } - ); - const gradientOffset = () => { const fistValue = new BigNumber(chartData?.[0]?.value ?? '0'); const apendedData = chartData.map((i: any) => @@ -111,7 +75,7 @@ export const ChartLine = ({ 'has-only-start-end-tick': hasOnlyStartEndTick })} > - + <> @@ -220,7 +184,7 @@ export const ChartLine = ({ /> )} - + ); }; diff --git a/src/components/Chart/ChartLineContainer.tsx b/src/components/Chart/ChartLineContainer.tsx new file mode 100644 index 000000000..c953d002a --- /dev/null +++ b/src/components/Chart/ChartLineContainer.tsx @@ -0,0 +1,28 @@ +import { JSXElementConstructor, memo, ReactElement } from 'react'; +import { ResponsiveContainer, LineChart } from 'recharts'; +import { MergedChartDataType } from './helpers/types'; + +interface ChartLineContainerUIType { + children: ReactElement>; + data: MergedChartDataType[]; + width?: string | number; + height?: string | number; +} + +export const ChartLineContainer = memo( + ({ children, data, width, height }: ChartLineContainerUIType) => { + if (width && height) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); + } +); From a39eb3a425d050eaf3c30f3887d9671e94996ad3 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 14 Jan 2026 17:20:14 +0200 Subject: [PATCH 11/21] update logs --- .../websocket/useRegisterWebsocketListener.ts | 27 ++++++++++--------- .../slices/transactions/customTransfers.ts | 24 +++++------------ 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index 13b500df3..c9c520744 100644 --- a/src/hooks/websocket/useRegisterWebsocketListener.ts +++ b/src/hooks/websocket/useRegisterWebsocketListener.ts @@ -65,7 +65,10 @@ export function useRegisterWebsocketListener({ if (!hasSubscription) { websocket.emit(subscriptionName, websocketConfig, (response: any) => { - console.info(`New Websocket Subscription ${subscriptionName}`); + console.info( + `New Websocket Subscription ${subscriptionName}`, + response + ); if (response?.status !== 'success') { websocketSubscriptions.delete(subscription); websocketPendingSubscriptions.delete(subscription); @@ -78,7 +81,15 @@ export function useRegisterWebsocketListener({ } websocket.on(event, (response: any) => { - if (document.hidden) { + const isStatsEvent = event === WebsocketEventsEnum.statsUpdate; + const isCustomEvent = [ + WebsocketEventsEnum.customTransactionUpdate, + WebsocketEventsEnum.customTransferUpdate, + WebsocketEventsEnum.customEventUpdate + ].includes(event); + + // avoid battery/ram usage on bg on general events, keep specific ones and stats + if (document.hidden && !(isStatsEvent || isCustomEvent)) { return; } @@ -86,16 +97,8 @@ export function useRegisterWebsocketListener({ websocketPendingSubscriptions.delete(subscription); websocketActiveSubscriptions.add(subscription); } - if ( - // TODO - temp - ![ - 'transactionUpdate', - 'blocksUpdate', - 'poolUpdate', - 'statsUpdate', - 'eventsUpdate' - ].includes(event) - ) { + // TODO - Temp + if (isCustomEvent) { console.info(`Client ${event}:`, response); } onWebsocketEvent(response); diff --git a/src/redux/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts index 7aba2cdc9..2122833b4 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -35,18 +35,12 @@ export const customTransfersSlice = createSlice({ const updated = new Map(); const result: UITransactionType[] = []; - console.info( - '------existing', - existing.map((ex) => ex.txHash) - ); - - for (const tx of existing) updated.set(tx.txHash, tx); - for (const tx of incoming) updated.set(tx.txHash, { ...tx, isNew: true }); - - console.info( - '------incoming', - incoming.map((ex) => ex.txHash) - ); + for (const tx of existing) { + updated.set(tx.txHash, tx); + } + for (const tx of incoming) { + updated.set(tx.txHash, { ...tx, isNew: true }); + } for (const tx of incoming) { if (!existingSet.has(tx.txHash)) { @@ -59,11 +53,8 @@ export const customTransfersSlice = createSlice({ } const trimmedTransactions = result.slice(0, action.payload.size); - console.info( - '------result', - trimmedTransactions.map((ex) => ex.txHash) - ); + state.uuid = action.payload.uuid; state.transactions = trimmedTransactions; if (action.payload.transactionsCount !== ELLIPSIS) { @@ -72,7 +63,6 @@ export const customTransfersSlice = createSlice({ state.isDataReady = action.payload.isDataReady; state.isWebsocket = action.payload.isWebsocket; - state.uuid = action.payload.uuid; }, pauseCustomTransferRefresh: (state: TransactionSliceType) => { state.isRefreshPaused = true; From 19141bc220e46dba75a7c8fd4aa2bd2f08203678 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 15 Jan 2026 16:37:48 +0200 Subject: [PATCH 12/21] unsubscripe from ws subscriptions if not needed ( extept stats ) --- src/helpers/processData/index.ts | 1 + src/helpers/processData/processListUpdates.ts | 38 +++++++++++++++++++ .../websocket/useRegisterWebsocketListener.ts | 16 ++++---- .../slices/transactions/customTransfers.ts | 35 ++++------------- 4 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 src/helpers/processData/processListUpdates.ts 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..35dde31d9 --- /dev/null +++ b/src/helpers/processData/processListUpdates.ts @@ -0,0 +1,38 @@ +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) { + result.push(updated.get(entry[uniqueKey])!); + } + + return result.slice(0, size); +}; diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index c9c520744..15b5b9f07 100644 --- a/src/hooks/websocket/useRegisterWebsocketListener.ts +++ b/src/hooks/websocket/useRegisterWebsocketListener.ts @@ -48,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 || @@ -81,13 +88,6 @@ export function useRegisterWebsocketListener({ } websocket.on(event, (response: any) => { - const isStatsEvent = event === WebsocketEventsEnum.statsUpdate; - const isCustomEvent = [ - WebsocketEventsEnum.customTransactionUpdate, - WebsocketEventsEnum.customTransferUpdate, - WebsocketEventsEnum.customEventUpdate - ].includes(event); - // avoid battery/ram usage on bg on general events, keep specific ones and stats if (document.hidden && !(isStatsEvent || isCustomEvent)) { return; @@ -106,7 +106,7 @@ export function useRegisterWebsocketListener({ return () => { websocket?.off(event); - if (uuid) { + if (!isStatsEvent) { websocket.emit( `un${subscriptionName}`, websocketConfig, diff --git a/src/redux/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts index 2122833b4..76c6f630c 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -1,10 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; -import { - CustomTransfersSliceType, - TransactionSliceType, - UITransactionType -} from 'types'; +import { processListUpdates } from 'helpers'; +import { CustomTransfersSliceType, TransactionSliceType } from 'types'; import { getInitialTransactionsState } from './transactions'; export const getInitialCustomTransfersState = (): CustomTransfersSliceType => { @@ -31,28 +28,12 @@ export const customTransfersSlice = createSlice({ const existing = state.transactions; const incoming = action.payload.transactions; - const existingSet = new Set(existing.map((tx) => tx.txHash)); - const updated = new Map(); - const result: UITransactionType[] = []; - - for (const tx of existing) { - updated.set(tx.txHash, tx); - } - for (const tx of incoming) { - updated.set(tx.txHash, { ...tx, isNew: true }); - } - - for (const tx of incoming) { - if (!existingSet.has(tx.txHash)) { - result.push(updated.get(tx.txHash)!); - } - } - - for (const tx of existing) { - result.push(updated.get(tx.txHash)!); - } - - const trimmedTransactions = result.slice(0, action.payload.size); + const trimmedTransactions = processListUpdates({ + existing, + incoming, + uniqueKey: 'txHash', + size: action.payload.size + }); state.uuid = action.payload.uuid; state.transactions = trimmedTransactions; From 859bbc992c9925504a59e101c7c29819a557ecd6 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 15 Jan 2026 17:03:59 +0200 Subject: [PATCH 13/21] update custom slices --- src/redux/slices/transactions/customEvents.ts | 45 +++++++++++----- .../slices/transactions/customTransactions.ts | 52 +++++++++++++------ .../slices/transactions/customTransfers.ts | 27 +++++----- src/types/events.types.ts | 5 ++ src/types/transaction.types.ts | 2 +- 5 files changed, 86 insertions(+), 45 deletions(-) diff --git a/src/redux/slices/transactions/customEvents.ts b/src/redux/slices/transactions/customEvents.ts index a0adb1572..0d981fabb 100644 --- a/src/redux/slices/transactions/customEvents.ts +++ b/src/redux/slices/transactions/customEvents.ts @@ -1,25 +1,42 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ELLIPSIS } from 'appConstants'; -import { EventsSliceType, UIEventType } from 'types'; +import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; +import { processListUpdates } from 'helpers'; +import { CustomEventsSliceType, EventsSliceType } from 'types'; import { getInitialEventsState } from './events'; +export const getInitialCustomEventsState = (): CustomEventsSliceType => { + return { + ...getInitialEventsState(), + uuid: undefined, + size: PAGE_SIZE + }; +}; + export const customEventsSlice = createSlice({ name: 'customEventsSlice', - initialState: getInitialEventsState(), + initialState: getInitialCustomEventsState(), reducers: { setCustomEvents: ( - state: EventsSliceType, - action: PayloadAction + state: CustomEventsSliceType, + action: PayloadAction ) => { - const existingHashes = state.events.map( - (event: UIEventType) => event.txHash - ); - const newEvents = action.payload.events.map((event: UIEventType) => ({ - ...event, - isNew: !existingHashes.includes(event.txHash) - })); - - state.events = newEvents; + 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; diff --git a/src/redux/slices/transactions/customTransactions.ts b/src/redux/slices/transactions/customTransactions.ts index 0f33f1fc9..475fc03f7 100644 --- a/src/redux/slices/transactions/customTransactions.ts +++ b/src/redux/slices/transactions/customTransactions.ts @@ -1,25 +1,43 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ELLIPSIS } from 'appConstants'; -import { TransactionSliceType } from 'types'; +import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; +import { processListUpdates } from 'helpers'; +import { CustomTransactionSliceType, TransactionSliceType } from 'types'; import { getInitialTransactionsState } from './transactions'; +export const getInitialCustomTransactionsState = + (): CustomTransactionSliceType => { + return { + ...getInitialTransactionsState(), + uuid: undefined, + size: PAGE_SIZE + }; + }; + export const customTransactionsSlice = createSlice({ name: 'customTransactionsSlice', - initialState: getInitialTransactionsState(), + initialState: getInitialCustomTransactionsState(), reducers: { setCustomTransactions: ( - state: TransactionSliceType, - action: PayloadAction + state: CustomTransactionSliceType, + action: PayloadAction ) => { - const existingHashes = state.transactions.map((b) => b.txHash); - const newCustomTransactions = action.payload.transactions.map( - (transaction) => ({ - ...transaction, - isNew: !existingHashes.includes(transaction.txHash) - }) - ); + 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.transactions = newCustomTransactions; + state.uuid = action.payload.uuid; + state.transactions = trimmedTransactions; if (action.payload.transactionsCount !== ELLIPSIS) { state.transactionsCount = action.payload.transactionsCount; @@ -28,10 +46,10 @@ export const customTransactionsSlice = createSlice({ state.isDataReady = action.payload.isDataReady; state.isWebsocket = action.payload.isWebsocket; }, - pauseCustomTxRefresh: (state: TransactionSliceType) => { + pauseCustomTransactionsRefresh: (state: TransactionSliceType) => { state.isRefreshPaused = true; }, - resumeCustomTxRefresh: (state: TransactionSliceType) => { + resumeCustomTransactionsRefresh: (state: TransactionSliceType) => { state.isRefreshPaused = false; } } @@ -39,8 +57,8 @@ export const customTransactionsSlice = createSlice({ export const { setCustomTransactions, - pauseCustomTxRefresh, - resumeCustomTxRefresh + 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 index 76c6f630c..2443b76ed 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -1,24 +1,25 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; import { processListUpdates } from 'helpers'; -import { CustomTransfersSliceType, TransactionSliceType } from 'types'; +import { CustomTransactionSliceType, TransactionSliceType } from 'types'; import { getInitialTransactionsState } from './transactions'; -export const getInitialCustomTransfersState = (): CustomTransfersSliceType => { - return { - ...getInitialTransactionsState(), - uuid: undefined, - size: PAGE_SIZE +export const getInitialCustomTransfersState = + (): CustomTransactionSliceType => { + return { + ...getInitialTransactionsState(), + uuid: undefined, + size: PAGE_SIZE + }; }; -}; export const customTransfersSlice = createSlice({ name: 'customTransfersSlice', initialState: getInitialCustomTransfersState(), reducers: { setCustomTransfers: ( - state: CustomTransfersSliceType, - action: PayloadAction + state: CustomTransactionSliceType, + action: PayloadAction ) => { if (state.uuid && state.uuid !== action.payload.uuid) { state.transactions = []; @@ -45,10 +46,10 @@ export const customTransfersSlice = createSlice({ state.isDataReady = action.payload.isDataReady; state.isWebsocket = action.payload.isWebsocket; }, - pauseCustomTransferRefresh: (state: TransactionSliceType) => { + pauseCustomTransfersRefresh: (state: TransactionSliceType) => { state.isRefreshPaused = true; }, - resumeCustomTtransferRefresh: (state: TransactionSliceType) => { + resumeCustomTransfersRefresh: (state: TransactionSliceType) => { state.isRefreshPaused = false; } } @@ -56,8 +57,8 @@ export const customTransfersSlice = createSlice({ export const { setCustomTransfers, - pauseCustomTransferRefresh, - resumeCustomTtransferRefresh + pauseCustomTransfersRefresh, + resumeCustomTransfersRefresh } = customTransfersSlice.actions; export const customTransfersReducer = customTransfersSlice.reducer; diff --git a/src/types/events.types.ts b/src/types/events.types.ts index 6c3bbd302..23fadbc0e 100644 --- a/src/types/events.types.ts +++ b/src/types/events.types.ts @@ -24,6 +24,11 @@ export interface EventsSliceType extends SliceType { eventsCount: number | typeof ELLIPSIS; } +export interface CustomEventsSliceType extends EventsSliceType { + size: number; + uuid?: string; +} + export enum TransactionEventIdentifiersEnum { ESDTNFTTransfer = 'ESDTNFTTransfer', ESDTNFTBurn = 'ESDTNFTBurn', diff --git a/src/types/transaction.types.ts b/src/types/transaction.types.ts index 3ae3a4859..9ae0273e6 100644 --- a/src/types/transaction.types.ts +++ b/src/types/transaction.types.ts @@ -280,7 +280,7 @@ export interface TransactionSliceType extends SliceType { transactionsCount: number | typeof ELLIPSIS; } -export interface CustomTransfersSliceType extends TransactionSliceType { +export interface CustomTransactionSliceType extends TransactionSliceType { size: number; uuid?: string; } From 78075815b29ad0c33f24a07174f404a2153bdf81 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 15 Jan 2026 17:50:07 +0200 Subject: [PATCH 14/21] updated transfersCount, cleanup --- src/components/Chart/ChartLineContainer.tsx | 2 +- src/helpers/processData/processListUpdates.ts | 3 ++ src/hooks/fetch/useFetchCustomTransfers.ts | 33 ++++++++++++------- .../websocket/useRegisterWebsocketListener.ts | 7 ++-- src/redux/slices/transactions/customEvents.ts | 7 ++-- .../slices/transactions/customTransactions.ts | 7 ++-- .../slices/transactions/customTransfers.ts | 7 ++-- src/types/events.types.ts | 1 - src/types/transaction.types.ts | 1 - 9 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/components/Chart/ChartLineContainer.tsx b/src/components/Chart/ChartLineContainer.tsx index c953d002a..f4a80798f 100644 --- a/src/components/Chart/ChartLineContainer.tsx +++ b/src/components/Chart/ChartLineContainer.tsx @@ -11,7 +11,7 @@ interface ChartLineContainerUIType { export const ChartLineContainer = memo( ({ children, data, width, height }: ChartLineContainerUIType) => { - if (width && height) { + if (typeof width === 'number' && typeof height === 'number') { return ( {children} diff --git a/src/helpers/processData/processListUpdates.ts b/src/helpers/processData/processListUpdates.ts index 35dde31d9..55da61c2a 100644 --- a/src/helpers/processData/processListUpdates.ts +++ b/src/helpers/processData/processListUpdates.ts @@ -31,6 +31,9 @@ export const processListUpdates = ({ } for (const entry of existing) { + if (entry[uniqueKey] === undefined) { + continue; + } result.push(updated.get(entry[uniqueKey])!); } diff --git a/src/hooks/fetch/useFetchCustomTransfers.ts b/src/hooks/fetch/useFetchCustomTransfers.ts index 58812be35..30b7bdbc9 100644 --- a/src/hooks/fetch/useFetchCustomTransfers.ts +++ b/src/hooks/fetch/useFetchCustomTransfers.ts @@ -35,18 +35,27 @@ export const useFetchCustomTransfers = (props: FetchCustomTransfersProps) => { } const { transfers } = event; - dispatch( - setCustomTransfers({ - transactions: transfers, - transactionsCount: ELLIPSIS, - size, - uuid: props.uuid, - isWebsocket: false, // keep api, only latest updates fetched fron ws - isDataReady: true - }) - ); + 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 + } }, - [props] + [size, props.uuid, transactionsCount] ); const onApiData = useCallback( @@ -65,7 +74,7 @@ export const useFetchCustomTransfers = (props: FetchCustomTransfersProps) => { }) ); }, - [props] + [size, props.uuid, dataCountPromise] ); const { fetchData, dataChanged } = useFetchApiData({ diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index 15b5b9f07..4617ba057 100644 --- a/src/hooks/websocket/useRegisterWebsocketListener.ts +++ b/src/hooks/websocket/useRegisterWebsocketListener.ts @@ -88,8 +88,11 @@ export function useRegisterWebsocketListener({ } websocket.on(event, (response: any) => { - // avoid battery/ram usage on bg on general events, keep specific ones and stats - if (document.hidden && !(isStatsEvent || isCustomEvent)) { + if ( + typeof document !== 'undefined' && + document.hidden && + !(isStatsEvent || isCustomEvent) + ) { return; } diff --git a/src/redux/slices/transactions/customEvents.ts b/src/redux/slices/transactions/customEvents.ts index 0d981fabb..c2e6815e6 100644 --- a/src/redux/slices/transactions/customEvents.ts +++ b/src/redux/slices/transactions/customEvents.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; +import { ELLIPSIS } from 'appConstants'; import { processListUpdates } from 'helpers'; import { CustomEventsSliceType, EventsSliceType } from 'types'; import { getInitialEventsState } from './events'; @@ -7,8 +7,7 @@ import { getInitialEventsState } from './events'; export const getInitialCustomEventsState = (): CustomEventsSliceType => { return { ...getInitialEventsState(), - uuid: undefined, - size: PAGE_SIZE + uuid: undefined }; }; @@ -18,7 +17,7 @@ export const customEventsSlice = createSlice({ reducers: { setCustomEvents: ( state: CustomEventsSliceType, - action: PayloadAction + action: PayloadAction ) => { if (state.uuid && state.uuid !== action.payload.uuid) { state.events = []; diff --git a/src/redux/slices/transactions/customTransactions.ts b/src/redux/slices/transactions/customTransactions.ts index 475fc03f7..a55ca7fee 100644 --- a/src/redux/slices/transactions/customTransactions.ts +++ b/src/redux/slices/transactions/customTransactions.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; +import { ELLIPSIS } from 'appConstants'; import { processListUpdates } from 'helpers'; import { CustomTransactionSliceType, TransactionSliceType } from 'types'; import { getInitialTransactionsState } from './transactions'; @@ -8,8 +8,7 @@ export const getInitialCustomTransactionsState = (): CustomTransactionSliceType => { return { ...getInitialTransactionsState(), - uuid: undefined, - size: PAGE_SIZE + uuid: undefined }; }; @@ -19,7 +18,7 @@ export const customTransactionsSlice = createSlice({ reducers: { setCustomTransactions: ( state: CustomTransactionSliceType, - action: PayloadAction + action: PayloadAction ) => { if (state.uuid && state.uuid !== action.payload.uuid) { state.transactions = []; diff --git a/src/redux/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts index 2443b76ed..308f0a1d6 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { ELLIPSIS, PAGE_SIZE } from 'appConstants'; +import { ELLIPSIS } from 'appConstants'; import { processListUpdates } from 'helpers'; import { CustomTransactionSliceType, TransactionSliceType } from 'types'; import { getInitialTransactionsState } from './transactions'; @@ -8,8 +8,7 @@ export const getInitialCustomTransfersState = (): CustomTransactionSliceType => { return { ...getInitialTransactionsState(), - uuid: undefined, - size: PAGE_SIZE + uuid: undefined }; }; @@ -19,7 +18,7 @@ export const customTransfersSlice = createSlice({ reducers: { setCustomTransfers: ( state: CustomTransactionSliceType, - action: PayloadAction + action: PayloadAction ) => { if (state.uuid && state.uuid !== action.payload.uuid) { state.transactions = []; diff --git a/src/types/events.types.ts b/src/types/events.types.ts index 23fadbc0e..fe7521a13 100644 --- a/src/types/events.types.ts +++ b/src/types/events.types.ts @@ -25,7 +25,6 @@ export interface EventsSliceType extends SliceType { } export interface CustomEventsSliceType extends EventsSliceType { - size: number; uuid?: string; } diff --git a/src/types/transaction.types.ts b/src/types/transaction.types.ts index 9ae0273e6..2db676135 100644 --- a/src/types/transaction.types.ts +++ b/src/types/transaction.types.ts @@ -281,7 +281,6 @@ export interface TransactionSliceType extends SliceType { } export interface CustomTransactionSliceType extends TransactionSliceType { - size: number; uuid?: string; } From 173bcb0a1aeb113bb93170e46ff61df631826056 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 15 Jan 2026 18:11:43 +0200 Subject: [PATCH 15/21] remove logs --- .../websocket/useRegisterWebsocketListener.ts | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/hooks/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index 4617ba057..63810af80 100644 --- a/src/hooks/websocket/useRegisterWebsocketListener.ts +++ b/src/hooks/websocket/useRegisterWebsocketListener.ts @@ -72,10 +72,10 @@ export function useRegisterWebsocketListener({ if (!hasSubscription) { websocket.emit(subscriptionName, websocketConfig, (response: any) => { - console.info( - `New Websocket Subscription ${subscriptionName}`, - response - ); + // console.info( + // `New Websocket Subscription ${subscriptionName}`, + // response + // ); if (response?.status !== 'success') { websocketSubscriptions.delete(subscription); websocketPendingSubscriptions.delete(subscription); @@ -100,10 +100,9 @@ export function useRegisterWebsocketListener({ websocketPendingSubscriptions.delete(subscription); websocketActiveSubscriptions.add(subscription); } - // TODO - Temp - if (isCustomEvent) { - console.info(`Client ${event}:`, response); - } + // if (isCustomEvent) { + // console.info(`Client ${event}:`, response); + // } onWebsocketEvent(response); }); @@ -112,13 +111,13 @@ export function useRegisterWebsocketListener({ if (!isStatsEvent) { websocket.emit( `un${subscriptionName}`, - websocketConfig, - (response: any) => { - console.info( - `Unsubscribe Subscription ${subscriptionName}`, - response - ); - } + websocketConfig + // (response: any) => { + // console.info( + // `Unsubscribe Subscription ${subscriptionName}`, + // response + // ); + // } ); websocketPendingSubscriptions.delete(subscription); websocketSubscriptions.delete(subscription); From 7534424f7bd678fdd6090427c110320f006464f4 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 15 Jan 2026 18:14:24 +0200 Subject: [PATCH 16/21] include withTxsRelayedByAddress --- src/pages/AccountDetails/AccountTransactions.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/AccountDetails/AccountTransactions.tsx b/src/pages/AccountDetails/AccountTransactions.tsx index 2df22cfbc..3b4a538c9 100644 --- a/src/pages/AccountDetails/AccountTransactions.tsx +++ b/src/pages/AccountDetails/AccountTransactions.tsx @@ -29,7 +29,8 @@ export const AccountTransactions = () => { subscription: WebsocketSubcriptionsEnum.subscribeCustomTransfers, event: WebsocketEventsEnum.customTransferUpdate, filters: { - address + address, + withTxsRelayedByAddress: true }, websocketConfig: { address } }); From 9983379829d6a43b7f9a20142d1ee09c9f119716 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 15 Jan 2026 18:15:18 +0200 Subject: [PATCH 17/21] no need for Number --- src/components/Chart/ChartLineContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Chart/ChartLineContainer.tsx b/src/components/Chart/ChartLineContainer.tsx index f4a80798f..d4b81bbce 100644 --- a/src/components/Chart/ChartLineContainer.tsx +++ b/src/components/Chart/ChartLineContainer.tsx @@ -13,7 +13,7 @@ export const ChartLineContainer = memo( ({ children, data, width, height }: ChartLineContainerUIType) => { if (typeof width === 'number' && typeof height === 'number') { return ( - + {children} ); From 909683bc980c70df015e21d534ba4c0a371c23ea Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Mon, 19 Jan 2026 10:32:27 +0200 Subject: [PATCH 18/21] show websocket logs on development env only --- src/helpers/processData/processListUpdates.ts | 1 + .../websocket/useRegisterWebsocketListener.ts | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/helpers/processData/processListUpdates.ts b/src/helpers/processData/processListUpdates.ts index 55da61c2a..102e9d479 100644 --- a/src/helpers/processData/processListUpdates.ts +++ b/src/helpers/processData/processListUpdates.ts @@ -37,5 +37,6 @@ export const processListUpdates = ({ 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/websocket/useRegisterWebsocketListener.ts b/src/hooks/websocket/useRegisterWebsocketListener.ts index 63810af80..9f6bdde49 100644 --- a/src/hooks/websocket/useRegisterWebsocketListener.ts +++ b/src/hooks/websocket/useRegisterWebsocketListener.ts @@ -72,10 +72,12 @@ export function useRegisterWebsocketListener({ if (!hasSubscription) { websocket.emit(subscriptionName, websocketConfig, (response: any) => { - // console.info( - // `New Websocket Subscription ${subscriptionName}`, - // response - // ); + if (import.meta.env.DEV) { + console.info( + `New Websocket Subscription ${subscriptionName}`, + response + ); + } if (response?.status !== 'success') { websocketSubscriptions.delete(subscription); websocketPendingSubscriptions.delete(subscription); @@ -100,9 +102,6 @@ export function useRegisterWebsocketListener({ websocketPendingSubscriptions.delete(subscription); websocketActiveSubscriptions.add(subscription); } - // if (isCustomEvent) { - // console.info(`Client ${event}:`, response); - // } onWebsocketEvent(response); }); @@ -111,13 +110,15 @@ export function useRegisterWebsocketListener({ if (!isStatsEvent) { websocket.emit( `un${subscriptionName}`, - websocketConfig - // (response: any) => { - // console.info( - // `Unsubscribe Subscription ${subscriptionName}`, - // response - // ); - // } + websocketConfig, + (response: any) => { + if (import.meta.env.DEV) { + console.info( + `Unsubscribe Subscription ${subscriptionName}`, + response + ); + } + } ); websocketPendingSubscriptions.delete(subscription); websocketSubscriptions.delete(subscription); From e7e8cece5e1bb8254935c6815e36b889a8c05130 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Tue, 20 Jan 2026 18:23:26 +0200 Subject: [PATCH 19/21] use search param instead of name on accounts --- src/pages/Accounts/Accounts.tsx | 4 ++-- src/pages/Applications/Applications.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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) { From 6b46692f40ccf6ad117062b80a717033ba577c3f Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 21 Jan 2026 19:11:27 +0200 Subject: [PATCH 20/21] clear existing customTransfers on api response ( filters/etc ) --- src/hooks/fetch/useFetchCustomTransfers.ts | 1 + src/redux/slices/transactions/customTransfers.ts | 5 ++++- src/types/transaction.types.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hooks/fetch/useFetchCustomTransfers.ts b/src/hooks/fetch/useFetchCustomTransfers.ts index 30b7bdbc9..123165b33 100644 --- a/src/hooks/fetch/useFetchCustomTransfers.ts +++ b/src/hooks/fetch/useFetchCustomTransfers.ts @@ -66,6 +66,7 @@ export const useFetchCustomTransfers = (props: FetchCustomTransfersProps) => { transactions: transactionsData.data ?? [], transactionsCount: transactionsCountData?.data ?? ELLIPSIS, isWebsocket: false, + clearExisting: true, size, uuid: props.uuid, isDataReady: diff --git a/src/redux/slices/transactions/customTransfers.ts b/src/redux/slices/transactions/customTransfers.ts index 308f0a1d6..6092ac72c 100644 --- a/src/redux/slices/transactions/customTransfers.ts +++ b/src/redux/slices/transactions/customTransfers.ts @@ -20,7 +20,10 @@ export const customTransfersSlice = createSlice({ state: CustomTransactionSliceType, action: PayloadAction ) => { - if (state.uuid && state.uuid !== action.payload.uuid) { + if ( + action.payload.clearExisting || + (state.uuid && state.uuid !== action.payload.uuid) + ) { state.transactions = []; state.transactionsCount = ELLIPSIS; } diff --git a/src/types/transaction.types.ts b/src/types/transaction.types.ts index 2db676135..1642640ae 100644 --- a/src/types/transaction.types.ts +++ b/src/types/transaction.types.ts @@ -282,6 +282,7 @@ export interface TransactionSliceType extends SliceType { export interface CustomTransactionSliceType extends TransactionSliceType { uuid?: string; + clearExisting?: boolean; } export interface TransactionInPoolSliceType extends SliceType { From c37a1c126ed61630505de60d9c0778cd8180010a Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Thu, 22 Jan 2026 16:30:30 +0200 Subject: [PATCH 21/21] update changelog --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d85f47c84..0a2851c6e 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] +- ## [[2.3.3](https://github.com/multiversx/mx-explorer-dapp/pull/213)] - 2026-01-22 + +- [Websocket Transfers](https://github.com/multiversx/mx-explorer-dapp/pull/210) + - ## [[2.3.2](https://github.com/multiversx/mx-explorer-dapp/pull/208)] - 2025-11-27 - [Handle deprecatedRelayedV1V2](https://github.com/multiversx/mx-explorer-dapp/pull/208) diff --git a/package.json b/package.json index c00593932..29165310e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mx-explorer-dapp", "description": "MultiversX Blockchain Explorer", - "version": "2.3.2", + "version": "2.3.3", "author": "MultiversX", "license": "GPL-3.0-or-later", "repository": "multiversx/mx-explorer-dapp",