Skip to content

feat: wallet-based transaction filtering with persistent caching#22

Open
Jitmisra wants to merge 1 commit intoDjedAlliance:mainfrom
Jitmisra:feature/wallet-transaction-filtering
Open

feat: wallet-based transaction filtering with persistent caching#22
Jitmisra wants to merge 1 commit intoDjedAlliance:mainfrom
Jitmisra:feature/wallet-transaction-filtering

Conversation

@Jitmisra
Copy link

@Jitmisra Jitmisra commented Dec 13, 2025

PR Title

feat: Wallet-based transaction filtering with persistent caching

PR Description

🎯 Fixes #6

Summary

Implements wallet-based transaction filtering and persistent caching to improve dashboard performance and reduce unnecessary blockchain queries.


🏗️ Architecture

┌────────────────────────────────────────────────────────────────────────────┐
│                           DATA FLOW DIAGRAM                                │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                            │
│  ┌─────────────┐         ┌──────────────────┐         ┌────────────────┐  │
│  │   WALLET    │────────▶│  Merchant Addr   │────────▶│  localStorage  │  │
│  │   CONNECT   │         │    Captured      │         │   Persisted    │  │
│  └─────────────┘         └──────────────────┘         └────────────────┘  │
│                                                                            │
│  ┌─────────────┐         ┌──────────────────┐         ┌────────────────┐  │
│  │   FETCH     │────────▶│  Filter by       │────────▶│    CACHE       │  │
│  │   DATA      │         │  receiver addr   │         │ + lastBlock    │  │
│  └─────────────┘         └──────────────────┘         └────────────────┘  │
│                                                                            │
│  ┌─────────────┐         ┌──────────────────┐         ┌────────────────┐  │
│  │ FETCH FRESH │────────▶│  Start from      │────────▶│   APPEND +     │  │
│  │    DATA     │         │  lastSyncedBlock │         │   DEDUPE       │  │
│  └─────────────┘         └──────────────────┘         └────────────────┘  │
│                                                                            │
└────────────────────────────────────────────────────────────────────────────┘

✨ Key Features

1. Transaction Filtering

  • Filters transactions where connected wallet is the receiver (merchant)
  • Merchant address captured on wallet connect
  • Passed to TransactionService for blockchain-level filtering

2. Persistent Caching

  • Transactions stored in localStorage with indefinite retention
  • Cache keyed by merchant address (auto-invalidates on wallet change)
  • Survives page refreshes and browser restarts

3. Incremental Fetching

  • Tracks lastSyncedBlock for each merchant
  • "Fetch Fresh Data" only queries blocks after last sync
  • Deduplicates by transaction hash before appending
  • Significantly reduces blockchain RPC calls

4. SSR Safety

  • getStoredMerchantAddress() checks for browser environment
  • No localStorage access during server-side rendering

📁 Files Changed

File Changes Purpose
hooks/use-wallet.ts +35 Merchant address persistence
hooks/use-transactions.ts +95 Persistent cache + incremental fetch
lib/transaction-service.ts +15 Support startFromBlock parameter
app/transactions/page.tsx +180 UI updates + "Fetch Fresh Data"
app/page.tsx +20 Wallet connection guidance

Total: 5 files, ~350 lines of focused changes


🖥️ UI Improvements

┌─────────────────────────────────────────────────────────────────┐
│  TRANSACTIONS PAGE                                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Transaction Network                                            │
│  Filtering by: 0x2881...e82F          [Fetch Fresh Data] [Clear]│
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ ⚠️ No transactions found for your wallet                │   │
│  │                                                          │   │
│  │ If you don't have any StablePay transactions:           │   │
│  │ • Make a transaction on the merchant demo website       │   │
│  │ • Visit mordor.djed.one to buy stablecoins              │   │
│  │ • Click "Fetch Fresh Data" after making a transaction   │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  Last synced: Block #7234567                                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

