Skip to content

Comments

Frontend#46

Open
bitstarkbridge wants to merge 8 commits intoTrustless-Work:mainfrom
bitstarkbridge:Frontend
Open

Frontend#46
bitstarkbridge wants to merge 8 commits intoTrustless-Work:mainfrom
bitstarkbridge:Frontend

Conversation

@bitstarkbridge
Copy link

@bitstarkbridge bitstarkbridge commented Jan 22, 2026

#43 close

Summary by CodeRabbit

  • New Features

    • System theme handling and accessible theme toggle
    • Network switching for error recovery
    • Recent contract events tab
    • Modal-based transaction & event history with detail view
  • Improvements

    • Clearer, network-aware error messages and recovery actions
    • Contract ID validation with targeted feedback
    • Network-aware data loading and refresh when network changes
    • Paginated event loading with empty/error states handled gracefully

- 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

Adds network-aware fetching and event history UI, integrates next-themes into theme handling, introduces network-switch error recovery, and refactors escrow transaction/event display into modal-driven flows with expanded error handling and input validation.

Changes

Cohort / File(s) Summary
Layout & Theme
src/app/layout.tsx, src/components/ui/theme-toggle.tsx, src/custom.d.ts
Adds suppressHydrationWarning on <html>, imports ThemeProvider/useTheme from next-themes, replaces manual theme logic with useTheme, and adds CSS module declaration.
Page / Network Context
src/app/page.tsx
Adds network-switching support to Home for error recovery via useNetwork and getNetworkConfig; removes previous hero inline history UI.
Escrow Details & Error Handling
src/components/escrow/EscrowDetails.tsx, src/components/escrow/error-display.tsx
Makes EscrowDetails network-aware (fetches events + transactions), surfaces network-switch action in ErrorDisplay, and wires error-driven network switching and route refresh.
Event UI Components
src/components/escrow/EventTable.tsx, src/components/escrow/TransactionHistoryModal.tsx
Adds EventTable (mobile/desktop, loading/error/empty states, pagination) and TransactionHistoryModal (tabs for Transactions/Events, responsive, callbacks).
Transaction Detail Modal
src/components/escrow/TransactionDetailModal.tsx
Added network prop and updated data fetch to fetchTransactionDetails(txHash, network) with dependency on network.
Fetchers & Types
src/utils/transactionFetcher.ts
Makes fetchers network-aware via getNetworkConfig(network), adds fetchEvents and EventMetadata/EventResponse types, and improves RPC/error resilience and validation.
Ledger-key & Escrow Hook
src/utils/ledgerkeycontract.ts, src/hooks/useEscrowData.ts
Removes network param from getLedgerKeyContractCode and return null on fetch issues; adds contract ID format validation and network-aware error messages in useEscrowData (default isMobile).
Network Config
src/lib/network-config.ts
Removed mainnet config fields (mainnet now an empty object); testnet unchanged.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Page
  participant NetworkContext
  participant Fetchers
  participant UI_Modals

  User->>Page: open escrow page (contractId)
  Page->>NetworkContext: read currentNetwork
  Page->>Fetchers: fetchTransactions(contractId, currentNetwork)
  Page->>Fetchers: fetchEvents(contractId, currentNetwork)
  Fetchers-->>Page: transactions + events (or error)
  Page->>UI_Modals: render TransactionHistoryModal with data
  alt RPC / Contract Not Found Error
    UI_Modals->>Page: show ErrorDisplay (isContractNotFound)
    User->>UI_Modals: click "Switch Network"
    UI_Modals->>NetworkContext: setNetwork(otherNetwork)
    NetworkContext-->>Page: network updated
    Page->>Fetchers: re-fetch transactions/events with new network
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • techrebelgit

Poem

🐇✨ I dug a tunnel through the code tonight,
Networks swapped like carrots in moonlight,
Events and modals hop into view,
Themes settle softly — light or dark, it's true,
Hooray! — a rabbit's small deploy delight.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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 pull request title 'Frontend' is vague and non-descriptive, failing to convey meaningful information about the changeset's main objectives or key features. Consider revising the title to reflect the main changes, such as: 'Add Recent Contract Events feature and improve theme hydration' or 'Implement event fetching with network-aware error handling and theme provider integration'.
✅ 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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

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

Caution

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

⚠️ Outside diff range comments (6)
src/app/layout.tsx (1)

37-42: ⚠️ Potential issue | 🟠 Major

metadataBase is hardcoded to localhost — broken in production

new URL("https://localhost:3000") will be used to resolve relative OG/canonical URLs in production, producing https://localhost:3000/... links in social previews and SEO metadata. This should come from an environment variable.

🐛 Proposed fix
-  metadataBase: new URL("https://localhost:3000"),
+  metadataBase: new URL(
+    process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
+  ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` around lines 37 - 42, The metadata object hardcodes
metadataBase via new URL("https://localhost:3000"), causing wrong OG/canonical
links in production; change metadata.metadataBase to construct the URL from an
environment variable (e.g. process.env.NEXT_PUBLIC_SITE_URL or
NEXT_PUBLIC_BASE_URL) and validate/parse it with new URL(...) (with a sensible
fallback or throw when missing) so production builds resolve relative URLs
correctly; update the metadata export (symbol: metadata and property:
metadataBase) to use the env var instead of the hardcoded localhost.
src/components/escrow/EscrowDetails.tsx (2)

50-50: 🛠️ Refactor suggestion | 🟠 Major

useNetwork() is called twice — consolidate into one call.

Line 50 destructures { currentNetwork } and Line 72 destructures { setNetwork }. Two calls to the same hook are redundant and can diverge if the context implementation ever memoizes per-call. Destructure both from one call.

Proposed fix
-  const { currentNetwork } = useNetwork();
+  const { currentNetwork, setNetwork } = useNetwork();
   // ... (other state declarations)

-  // Network switching for error recovery
-  const { setNetwork } = useNetwork();

Also applies to: 71-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/escrow/EscrowDetails.tsx` at line 50, The component calls
useNetwork() twice (once to get currentNetwork and again to get setNetwork);
consolidate into a single hook call by destructuring both currentNetwork and
setNetwork from the same useNetwork() invocation (replace the two separate lines
with one const { currentNetwork, setNetwork } = useNetwork()), and update any
downstream references in EscrowDetails to use these variables so there is only
one hook call.

103-122: ⚠️ Potential issue | 🔴 Critical

Stale closure: fetchTransactionData captures currentNetwork but has no dependency array.

useCallback at Line 103 closes over currentNetwork (used at Line 109), but the dependency array is missing — the callback is created once on mount. After a network switch, fetchTransactionData still uses the original currentNetwork value, so transactions are fetched from the wrong network.

Proposed fix: add dependency array
       } finally {
         setTransactionLoading(false);
       }
     },
