Skip to content

Fix canonical vault routing and harden deep-link validation#82

Open
Kartikey44 wants to merge 3 commits intoStabilityNexus:mainfrom
Kartikey44:fix/canonical-vault-routing
Open

Fix canonical vault routing and harden deep-link validation#82
Kartikey44 wants to merge 3 commits intoStabilityNexus:mainfrom
Kartikey44:fix/canonical-vault-routing

Conversation

@Kartikey44
Copy link

@Kartikey44 Kartikey44 commented Dec 14, 2025

Summary

This PR fixes broken vault navigation caused by the legacy /v?... route
and enforces a single canonical vault routing format.

Changes

  • Replaced invalid /v?... navigation with /${vaultAddress}?chainId=<id>
  • Added centralized vault address and chain ID validation
  • Integrated validation across:
    • Find Vault
    • Explorer
    • Vault Detail page
  • Hardened deep-links using server-side validation

Benefits

  • Fixes broken deep-links
  • Prevents invalid inputs from reaching routing and contract layers
  • Improves UI stability and security
  • Aligns with open-source and GSSoC quality standards

Summary by CodeRabbit

  • New Features

    • Route validation for vault addresses and chain IDs with clear inline errors and a supported-chains list.
    • Canonical navigation using vault address + chainId; invalid routes redirect to Not Found.
  • Improvements

    • Streamlined vault card UI with compact header, chain badge, and full-width "Enter Vault" action.
    • Find-vault dialog now validates input, shows errors, trims address input, and only navigates on success.
    • Simplified vault interaction page with clearer loading/error states and leaner main sections.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

Walkthrough

Adds a centralized vault/chain validation utility and applies runtime route validation across the vault page, explorer card, find-vault form, and InteractionClient call sites; routes are normalized to the canonical /{vaultId}?chainId={chainId} pattern and invalid params produce 404 or client errors.

Changes