✅ Acceptance Criteria

  • Only merchant-relevant transactions are displayed
  • Cached transactions persist across refreshes
  • Incremental fetch works based on lastSyncedBlock
  • No previously fetched data is lost on refresh or refetch
  • Wallet connection prompts shown when not connected
  • Helpful guidance when no transactions found

🧪 Testing Checklist

  • Connect wallet → merchant address persisted to localStorage
  • Fetch transactions → filtered by receiver address
  • Page refresh → cached data loads instantly
  • "Fetch Fresh Data" → only new transactions fetched
  • Disconnect wallet → cache cleared
  • Different wallet → old cache invalidated
  • Build passes without errors

🔄 Cache Flow

┌──────────────────────────────────────────────────────────────────┐
│                        CACHE STRUCTURE                           │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  localStorage['stablepay_transactions'] = {                      │
│    transactions: [...],      // Serialized tx array              │
│    lastSyncedBlock: "7234567", // For incremental fetch          │
│    merchantAddress: "0x..."    // Cache invalidation key         │
│  }                                                               │
│                                                                  │
│  localStorage['stablepay_merchant_address'] = "0x..."            │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

📊 Impact

Metric Before After
Full blockchain scan on every fetch Yes No (incremental)
Data persists across refresh No (5min expiry) Yes (indefinite)
Wallet-specific filtering No Yes
Deduplication No Yes
SSR safe No Yes
Monosnap.screencast.2025-12-14.02-41-23.mp4

Summary by CodeRabbit

Release Notes

  • New Features

    • Wallet integration required; users must connect wallet to access dashboard and transactions.
    • Real-time blockchain data now replaces static mock data.
    • Added "Fetch Fresh Data" option for transaction refresh.
    • Transaction details now include Etherscan viewing capability.
    • Last synced block information displayed in transaction page.
  • Bug Fixes

    • Improved transaction deduplication and data persistence.

✏️ Tip: You can customize this high-level summary in your review settings.

Implements Issue DjedAlliance#6 requirements:
- Filter transactions by merchant wallet address (receiver)
- Persist merchant address on wallet connect
- Persistent localStorage cache (indefinite, no expiry)
- Track lastSyncedBlock for incremental updates
- Incremental fetching (only new transactions after last sync)
- Deduplication by transaction hash
- SSR-safe localStorage access
- Clear cache on wallet disconnect
- Helpful UI messages when no transactions found
@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

Walkthrough

The PR implements wallet-based transaction filtering and persistent caching. It removes mock data from dashboard pages, adds wallet connectivity checks, introduces localStorage-backed merchant-scoped caching with incremental fetching support, and refactors UI components to reflect wallet connection status and real transaction data.

Changes