-  );
+    [currentNetwork],
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/escrow/EscrowDetails.tsx` around lines 103 - 122, The
fetchTransactionData callback closes over currentNetwork but has no dependency
array, causing it to keep using the initial network after a switch; update the
useCallback for fetchTransactionData to include currentNetwork (and any
non-stable helpers like fetchTransactions) in its dependency array so the
callback is recreated when the network (or fetchTransactions) changes, ensuring
the call at setTransactionResponse/fetchTransactions uses the latest network.
src/app/page.tsx (2)

62-67: ⚠️ Potential issue | 🔴 Critical

Broken JSX: unclosed <section> tag and empty hero content.

The <section> at Line 65 is opened but never closed — the next tags are </div> closers for parent elements. This produces invalid JSX and will likely fail at build time or cause hydration errors. Additionally, the hero section renders no visible content.

It appears the hero content (SearchCard, ErrorDisplay, imagery) was removed but the containing structure wasn't cleaned up. Either restore the intended content or remove the empty skeleton entirely.

Proposed fix: remove the empty broken section
         <main className="w-full sm:w-7xl container mx-auto px-4 py-12">
-          {/* HERO */}
-          <div className="relative">
-            <div className="relative bg-white/95 dark:bg-card rounded-3xl p-8 md:p-12 shadow-2xl border border-gray-200/60 dark:border-border">
-              <section className="flex flex-col-reverse md:flex-row items-center gap-8 md:gap-16">
-            </div>
-          </div>
+          {/* TODO: Restore hero content */}
         </main>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/page.tsx` around lines 62 - 67, In src/app/page.tsx the HERO block
contains an unclosed and empty <section> (inside the relative bg-white/...
container) which breaks JSX; either remove the empty <section> element entirely
or restore the intended children (e.g., SearchCard, ErrorDisplay, imagery
components) and properly close the <section> tag so that the parent <div>
closers match; ensure the section is balanced (open/close) and contains the
expected content or is removed to avoid invalid JSX/hydration errors.

3-14: 🛠️ Refactor suggestion | 🟠 Major

Numerous unused imports and dead code after content removal.

With the hero content removed, the following are now unused:

  • Imports: useState, motion, Image, SearchCard, ErrorDisplay, EXAMPLE_CONTRACT_IDS, useRouter
  • State/variables: contractId, error, isSearchFocused, router
  • Functions: handleNavigate, handleKeyDown, handleUseExample, handleSwitchNetwork, otherNetwork, switchNetworkLabel

If this page is intentionally being gutted, clean up the dead code. Otherwise, restore the intended UI.