Cohort / File(s) Summary
Validation Infrastructure
\utils/validation.ts``
New module exporting SUPPORTED_CHAIN_IDS, validateVaultAddress(), validateChainId(), and validateVaultRoute() returning { valid: boolean; error?: string }.
Vault Page (routing + guard)
\app/[vaultId]/page.tsx``
Removed static params; added VaultPageProps and runtime call to validateVaultRoute(vaultId, chainId), calls notFound() when invalid, and renders InteractionClient with validated props.
Interaction client API change
\app/[vaultId]/InteractionClient.tsx``
Component now uses InteractionClientProps (vaultId, chainId), derives vaultAddress from vaultId, centralizes data fetching/caching and simplifies vault load logic.
Explorer card UI & flow
\components/Explorer/CardExplorer.tsx``
UI simplified; continue flow now validates via validateVaultRoute before navigating to /{vaultId}?chainId={chainId}; invalid cases show a destructive toast.
FindVault form UI & flow
\components/FindVault/FindVaultForm.tsx``
Replaced navigation with validation step using validateVaultRoute; chainId typing relaxed (number

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as CardExplorer / FindVaultForm
    participant Validator as validateVaultRoute
    participant Router as Next Router
    participant VaultPage as app/[vaultId]/page.tsx
    participant Interaction as InteractionClient

    User->>UI: submit vaultId + chainId
    UI->>Validator: validateVaultRoute(vaultId, chainId)
    alt valid
        Validator-->>UI: { valid: true }
        UI->>Router: navigate to /{vaultId}?chainId={chainId}
        Router->>VaultPage: render with params & searchParams
        VaultPage->>Validator: validateVaultRoute(vaultId, chainId)
        alt valid
            Validator-->>VaultPage: { valid: true }
            VaultPage->>Interaction: render InteractionClient(vaultId, chainId)
            Interaction-->>User: vault UI
        else invalid
            Validator-->>VaultPage: { valid: false }
            VaultPage-->>User: notFound() (404)
        end
    else invalid
        Validator-->>UI: { valid: false, error }
        UI-->>User: show error toast / inline error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to:
    • InteractionClient signature and all call sites to ensure consistent prop passing and types.
    • Validation rules in utils/validation.ts (SUPPORTED_CHAIN_IDS and error messages).
    • Routing changes to the canonical /{vaultId}?chainId={chainId} pattern across UI components.
    • Data-fetching and caching logic in InteractionClient (IndexedDB keys, fallback flows).
    • Effects of notFound() on rendering and potential SSR/SSG implications.

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Zahnentferner

Poem

🐰 I hopped through routes both near and far,
I checked each vault ID like a tiny star.
Chains validated, paths now neat,
From form to page the flow's complete.
Hop, validate, then celebrate! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: establishing a canonical vault routing pattern and adding validation for deep-links. It directly reflects the primary objectives evident in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

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

🧹 Nitpick comments (1)
app/[vaultId]/page.tsx (1)

17-17: Coercing undefined to NaN requires validation to catch.

When searchParams.chainId is absent, Number(undefined) returns NaN. The subsequent validation correctly identifies this as invalid, but the intermediate NaN state could be made more explicit.

Consider making the undefined case explicit:

-  const chainId = Number(searchParams.chainId)
+  const chainId = searchParams.chainId ? Number(searchParams.chainId) : NaN

This makes the intent clearer without changing behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 787e368 and 92a893a.

📒 Files selected for processing (4)
  • app/[vaultId]/page.tsx (1 hunks)
  • components/Explorer/CardExplorer.tsx (5 hunks)
  • components/FindVault/FindVaultForm.tsx (2 hunks)
  • utils/validation.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/[vaultId]/page.tsx (1)
utils/validation.ts (1)
  • validateVaultRoute (13-26)
components/FindVault/FindVaultForm.tsx (1)
utils/validation.ts (1)
  • validateVaultRoute (13-26)
components/Explorer/CardExplorer.tsx (3)
utils/props.ts (1)
  • vaultsProps (1-9)
utils/validation.ts (1)
  • validateVaultRoute (13-26)
utils/chains.ts (2)
  • getChainColor (224-226)
  • getChainName (179-181)
🔇 Additional comments (7)
utils/validation.ts (2)

5-7: LGTM! Address validation uses viem's battle-tested isAddress.

The function correctly delegates to viem's isAddress, which handles both checksummed and non-checksummed Ethereum addresses.


13-26: Solid centralized validation with clear error messages.

The function correctly validates both address and chain ID, returning structured error objects that consumers can use for user feedback. The integration across FindVaultForm, CardExplorer, and the vault page ensures consistent route validation.

components/FindVault/FindVaultForm.tsx (3)

31-45: Excellent validation integration with clear user feedback.

The function correctly converts the chainId to a number, validates both inputs using validateVaultRoute, and provides user-facing error messages before navigating to the canonical route. The error state is properly cleared on success.


92-92: Good practice trimming whitespace from address input.

Trimming prevents accidental leading/trailing spaces from invalidating otherwise correct addresses.


97-101: Clear error feedback improves user experience.

The conditional error block provides immediate, visible feedback when validation fails, with appropriate styling to draw attention.

app/[vaultId]/page.tsx (1)

19-23: Server-side validation guard prevents invalid routes from rendering.

Calling validateVaultRoute before rendering and invoking notFound() on failure provides defense-in-depth by rejecting bad routes at the page level, complementing client-side validation in forms and navigation handlers.

components/Explorer/CardExplorer.tsx (1)

53-70: Robust validation with user-friendly error feedback.

The handler validates the vault route before navigation and displays a descriptive toast message when validation fails, preventing navigation to invalid routes. The canonical route format /${vaultAddress}?chainId=${chainId} aligns with the PR's routing strategy.

Comment on lines 73 to 81
<Input
id='chain-id'
type='number'
placeholder='e.g., 1 (Ethereum), 534351 (Scroll Sepolia)'
value={chainId || ''}
onChange={e => setChainId(Number(e.target.value))}
className="bg-background/50 backdrop-blur-sm border-primary/30 focus:border-primary/60
transition-all duration-300 hover:border-primary/50 text-foreground placeholder:text-muted-foreground"
type="number"
placeholder="e.g., 1 (Ethereum), 534351 (Scroll Sepolia)"
value={chainId}
onChange={e =>
setChainId(e.target.value ? Number(e.target.value) : '')
}
className="bg-background/50 backdrop-blur-sm border-primary/30"
/>
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

Input allows non-numeric characters resulting in NaN.

When a user types non-numeric text (e.g., "abc"), Number("abc") returns NaN, which is then stored in chainId. While validation later catches this, the UX could be improved by either:

  • Restricting input to digits only via pattern or input filtering
  • Showing immediate feedback when non-numeric input is detected

Consider this improvement for immediate validation feedback:

 <Input
   type="number"
   placeholder="e.g., 1 (Ethereum), 534351 (Scroll Sepolia)"
   value={chainId}
   onChange={e =>
-    setChainId(e.target.value ? Number(e.target.value) : '')
+    {
+      const value = e.target.value
+      if (!value) {
+        setChainId('')
+      } else {
+        const num = Number(value)
+        if (!isNaN(num)) {
+          setChainId(num)
+        }
+      }
+    }
   }
   className="bg-background/50 backdrop-blur-sm border-primary/30"
 />
📝 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
<Input
id='chain-id'
type='number'
placeholder='e.g., 1 (Ethereum), 534351 (Scroll Sepolia)'
value={chainId || ''}
onChange={e => setChainId(Number(e.target.value))}
className="bg-background/50 backdrop-blur-sm border-primary/30 focus:border-primary/60
transition-all duration-300 hover:border-primary/50 text-foreground placeholder:text-muted-foreground"
type="number"
placeholder="e.g., 1 (Ethereum), 534351 (Scroll Sepolia)"
value={chainId}
onChange={e =>
setChainId(e.target.value ? Number(e.target.value) : '')
}
className="bg-background/50 backdrop-blur-sm border-primary/30"
/>
<Input
type="number"
placeholder="e.g., 1 (Ethereum), 534351 (Scroll Sepolia)"
value={chainId}
onChange={e =>
{
const value = e.target.value
if (!value) {
setChainId('')
} else {
const num = Number(value)
if (!isNaN(num)) {
setChainId(num)
}
}
}
}
className="bg-background/50 backdrop-blur-sm border-primary/30"
/>

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

🧹 Nitpick comments (1)
app/[vaultId]/InteractionClient.tsx (1)

45-61: Avoid invalid “0x0” sentinel addresses leaking into UI/links
'0x0' isn’t a valid 20-byte address; it can produce broken explorer links or downstream formatting issues if rendered before real data arrives. Consider using the canonical zero-address string.

-  const [vaultCreator, setVaultCreator] = useState<`0x${string}`>('0x0')
-  const [coinAddress, setCoinAddress] = useState<`0x${string}`>('0x0')
+  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const
+  const [vaultCreator, setVaultCreator] = useState<`0x${string}`>(ZERO_ADDRESS)
+  const [coinAddress, setCoinAddress] = useState<`0x${string}`>(ZERO_ADDRESS)

  const [vault, setVault] = useState<vaultsProps>({
-    coinAddress: '0x0',
+    coinAddress: ZERO_ADDRESS,
     coinName: '',
     coinSymbol: '',
     hodlCoinSymbol: '',
     decimals: 0,
     vaultAddress: vaultAddress,
     chainId,
   })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92a893a and 0047828.

📒 Files selected for processing (1)
  • app/[vaultId]/InteractionClient.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/[vaultId]/InteractionClient.tsx (4)
utils/props.ts (1)
  • vaultsProps (1-9)
utils/indexedDB.ts (1)
  • indexedDBManager (787-787)
utils/chains.ts (1)
  • getChainName (179-181)
components/Vault/VaultInformation.tsx (1)
  • VaultInformation (8-202)
🔇 Additional comments (2)
app/[vaultId]/InteractionClient.tsx (2)

28-37: Props-based routing input is a good simplification
Moving vaultId/chainId into explicit props (vs parsing search params inside) makes the component easier to reason about and aligns with server-side route validation.


79-177: [Rewritten review comment]
[Classification tag]

Comment on lines 109 to 111
const publicClient = getPublicClient(config as any, { chainId })
if (!publicClient) throw new Error('No public client')

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
set -euo pipefail

# 1) Find and display package.json content (especially versions)
echo "=== WAGMI/VIEM VERSIONS ==="
fd -a 'package.json' | head -1 | xargs cat | python3 -m json.tool | grep -A 2 -B 2 '"wagmi"\|"viem"\|"@wagmi"'

# 2) Find all getPublicClient usages to check signature consistency
echo -e "\n=== GETPUBLICCLIENT USAGES ==="
rg -n 'getPublicClient\s*\(' --type ts --type tsx -B 3 -A 3

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Read package.json directly (simpler approach)
echo "=== PACKAGE.JSON WAGMI/VIEM VERSIONS ==="
if [ -f package.json ]; then
  cat package.json | grep -E '"(wagmi|viem|@wagmi)' | head -10
fi

# 2) Read the specific file mentioned in the review
echo -e "\n=== INTERACTIONCLIENT.TSX CONTEXT (Lines 100-120) ==="
if [ -f "app/[vaultId]/InteractionClient.tsx" ]; then
  sed -n '100,120p' "app/[vaultId]/InteractionClient.tsx"
fi

# 3) Find getPublicClient calls - simpler search
echo -e "\n=== GETPUBLICCLIENT CALLS ==="
rg 'getPublicClient' --type ts --type tsx --max-count 10 -n

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 998


🌐 Web query:

wagmi v2 getPublicClient signature documentation

💡 Result:

The Wagmi v2 getPublicClient API:

  • Import:
    import { getPublicClient } from '@wagmi/core'. [1]

  • Usage / signature (TypeScript):
    const publicClient = getPublicClient(config?, { chainId?: number }); // returns PublicClient | undefined. [1][2]

  • Types referenced:
    GetClientParameters (for the params) and GetPublicClientReturnType (PublicClient | undefined). [1]

Sources:
[1] Wagmi docs — getPublicClient. [2] Wagmi v1.x docs (migration / usage reference).


Remove as any cast and properly type the config parameter
The getPublicClient(config, { chainId }) signature is correct for wagmi v2.13.3, but masking config with as any prevents TypeScript from catching type errors. Either import and use the proper config type (likely from @wagmi/core) or use proper type inference instead of the cast.

🤖 Prompt for AI Agents
In app/[vaultId]/InteractionClient.tsx around lines 109-111, remove the unsafe
`as any` cast on `config` and correctly type it: import the proper wagmi config
type from the wagmi/@wagmi/core package (whichever export your version exposes),
annotate the `config` variable/parameter with that type so TypeScript can
validate it, and then call `getPublicClient(config, { chainId })` without
casting; ensure any mismatched fields are fixed to match the imported type.

Comment on lines 224 to 230
<ActionsVault
vault={vault}
priceHodl={balances.priceHodl}
coinBalance={balances.coinBalance}
hodlCoinBalance={balances.hodlCoinBalance}
getBalances={() => {}}
/>
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:

find . -type f -name "*.tsx" -o -name "*.ts" | xargs grep -l "ActionsVault" | head -20

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 150


🏁 Script executed:

cat -n ./components/Vault/Actions.tsx

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 1201


🏁 Script executed:

find . -type f -name "HodlBox*" -o -name "UnhodlBox*" | head -10

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 145


🏁 Script executed:

cat -n ./components/Vault/HodlBox.tsx

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 12124


🏁 Script executed:

cat -n ./components/Vault/UnhodlBox.tsx

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 13191


🏁 Script executed:

cat -n ./app/[vaultId]/InteractionClient.tsx | head -240 | tail -50

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 1662


🏁 Script executed:

head -100 ./app/[vaultId]/InteractionClient.tsx

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 3031


🏁 Script executed:

sed -n '100,190p' ./app/[vaultId]/InteractionClient.tsx

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 2529


🏁 Script executed:

rg "getBalances|setBalances" ./app/[vaultId]/InteractionClient.tsx -A 3 -B 3

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 410


🏁 Script executed:

rg "setBalances" ./app/[vaultId]/InteractionClient.tsx -B 5 -A 2

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 238


🏁 Script executed:

rg "coinBalance|hodlCoinBalance" ./app/[vaultId]/InteractionClient.tsx

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 219


🏁 Script executed:

find . -type f -name "*.tsx" -o -name "*.ts" | xargs grep -l "readContract.*balanceOf" | head -10

Repository: StabilityNexus/hodlCoin-Solidity-WebUI

Length of output: 64


getBalances must be implemented or made optional with fallback—currently broken post-transaction balance refresh

HodlBox (line 132) and UnhodlBox (line 137) both call await getBalances() after stake/unstake transactions to refresh balances. Passing () => {} leaves balances stale, breaking the post-transaction UI update flow. Either provide a working refresh function from the parent or make the prop optional with an internal refetch mechanism.

🤖 Prompt for AI Agents
In app/[vaultId]/InteractionClient.tsx around lines 224-230, getBalances is
passed as a no-op (() => {}) to ActionsVault which breaks post-transaction
balance refresh because HodlBox and UnhodlBox call await getBalances(); replace
the no-op with the real refresh function from the parent (pass the actual
balances refetch function used in this component) OR change ActionsVault's prop
to optional and implement an internal fallback: update the prop type to
getBalances?: () => Promise<void>, and inside ActionsVault call (getBalances ??
internalRefetch)(). Ensure the parent either provides the real async refetch
implementation or ActionsVault performs its own balance refetch when getBalances
is undefined.

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

♻️ Duplicate comments (1)
components/FindVault/FindVaultForm.tsx (1)

54-61: Input still allows NaN when non-numeric text is entered.

Similar to the previous review comment, when non-numeric text is pasted or entered (despite type="number"), Number(e.target.value) returns NaN, which is stored in the chainId state. This creates a poor user experience with delayed error feedback only appearing on submit.

Consider this approach for immediate validation:

 <Input
   placeholder="Chain ID"
   type="number"
   value={chainId}
   onChange={e => {
-    setChainId(e.target.value ? Number(e.target.value) : '')
+    const value = e.target.value
+    if (!value) {
+      setChainId('')
+    } else {
+      const num = Number(value)
+      if (!isNaN(num)) {
+        setChainId(num)
+      }
+    }
   }}
 />
🧹 Nitpick comments (3)
components/FindVault/FindVaultForm.tsx (2)

63-67: Trim on every keystroke may hinder typing flow.

The .trim() on line 66 executes on every keystroke, preventing users from typing spaces even temporarily (e.g., pasting addresses with spaces, or natural typing patterns). Consider trimming only on blur or during validation.

 <Input
   placeholder="Vault Address"
   value={vaultAddress}
-  onChange={e => setVaultAddress(e.target.value.trim())}
+  onChange={e => setVaultAddress(e.target.value)}
+  onBlur={e => setVaultAddress(e.target.value.trim())}
 />

69-69: Consider improving error message accessibility.

The error message is displayed using a plain <p> tag without semantic or accessibility attributes. For better accessibility and consistency, consider using a proper alert component or adding appropriate ARIA attributes.

-{error && <p className="text-red-500">{error}</p>}
+{error && (
+  <div role="alert" className="text-sm text-red-500 bg-red-50 p-2 rounded">
+    {error}
+  </div>
+)}
components/Explorer/CardExplorer.tsx (1)

40-52: Validation in explorer context should log unexpected failures.

While defensive validation is good practice, if validation fails here it indicates the explorer is displaying corrupt or invalid vault data from the backend. This scenario should never occur in production and suggests a data integrity issue.

Consider distinguishing between expected validation failures (user input) and unexpected ones (data corruption):

 const handleContinue = () => {
   const validation = validateVaultRoute(
     vault.vaultAddress,
     vault.chainId
   )

   if (!validation.valid) {
+    console.error('Explorer displayed invalid vault:', {
+      vaultAddress: vault.vaultAddress,
+      chainId: vault.chainId,
+      error: validation.error
+    })
     toast({ title: 'Invalid vault', variant: 'destructive' })
     return
   }

   router.push(`/${vault.vaultAddress}?chainId=${vault.chainId}`)
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0047828 and a5c9831.

📒 Files selected for processing (5)
  • app/[vaultId]/InteractionClient.tsx (1 hunks)
  • app/[vaultId]/page.tsx (1 hunks)
  • components/Explorer/CardExplorer.tsx (1 hunks)
  • components/FindVault/FindVaultForm.tsx (1 hunks)
  • utils/validation.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • utils/validation.ts
  • app/[vaultId]/page.tsx
  • app/[vaultId]/InteractionClient.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
components/FindVault/FindVaultForm.tsx (3)
utils/validation.ts (1)
  • validateVaultRoute (35-48)
components/ui/alert-dialog.tsx (8)
  • AlertDialog (130-130)
  • AlertDialogTrigger (133-133)
  • AlertDialogContent (134-134)
  • AlertDialogHeader (135-135)
  • AlertDialogTitle (137-137)
  • AlertDialogFooter (136-136)
  • AlertDialogCancel (140-140)
  • AlertDialogAction (139-139)
components/ui/input.tsx (1)
  • Input (25-25)
components/Explorer/CardExplorer.tsx (7)
utils/props.ts (1)
  • vaultsProps (1-9)
utils/favorites.ts (2)
  • useFavorites (243-284)
  • isFavorite (77-101)
utils/validation.ts (1)
  • validateVaultRoute (35-48)
components/ui/use-toast.ts (1)
  • toast (194-194)
components/ui/card.tsx (3)
  • Card (79-79)
  • CardHeader (79-79)
  • CardContent (79-79)
utils/chains.ts (2)
  • getChainColor (224-226)
  • getChainName (179-181)
components/ui/button.tsx (1)
  • Button (56-56)
🔇 Additional comments (2)
components/Explorer/CardExplorer.tsx (2)

12-12: Great improvement: centralized chain utilities now used.

The import of getChainColor from @/utils/chains addresses the previous review comment about code duplication. This maintains a single source of truth for chain-related styling.


54-69: Clean, semantic card structure.

The simplified UI follows good practices with semantic HTML, proper use of centralized utilities (getChainName, getChainColor), and an accessible button with clear action text.

import { vaultsProps } from '@/utils/props'
import { useRouter } from 'next/navigation'
import { ArrowRight, TrendingUp, Star, StarOff} from 'lucide-react'
import { ArrowRight, Star, StarOff } from 'lucide-react'
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused icon imports.

The Star and StarOff icons are imported but never rendered in the component. The favorite functionality tracking exists (lines 24-38) but the UI elements have been removed.

-import { ArrowRight, Star, StarOff } from 'lucide-react'
+import { ArrowRight } from 'lucide-react'
📝 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
import { ArrowRight, Star, StarOff } from 'lucide-react'
import { ArrowRight } from 'lucide-react'
🤖 Prompt for AI Agents
In components/Explorer/CardExplorer.tsx around line 7, the Star and StarOff
icons are imported but unused; remove them from the import statement (leave
ArrowRight) to clean up unused imports, or if the favorite UI should exist,
re-add the favorite button markup that uses Star/StarOff and wire it to the
existing favorite state/handlers on lines ~24-38 so the icons are actually
rendered.

Comment on lines 24 to +38
const [isFavorited, setIsFavorited] = useState(false)
const [favoriteLoading, setFavoriteLoading] = useState(false)
const chainId = vault.chainId
const router = useRouter()
const { isFavorite, toggleFavorite } = useFavorites()
const { address: userAddress } = useAccount()
const { address } = useAccount()

const checkFavoriteStatus = useCallback(async () => {
if (!userAddress) {
setIsFavorited(false)
return
}

try {
const favoriteStatus = await isFavorite(vault.vaultAddress, vault.chainId, userAddress)
setIsFavorited(favoriteStatus)
} catch (error) {
console.error('Error checking favorite status:', error)
setIsFavorited(false)
}
}, [vault.vaultAddress, vault.chainId, userAddress, isFavorite])
const checkFavorite = useCallback(async () => {
if (!address) return
setIsFavorited(
await isFavorite(vault.vaultAddress, vault.chainId, address)
)
}, [vault, address, isFavorite])

useEffect(() => {
checkFavoriteStatus()
}, [checkFavoriteStatus])
checkFavorite()
}, [checkFavorite])
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused favorite tracking logic.

