Skip to content

Conversation

@bitstarkbridge
Copy link

@bitstarkbridge bitstarkbridge commented Jan 22, 2026

#43 close

Summary by CodeRabbit

Release Notes

  • New Features

    • System theme detection with dark/light mode support
    • Network switching capability to recover from errors
    • Recent contract events tab in transaction history view
    • Modal-based transaction and event history interface
  • Improvements

    • Enhanced error messages with network recommendations
    • Contract ID validation with clearer feedback
    • Improved mainnet network connection stability

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

- Add fetchEvents function using Stellar RPC getEvents
- Create EventTable component for displaying contract events
- Add Events tab to transaction history view
- Handle empty/error states gracefully
- Events limited to ~7 days RPC retention
- Network-aware (testnet/mainnet)
- No persistence or ingestion as required
- Calculate startLedger as latestLedger - 121000 (~7 days)
- Import jsonRpcCall from lib/rpc
- This fixes 'startLedger must be positive' error from RPC
- Replace custom theme logic with next-themes ThemeProvider
- Add ThemeProvider to layout with proper SSR configuration
- Update ThemeToggle to use useTheme hook with mounted state
- Add suppressHydrationWarning to html element
- Fixes server/client mismatch in theme state
- Make startLedger calculation more conservative (~3 days instead of 7)
- Add retry logic when startLedger is out of RPC retention range
- Gracefully fallback to no startLedger if range calculation fails
- Prevents 'startLedger must be within the ledger range' errors
- Make contract not found errors more user-friendly
- Add specific error handling for invalid contract IDs
- Improve event fetching error messages
- Handle RPC retention limit errors gracefully
- Better UX for network and validation errors
- Validate contract ID format before API calls
- Prevent invalid requests to RPC endpoints
- Show user-friendly error messages for invalid IDs
- Improve overall user experience and reduce API errors
@vercel
Copy link
Contributor

vercel bot commented Jan 22, 2026

@Emmanuelluxury is attempting to deploy a commit to the Trustless Work Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

This PR implements network-aware event history features with theme provider integration. Changes include adding next-themes support for system theme detection, introducing event-fetching capabilities alongside transactions, refactoring transaction history into a modal interface, enhancing error recovery via network switching, and updating data fetchers to accept network parameters with improved error handling.

Changes

Cohort / File(s) Summary
Theme Provider & System Detection
src/app/layout.tsx, src/components/ui/theme-toggle.tsx, src/custom.d.ts
Integrates next-themes for system theme detection and SSR-safe theme toggling; replaces localStorage/DOM class manipulation with useTheme hook; adds CSS module declaration support.
Network Switching & Error Recovery
src/app/page.tsx, src/components/escrow/error-display.tsx
Adds network switching UI action for "contract not found" errors; imports useNetwork hook to compute alternate network; introduces handleSwitchNetwork handler to recover from network mismatches.
Event History Feature (New Components)
src/components/escrow/EventTable.tsx, src/components/escrow/TransactionHistoryModal.tsx
Introduces EventTable for displaying contract events with mobile/desktop layouts, loading/error states, and load-more pagination; creates TransactionHistoryModal with dual-tab interface (Transactions + Recent Events) replacing inline history display.
Event Data Fetching & Types
src/utils/transactionFetcher.ts
Adds network-parameter support to fetchTransactions/fetchTransactionDetails; introduces fetchEvents function with RPC-based event retrieval; exports EventMetadata and EventResponse types for event data representation.
Escrow Data Fetching & Validation
src/utils/ledgerkeycontract.ts, src/hooks/useEscrowData.ts
Updates getLedgerKeyContractCode to return null on network/contract-not-found errors and adds contract ID format validation (C-prefix + 55 chars); enhances useEscrowData with network-aware error messages and early validation checks.
Transaction Detail & Modal Updates
src/components/escrow/TransactionDetailModal.tsx, src/components/escrow/EscrowDetails.tsx
Adds network parameter to TransactionDetailModal; refactors EscrowDetails to fetch both transactions and events concurrently, render TransactionHistoryModal for history, and pass network-aware error recovery handlers.
Network Configuration
src/lib/network-config.ts
Updates mainnet RPC URL from onfinality endpoint to https://soroban-mainnet.stellar.org.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰✨ A theme provider hops in, light and dark now dance,
Events burst forth like clover, networks switch per chance,
Modals bloom with history, errors find their way,
Hopping through the Soroban—a fetcher's finest day! 🌙

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Frontend' is vague and does not meaningfully describe the specific changes in the pull request, which include theme handling, event history features, network awareness, and error recovery. Use a more specific, descriptive title that summarizes the main changes, such as 'Add recent contract events feature with theme provider and network switching' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/escrow/EscrowDetails.tsx (1)