Also applies to: 22-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/page.tsx` around lines 3 - 14, The page still contains many
now-unused imports, state variables and functions after the hero content was
removed; either remove them or restore the UI that uses them: delete the unused
imports (useState, motion, Image, SearchCard, ErrorDisplay,
EXAMPLE_CONTRACT_IDS, useRouter), remove the unused state/variables (contractId,
error, isSearchFocused, router) and the unused functions (handleNavigate,
handleKeyDown, handleUseExample, handleSwitchNetwork) plus network helpers
(otherNetwork, switchNetworkLabel), or reintroduce the corresponding JSX and
logic that references these symbols (e.g., re-add the hero/SearchCard, error
display, example contract usage, and router navigation) so there are no dead
references left in page.tsx.
src/utils/ledgerkeycontract.ts (1)

31-33: ⚠️ Potential issue | 🔴 Critical

Critical: Syntax error in function signature — missing closing parenthesis, return type, and network parameter.

The function declaration is broken at lines 31–33:

export async function getLedgerKeyContractCode(
  contractId: string,
  try {

The parameter list is incomplete (try { cannot appear in a parameter list), and the function is called with two arguments (line 37 in useEscrowData.ts: getLedgerKeyContractCode(contractId, network)), but network is missing from the signature. Line 47 then uses getNetworkConfig(network), which will fail.

Additionally, remove the debug logging at lines 95–99. The console.log with full JSON stringification of contract storage is too verbose for production.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ledgerkeycontract.ts` around lines 31 - 33, The function signature
for getLedgerKeyContractCode is malformed and missing the network parameter and
return type; change its declaration to export async function
getLedgerKeyContractCode(contractId: string, network: string): Promise<string> {
and move the try { ... } block inside the function body (ensuring you have a
matching catch/throw and return a string), update any internal uses to call
getNetworkConfig(network) correctly, and ensure errors are propagated or logged
via the catch. Also remove the verbose debug console.log that JSON.stringify's
contract storage (the block around the debug logging) to avoid leaking large
production data. Ensure there are no leftover syntax errors after these edits
and that getLedgerKeyContractCode is callable as
getLedgerKeyContractCode(contractId, network).
🧹 Nitpick comments (4)
src/components/escrow/EscrowDetails.tsx (1)

184-194: Remove hardcoded DEBUG = true before merging.

const DEBUG = true enables verbose console logging of live balance details in production. Gate behind an environment variable or remove entirely.

Proposed fix
-  const DEBUG = true;
-
-  useEffect(() => {
-    if (!DEBUG) return;
-    console.log("[DBG][EscrowDetails] token live balance:", {
-      ledgerBalance,
-      decimals,
-      mismatch,
-    });
-  }, [ledgerBalance, decimals, mismatch]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/escrow/EscrowDetails.tsx` around lines 184 - 194, The file
contains a hardcoded debug flag (const DEBUG = true) in the EscrowDetails
component that enables console logging inside the useEffect; replace this by
removing the hardcoded flag or gating it behind a runtime config/env var (e.g.,
process.env.NODE_ENV !== 'production' or a dedicated REACT_APP_DEBUG_ESCROW) and
update the useEffect conditional so the console.log of ledgerBalance, decimals,
mismatch only runs when the env-driven flag is truthy; locate the DEBUG
declaration and the useEffect in EscrowDetails and adjust both accordingly.
src/utils/ledgerkeycontract.ts (1)

96-99: Remove or gate verbose debug logging before merging.

console.log("[DEBUG] Raw contract storage:", JSON.stringify(storage, null, 2)) dumps the entire contract storage to the console in production. This can be noisy and may leak sensitive on-chain data structure details.

Proposed fix
-    console.log(
-      "[DEBUG] Raw contract storage:",
-      JSON.stringify(storage, null, 2),
-    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ledgerkeycontract.ts` around lines 96 - 99, Remove or gate the
verbose console.log that prints the entire contract storage: replace the
unconditional console.log("[DEBUG] Raw contract storage:",
JSON.stringify(storage, null, 2)) with a non-production-safe alternative —
either remove it entirely or wrap it behind a debug flag (e.g., check
process.env.DEBUG or process.env.NODE_ENV !== 'production') or route it to a
proper logger at debug level (e.g., processLogger.debug) so the storage variable
dump only occurs when explicitly enabled; update the statement in
src/utils/ledgerkeycontract.ts around the storage variable and the existing
console.log call accordingly.
src/components/escrow/error-display.tsx (2)

25-25: Hardcoded light-mode colors on the switch-network button.

bg-white hover:bg-gray-50 border-red-300 text-red-700 hover:text-red-800 won't adapt to dark mode. Consider adding dark variants or using semantic theme tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/escrow/error-display.tsx` at line 25, The switch-network
button is using hardcoded light-mode utility classes ("bg-white hover:bg-gray-50
border-red-300 text-red-700 hover:text-red-800") that won't adapt to dark mode;
update the className on the switch-network button in the ErrorDisplay component
to include dark: variants or use semantic theme tokens (e.g., add dark:bg-...,
dark:hover:bg-..., dark:border-..., dark:text-... or replace with design-system
token classes like bg-[token] text-[token] border-[token]) so the background,
border and text colors adapt in dark mode and on hover.

4-5: Fragile: button visibility depends on exact error message substrings.

isContractNotFound checks for "Contract not found" AND "Try switching to" in the error string. This tightly couples the detection logic to the exact wording in useEscrowData.ts (Line 42). If anyone tweaks that message, the switch-network button silently disappears.

Consider passing a structured flag (e.g., isContractNotFound?: boolean) as a prop instead of parsing the error string.

Proposed approach
 interface ErrorDisplayProps {
   error: string | null;
+  isContractNotFound?: boolean;
   onSwitchNetwork?: () => void;
   switchNetworkLabel?: string;
 }

-export const ErrorDisplay = ({ error, onSwitchNetwork, switchNetworkLabel }: ErrorDisplayProps) => {
-  const isContractNotFound = error?.includes("Contract not found") && error?.includes("Try switching to")
+export const ErrorDisplay = ({ error, isContractNotFound, onSwitchNetwork, switchNetworkLabel }: ErrorDisplayProps) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/escrow/error-display.tsx` around lines 4 - 5, The visibility
of the switch-network button is fragile because ErrorDisplay derives
isContractNotFound by parsing the error string; add an explicit boolean prop
(e.g., isContractNotFound?: boolean) to ErrorDisplay and use that prop instead
of parsing error in the component (replace the current isContractNotFound
computation). Update all call sites that render ErrorDisplay (the place that
consumes useEscrowData in your codebase) to pass the structured flag returned
from useEscrowData (or computed where the error originates) so detection is not
tied to exact error message text; keep the error prop for display but never rely
on string.includes inside ErrorDisplay.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/layout.tsx`:
- Line 6: ThemeProvider is imported but not rendered, so child useTheme() calls
won't work; wrap the existing <Suspense> ... {children} block with
<ThemeProvider> (so ThemeProvider is the parent of Suspense and the app
children) and pass any required props (e.g., defaultTheme or attribute) as
needed for your setup; ensure you remove the unused import warning by keeping
the import and placing ThemeProvider around the {children} subtree where
Suspense is mounted so theme context is available to components using
useTheme().
- Line 56: The <html> element currently sets suppressHydrationWarning
unconditionally; update layout.tsx to only add suppressHydrationWarning in
development by following the same pattern used for customBodyProps (i.e.,
compute dev-only props when process.env.NODE_ENV === 'development' or a shared
isDev flag) and spread those onto <html>, or factor customBodyProps and the html
dev prop into a shared devProps constant and spread devProps into both <html>
and <body> so the suppression is gated to dev only.
- Around line 61-62: The Suspense block in layout.tsx is empty and swallows all
page content; restore the JSX pass-through by placing the {children} inside the
Suspense (i.e., <Suspense fallback={...}>{children}</Suspense>) so routes render
again, and additionally wrap the children with the ThemeProvider component
(e.g., <ThemeProvider>{children}</ThemeProvider>) to preserve theming; update
the layout component where Suspense is used to nest ThemeProvider around
{children} (or vice versa per app conventions) and remove the empty Suspense
placeholder.
- Line 5: Remove the unused import of NetworkProvider from src/app/layout.tsx:
locate the import statement that references NetworkProvider and delete it (the
symbol to remove is NetworkProvider). Ensure no other references to
NetworkProvider remain in this file; run a quick search for NetworkProvider to
confirm it's not used before committing.

In `@src/components/escrow/EscrowDetails.tsx`:
- Around line 124-131: The component is calling a missing fetchEventData and
using undeclared event-related state used by TransactionHistoryModal; create
state variables analogous to the transaction state (e.g., events, eventLoading,
eventError, eventResponse, nextEventCursor) following the pattern used around
fetchTransactionData and the transaction state declarations, implement
fetchEventData(contractId, rpcUrl) that uses the imported fetchEvents to set
eventLoading, eventError, eventResponse, events and nextEventCursor, and add a
handleLoadMoreEvents function to page using nextEventCursor; ensure the effect
that calls fetchEventData (and other callers) use these names so the file
compiles.

In `@src/components/ui/theme-toggle.tsx`:
- Around line 3-37: The ThemeToggle component is broken: call useTheme() to get
{ theme, resolvedTheme, setTheme }, add a mounted state via useState(false) and
set it true in a useEffect (instead of returning JSX from inside useEffect),
remove the stray open useEffect that currently wraps JSX, and implement the
existing guard if (!mounted) at component level to return the placeholder
button; update toggle to use resolvedTheme/isDark (not theme) when calling
setTheme, and remove unused imports like safeLocalStorage if not needed. Target
symbols: ThemeToggle, useTheme, mounted, setMounted (useState), useEffect
(mounting effect), resolvedTheme, isDark, toggle, setTheme.

In `@src/lib/network-config.ts`:
- Around line 17-19: Replace the empty NETWORK_CONFIGS.mainnet entry with a full
mainnet config object matching the shape used by other networks (at minimum set
name, id/key, rpcUrl, networkPassphrase, and explorerUrl) so
getNetworkConfig('mainnet') returns defined values; update the
NETWORK_CONFIGS.mainnet object in src/lib/network-config.ts (the same keys used
by other entries) to include name: "Mainnet" (or equivalent display name used by
the UI), a valid rpcUrl, the correct networkPassphrase, and any other fields
referenced elsewhere (so encodeURIComponent(config.name),
getNetworkConfig(...).rpcUrl, and cfg.networkPassphrase are not undefined).

In `@src/utils/transactionFetcher.ts`:
- Around line 91-100: The current catch blocks in transactionFetcher.ts (in the
fetchTransactionsFromRPC function) always return a generic retentionNotice
"Unable to connect to RPC..." even for HTTP errors, JSON.parse failures and RPC
error responses; update the error handling to set context-specific messages: for
HTTP/network errors include "Network error contacting RPC" with the
error.message or status, for JSON parse failures use "Invalid response from RPC
(malformed JSON)" and for RPC response errors use the RPC-provided error message
or "RPC error" plus details; ensure the returned object (transactions,
latestLedger, oldestLedger, hasMore, retentionNotice) uses these specific
messages in each catch branch and preserve existing return shape.
- Around line 219-228: The guard comparing estimatedStartLedger to latestLedger
is always true and should be fixed: either remove the dead if and always set
params.startLedger = Math.max(1, estimatedStartLedger), or (preferred) query the
node for its actual oldest available ledger (via jsonRpcCall like a
getLedgerRange/getOldestLedger RPC) and then set params.startLedger =
Math.max(1, estimatedStartLedger, oldestLedger) so you avoid requesting ranges
older than the node supports; update the block around estimatedStartLedger,
latestLedger and params.startLedger (references: estimatedStartLedger,
latestLedger, params.startLedger, jsonRpcCall/getLatestLedger) accordingly.
- Around line 48-51: The function signature for fetchTransactions is malformed
(it currently contains an invalid token "in" and is missing the expected
parameters); update the signature of fetchTransactions(contractId: string,
network: NetworkType, options?: FetchOptions): Promise<TransactionResponse> (or
whatever concrete types are used elsewhere) to match its usage, ensuring you
import or reference the correct Network/Options types and update any callers if
necessary; fix the parameter order and optionality in the export declared in
fetchTransactions so the function compiles and aligns with all internal
references to network and options.

---

Outside diff comments:
In `@src/app/layout.tsx`:
- Around line 37-42: The metadata object hardcodes metadataBase via new
URL("https://localhost:3000"), causing wrong OG/canonical links in production;
change metadata.metadataBase to construct the URL from an environment variable
(e.g. process.env.NEXT_PUBLIC_SITE_URL or NEXT_PUBLIC_BASE_URL) and
validate/parse it with new URL(...) (with a sensible fallback or throw when
missing) so production builds resolve relative URLs correctly; update the
metadata export (symbol: metadata and property: metadataBase) to use the env var
instead of the hardcoded localhost.

In `@src/app/page.tsx`:
- Around line 62-67: In src/app/page.tsx the HERO block contains an unclosed and
empty <section> (inside the relative bg-white/... container) which breaks JSX;
either remove the empty <section> element entirely or restore the intended
children (e.g., SearchCard, ErrorDisplay, imagery components) and properly close
the <section> tag so that the parent <div> closers match; ensure the section is
balanced (open/close) and contains the expected content or is removed to avoid
invalid JSX/hydration errors.
- Around line 3-14: The page still contains many now-unused imports, state
variables and functions after the hero content was removed; either remove them
or restore the UI that uses them: delete the unused imports (useState, motion,
Image, SearchCard, ErrorDisplay, EXAMPLE_CONTRACT_IDS, useRouter), remove the
unused state/variables (contractId, error, isSearchFocused, router) and the
unused functions (handleNavigate, handleKeyDown, handleUseExample,
handleSwitchNetwork) plus network helpers (otherNetwork, switchNetworkLabel), or
reintroduce the corresponding JSX and logic that references these symbols (e.g.,
re-add the hero/SearchCard, error display, example contract usage, and router
navigation) so there are no dead references left in page.tsx.

In `@src/components/escrow/EscrowDetails.tsx`:
- Line 50: The component calls useNetwork() twice (once to get currentNetwork
and again to get setNetwork); consolidate into a single hook call by
destructuring both currentNetwork and setNetwork from the same useNetwork()
invocation (replace the two separate lines with one const { currentNetwork,
setNetwork } = useNetwork()), and update any downstream references in
EscrowDetails to use these variables so there is only one hook call.
- Around line 103-122: The fetchTransactionData callback closes over
currentNetwork but has no dependency array, causing it to keep using the initial
network after a switch; update the useCallback for fetchTransactionData to
include currentNetwork (and any non-stable helpers like fetchTransactions) in
its dependency array so the callback is recreated when the network (or
fetchTransactions) changes, ensuring the call at
setTransactionResponse/fetchTransactions uses the latest network.

In `@src/utils/ledgerkeycontract.ts`:
- Around line 31-33: The function signature for getLedgerKeyContractCode is
malformed and missing the network parameter and return type; change its
declaration to export async function getLedgerKeyContractCode(contractId:
string, network: string): Promise<string> { and move the try { ... } block
inside the function body (ensuring you have a matching catch/throw and return a
string), update any internal uses to call getNetworkConfig(network) correctly,
and ensure errors are propagated or logged via the catch. Also remove the
verbose debug console.log that JSON.stringify's contract storage (the block
around the debug logging) to avoid leaking large production data. Ensure there
are no leftover syntax errors after these edits and that
getLedgerKeyContractCode is callable as getLedgerKeyContractCode(contractId,
network).

---

Duplicate comments:
In `@src/components/escrow/TransactionDetailModal.tsx`:
- Around line 170-195: The explorer links in TransactionDetailModal incorrectly
use process.env.NEXT_PUBLIC_STELLAR_NETWORK and a hardcoded "testnet" instead of
the component's network prop, and the first Button shows a Copy icon but opens a
URL; update both Buttons to build the URL using the network prop (e.g.,
`${network}/tx/${details.txHash}`) and make the Copy-button actually copy that
URL to the clipboard (use navigator.clipboard.writeText with the constructed
URL) while keeping the ExternalLink-button to window.open the same URL in a new
tab; reference the TransactionDetailModal component, details.txHash, network
prop, the two Button elements, and the Copy and ExternalLink icons when locating
changes.

In `@src/components/ui/theme-toggle.tsx`:
- Around line 36-37: The toggle currently uses the raw theme variable which may
be "system" and flips incorrectly; update the toggle to use the resolvedTheme
via the isDark flag instead — replace the body of the toggle function so it
calls setTheme(isDark ? "light" : "dark") (referencing isDark, resolvedTheme,
toggle, setTheme, and theme to locate the code).

In `@src/hooks/useEscrowData.ts`:
- Around line 19-22: The hook's early-return when contractId is falsy only calls
setError but leaves previous escrow results in state; update the useEscrowData
logic so that when contractId is empty you also clear prior results by resetting
the raw and organized state (e.g., setRaw to an empty value and setOrganized to
its empty representation) and ensure loading is false and error is set — modify
the block that checks contractId to call setRaw(...) and setOrganized(...)
alongside setError(contractId missing) so stale data is not displayed.

In `@src/utils/transactionFetcher.ts`:
- Around line 275-313: The current error handling in transactionFetcher (the
block checking data.error and the outer catch that logs then returns an empty
EventResponse) is swallowing connectivity/RPC errors and returning empty
results; update the error flow so connectivity or RPC failures are surfaced
instead of returning an empty list: in the data.error branch (where you check
data.error.message.includes("Unable to connect")) throw or return a
failure-indicating response that includes the original error message (or an
error field) rather than silent empty events, and in the outer catch replace the
silent fallback with either rethrowing the error or returning an EventResponse
that contains an explicit error indicator (including the caught error) so
callers of the function can distinguish "no events" from "RPC/connectivity
failure" (refer to variables data.error, result, and the returned shape {
events, latestLedger, cursor, hasMore } in transactionFetcher.ts).

---

Nitpick comments:
In `@src/components/escrow/error-display.tsx`:
- Line 25: The switch-network button is using hardcoded light-mode utility
classes ("bg-white hover:bg-gray-50 border-red-300 text-red-700
hover:text-red-800") that won't adapt to dark mode; update the className on the
switch-network button in the ErrorDisplay component to include dark: variants or
use semantic theme tokens (e.g., add dark:bg-..., dark:hover:bg-...,
dark:border-..., dark:text-... or replace with design-system token classes like
bg-[token] text-[token] border-[token]) so the background, border and text
colors adapt in dark mode and on hover.
- Around line 4-5: The visibility of the switch-network button is fragile
because ErrorDisplay derives isContractNotFound by parsing the error string; add
an explicit boolean prop (e.g., isContractNotFound?: boolean) to ErrorDisplay
and use that prop instead of parsing error in the component (replace the current
isContractNotFound computation). Update all call sites that render ErrorDisplay
(the place that consumes useEscrowData in your codebase) to pass the structured
flag returned from useEscrowData (or computed where the error originates) so
detection is not tied to exact error message text; keep the error prop for
display but never rely on string.includes inside ErrorDisplay.

In `@src/components/escrow/EscrowDetails.tsx`:
- Around line 184-194: The file contains a hardcoded debug flag (const DEBUG =
true) in the EscrowDetails component that enables console logging inside the
useEffect; replace this by removing the hardcoded flag or gating it behind a
runtime config/env var (e.g., process.env.NODE_ENV !== 'production' or a
dedicated REACT_APP_DEBUG_ESCROW) and update the useEffect conditional so the
console.log of ledgerBalance, decimals, mismatch only runs when the env-driven
flag is truthy; locate the DEBUG declaration and the useEffect in EscrowDetails
and adjust both accordingly.

In `@src/utils/ledgerkeycontract.ts`:
- Around line 96-99: Remove or gate the verbose console.log that prints the
entire contract storage: replace the unconditional console.log("[DEBUG] Raw
contract storage:", JSON.stringify(storage, null, 2)) with a non-production-safe
alternative — either remove it entirely or wrap it behind a debug flag (e.g.,
check process.env.DEBUG or process.env.NODE_ENV !== 'production') or route it to
a proper logger at debug level (e.g., processLogger.debug) so the storage
variable dump only occurs when explicitly enabled; update the statement in
src/utils/ledgerkeycontract.ts around the storage variable and the existing
console.log call accordingly.

@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Suspense } from "react";
import { NetworkProvider } from "@/contexts/NetworkContext";
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:

# Check if NetworkProvider is used within src/app/layout.tsx
cat src/app/layout.tsx

Repository: Trustless-Work/escrow-viewer

Length of output: 1960


🏁 Script executed:

# Search for NetworkProvider usage outside of layout.tsx and NetworkContext definition
rg -n "NetworkProvider" --type ts --type tsx -g '!src/app/layout.tsx' -g '!src/contexts/NetworkContext*'

Repository: Trustless-Work/escrow-viewer

Length of output: 99


🏁 Script executed:

# Search for NetworkProvider usage excluding layout.tsx and NetworkContext definition
rg "NetworkProvider" --type-add 'web:*.{ts,tsx}' -t web -g '!src/app/layout.tsx' -g '!src/contexts/NetworkContext*'

Repository: Trustless-Work/escrow-viewer

Length of output: 54


🏁 Script executed:

# Alternative: simple grep-based search
rg "NetworkProvider" -g '!src/app/layout.tsx' -g '!src/contexts/NetworkContext*'

Repository: Trustless-Work/escrow-viewer

Length of output: 54


Remove the unused NetworkProvider import

NetworkProvider is imported on line 5 but not used anywhere in this file or elsewhere in the codebase. Remove this dead code import.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` at line 5, Remove the unused import of NetworkProvider
from src/app/layout.tsx: locate the import statement that references
NetworkProvider and delete it (the symbol to remove is NetworkProvider). Ensure
no other references to NetworkProvider remain in this file; run a quick search
for NetworkProvider to confirm it's not used before committing.

import "./globals.css";
import { Suspense } from "react";
import { NetworkProvider } from "@/contexts/NetworkContext";
import { ThemeProvider } from "next-themes";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

ThemeProvider is imported but never rendered — themes are non-functional

Despite the stated goal of resolving theme hydration by adopting next-themes, ThemeProvider is never placed in the JSX tree. Without it wrapping the app, useTheme() calls in child components will fall back to defaults and theme switching will have no effect. See the proposed fix in the {children} comment above — ThemeProvider should wrap the Suspense block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` at line 6, ThemeProvider is imported but not rendered, so
child useTheme() calls won't work; wrap the existing <Suspense> ... {children}
block with <ThemeProvider> (so ThemeProvider is the parent of Suspense and the
app children) and pass any required props (e.g., defaultTheme or attribute) as
needed for your setup; ensure you remove the unused import warning by keeping
the import and placing ThemeProvider around the {children} subtree where
Suspense is mounted so theme context is available to components using
useTheme().

}) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
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

suppressHydrationWarning on <html> fires unconditionally in production

The customBodyProps pattern on <body> (lines 45–48) correctly gates the warning suppression to development only. The new suppressHydrationWarning on <html> has no such guard, so real hydration mismatches on the root element will be silently swallowed in production.

🛡️ Proposed fix — mirror the dev-only guard from customBodyProps
-    <html lang="en" suppressHydrationWarning>
+    <html
+      lang="en"
+      {...(process.env.NODE_ENV === "development"
+        ? { suppressHydrationWarning: true }
+        : {})}
+    >

Alternatively, consolidate both customBodyProps and this new guard into a shared devProps constant.

📝 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
<html lang="en" suppressHydrationWarning>
<html
lang="en"
{...(process.env.NODE_ENV === "development"
? { suppressHydrationWarning: true }
: {})}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` at line 56, The <html> element currently sets
suppressHydrationWarning unconditionally; update layout.tsx to only add
suppressHydrationWarning in development by following the same pattern used for
customBodyProps (i.e., compute dev-only props when process.env.NODE_ENV ===
'development' or a shared isDev flag) and spread those onto <html>, or factor
customBodyProps and the html dev prop into a shared devProps constant and spread
devProps into both <html> and <body> so the suppression is gated to dev only.

Comment on lines 61 to 62
<Suspense fallback={<div>Loading...</div>}>
<NetworkProvider>{children}</NetworkProvider>
</Suspense>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

{children} is missing — the entire app renders blank

The <Suspense> block has no children. Every page/route's content is silently swallowed; the rendered HTML body will be empty. This is an app-breaking regression introduced when NetworkProvider was removed without preserving the {children} pass-through.

🐛 Proposed fix — restore children and wrap with ThemeProvider
-        <Suspense fallback={<div>Loading...</div>}>
-        </Suspense>
+        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
+          <Suspense fallback={<div>Loading...</div>}>
+            {children}
+          </Suspense>
+        </ThemeProvider>
📝 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
<Suspense fallback={<div>Loading...</div>}>
<NetworkProvider>{children}</NetworkProvider>
</Suspense>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Suspense fallback={<div>Loading...</div>}>
{children}
</Suspense>
</ThemeProvider>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` around lines 61 - 62, The Suspense block in layout.tsx is
empty and swallows all page content; restore the JSX pass-through by placing the
{children} inside the Suspense (i.e., <Suspense
fallback={...}>{children}</Suspense>) so routes render again, and additionally
wrap the children with the ThemeProvider component (e.g.,
<ThemeProvider>{children}</ThemeProvider>) to preserve theming; update the
layout component where Suspense is used to nest ThemeProvider around {children}
(or vice versa per app conventions) and remove the empty Suspense placeholder.

Comment on lines +124 to +131
// Initial + network-change fetch (escrow + txs + events)
useEffect(() => {
if (!contractId) return;
// useEscrowData auto-refreshes on contractId change; just ensure txs loaded:
// useEscrowData auto-refreshes on contractId change; just ensure txs and events loaded:
fetchTransactionData(contractId);
}, [contractId, currentNetwork, fetchTransactionData]);
const { rpcUrl } = getNetworkConfig(currentNetwork);
fetchEventData(contractId, rpcUrl);
}, [contractId, currentNetwork, fetchTransactionData, fetchEventData]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd 'EscrowDetails.tsx' --type f --exec cat -n {} | head -300