The favorite state and checking logic execute on every render, but the state is never used in the UI (lines 54-69 show no favorite indicator or toggle button). The imported toggleFavorite function is also unused.

Remove the unused code:

 export default function CardExplorer({ vault }: { vault: ExtendedVaultProps }) {
-  const [isFavorited, setIsFavorited] = useState(false)
   const router = useRouter()
-  const { isFavorite, toggleFavorite } = useFavorites()
-  const { address } = useAccount()
-
-  const checkFavorite = useCallback(async () => {
-    if (!address) return
-    setIsFavorited(
-      await isFavorite(vault.vaultAddress, vault.chainId, address)
-    )
-  }, [vault, address, isFavorite])
-
-  useEffect(() => {
-    checkFavorite()
-  }, [checkFavorite])

   const handleContinue = () => {

Also remove unused imports:

-import { useFavorites } from '@/utils/favorites'
-import { useAccount } from 'wagmi'
🤖 Prompt for AI Agents
In components/Explorer/CardExplorer.tsx around lines 24 to 38, remove the unused
favorites tracking logic: delete the isFavorited state, the checkFavorite
useCallback, the useEffect that calls it, and remove the unused toggleFavorite
and isFavorite references; also remove the useAccount/address usage and any
related imports (useFavorites, useAccount) if they are no longer used elsewhere
in the file, and adjust the hook dependency list/refs accordingly so lint and
build errors are resolved.

Comment on lines 32 to 44
const handleContinue = () => {
if (vaultAddress && chainId) {
router.push(`/v?chainId=${chainId}&vault=${vaultAddress}`)
const validation = validateVaultRoute(
vaultAddress,
Number(chainId)
)

if (!validation.valid) {
setError(validation.error!)
return
}

router.push(`/${vaultAddress}?chainId=${chainId}`)
}
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

Empty chainId produces 0, potentially bypassing validation.

When chainId is an empty string (initial state), Number(chainId) at line 35 evaluates to 0, which is then passed to validateVaultRoute. If the validation logic doesn't explicitly reject chain ID 0, this could lead to invalid navigation attempts.

Apply this diff to ensure empty input is caught:

 const handleContinue = () => {
+  if (!vaultAddress || chainId === '') {
+    setError('Please enter both Chain ID and Vault Address')
+    return
+  }
+
   const validation = validateVaultRoute(
     vaultAddress,
     Number(chainId)
   )

   if (!validation.valid) {
     setError(validation.error!)
     return
   }

+  setError(null)
   router.push(`/${vaultAddress}?chainId=${chainId}`)
 }

Note: Also added setError(null) before navigation to clear any previous error state.

🤖 Prompt for AI Agents
In components/FindVault/FindVaultForm.tsx around lines 32 to 44, Number(chainId)
turns an empty string into 0 which can bypass validation; update handleContinue
to first check for an empty chainId (or that Number(chainId) is NaN) and
setError with a suitable message and return if invalid, only then parse chainId
to a Number and call validateVaultRoute; also clear any previous error with
setError(null) immediately before calling router.push so navigation isn’t
blocked by stale 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