242-268: Avoid shipping DEBUG logging enabled

Line 242 hard-codes DEBUG = true, which will log escrow data in production. That can leak sensitive data and add noise. Gate this behind a config flag or disable by default.

🔧 Suggested quick fix
-const DEBUG = true;
+const DEBUG = false; // enable locally via config if needed
🤖 Fix all issues with AI agents
In `@src/components/escrow/EventTable.tsx`:
- Around line 109-110: The elements in the EventTable component that include the
className string containing "cursor-pointer" (the card-like divs with classes
starting "bg-white/50 ... cursor-pointer ..." and the similar dark-mode variant)
imply clickability but have no onClick handler; either remove "cursor-pointer"
from those className values or add a proper onClick prop (e.g., onRowClick or
onSelect) and corresponding handler function in the EventTable component to
implement the intended interaction; locate the offending className occurrences
in the EventTable component to update the class strings or wire up the click
handler.

In `@src/components/escrow/TransactionDetailModal.tsx`:
- Around line 60-68: The explorer links in TransactionDetailModal are still
using an env var and a hardcoded testnet URL and therefore can point to the
wrong network after switching; update the component to derive a single explorer
base URL from the current network value (the network prop/state used alongside
fetchTransactionDetails and txHash) — e.g., compute derivedExplorerBase =
getExplorerBaseForNetwork(network) or map network => base inside
TransactionDetailModal, then replace the two hardcoded usages for the explorer
buttons with this derivedExplorerBase (reference the transaction hash via
txHash) so both buttons consistently point to the selected network.

In `@src/components/ui/theme-toggle.tsx`:
- Around line 37-38: isDark is computed from resolvedTheme but toggle uses theme
(which can be "system"), causing incorrect flips when theme === "system"; change
toggle to base its decision on resolvedTheme (use isDark or resolvedTheme) so
setTheme sets "light" when resolvedTheme === "dark" and "dark" otherwise; update
the toggle function (and any references) to call setTheme(isDark ? "light" :
"dark") instead of using theme.

In `@src/custom.d.ts`:
- Around line 1-4: Remove the unused ambient module declaration "declare module
'*.css'" (the block that exports a default any) — delete that declaration (and
the file containing it if it only contains this declaration) and ensure no other
code imports CSS using that default-export pattern before committing.

In `@src/hooks/useEscrowData.ts`:
- Around line 18-30: The early return when contractId is falsy currently calls
setError but doesn't clear previous data; update the branch that checks if
(!contractId) to also clear stale state by calling setRaw(null),
setOrganized(null) and setLoading(false) before returning so previous escrow
data isn't shown while the error is displayed; modify the same hook where
contractId is validated (useEscrowData, and the setError call) to include these
state resets.

In `@src/utils/transactionFetcher.ts`:
- Around line 287-325: The code currently swallows connection errors in the
transaction fetch path (the data.error "Unable to connect" branch and the catch
block) and returns an empty EventResponse, which hides connectivity failures;
update the fetch function (the async fetch logic that builds EventMetadata and
returns {events, latestLedger, cursor, hasMore}) to propagate errors instead of
returning empty results: replace the data.error "Unable to connect" early-return
with throwing an Error (or rethrowing the original error) and in the catch block
rethrow the caught error (or throw a new Error with context) so callers of this
transaction fetch routine receive an exception (or alternatively modify the
EventResponse contract to include an error field and populate it) — change the
branches around data.error and the catch to throw rather than returning empty
events to allow the UI to surface real errors.
🧹 Nitpick comments (3)
src/utils/ledgerkeycontract.ts (2)

50-86: Mixed error handling creates ambiguous return semantics.

The function now has multiple return paths with different meanings:

  • null: network error, HTTP error, JSON parse error, RPC error, or contract not found
  • []: escrow entry exists but val.map is missing/not an array (line 114)
  • EscrowMap: success with data
  • Thrown error: missing contract instance, storage, or escrow data