Repository: Trustless-Work/escrow-viewer

Length of output: 11848


🏁 Script executed:

rg -n 'fetchEventData|const \[events|isHistoryModalOpen|eventLoading|eventError|eventResponse|handleLoadMoreEvents' src/components/escrow/EscrowDetails.tsx

Repository: Trustless-Work/escrow-viewer

Length of output: 513


🏁 Script executed:

rg -n 'fetchEventData|const fetchEventData|function fetchEventData' --type ts --type tsx

Repository: Trustless-Work/escrow-viewer

Length of output: 99


🏁 Script executed:

rg -n 'useEventData|useEvents' --type ts --type tsx

Repository: Trustless-Work/escrow-viewer

Length of output: 99


🏁 Script executed:

rg -n 'fetchEventData|const fetchEventData|function fetchEventData'

Repository: Trustless-Work/escrow-viewer

Length of output: 443


🏁 Script executed:

rg -n 'useEventData|useEvents'

Repository: Trustless-Work/escrow-viewer

Length of output: 54


🏁 Script executed:

rg -n 'const \[events|const \[eventLoading|const \[eventError|const \[eventResponse|const \[isHistoryModalOpen|const handleLoadMoreEvents'

Repository: Trustless-Work/escrow-viewer

Length of output: 54


🏁 Script executed:

rg -n 'events|isHistoryModalOpen|eventLoading|eventError|eventResponse|handleLoadMoreEvents' src/components/escrow/EscrowDetails.tsx | head -20

Repository: Trustless-Work/escrow-viewer

Length of output: 494


🏁 Script executed:

head -40 src/components/escrow/EscrowDetails.tsx | cat -n

Repository: Trustless-Work/escrow-viewer

Length of output: 2148


🏁 Script executed:

rg -n 'useState.*event' src/components/escrow/EscrowDetails.tsx

Repository: Trustless-Work/escrow-viewer

Length of output: 54


Add missing fetchEventData function and event-related state variables.

The component calls fetchEventData on lines 130, 143, and 163, but this function is never declared. Additionally, the TransactionHistoryModal on lines 262–277 requires events, eventLoading, eventError, eventResponse, handleLoadMoreEvents, and isHistoryModalOpen—none of which are declared in this file. While fetchEvents is imported on line 26, you need to create fetchEventData (similar to the fetchTransactionData pattern on lines 103–122) and declare the corresponding state variables (similar to the transaction state on lines 94–100). Without these, the component will not compile.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/escrow/EscrowDetails.tsx` around lines 124 - 131, The
component is calling a missing fetchEventData and using undeclared event-related
state used by TransactionHistoryModal; create state variables analogous to the
transaction state (e.g., events, eventLoading, eventError, eventResponse,
nextEventCursor) following the pattern used around fetchTransactionData and the
transaction state declarations, implement fetchEventData(contractId, rpcUrl)
that uses the imported fetchEvents to set eventLoading, eventError,
eventResponse, events and nextEventCursor, and add a handleLoadMoreEvents
function to page using nextEventCursor; ensure the effect that calls
fetchEventData (and other callers) use these names so the file compiles.

Comment on lines +3 to +37
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { safeLocalStorage } from "@/utils/storage";
import { cn } from "@/lib/utils";

export function ThemeToggle() {
const [isDark, setIsDark] = useState<boolean>(false);
const [mounted, setMounted] = useState(false);

// Only access localStorage after component mounts (client-side only)
useEffect(() => {
setMounted(true);
const stored = safeLocalStorage.getItem("theme");
if (stored) {
setIsDark(stored === "dark");
} else if (
typeof window !== "undefined" &&
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
setIsDark(true);
}
}, []);

// Apply theme class to document
useEffect(() => {
if (!mounted) return;
const root = document.documentElement;
if (isDark) {
root.classList.add("dark");
} else {
root.classList.remove("dark");
}
safeLocalStorage.setItem("theme", isDark ? "dark" : "light");
}, [isDark, mounted]);

const toggle = () => setIsDark((v) => !v);
if (!mounted) {
return (
<button
aria-label="Toggle dark mode"
className="inline-flex items-center gap-2 rounded-full px-3 py-1 text-sm bg-gray-100 dark:bg-slate-800 border border-gray-200 dark:border-slate-700 hover:shadow-sm transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-700">
<circle cx="12" cy="12" r="5"></circle>
<path d="M12 1v2"></path>
<path d="M12 21v2"></path>
<path d="M4.22 4.22l1.42 1.42"></path>
<path d="M18.36 18.36l1.42 1.42"></path>
<path d="M1 12h2"></path>
<path d="M21 12h2"></path>
<path d="M4.22 19.78l1.42-1.42"></path>
<path d="M18.36 5.64l1.42-1.42"></path>
</svg>
<span className="sr-only">Theme</span>
<span className="hidden sm:inline text-sm font-medium text-gray-700 dark:text-gray-200">Light</span>
</button>
);
}

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 | 🔴 Critical

Critical: Component is broken — missing hook calls and malformed useEffect.

The refactoring from manual theme management to next-themes is incomplete:

  1. useTheme() is never calledresolvedTheme, theme, and setTheme (used on lines 36–37) are all undefined → ReferenceError at runtime.
  2. mounted state is never declared — no useState call, so mounted (line 13, 40) is undefined → the guard always falls through incorrectly.
  3. Dangling useEffect(() => { at line 11 — it opens a callback that is never closed. The new code (lines 13–37) was inserted inside this callback, but useEffect callbacks cannot return JSX. This is a syntax/type error.
  4. Duplicate if (!mounted) blocks — lines 13–34 (new, inside broken useEffect) and lines 40–52 (old, at component level).
  5. Unused importssafeLocalStorage and potentially useEffect/useState if the old logic is fully replaced by next-themes.

The component will not compile or render in its current state.

Proposed fix — complete the next-themes migration
 "use client";
 
 import { useTheme } from "next-themes";
-import { useEffect, useState } from "react";
-import { safeLocalStorage } from "@/utils/storage";
+import { useState, useEffect } from "react";
 import { cn } from "@/lib/utils";
 
 export function ThemeToggle() {
+  const { resolvedTheme, setTheme } = useTheme();
+  const [mounted, setMounted] = useState(false);
 
-  // Apply theme class to document
-  useEffect(() => {
-
-  if (!mounted) {
-    return (
-      <button
-        aria-label="Toggle dark mode"
-        className="inline-flex items-center gap-2 rounded-full px-3 py-1 text-sm bg-gray-100 dark:bg-slate-800 border border-gray-200 dark:border-slate-700 hover:shadow-sm transition-colors"
-      >
-        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-700">
-          <circle cx="12" cy="12" r="5"></circle>
-          <path d="M12 1v2"></path>
-          <path d="M12 21v2"></path>
-          <path d="M4.22 4.22l1.42 1.42"></path>
-          <path d="M18.36 18.36l1.42 1.42"></path>
-          <path d="M1 12h2"></path>
-          <path d="M21 12h2"></path>
-          <path d="M4.22 19.78l1.42-1.42"></path>
-          <path d="M18.36 5.64l1.42-1.42"></path>
-        </svg>
-        <span className="sr-only">Theme</span>
-        <span className="hidden sm:inline text-sm font-medium text-gray-700 dark:text-gray-200">Light</span>
-      </button>
-    );
-  }
+  useEffect(() => { setMounted(true); }, []);
 
-  const isDark = resolvedTheme === "dark";
-  const toggle = () => setTheme(theme === "dark" ? "light" : "dark");
+  const isDark = resolvedTheme === "dark";
+  const toggle = () => setTheme(isDark ? "light" : "dark");
 
   // Avoid hydration mismatch by not rendering until mounted
   if (!mounted) {

Note: the toggle function should use isDark (derived from resolvedTheme) instead of theme, as previously flagged — theme can be "system", causing incorrect toggling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/theme-toggle.tsx` around lines 3 - 37, The ThemeToggle
component is broken: call useTheme() to get { theme, resolvedTheme, setTheme },
add a mounted state via useState(false) and set it true in a useEffect (instead
of returning JSX from inside useEffect), remove the stray open useEffect that
currently wraps JSX, and implement the existing guard if (!mounted) at component
level to return the placeholder button; update toggle to use
resolvedTheme/isDark (not theme) when calling setTheme, and remove unused
imports like safeLocalStorage if not needed. Target symbols: ThemeToggle,
useTheme, mounted, setMounted (useState), useEffect (mounting effect),
resolvedTheme, isDark, toggle, setTheme.

