diff --git a/src/App.tsx b/src/App.tsx
index 844e0cd6..111fced9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -45,8 +45,17 @@ import { AuthProvider } from '@src/auth/context/web3Auth';
import { ResponsiveOverlay } from '@src/components/responsive-overlay';
import { Buffer } from 'buffer';
-import { Provider } from 'react-redux';
+import {Provider, useDispatch, useSelector} from 'react-redux';
import { MetaMaskProvider } from '@metamask/sdk-react';
+import {useNotifications} from "@src/hooks/use-notifications.ts";
+import {useSnackbar} from "notistack";
+import {useEffect} from "react";
+import {setGlobalNotifier} from "@notifications/internal-notifications.ts";
+import {publicClientWebSocket} from "@src/clients/viem/publicClient.ts";
+import {GLOBAL_CONSTANTS} from "@src/config-global.ts";
+import LedgerVaultAbi from "@src/config/abi/LedgerVault.json";
+import {setBlockchainEvents} from "@redux/blockchain-events";
+import {subscribeToNotifications} from "@src/utils/subscribe-notifications-supabase.ts";
window.Buffer = Buffer;
@@ -103,10 +112,7 @@ export default function App() {
-
-
-
-
+
@@ -117,3 +123,80 @@ export default function App() {
);
}
+
+
+interface EventArgs {
+ recipient?: string;
+ origin?: string;
+}
+const AppContent = () => {
+ const dispatch = useDispatch();
+ const sessionData = useSelector((state: any) => state.auth.session);
+ const { getNotifications } = useNotifications();
+ const { enqueueSnackbar } = useSnackbar();
+
+ useEffect(() => {
+ // Set the global reference so we can call notify(...) anywhere.
+ setGlobalNotifier(enqueueSnackbar);
+ }, [enqueueSnackbar]);
+
+ const watchEvent = (eventName: string, args: EventArgs, logText: string) => {
+ const results = publicClientWebSocket.watchContractEvent({
+ address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS,
+ abi: LedgerVaultAbi.abi,
+ eventName,
+ args,
+ onLogs: (logs) => {
+ console.log(logText, logs);
+ dispatch(setBlockchainEvents(logs));
+ },
+ });
+
+ console.log('Watching', eventName, 'events');
+ console.log('Results', results);
+
+ return results;
+ };
+
+ useEffect(() => {
+ if (!sessionData?.address) return;
+
+ const events = [
+ { name: 'FundsDeposited', args: { recipient: sessionData?.address }, logText: 'New deposit (user as recipient):' },
+ { name: 'FundsWithdrawn', args: { origin: sessionData?.address }, logText: 'New withdraw (user as origin):' },
+ { name: 'FundsTransferred', args: { origin: sessionData?.address }, logText: 'New transfer from me:' },
+ { name: 'FundsTransferred', args: { recipient: sessionData?.address }, logText: 'New transfer to me:' },
+ { name: 'FundsLocked', args: { account: sessionData?.address }, logText: 'New funds locked:' },
+ { name: 'FundsClaimed', args: { claimer: sessionData?.address }, logText: 'New funds claimed:' },
+ { name: 'FundsReserved', args: { from: sessionData?.address }, logText: 'New funds reserved:' },
+ { name: 'FundsCollected', args: { from: sessionData?.address }, logText: 'New funds collected:' },
+ { name: 'FundsReleased', args: { to: sessionData?.address }, logText: 'New funds released:' },
+ ];
+
+ const unwatchers = events.map(event => watchEvent(event.name, event.args, event.logText));
+
+ return () => {
+ unwatchers.forEach(unwatch => unwatch());
+ };
+ }, [sessionData?.address]);
+
+
+ useEffect(() => {
+ if (sessionData?.profile?.id) {
+ // Subscribe to notifications channel
+ subscribeToNotifications(sessionData?.profile?.id, dispatch, ['notifications']);
+
+ // Set the notifications in first render
+ getNotifications(sessionData?.profile?.id).then(() => {});
+ }
+ }, [sessionData?.profile?.id, dispatch]);
+
+ return (
+ <>
+
+
+
+
+ >
+ )
+}
diff --git a/src/hooks/use-get-smart-wallet-transactions.ts b/src/hooks/use-get-smart-wallet-transactions.ts
index 2a3203ca..6de24ee9 100644
--- a/src/hooks/use-get-smart-wallet-transactions.ts
+++ b/src/hooks/use-get-smart-wallet-transactions.ts
@@ -1,11 +1,14 @@
import { useState, useEffect } from 'react';
-import { parseAbiItem, formatUnits, Address } from 'viem';
import { useDispatch, useSelector } from 'react-redux';
+import { parseAbiItem, formatUnits, Address } from 'viem';
import { publicClient } from '@src/clients/viem/publicClient.ts';
import { GLOBAL_CONSTANTS } from '@src/config-global.ts';
import LedgerVaultAbi from '@src/config/abi/LedgerVault.json';
import { addTransaction, setTransactions } from '@redux/transactions';
+/**
+ * Type definition for a transaction log, including event data and relevant block/transaction metadata.
+ */
export type TransactionLog = {
address: string;
args: {
@@ -29,17 +32,126 @@ export type TransactionLog = {
transactionIndex: number;
};
-const useGetSmartWalletTransactions = () => {
+/**
+ * Configuration object for each event:
+ * - eventName: Name of the event in the smart contract ABI.
+ * - args: Address-related arguments used to filter logs (e.g., recipient, origin).
+ * - getEventType: Function to determine a custom "event type" (e.g., transferTo, transferFrom) based on the log contents and the user's address.
+ */
+type EventConfig = {
+ eventName: string;
+ args: Record;
+ getEventType: (log: any, userAddress: string) => string;
+};
+
+/**
+ * Hook to retrieve smart wallet transactions by querying logs from the LedgerVault contract.
+ * It also manages live updates when new events are detected in real time.
+ */
+export default function useGetSmartWalletTransactions() {
const dispatch = useDispatch();
const sessionData = useSelector((state: any) => state.auth.session);
const blockchainEvents = useSelector((state: any) => state.blockchainEvents.events);
const transactions = useSelector((state: any) => state.transactions.transactions);
- // Local states for loading and error
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- // Function to fetch historical logs
+ /**
+ * We define all event configurations that we want to capture.
+ * Each configuration includes:
+ * - The event name.
+ * - An object "args" that indicates which fields in the log must match the user's address.
+ * - A function to map the raw event to a custom "event type" (transferFrom, transferTo, deposit, etc.).
+ */
+ const eventConfigs: EventConfig[] = [
+ {
+ eventName: 'FundsTransferred',
+ args: { recipient: sessionData?.address || '' },
+ getEventType: (log, userAddress) =>
+ log.args.origin === userAddress ? 'transferTo' : 'transferFrom',
+ },
+ {
+ eventName: 'FundsTransferred',
+ args: { origin: sessionData?.address || '' },
+ getEventType: (log, userAddress) =>
+ log.args.origin === userAddress ? 'transferTo' : 'transferFrom',
+ },
+ {
+ eventName: 'FundsDeposited',
+ args: { recipient: sessionData?.address || '' },
+ getEventType: () => 'deposit',
+ },
+ {
+ eventName: 'FundsWithdrawn',
+ args: { origin: sessionData?.address || '' },
+ getEventType: () => 'withdraw',
+ },
+ {
+ eventName: 'FundsLocked',
+ args: { account: sessionData?.address || '' },
+ getEventType: () => 'locked',
+ },
+ {
+ eventName: 'FundsClaimed',
+ args: { claimer: sessionData?.address || '' },
+ getEventType: () => 'claimed',
+ },
+ {
+ eventName: 'FundsReserved',
+ args: { from: sessionData?.address || '' },
+ getEventType: () => 'reserved',
+ },
+ {
+ eventName: 'FundsCollected',
+ args: { from: sessionData?.address || '' },
+ getEventType: () => 'collected',
+ },
+ {
+ eventName: 'FundsReleased',
+ args: { to: sessionData?.address || '' },
+ getEventType: () => 'released',
+ },
+ ];
+
+ /**
+ * Helper function to create the event signature needed by viem's parseAbiItem().
+ * For example, an event signature looks like "event FundsTransferred(address indexed origin, address indexed recipient, ...)".
+ */
+ const createEventSignature = (event: any): string => {
+ if (!event || !event.name || !event.inputs) {
+ throw new Error('Invalid event in ABI');
+ }
+ const inputs = event.inputs
+ .map((input: any) => `${input.type}${input.indexed ? ' indexed' : ''} ${input.name}`)
+ .join(', ');
+ return `event ${event.name}(${inputs})`;
+ };
+
+ /**
+ * Generate a dictionary (object) of parsed ABIs based on all unique event names in the eventConfigs.
+ * a) Extract unique event names (e.g. FundsTransferred, FundsDeposited, etc.).
+ * b) Find those events in the LedgerVaultAbi and parse them with parseAbiItem().
+ */
+ const uniqueEventNames = Array.from(
+ new Set(eventConfigs.map((config) => config.eventName)) // Removes duplicates
+ );
+
+ const parsedAbis = uniqueEventNames.reduce((acc, eventName) => {
+ const eventAbi = LedgerVaultAbi.abi.find(
+ (item: any) => item.type === 'event' && item.name === eventName
+ );
+ if (!eventAbi) {
+ throw new Error(`No definition found for event ${eventName} in the ABI`);
+ }
+ acc[eventName] = parseAbiItem(createEventSignature(eventAbi));
+ return acc;
+ }, {} as Record>);
+
+ /**
+ * Function to fetch historical logs from the LedgerVault contract, using the user's address as a filter.
+ * The logs are then sorted, processed, and stored in Redux.
+ */
const fetchLogs = async () => {
if (!sessionData?.address) {
setLoading(false);
@@ -50,181 +162,112 @@ const useGetSmartWalletTransactions = () => {
setLoading(true);
setError(null);
- // Define ABI for events to monitor
- const eventsAbi = {
- FundsTransferred: parseAbiItem(
- createEventSignature(
- LedgerVaultAbi.abi.find(
- (item: any) => item.type === 'event' && item.name === 'FundsTransferred'
- )
- )
- ),
- FundsDeposited: parseAbiItem(
- createEventSignature(
- LedgerVaultAbi.abi.find(
- (item: any) => item.type === 'event' && item.name === 'FundsDeposited'
- )
- )
- ),
- FundsWithdrawn: parseAbiItem(
- createEventSignature(
- LedgerVaultAbi.abi.find(
- (item: any) => item.type === 'event' && item.name === 'FundsWithdrawn'
- )
- )
- ),
- };
-
- // Fetch logs for each event
- const [transfersToMe, transfersFromMe, deposits, withdraws] = await Promise.all([
- publicClient.getLogs({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
- event: eventsAbi.FundsTransferred as any,
- args: { recipient: sessionData.address },
- fromBlock: 0n,
- toBlock: 'latest',
- }),
- publicClient.getLogs({
+ // a) Build an array of promises, one for each eventConfig, calling publicClient.getLogs.
+ const promises = eventConfigs.map(({ eventName, args }) => {
+ return publicClient.getLogs({
address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
- event: eventsAbi.FundsTransferred as any,
- args: { origin: sessionData.address },
+ event: parsedAbis[eventName] as any,
+ args,
fromBlock: 0n,
toBlock: 'latest',
- }),
- publicClient.getLogs({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
- event: eventsAbi.FundsDeposited as any,
- args: { recipient: sessionData.address },
- fromBlock: 0n,
- toBlock: 'latest',
- }),
- publicClient.getLogs({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS as Address,
- event: eventsAbi.FundsWithdrawn as any,
- args: { origin: sessionData.address },
- fromBlock: 0n,
- toBlock: 'latest',
- }),
- ]);
+ });
+ });
+
+ // b) Execute all the promises in parallel.
+ const results = await Promise.all(promises);
- const allLogs = [...transfersToMe, ...transfersFromMe, ...deposits, ...withdraws];
+ // c) Flatten the array of arrays of logs into one array.
+ const allLogs = results.flat();
- // Add timestamps and format details for each log
+ // d) Fetch block timestamps for each log and map them to a structured format.
const logsWithDetails = await Promise.all(
allLogs.map(async (log: any) => {
const block = await publicClient.getBlock({ blockNumber: log.blockNumber });
- // Determine the event type
- const event = (() => {
- switch (log.eventName) {
- case 'FundsTransferred':
- return log.args.origin === sessionData.address ? 'transferTo' : 'transferFrom';
- case 'FundsDeposited':
- return 'deposit';
- case 'FundsWithdrawn':
- return 'withdraw';
- default:
- return 'unknown';
- }
- })();
+ // Find the event config to determine the custom "eventType".
+ const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName);
+ const eventType = foundConfig
+ ? foundConfig.getEventType(log, sessionData?.address)
+ : 'unknown';
return {
...log,
- timestamp: block.timestamp, // UNIX timestamp of the block
- readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), // Human-readable date
- formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', // Convert amount from wei to ether
- event,
+ timestamp: block.timestamp,
+ readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(),
+ formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0',
+ event: eventType,
};
})
);
- // Sort logs by block number and transaction index
+ // e) Sort logs by blockNumber descending, then by transactionIndex descending.
const sortedLogs = logsWithDetails.sort((a, b) => {
const blockDifference = Number(b.blockNumber) - Number(a.blockNumber);
if (blockDifference !== 0) return blockDifference;
return Number(b.transactionIndex) - Number(a.transactionIndex);
});
- // Dispatch the setTransactions action to store the logs in Redux
+ // Finally, update Redux state with the sorted logs.
dispatch(setTransactions(sortedLogs));
} catch (err) {
console.error('Error fetching logs:', err);
- setError(err instanceof Error ? err.message : 'An unknown error occurred');
+ setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
- // Helper function to create event signatures
- const createEventSignature = (event: any): string => {
- if (!event || !event.name || !event.inputs) {
- throw new Error('Invalid event in ABI');
- }
- const inputs = event.inputs
- .map((input: any) => `${input.type}${input.indexed ? ' indexed' : ''} ${input.name}`)
- .join(', ');
- return `event ${event.name}(${inputs})`;
- };
-
- // Effect to fetch historical logs when the address changes
+ /**
+ * useEffect hook that fires when the user's address changes, triggering the fetchLogs function.
+ * If there's already a list of transactions, we can stop showing the loader.
+ */
useEffect(() => {
fetchLogs();
-
- // Disable loader if data is already available
if (transactions.length) {
setLoading(false);
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionData?.address]);
- // Effect to handle real-time events from blockchainEvents
+ /**
+ * Real-time events handling:
+ * When a new event is picked up in `blockchainEvents`, we check if it's from the LedgerVault contract and
+ * if it's one of the recognized event names. If yes, we process it similarly (fetch block info, add extra fields)
+ * and then dispatch addTransaction to Redux.
+ */
useEffect(() => {
- if (!blockchainEvents || blockchainEvents.length === 0) return;
+ if (!blockchainEvents?.length) return;
- // Iterate over new blockchain events
blockchainEvents.forEach(async (log: any) => {
- // Process only relevant events
+ // Filter out logs not from our contract or event names not in use.
if (
log.address !== GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS ||
- !['FundsTransferred', 'FundsDeposited', 'FundsWithdrawn'].includes(log.eventName)
+ !uniqueEventNames.includes(log.eventName)
) {
return;
}
try {
const block = await publicClient.getBlock({ blockNumber: log.blockNumber });
+ const foundConfig = eventConfigs.find((c) => c.eventName === log.eventName);
+ const eventType = foundConfig
+ ? foundConfig.getEventType(log, sessionData?.address)
+ : 'unknown';
- // Determine the event type
- const event = (() => {
- switch (log.eventName) {
- case 'FundsTransferred':
- return log.args.origin === sessionData.address ? 'transferTo' : 'transferFrom';
- case 'FundsDeposited':
- return 'deposit';
- case 'FundsWithdrawn':
- return 'withdraw';
- default:
- return 'unknown';
- }
- })();
-
- // Create a formatted transaction log
const formattedLog = {
...log,
- timestamp: block.timestamp, // UNIX timestamp of the block
- readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(), // Human-readable date
- formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0', // Convert amount from wei to ether
- event,
+ timestamp: block.timestamp,
+ readableDate: new Date(Number(block.timestamp) * 1000).toLocaleString(),
+ formattedAmount: log.args.amount ? formatUnits(log.args.amount, 18) : '0',
+ event: eventType,
};
- // Dispatch the addTransaction action to add the new log to Redux
dispatch(addTransaction(formattedLog));
} catch (err) {
console.error('Error processing real-time log:', err);
}
});
- }, [blockchainEvents, sessionData?.address, dispatch]);
+ }, [blockchainEvents, sessionData?.address, dispatch, uniqueEventNames, eventConfigs]);
return { transactions, loading, error };
-};
-
-export default useGetSmartWalletTransactions;
+}
diff --git a/src/routes/sections/index.tsx b/src/routes/sections/index.tsx
index 5fd497ef..de09be1d 100644
--- a/src/routes/sections/index.tsx
+++ b/src/routes/sections/index.tsx
@@ -1,96 +1,9 @@
import { Navigate, useRoutes } from 'react-router-dom';
-import { GLOBAL_CONSTANTS, PATH_AFTER_LOGIN } from '@src/config-global';
+import { PATH_AFTER_LOGIN } from '@src/config-global';
import { dashboardRoutes } from './dashboard';
import NotFoundPage from '../../pages/404';
-import { useEffect } from 'react';
-import { subscribeToNotifications } from '@src/utils/subscribe-notifications-supabase.ts';
-import { useDispatch, useSelector } from 'react-redux';
-import { useNotifications } from '@src/hooks/use-notifications.ts';
-import { publicClientWebSocket } from '@src/clients/viem/publicClient.ts';
-import LedgerVaultAbi from '@src/config/abi/LedgerVault.json';
-import { setBlockchainEvents } from '@redux/blockchain-events';
-import { setGlobalNotifier } from '@notifications/internal-notifications.ts';
-import { useSnackbar } from 'notistack';
-
export default function Router() {
- const dispatch = useDispatch();
- const sessionData = useSelector((state: any) => state.auth.session);
- const { getNotifications } = useNotifications();
- const { enqueueSnackbar } = useSnackbar();
-
- useEffect(() => {
- // Set the global reference so we can call notify(...) anywhere.
- setGlobalNotifier(enqueueSnackbar);
- }, [enqueueSnackbar]);
-
- useEffect(() => {
- if (!sessionData?.address) return;
-
- // FundsDeposited (when i am the recipient)
- const unwatchDeposit = publicClientWebSocket.watchContractEvent({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS,
- abi: LedgerVaultAbi.abi,
- eventName: 'FundsDeposited',
- args: { recipient: sessionData?.address },
- onLogs: (logs) => {
- console.log('New deposit (user as recipient):', logs);
- dispatch(setBlockchainEvents(logs));
- },
- });
-
- // FundsWithdrawn (when i am the origin)
- const unwatchWithdraw = publicClientWebSocket.watchContractEvent({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS,
- abi: LedgerVaultAbi.abi,
- eventName: 'FundsWithdrawn',
- args: { origin: sessionData?.address },
- onLogs: (logs) => {
- console.log('New withdraw (user as origin):', logs);
- dispatch(setBlockchainEvents(logs));
- },
- });
-
- // FundsTransferred (when I send)
- const unwatchTransferFrom = publicClientWebSocket.watchContractEvent({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS,
- abi: LedgerVaultAbi.abi,
- eventName: 'FundsTransferred',
- args: { origin: sessionData?.address },
- onLogs: (logs) => {
- console.log('New transfer from me:', logs);
- dispatch(setBlockchainEvents(logs));
- },
- });
-
- // FundsTransferred (when I receive)
- const unwatchTransferTo = publicClientWebSocket.watchContractEvent({
- address: GLOBAL_CONSTANTS.LEDGER_VAULT_ADDRESS,
- abi: LedgerVaultAbi.abi,
- eventName: 'FundsTransferred',
- args: { recipient: sessionData?.address },
- onLogs: (logs) => {
- console.log('New transfer to me:', logs);
- dispatch(setBlockchainEvents(logs));
- },
- });
-
- return () => {
- unwatchDeposit();
- unwatchWithdraw();
- unwatchTransferFrom();
- unwatchTransferTo();
- };
- }, [sessionData?.address]);
-
- useEffect(() => {
- if (sessionData?.profile?.id) {
- // Subscribe to notifications channel
- subscribeToNotifications(sessionData?.profile?.id, dispatch, ['notifications']);
- // Set the notifications in first render
- getNotifications(sessionData?.profile?.id).then(() => {});
- }
- }, [sessionData?.profile?.id, dispatch]);
return useRoutes([
{
diff --git a/src/sections/finance/components/finance-transactions-history.tsx b/src/sections/finance/components/finance-transactions-history.tsx
index fc9847d9..4ef33ad0 100644
--- a/src/sections/finance/components/finance-transactions-history.tsx
+++ b/src/sections/finance/components/finance-transactions-history.tsx
@@ -30,7 +30,11 @@ import useGetSmartWalletTransactions from '@src/hooks/use-get-smart-wallet-trans
import { processTransactionData } from '@src/utils/finance-graphs/groupedTransactions';
import FinanceOverlayLoader from '@src/sections/finance/components/finance-overlay-loader.tsx';
-const STATUS_OPTIONS = [{ value: 'all', label: 'All' }, ...TRANSACTIONS_TYPES.slice(0, -2)];
+const STATUS_OPTIONS = [
+ { value: 'all', label: 'All' },
+ ...TRANSACTIONS_TYPES,
+ { value: 'other', label: 'Other' }
+];
const TABLE_HEAD = [
{ id: 'name', label: 'Transaction Info', width: 20 },
@@ -88,6 +92,10 @@ export default function FinanceTransactionsHistory() {
[handleFilters]
);
+ const removeDuplicatesById = (array: any[]) => {
+ return Array.from(new Map(array.map((item) => [item.id, item])).values());
+ }
+
return (
<>
- {tab.value === 'all' && transactionData.length}
+ {tab.value === 'all' &&
+ removeDuplicatesById(transactionData).length
+ }
{tab.value === 'transferFrom' &&
- transactionData.filter(
- (t) =>
- t.type.toLowerCase() === 'transferto' || t.type.toLowerCase() === 'withdraw'
- ).length}
+ removeDuplicatesById(
+ transactionData.filter(
+ (t) =>
+ t.type.toLowerCase() === 'transferto' ||
+ t.type.toLowerCase() === 'withdraw'
+ )
+ ).length
+ }
{tab.value === 'transferTo' &&
- transactionData.filter(
- (t) =>
- t.type.toLowerCase() === 'transferfrom' ||
- t.type.toLowerCase() === 'deposit'
- ).length}
+ removeDuplicatesById(
+ transactionData.filter(
+ (t) =>
+ t.type.toLowerCase() === 'transferfrom' ||
+ t.type.toLowerCase() === 'deposit'
+ )
+ ).length
+ }
+ {tab.value === 'other' &&
+ removeDuplicatesById(
+ transactionData.filter(
+ (t) =>
+ t.type.toLowerCase() === 'locked' ||
+ t.type.toLowerCase() === 'claimed' ||
+ t.type.toLowerCase() === 'reserved' ||
+ t.type.toLowerCase() === 'collected' ||
+ t.type.toLowerCase() === 'released'
+ )
+ ).length
+ }
}
/>
@@ -224,7 +254,23 @@ function applyFilter({
(t) => t.type.toLowerCase() === 'transferfrom' || t.type.toLowerCase() === 'deposit'
);
}
+
+ if (status === 'other') {
+ filteredData = filteredData.filter(
+ (t) =>
+ t.type.toLowerCase() === 'locked' ||
+ t.type.toLowerCase() === 'claimed' ||
+ t.type.toLowerCase() === 'reserved' ||
+ t.type.toLowerCase() === 'collected' ||
+ t.type.toLowerCase() === 'released'
+ );
+ }
}
+ // delete duplicated items
+ filteredData = Array.from(
+ new Map(filteredData.map((item) => [item.id, item])).values()
+ );
+
return filteredData;
}
diff --git a/src/sections/finance/components/finance-transactions-table-row.tsx b/src/sections/finance/components/finance-transactions-table-row.tsx
index 349b0eb5..46e434bf 100644
--- a/src/sections/finance/components/finance-transactions-table-row.tsx
+++ b/src/sections/finance/components/finance-transactions-table-row.tsx
@@ -22,16 +22,37 @@ type Props = {
// ----------------------------------------------------------------------
const urlTxBase = 'https://www.oklink.com/es-la/amoy/tx/';
+const COLORS = {
+ success: '#00AB55',
+ danger: '#FF4842',
+ warning: '#dc9f00',
+ info: '#3a7dd5',
+}
+
+const TX_COLORS: any = {
+ 'transferTo': COLORS.danger,
+ 'transferFrom': COLORS.success,
+ 'deposit': COLORS.success,
+ 'withdraw': COLORS.danger,
+ 'locked': COLORS.info,
+ 'claimed': COLORS.success,
+ 'reserved': COLORS.warning,
+ 'collected': COLORS.success,
+ 'released': COLORS.success,
+}
// ----------------------------------------------------------------------
export default function FinanceTransactionTableRow({ row, selected }: Props) {
- const { date, name, amount, avatarUrl, message, category, id } = row;
+ const { date, name, amount, avatarUrl, message, category, id, type } = row;
const dateObject = new Date(Number(date) * 1000);
const dateLbl = format(dateObject, 'dd/MM/yyyy');
const timeLbl = format(dateObject, 'p');
+ console.log('row')
+ console.log(row)
+
const renderPrimary = (
@@ -61,7 +82,7 @@ export default function FinanceTransactionTableRow({ row, selected }: Props) {
-
+
{category === 'income' ? '' : '-'} {amount} MMC
diff --git a/src/sections/user/profile-header.tsx b/src/sections/user/profile-header.tsx
index d877a4d2..bb598af0 100644
--- a/src/sections/user/profile-header.tsx
+++ b/src/sections/user/profile-header.tsx
@@ -56,8 +56,6 @@ import BadgeVerified from "@src/components/user-item/BadgeVerified.tsx";
const urlToShare = 'https://app.watchit.movie/profileId';
const urlAttestationBase = 'https://polygon-amoy.easscan.org/attestation/view/';
-// const GeoAddress = '0xEFBBD14082cF2FbCf5Badc7ee619F0f4e36D0A5B'
-
const shareLinks = [
{
icon: 'mingcute:social-x-line',
diff --git a/src/types/transaction.ts b/src/types/transaction.ts
index 2502d64a..861cd929 100644
--- a/src/types/transaction.ts
+++ b/src/types/transaction.ts
@@ -7,6 +7,4 @@ export type IOrderTableFilters = {
export const TRANSACTIONS_TYPES = [
{ value: 'transferTo', label: 'Income' },
{ value: 'transferFrom', label: 'Outcomes' },
- { value: 'deposit', label: 'Deposit' },
- { value: 'withdraw', label: 'Withdraw' },
];
diff --git a/src/utils/finance-graphs/groupedTransactions.ts b/src/utils/finance-graphs/groupedTransactions.ts
index 15149a76..13240f77 100644
--- a/src/utils/finance-graphs/groupedTransactions.ts
+++ b/src/utils/finance-graphs/groupedTransactions.ts
@@ -115,19 +115,79 @@ export type ProcessedTransactionData = {
amount: string | null;
};
+type EventName =
+ | 'transferFrom'
+ | 'transferTo'
+ | 'deposit'
+ | 'withdraw'
+ | 'locked'
+ | 'claimed'
+ | 'reserved'
+ | 'collected'
+ | 'released';
+
+type EventConfig = {
+ getName: (args: any) => string;
+ getAvatarUrl: (args: any) => string;
+};
+
+const eventConfig: Record = {
+ transferFrom: {
+ getName: (args) => args.origin,
+ getAvatarUrl: (args) => dicebear(args.origin),
+ },
+ transferTo: {
+ getName: (args) => args.recipient,
+ getAvatarUrl: (args) => dicebear(args.recipient),
+ },
+ deposit: {
+ getName: (args) => args.recipient,
+ getAvatarUrl: (args) => dicebear(args.recipient),
+ },
+ withdraw: {
+ getName: (args) => args.origin,
+ getAvatarUrl: (args) => dicebear(args.origin),
+ },
+ locked: {
+ getName: (args) => args.account,
+ getAvatarUrl: (args) => dicebear(args.account),
+ },
+ claimed: {
+ getName: (args) => args.claimer,
+ getAvatarUrl: (args) => dicebear(args.claimer),
+ },
+ reserved: {
+ getName: (args) => args.from,
+ getAvatarUrl: (args) => dicebear(args.from),
+ },
+ collected: {
+ getName: (args) => args.from,
+ getAvatarUrl: (args) => dicebear(args.from),
+ },
+ released: {
+ getName: (args) => args.to,
+ getAvatarUrl: (args) => dicebear(args.to),
+ },
+};
+
export const processTransactionData = (data: TransactionLog[]): ProcessedTransactionData[] => {
- return data?.map((transaction, _index) => ({
- id: transaction.transactionHash,
- name:
- transaction.event === 'transferFrom' ? transaction.args.origin : transaction.args.recipient,
- avatarUrl: dicebear(transaction.event === 'transferFrom' ? transaction.args.origin : transaction.args.recipient),
- type: transaction.event,
- message: parseTransactionTypeLabel(transaction.event),
- category: parseTransactionType(transaction.event),
- date: transaction.timestamp,
- status: 'completed',
- amount: transaction.formattedAmount,
- }));
+ return data.map((transaction) => {
+ const config = eventConfig[transaction.event as EventName];
+ const name = config ? config.getName(transaction.args) : 'Unknown';
+ const avatarUrl = config ? config.getAvatarUrl(transaction.args) : dicebear('default');
+
+ return {
+ id: transaction.transactionHash,
+ name,
+ avatarUrl,
+ type: transaction.event,
+ message: parseTransactionTypeLabel(transaction.event),
+ category: parseTransactionType(transaction.event),
+ date: transaction.timestamp,
+ status: 'completed',
+ amount: transaction.formattedAmount,
+ };
+ });
};
const parseTransactionTypeLabel = (type: string): string => {
@@ -140,6 +200,16 @@ const parseTransactionTypeLabel = (type: string): string => {
return 'Deposited';
case 'withdraw':
return 'Withdraw';
+ case 'locked':
+ return 'Locked';
+ case 'claimed':
+ return 'Claimed';
+ case 'reserved':
+ return 'Reserved';
+ case 'collected':
+ return 'Collected';
+ case 'released':
+ return 'Released';
default:
return type;
@@ -157,6 +227,16 @@ const parseTransactionType = (type: string): string => {
return 'income';
case 'withdraw':
return 'outcome';
+ case 'locked':
+ return 'other';
+ case 'claimed':
+ return 'other';
+ case 'reserved':
+ return 'other';
+ case 'collected':
+ return 'other';
+ case 'released':
+ return 'other';
default:
return type;