Cohort / File(s) Summary
Dashboard Pages
dashboard/app/page.tsx, dashboard/app/transactions/page.tsx
Removed mock data imports; integrated wallet connectivity checks via useWallet hook; added conditional UI flows for wallet connection prompts, transaction data display, and "Connect wallet & fetch transactions" messaging; restructured header descriptions and transaction table/modal layouts; added Etherscan links in transaction details.
Transaction & Wallet Hooks
dashboard/hooks/use-transactions.ts, dashboard/hooks/use-wallet.ts
Added persistent localStorage caching with merchant address scoping; introduced lastSyncedBlock state for incremental fetching; reworked cache loading to verify merchant address match and convert BigInt serialization; added fetchFreshData and clearCache helpers; extended useTransactions public API (lastSyncedBlock, fetchFreshData, clearCache); added getStoredMerchantAddress export for SSR-safe merchant address retrieval; integrated cross-session persistent storage on wallet connect/disconnect.
Transaction Service
dashboard/lib/transaction-service.ts
Introduced startFromBlock parameter and MAX_BLOCK_RANGE looping for incremental event fetching; added merchantAddress filtering via receiver field; consolidated event accumulation and final data transformation; removed debug console logs.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Dashboard as Dashboard UI
    participant WalletHook as useWallet Hook
    participant TxnHook as useTransactions Hook
    participant TxnService as TransactionService
    participant Blockchain as Blockchain
    participant Storage as localStorage

    User->>Dashboard: Connect Wallet
    Dashboard->>WalletHook: connectWallet()
    WalletHook->>Storage: Store merchant address
    WalletHook-->>Dashboard: isConnected=true, address

    User->>Dashboard: Fetch Transactions
    Dashboard->>TxnHook: fetchTransactions()
    TxnHook->>Storage: Load cached transactions & lastSyncedBlock
    alt Cached data exists
        Storage-->>TxnHook: Return cached transactions
        TxnHook-->>Dashboard: Display cached data
    else First fetch or manual refresh
        TxnHook->>TxnService: fetchStableCoinPurchases(merchantAddress, startFromBlock=null)
        TxnService->>Blockchain: Query events (full range or MAX_BLOCK_RANGE windows)
        Blockchain-->>TxnService: Transaction events for merchant
        TxnService->>TxnService: Deduplicate & map events
        TxnService-->>TxnHook: TransactionEvent[] with amounts & block info
        TxnHook->>TxnHook: Deduplicate against cache, merge results
        TxnHook->>Storage: Save merged transactions & lastSyncedBlock
        TxnHook-->>Dashboard: Display updated transactions
    end

    User->>Dashboard: Fetch Fresh Data
    Dashboard->>TxnHook: fetchFreshData()
    TxnHook->>TxnService: fetchStableCoinPurchases(merchantAddress, startFromBlock=lastSyncedBlock)
    TxnService->>Blockchain: Query events from lastSyncedBlock onwards
    Blockchain-->>TxnService: New transaction events
    TxnService-->>TxnHook: Incremental TransactionEvent[]
    TxnHook->>TxnHook: Append new, deduplicate, update lastSyncedBlock
    TxnHook->>Storage: Persist merged cache with new lastSyncedBlock
    TxnHook-->>Dashboard: Display updated full transaction list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • dashboard/hooks/use-transactions.ts: Significant additions—persistent merchant-scoped cache with serialization/deserialization, incremental fetching logic with block-based deduplication, and expanded public API. Verify cache invalidation on merchant mismatch, BigInt serialization correctness, and deduplication edge cases.
  • dashboard/hooks/use-wallet.ts: Cross-session merchant address persistence with SSR-safe retrieval; confirm localStorage interactions and disconnect cleanup.
  • dashboard/lib/transaction-service.ts: Incremental block-range fetching with loop-based windowing (MAX_BLOCK_RANGE); verify loop termination, merchantAddress filtering correctness, and event deduplication semantics.
  • dashboard/app/transactions/page.tsx & dashboard/app/page.tsx: Multiple conditional flows based on wallet state (connected vs. disconnected, hasFetched, totalTransactions); trace all render branches and state transitions.

Possibly related PRs

Poem

🐰 A dash of blockchain, a splash of cache,
No mock data left—we're here at last!
Incremental fetches, merchant in sight,
Persistent storage keeps wallets bright!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main change: wallet-based transaction filtering with persistent caching, which matches the primary objectives.
Linked Issues check ✅ Passed The PR successfully implements all key requirements: wallet-based filtering with merchant address capture, persistent localStorage caching with lastSyncedBlock tracking, and incremental fetching that appends new transactions without discarding cached data.
Out of Scope Changes check ✅ Passed All changes are scoped to the wallet filtering and caching objectives: hook implementations handle merchant storage and incremental fetching, TransactionService supports block-range queries, and UI pages display filtered transactions and cache status.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Jitmisra
Copy link
Author

team name bigbull

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (6)
dashboard/hooks/use-wallet.ts (2)

23-35: Edge case: isConnected true but address undefined could cause stale state.

When isConnected is true but address is not yet available (the else branch at line 30), the code restores from localStorage. However, if wagmi eventually provides a different address than what's stored (e.g., user switched accounts), the UI could briefly show stale data until the effect re-runs.