This makes it difficult for consumers to distinguish between "contract not found on this network" (recoverable) vs "network failure" (retry-able) vs "malformed data" (bug).

Consider returning a discriminated result type or at minimum, differentiating "not found" from "error":

// Option: Use a result type
type FetchResult = 
  | { status: 'success'; data: EscrowMap }
  | { status: 'not_found' }
  | { status: 'error'; message: string };

98-98: Remove debug logging before merge.

This debug log will output raw contract storage data to the console in production, which could leak sensitive information and pollute logs.

-    console.log("[DEBUG] Raw contract storage:", JSON.stringify(storage, null, 2));
src/components/escrow/error-display.tsx (1)

12-12: Fragile error detection via string matching.

The isContractNotFound check relies on exact substrings in the error message. If error messages are refactored elsewhere, this detection will silently break.

Consider passing an explicit flag or using an error type/code:

 interface ErrorDisplayProps {
   error: string | null
+  isRecoverableByNetworkSwitch?: boolean
   onSwitchNetwork?: () => void
   switchNetworkLabel?: string
 }

-  const isContractNotFound = error?.includes("Contract not found") && error?.includes("Try switching to")
+  // Use explicit flag passed by parent instead of string parsing

This decouples the display logic from error message formatting.

Comment on lines +109 to +110
className="bg-white/50 backdrop-blur-sm border border-gray-200 rounded-xl p-4 hover:shadow-md hover:bg-white/80 cursor-pointer transition-all duration-200 dark:bg-[#070708] dark:border-[rgba(255,255,255,0.04)] dark:hover:bg-[#0b1115]"
>
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

Remove pointer cursor or add click behavior

Line 109 and Line 187 set cursor-pointer but there’s no click handler, which implies interactivity that doesn’t exist. Remove the cursor style or wire up an onClick.

🔧 Suggested tweak (remove misleading cursor)
-                    className="bg-white/50 backdrop-blur-sm border border-gray-200 rounded-xl p-4 hover:shadow-md hover:bg-white/80 cursor-pointer transition-all duration-200 dark:bg-[`#070708`] dark:border-[rgba(255,255,255,0.04)] dark:hover:bg-[`#0b1115`]"
+                    className="bg-white/50 backdrop-blur-sm border border-gray-200 rounded-xl p-4 hover:shadow-md hover:bg-white/80 transition-all duration-200 dark:bg-[`#070708`] dark:border-[rgba(255,255,255,0.04)] dark:hover:bg-[`#0b1115`]"
-                          className="hover:bg-purple-50/50 cursor-pointer transition-all duration-200 group dark:hover:bg-[`#081014`]/40"
+                          className="hover:bg-purple-50/50 transition-all duration-200 group dark:hover:bg-[`#081014`]/40"

Also applies to: 187-188

🤖 Prompt for AI Agents
In `@src/components/escrow/EventTable.tsx` around lines 109 - 110, The elements in
the EventTable component that include the className string containing
"cursor-pointer" (the card-like divs with classes starting "bg-white/50 ...
cursor-pointer ..." and the similar dark-mode variant) imply clickability but
have no onClick handler; either remove "cursor-pointer" from those className
values or add a proper onClick prop (e.g., onRowClick or onSelect) and
corresponding handler function in the EventTable component to implement the
intended interaction; locate the offending className occurrences in the
EventTable component to update the class strings or wire up the click handler.

Comment on lines +60 to +68
const transactionDetails = await fetchTransactionDetails(txHash, network);
setDetails(transactionDetails);
} catch (err) {
setError("Failed to fetch transaction details");
console.error("Error fetching transaction details:", err);
} finally {
setLoading(false);
}
}, [txHash]);
}, [txHash, network]);
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

Align explorer links with the selected network

Line 60 makes the fetch network-aware, but the explorer buttons still use an env var and a hardcoded testnet URL (Line 163 and Line 171). That can send users to the wrong explorer after a network switch. Consider deriving a single explorer network from network (or config) and reuse it for both buttons.