Comment on lines 17 to 19
mainnet: {
name: "Mainnet",
rpcUrl: "https://stellar.api.onfinality.io/public",
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: "Public Global Stellar Network ; September 2015",
},

};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n src/lib/network-config.ts

Repository: Trustless-Work/escrow-viewer

Length of output: 5206


🏁 Script executed:

rg -n 'getNetworkConfig' --type=ts --type=tsx -A2

Repository: Trustless-Work/escrow-viewer

Length of output: 99


🏁 Script executed:

rg -n 'getNetworkConfig' -A2

Repository: Trustless-Work/escrow-viewer

Length of output: 4990


🏁 Script executed:

rg -n 'mainnet' -i --type=ts --type=jsx -B2 -A2

Repository: Trustless-Work/escrow-viewer

Length of output: 99


🏁 Script executed:

rg -n 'mainnet' -i -B2 -A2

Repository: Trustless-Work/escrow-viewer

Length of output: 12433


🏁 Script executed:

# Check if there's a tsconfig or build configuration that might suppress type errors
cat tsconfig.json 2>/dev/null || echo "No tsconfig.json found"

Repository: Trustless-Work/escrow-viewer

Length of output: 683


Empty mainnet config will cause runtime crashes across the codebase.

NETWORK_CONFIGS.mainnet is {}, so getNetworkConfig('mainnet') returns an object with all properties undefined. Mainnet is exposed in the UI (network-toggle.tsx), and accessing properties on a mainnet config will break:

  • src/app/page.tsx:28: getNetworkConfig(otherNetwork).nameundefined
  • src/hooks/useEscrowData.ts:42: getNetworkConfig(network).nameundefined
  • src/components/escrow/EscrowDetails.tsx:74: getNetworkConfig(otherNetwork).nameundefined
  • src/lib/network-config.ts:104: encodeURIComponent(config.name) → produces "undefined" string
  • src/components/escrow/EscrowDetails.tsx:129,142,162: getNetworkConfig(currentNetwork).rpcUrlundefined URL for fetch
  • src/utils/token-service.ts:233,291: cfg.networkPassphraseundefined passphrase