Consider adding a safeguard to clear stale state when the address becomes available and differs from localStorage:

     } else {
         // Restore from localStorage if connected but address not yet available
         const stored = getStoredMerchantAddress();
-        if (stored) setWalletAddress(stored);
+        if (stored && !address) setWalletAddress(stored);
     }

This is a minor edge case since the effect will re-run when address changes.


41-45: localStorage cleared before disconnect() completes.

If disconnect() throws or fails, the localStorage entry will already be removed, potentially causing state inconsistency. Consider clearing localStorage after successful disconnect, or within a try-catch that handles partial cleanup.

 const disconnectWallet = () => {
-    // Clear merchant address on disconnect
-    localStorage.removeItem(MERCHANT_ADDRESS_KEY);
     disconnect();
+    // Clear merchant address on disconnect
+    localStorage.removeItem(MERCHANT_ADDRESS_KEY);
 };

Note: Since disconnect() from wagmi typically doesn't throw synchronously, this is low risk but worth considering for defensive coding.

dashboard/lib/transaction-service.ts (1)

16-17: Magic numbers for block constants.

Consider documenting why DEFAULT_START_BLOCK = 6000000 was chosen (e.g., contract deployment block) and why MAX_BLOCK_RANGE = 49999 (likely RPC provider limit). A brief comment helps future maintainers:

-const DEFAULT_START_BLOCK = BigInt(6000000);
-const MAX_BLOCK_RANGE = BigInt(49999);
+// Contract deployment block on Sepolia
+const DEFAULT_START_BLOCK = BigInt(6000000);
+// Max block range per RPC request (publicnode limit is 50k)
+const MAX_BLOCK_RANGE = BigInt(49999);
dashboard/app/transactions/page.tsx (2)

14-21: Consider extracting shared utilities.

The formatAddress helper duplicates logic that could be useful elsewhere. Similarly, getRiskLevel thresholds (50, 100) are magic numbers that could benefit from named constants:

const RISK_THRESHOLDS = { HIGH: 100, MEDIUM: 50 } as const;

const getRiskLevel = (amount: string) => {
  const num = parseFloat(amount);
  if (num > RISK_THRESHOLDS.HIGH) return "high";
  if (num > RISK_THRESHOLDS.MEDIUM) return "medium";
  return "low";
};

68-70: Guard against null walletAddress in display.

When isConnected is true but walletAddress is still null (briefly during initialization), formatAddress(walletAddress || '') would display "0x... which isn't helpful. Consider a fallback:

-                {isConnected ? `Filtering by: ${formatAddress(walletAddress || '')}` : 'Connect wallet to filter transactions'}
+                {isConnected && walletAddress 
+                  ? `Filtering by: ${formatAddress(walletAddress)}` 
+                  : isConnected 
+                    ? 'Loading wallet address...' 
+                    : 'Connect wallet to filter transactions'}
dashboard/hooks/use-transactions.ts (1)

70-78: Consider handling other storage errors.