🔧 Suggested tweak (removes the hardcoded testnet URL)
-                        onClick={() => window.open(`https://stellar.expert/explorer/testnet/tx/${details.txHash}`, "_blank")}
+                        onClick={() => window.open(`https://stellar.expert/explorer/${process.env.NEXT_PUBLIC_STELLAR_NETWORK || "testnet"}/tx/${details.txHash}`, "_blank")}
🤖 Prompt for AI Agents
In `@src/components/escrow/TransactionDetailModal.tsx` around lines 60 - 68, The
explorer links in TransactionDetailModal are still using an env var and a
hardcoded testnet URL and therefore can point to the wrong network after
switching; update the component to derive a single explorer base URL from the
current network value (the network prop/state used alongside
fetchTransactionDetails and txHash) — e.g., compute derivedExplorerBase =
getExplorerBaseForNetwork(network) or map network => base inside
TransactionDetailModal, then replace the two hardcoded usages for the explorer
buttons with this derivedExplorerBase (reference the transaction hash via
txHash) so both buttons consistently point to the selected network.

Comment on lines +37 to +38
const isDark = resolvedTheme === "dark";
const toggle = () => setTheme(theme === "dark" ? "light" : "dark");
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

Inconsistent theme check between display and toggle logic.

isDark uses resolvedTheme (the actual resolved value), but toggle uses theme which can be "system". When theme is "system" and resolved to dark, clicking toggle will set "light" (correct), but if resolved to light, it will also set "light" (incorrect - should set "dark").

Proposed fix
  const isDark = resolvedTheme === "dark";
- const toggle = () => setTheme(theme === "dark" ? "light" : "dark");
+ const toggle = () => setTheme(isDark ? "light" : "dark");
📝 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
const isDark = resolvedTheme === "dark";
const toggle = () => setTheme(theme === "dark" ? "light" : "dark");
const isDark = resolvedTheme === "dark";
const toggle = () => setTheme(isDark ? "light" : "dark");
🤖 Prompt for AI Agents
In `@src/components/ui/theme-toggle.tsx` around lines 37 - 38, isDark is computed
from resolvedTheme but toggle uses theme (which can be "system"), causing
incorrect flips when theme === "system"; change toggle to base its decision on
resolvedTheme (use isDark or resolvedTheme) so setTheme sets "light" when
resolvedTheme === "dark" and "dark" otherwise; update the toggle function (and
any references) to call setTheme(isDark ? "light" : "dark") instead of using
theme.

Comment on lines +1 to +4
declare module '*.css' {
const content: any;
export default content;
} No newline at end of file
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how CSS files are actually imported in the codebase
echo "=== CSS import patterns in the codebase ==="
rg -n "import .* from ['\"].*\.css['\"]" --type ts --type tsx -g '!*.d.ts' || echo "No default CSS imports found"

echo ""
echo "=== Side-effect CSS imports ==="
rg -n "^import ['\"].*\.css['\"]" --type ts --type tsx -g '!*.d.ts' || echo "No side-effect CSS imports found"

echo ""
echo "=== Check if Next.js config exists ==="
fd -e js -e ts -e mjs "next.config" --exec cat {}

echo ""
echo "=== Check for CSS module imports ==="
rg -n "import .* from ['\"].*\.module\.css['\"]" --type ts --type tsx -g '!*.d.ts' || echo "No CSS module imports found"

echo ""
echo "=== List files in src directory to understand structure ==="
fd . src --max-depth 2 --type f

Repository: Trustless-Work/escrow-viewer

Length of output: 1119


Remove this unused declaration—no CSS is imported using this pattern in the codebase.

The ambient module declaration for CSS default exports is not used anywhere. While src/app/globals.css exists, it's handled by Next.js without requiring this declaration. Removing this file will clean up unused code.

🤖 Prompt for AI Agents
In `@src/custom.d.ts` around lines 1 - 4, Remove the unused ambient module
declaration "declare module '*.css'" (the block that exports a default any) —
delete that declaration (and the file containing it if it only contains this
declaration) and ensure no other code imports CSS using that default-export
pattern before committing.

Comment on lines +18 to +30
if (!contractId) {
setError("Please enter a contract ID");
return;
}

// Basic validation for contract ID format
if (!/^C[A-Z0-9]{55}$/.test(contractId)) {
setError("Invalid contract ID format. Contract IDs should start with 'C' followed by 55 alphanumeric characters.");
setRaw(null);
setOrganized(null);
setLoading(false);
return;
}
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