Restore the mainnet config with proper values.

Proposed fix
   mainnet: {
-
+    name: "Mainnet",
+    rpcUrl: "https://soroban.stellar.org",
+    horizonUrl: "https://horizon.stellar.org",
+    networkPassphrase: "Public Global Stellar Network ; September 2015",
+  },
📝 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
mainnet: {
name: "Mainnet",
rpcUrl: "https://stellar.api.onfinality.io/public",
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: "Public Global Stellar Network ; September 2015",
},
};
mainnet: {
name: "Mainnet",
rpcUrl: "https://soroban.stellar.org",
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: "Public Global Stellar Network ; September 2015",
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/network-config.ts` around lines 17 - 19, Replace the empty
NETWORK_CONFIGS.mainnet entry with a full mainnet config object matching the
shape used by other networks (at minimum set name, id/key, rpcUrl,
networkPassphrase, and explorerUrl) so getNetworkConfig('mainnet') returns
defined values; update the NETWORK_CONFIGS.mainnet object in
src/lib/network-config.ts (the same keys used by other entries) to include name:
"Mainnet" (or equivalent display name used by the UI), a valid rpcUrl, the
correct networkPassphrase, and any other fields referenced elsewhere (so
encodeURIComponent(config.name), getNetworkConfig(...).rpcUrl, and
cfg.networkPassphrase are not undefined).

Comment on lines 48 to 51
export async function fetchTransactions(
contractId: string,
options: FetchTransactionsOptions = {},
in
): Promise<TransactionResponse> {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Syntax error: fetchTransactions function signature is malformed.

Line 50 shows in which is not valid syntax. The network and options parameters are missing from the visible signature. This won't compile.

Expected signature (based on usage)
 export async function fetchTransactions(
   contractId: string,
-in
+  network: NetworkType,
+  options: FetchTransactionsOptions = {},
 ): Promise<TransactionResponse> {
📝 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
export async function fetchTransactions(
contractId: string,
options: FetchTransactionsOptions = {},
in
): Promise<TransactionResponse> {
export async function fetchTransactions(
contractId: string,
network: NetworkType,
options: FetchTransactionsOptions = {},
): Promise<TransactionResponse> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/transactionFetcher.ts` around lines 48 - 51, The function signature
for fetchTransactions is malformed (it currently contains an invalid token "in"
and is missing the expected parameters); update the signature of
fetchTransactions(contractId: string, network: NetworkType, options?:
FetchOptions): Promise<TransactionResponse> (or whatever concrete types are used
elsewhere) to match its usage, ensuring you import or reference the correct
Network/Options types and update any callers if necessary; fix the parameter
order and optionality in the export declared in fetchTransactions so the
function compiles and aligns with all internal references to network and
options.

Comment on lines +91 to +100
} catch (error) {
console.warn("Failed to fetch transactions from RPC:", error);
return {
transactions: [],
latestLedger: 0,
oldestLedger: 0,
hasMore: false,
retentionNotice: "Unable to connect to RPC. Please check your internet connection."
};
}
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