The QuotaExceededError check uses e.name, but some browsers throw DOMException with a different structure. A more defensive approach:

 try {
     localStorage.setItem(CACHE_KEY, JSON.stringify(cacheData));
 } catch (e) {
-    // Handle quota exceeded
-    if (e instanceof Error && e.name === 'QuotaExceededError') {
+    // Handle quota exceeded or other storage errors
+    if (e instanceof DOMException || (e instanceof Error && e.name === 'QuotaExceededError')) {
         localStorage.removeItem(CACHE_KEY);
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c12f638 and fbdb128.

📒 Files selected for processing (5)
  • dashboard/app/page.tsx (4 hunks)
  • dashboard/app/transactions/page.tsx (2 hunks)
  • dashboard/hooks/use-transactions.ts (1 hunks)
  • dashboard/hooks/use-wallet.ts (3 hunks)
  • dashboard/lib/transaction-service.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
dashboard/app/transactions/page.tsx (2)
dashboard/hooks/use-transactions.ts (1)
  • useTransactions (24-147)
dashboard/hooks/use-wallet.ts (1)
  • useWallet (16-53)
dashboard/app/page.tsx (1)
dashboard/hooks/use-wallet.ts (1)
  • useWallet (16-53)
dashboard/lib/transaction-service.ts (1)
dashboard/lib/config.ts (1)
  • getCurrentContractAddress (75-77)
dashboard/hooks/use-transactions.ts (2)
dashboard/lib/transaction-service.ts (2)
  • TransactionEvent (6-14)
  • transactionService (87-87)
dashboard/hooks/use-wallet.ts (1)
  • getStoredMerchantAddress (11-14)
🪛 ast-grep (0.40.0)
dashboard/hooks/use-wallet.ts

[warning] 25-25: Sensitive information detected in localStorage. Storing sensitive data like emails, usernames, or personal information in localStorage exposes it to malicious scripts and XSS attacks. Use secure storage alternatives or avoid storing sensitive data client-side.
Context: localStorage.setItem(MERCHANT_ADDRESS_KEY, address)
Note: [CWE-312] Cleartext Storage of Sensitive Information [REFERENCES]
- https://cwe.mitre.org/data/definitions/312.html
- https://owasp.org/www-community/vulnerabilities/HTML5_Local_Storage_Security

(local-storage-sensitive-information)

🔇 Additional comments (7)
dashboard/hooks/use-wallet.ts (1)

8-14: SSR-safe helper looks good.

The typeof window check properly guards against SSR issues. This pattern is correct for Next.js applications.

dashboard/app/page.tsx (2)

23-24: Success rate calculation is simplified.

The current logic returns 100% if any transactions exist, 0% otherwise. This assumes all fetched transactions are successful (which aligns with only fetching completed events from the blockchain). If the intent is to track actual success/failure rates in the future, consider adding a TODO comment.


74-89: Good UX guidance for users.

The conditional notices for wallet connection and empty transactions provide clear guidance. The link to mordor.djed.one helps users understand how to create test transactions.

dashboard/app/transactions/page.tsx (1)

162-260: Well-structured table with proper loading/error/empty states.

The table implementation handles all states correctly:

  • Loading state with spinner indication
  • Error display with message
  • Empty state with contextual guidance based on connection/fetch status
  • Click handler with modal for details

The use of stopPropagation on the external link button correctly prevents row click from firing.

dashboard/hooks/use-transactions.ts (3)

40-52: Robust cache validation with case-insensitive address comparison.

Good implementation:

  • Case-insensitive comparison handles checksum vs non-checksum addresses
  • Stale cache is cleared when merchant changes
  • Error handling removes corrupted cache

80-125: Stale closure risk in fetchTransactions.

The callback depends on transactions and lastSyncedBlock state, which are correctly included in the dependency array. However, if multiple rapid calls occur, there could be race conditions where transactions hasn't updated yet.

For critical production use, consider using a ref to track in-flight requests or using useReducer for atomic state updates. For this dashboard use case, the current implementation should be acceptable.


143-144: Public API exposes two fetch methods with different behaviors.

The return object exposes:

  • fetchTransactions: () => fetchTransactions(false) - full fetch
  • fetchFreshData - incremental fetch

This is a clean API design that hides the internal incremental flag from consumers. The wrapper functions ensure consistent behavior.

Comment on lines +35 to +42
{
label: "REVENUE GENERATED",
value: "T/A",
description: "Fetch transactions for data",
icon: "proccesor" as keyof typeof iconMap,
intent: "positive" as const,
direction: "up" as const,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: "T/A" should likely be "N/A".

The value "T/A" appears to be a typo for "N/A" (Not Available). This same typo appears in other stats as well (lines 29, 45).

-      value: "T/A",
+      value: "N/A",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
label: "REVENUE GENERATED",
value: "T/A",
description: "Fetch transactions for data",
icon: "proccesor" as keyof typeof iconMap,
intent: "positive" as const,
direction: "up" as const,
},
{
label: "REVENUE GENERATED",
value: "N/A",
description: "Fetch transactions for data",
icon: "proccesor" as keyof typeof iconMap,
intent: "positive" as const,
direction: "up" as const,
},
🤖 Prompt for AI Agents
In dashboard/app/page.tsx around lines 35 to 42 (and also check similar entries
at lines ~29 and ~45), the value string "T/A" is a typo and should be "N/A";
update the value fields for the affected stat objects to "N/A" (and scan the
file for any other occurrences of "T/A" in stats to replace them) so the
displayed notation correctly reads "N/A".

Comment on lines 26 to 27
const [selectedTransaction, setSelectedTransaction] = useState<any>(null)
const [isModalOpen, setIsModalOpen] = useState(false)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid any type for selectedTransaction.

Using any loses type safety. Since selectedTransaction comes from transactions, use the proper type:

-const [selectedTransaction, setSelectedTransaction] = useState<any>(null)
+const [selectedTransaction, setSelectedTransaction] = useState<TransactionEvent | null>(null)

You'll need to import TransactionEvent from the transaction service:

+import { TransactionEvent } from "@/lib/transaction-service"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In dashboard/app/transactions/page.tsx around lines 26-27, selectedTransaction
is typed as any which loses type safety; replace the any with the proper
TransactionEvent | null type, import TransactionEvent from the transaction
service at the top of the file, and update the useState generic to
useState<TransactionEvent | null>(null) (and adjust any handlers/props that set
or read selectedTransaction to expect TransactionEvent or null accordingly).

};
}, [transactions, lastSyncedBlock, saveToCache]);