Clear stale escrow data when contractId is empty

Line 18 returns early without clearing raw/organized, so stale data can remain visible while the error is shown. Consider clearing state and resetting loading before returning.

🔧 Suggested fix
   if (!contractId) {
     setError("Please enter a contract ID");
+    setRaw(null);
+    setOrganized(null);
+    setLoading(false);
     return;
   }
📝 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
if (!contractId) {
setError("Please enter a contract ID");
return;
}
// Basic validation for contract ID format
if (!/^C[A-Z0-9]{55}$/.test(contractId)) {
setError("Invalid contract ID format. Contract IDs should start with 'C' followed by 55 alphanumeric characters.");
setRaw(null);
setOrganized(null);
setLoading(false);
return;
}
if (!contractId) {
setError("Please enter a contract ID");
setRaw(null);
setOrganized(null);
setLoading(false);
return;
}
// Basic validation for contract ID format
if (!/^C[A-Z0-9]{55}$/.test(contractId)) {
setError("Invalid contract ID format. Contract IDs should start with 'C' followed by 55 alphanumeric characters.");
setRaw(null);
setOrganized(null);
setLoading(false);
return;
}
🤖 Prompt for AI Agents
In `@src/hooks/useEscrowData.ts` around lines 18 - 30, The early return when
contractId is falsy currently calls setError but doesn't clear previous data;
update the branch that checks if (!contractId) to also clear stale state by
calling setRaw(null), setOrganized(null) and setLoading(false) before returning
so previous escrow data isn't shown while the error is displayed; modify the
same hook where contractId is validated (useEscrowData, and the setError call)
to include these state resets.

Comment on lines +287 to +325
if (data.error) {
if (data.error.message?.includes("Unable to connect")) {
return {
events: [],
latestLedger: 0,
cursor: undefined,
hasMore: false
};
}
throw new Error(data.error.message || "Failed to fetch events");
}

const result = data.result;
const events: EventMetadata[] = (result.events || []).map((event: any) => ({
id: event.id,
type: event.type,
ledger: event.ledger,
contractId: event.contractId,
topics: event.topics || [],
value: event.value || "",
inSuccessfulContractCall: event.inSuccessfulContractCall
}));

return {
events,
latestLedger: result.latestLedger || 0,
cursor: result.cursor,
hasMore: !!result.cursor
};

} catch (error) {
console.error("Error fetching events:", error);

// Return graceful error response
return {
events: [],
latestLedger: 0,
hasMore: false
};
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

Surface event connectivity failures instead of silently returning empty results

Line 287–325 returns an empty EventResponse on connection errors, which makes the UI show “no events” instead of an error. Prefer bubbling the error (throw or add an error field) so callers can display a real error state.

🔧 Suggested fix (propagate errors)
-    if (data.error) {
-      if (data.error.message?.includes("Unable to connect")) {
-        return {
-          events: [],
-          latestLedger: 0,
-          cursor: undefined,
-          hasMore: false
-        };
-      }
-      throw new Error(data.error.message || "Failed to fetch events");
-    }
+    if (data.error) {
+      throw new Error(data.error.message || "Failed to fetch events");
+    }
@@
-  } catch (error) {
-    console.error("Error fetching events:", error);
-
-    // Return graceful error response
-    return {
-      events: [],
-      latestLedger: 0,
-      hasMore: false
-    };
-  }
+  } catch (error) {
+    console.error("Error fetching events:", error);
+    throw error;
+  }
🤖 Prompt for AI Agents
In `@src/utils/transactionFetcher.ts` around lines 287 - 325, The code currently
swallows connection errors in the transaction fetch path (the data.error "Unable
to connect" branch and the catch block) and returns an empty EventResponse,
which hides connectivity failures; update the fetch function (the async fetch
logic that builds EventMetadata and returns {events, latestLedger, cursor,
hasMore}) to propagate errors instead of returning empty results: replace the
data.error "Unable to connect" early-return with throwing an Error (or
rethrowing the original error) and in the catch block rethrow the caught error
(or throw a new Error with context) so callers of this transaction fetch routine
receive an exception (or alternatively modify the EventResponse contract to
include an error field and populate it) — change the branches around data.error
and the catch to throw rather than returning empty events to allow the UI to
surface real errors.

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.

2 participants