Misleading error message: "Unable to connect to RPC" used for non-connection errors.

The message "Unable to connect to RPC. Please check your internet connection." is returned for HTTP errors (Line 109), JSON parse failures (Line 123), and generic RPC errors (Line 148). These are different failure modes — an HTTP 500 or malformed JSON response doesn't mean the user's internet is down.

Use error-specific messages so users can take the correct remedial action.

Also applies to: 103-110, 117-125, 142-149

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/transactionFetcher.ts` around lines 91 - 100, The current catch
blocks in transactionFetcher.ts (in the fetchTransactionsFromRPC function)
always return a generic retentionNotice "Unable to connect to RPC..." even for
HTTP errors, JSON.parse failures and RPC error responses; update the error
handling to set context-specific messages: for HTTP/network errors include
"Network error contacting RPC" with the error.message or status, for JSON parse
failures use "Invalid response from RPC (malformed JSON)" and for RPC response
errors use the RPC-provided error message or "RPC error" plus details; ensure
the returned object (transactions, latestLedger, oldestLedger, hasMore,
retentionNotice) uses these specific messages in each catch branch and preserve
existing return shape.

Comment on lines +219 to +228
try {
const latestLedgerResponse = await jsonRpcCall<{ sequence: number }>(rpcUrl, "getLatestLedger");
const latestLedger = latestLedgerResponse.sequence;
// Conservative estimate: ~3 days (5 sec blocks * 86400 sec/day * 3 days)
const estimatedStartLedger = Math.max(1, latestLedger - 51840);

// Only use startLedger if it's reasonably recent (avoid invalid range errors)
if (estimatedStartLedger > latestLedger - 100000) { // Within last ~6 days
params.startLedger = estimatedStartLedger;
}
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

The startLedger guard on Line 226 is always true — it serves no purpose.

estimatedStartLedger is computed as latestLedger - 51840 (Line 223). The condition estimatedStartLedger > latestLedger - 100000 evaluates to latestLedger - 51840 > latestLedger - 100000, which simplifies to 100000 > 51840 — always true.

Either remove the guard or adjust the logic to actually validate something meaningful (e.g., check against the RPC's oldestLedger).

Proposed fix: remove dead guard
       const estimatedStartLedger = Math.max(1, latestLedger - 51840);
-
-      // Only use startLedger if it's reasonably recent (avoid invalid range errors)
-      if (estimatedStartLedger > latestLedger - 100000) { // Within last ~6 days
-        params.startLedger = estimatedStartLedger;
-      }
+      params.startLedger = estimatedStartLedger;
📝 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
try {
const latestLedgerResponse = await jsonRpcCall<{ sequence: number }>(rpcUrl, "getLatestLedger");
const latestLedger = latestLedgerResponse.sequence;
// Conservative estimate: ~3 days (5 sec blocks * 86400 sec/day * 3 days)
const estimatedStartLedger = Math.max(1, latestLedger - 51840);
// Only use startLedger if it's reasonably recent (avoid invalid range errors)
if (estimatedStartLedger > latestLedger - 100000) { // Within last ~6 days
params.startLedger = estimatedStartLedger;
}
try {
const latestLedgerResponse = await jsonRpcCall<{ sequence: number }>(rpcUrl, "getLatestLedger");
const latestLedger = latestLedgerResponse.sequence;
// Conservative estimate: ~3 days (5 sec blocks * 86400 sec/day * 3 days)
const estimatedStartLedger = Math.max(1, latestLedger - 51840);
params.startLedger = estimatedStartLedger;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/transactionFetcher.ts` around lines 219 - 228, The guard comparing
estimatedStartLedger to latestLedger is always true and should be fixed: either
remove the dead if and always set params.startLedger = Math.max(1,
estimatedStartLedger), or (preferred) query the node for its actual oldest
available ledger (via jsonRpcCall like a getLedgerRange/getOldestLedger RPC) and
then set params.startLedger = Math.max(1, estimatedStartLedger, oldestLedger) so
you avoid requesting ranges older than the node supports; update the block
around estimatedStartLedger, latestLedger and params.startLedger (references:
estimatedStartLedger, latestLedger, params.startLedger,
jsonRpcCall/getLatestLedger) accordingly.

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