const fetchFreshData = useCallback(() => fetchTransactions(true), [fetchTransactions]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Confusing naming: fetchFreshData performs incremental fetch, not full refresh.

fetchFreshData calls fetchTransactions(true) where true means incremental. The name "Fresh Data" suggests a full refresh, but it actually fetches only new blocks. Consider renaming for clarity:

-const fetchFreshData = useCallback(() => fetchTransactions(true), [fetchTransactions]);
+const fetchNewTransactions = useCallback(() => fetchTransactions(true), [fetchTransactions]);

Or update the UI label from "Fetch Fresh Data" to "Fetch New Transactions" to match the incremental behavior.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In dashboard/hooks/use-transactions.ts at line 127, the identifier
fetchFreshData is misleading because it calls fetchTransactions(true) which
performs an incremental fetch; rename the variable to a clearer name such as
fetchNewTransactions or fetchIncrementalTransactions and update all references
(callback declaration, dependency arrays, and any exports) accordingly, and also
change the corresponding UI label from "Fetch Fresh Data" to "Fetch New
Transactions" (or similar) so the name and UI reflect incremental behavior while
keeping the useCallback dependency on fetchTransactions intact.

Comment on lines 53 to +61
const purchaseEvents = await this.publicClient.getLogs({
address: getCurrentContractAddress() as `0x${string}`,
event: parseAbiItem('event BoughtStableCoins(address indexed buyer, address indexed receiver, uint256 amountSC, uint256 amountBC)'),
args: merchantAddress ? {
receiver: merchantAddress
}as any:undefined,
receiver: merchantAddress as `0x${string}`
} : undefined,
fromBlock,
toBlock
});
allEvents = [...allEvents, ...purchaseEvents];
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

viem getLogs decoded event args parseAbiItem

💡 Result:

Use parseAbiItem for the event when calling getLogs — viem will decode the event args into each Log's .args (use strict: true to guarantee .args is defined). Example:

import { publicClient, parseAbiItem } from 'viem'

const logs = await publicClient.getLogs({
address: '0xContractAddress',
event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
fromBlock: 16330000n,
toBlock: 16330050n,
strict: true // optional — only return logs that conform to the ABI so args is always present
})

for (const log of logs) {
console.log(log.args) // { from: '0x...', to: '0x...', value: 123n }
}

If you need to decode manually from topics/data, use decodeEventLog with the parsed ABI. [1][2][3]

Sources:
[1] viem getLogs docs.
[2] viem v1 getLogs (args & strict mode).
[3] viem decodeEventLog docs.


🏁 Script executed:

# First, let's find and examine the transaction-service.ts file
fd -t f "transaction-service.ts" | head -5

Repository: DjedAlliance/StablePay-MerchantDashboard

Length of output: 120


🏁 Script executed:

# Read the transaction-service.ts file to see the full context around lines 53-61
cat -n dashboard/lib/transaction-service.ts | sed -n '40,75p'

Repository: DjedAlliance/StablePay-MerchantDashboard

Length of output: 2047


Leverage viem's decoded event args to eliminate manual hex parsing.

viem's getLogs returns decoded args when parseAbiItem is used with strict typing. Replace the manual parsing on lines 67-69 with decoded values:

// Current: manual slicing and parsing of event.data
const rawData = event.data.slice(2);
const amountSCHex = '0x' + rawData.slice(0, 64);
const amountBCHex = '0x' + rawData.slice(64);

// Instead, use decoded args from viem:
// event.args.amountSC and event.args.amountBC are already bigints
// event.args.buyer and event.args.receiver are already checksummed addresses

This eliminates slicing errors and reduces risk by using viem's built-in decoding.

🤖 Prompt for AI Agents
In dashboard/lib/transaction-service.ts around lines 53 to 61 (and the
subsequent manual parsing at ~67-69), you are manually slicing event.data to
extract amountSC and amountBC; instead, use viem's decoded event args provided
on the returned log object when parseAbiItem is used with proper typing. Replace
the manual hex-slicing logic with direct reads from event.args.amountSC and
event.args.amountBC (they are bigints) and use event.args.buyer and
event.args.receiver for addresses; ensure any downstream code converts the
bigints to the expected form (string/number) as needed.

Comment on lines +66 to 79
return allEvents.map(event => {
const rawData = event.data.slice(2);
const amountSCHex = '0x' + rawData.slice(0, 64);
const amountBCHex = '0x' + rawData.slice(64);

return {
buyer: this.formatAddress(event.topics[1]),
receiver: this.formatAddress(event.topics[2]),
amountSC: (parseInt(amountSCHex, 16) / 1000000).toString(), // Convert to SC
amountBC: formatUnits(BigInt(amountBCHex), 18), // Convert to ETH
amountSC: (parseInt(amountSCHex, 16) / 1000000).toString(),
amountBC: formatUnits(BigInt(amountBCHex), 18),
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
};
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential precision loss with parseInt for large amounts.

parseInt(amountSCHex, 16) converts to a JavaScript number, which loses precision for values exceeding Number.MAX_SAFE_INTEGER (~9 quadrillion). For blockchain amounts, use BigInt throughout:

-            amountSC: (parseInt(amountSCHex, 16) / 1000000).toString(),
+            amountSC: (BigInt(amountSCHex) / BigInt(1000000)).toString(),

Additionally, consider extracting 1000000 as a named constant (e.g., STABLECOIN_DECIMALS = 6) for clarity.

🤖 Prompt for AI Agents
In dashboard/lib/transaction-service.ts around lines 66 to 79, the code uses
parseInt(amountSCHex, 16) which converts large token amounts to a JS Number and
can lose precision; replace that with BigInt(amountSCHex) and format the
stablecoin value using decimals (e.g., use a helper like
formatUnits(BigInt(amountSCHex), STABLECOIN_DECIMALS) or compute a string by
dividing BigInt and inserting a decimal), and extract 1000000 into a named
constant STABLECOIN_DECIMALS = 6 at the top of the file; update the returned
amountSC to use the BigInt-based formatting to preserve precision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Transaction Filtering + Persistent Caching Logic

1 participant