Skip to content

Conversation

@kumawatkaran523
Copy link
Contributor

@kumawatkaran523 kumawatkaran523 commented Sep 1, 2025

Summary by CodeRabbit

  • New Features

    • Added TokenPicker with search, modal, and copyable addresses.
    • Introduced custom token verification (symbol/name/decimals) with status indicators.
    • Enhanced invoice pages to auto-resolve token metadata and logos per network.
    • Added UI components: Dialog, Avatar, Badge (simplified), Separator, CopyButton.
  • Refactor

    • Replaced static token presets with dynamic, chain-aware token lists across the app.
  • Documentation

    • Overhauled README with branding, clearer setup, ETC deployment guide, and environment configuration.
  • Chores

    • Updated frontend dependencies (Radix UI components, framer-motion) and added react-window.

@coderabbitai
Copy link

coderabbitai bot commented Sep 1, 2025

Walkthrough

Replaces static TOKEN_PRESETS with a dynamic token-list and search flow, adds a TokenPicker component and related UI primitives, introduces hooks for fetching/searching tokens, integrates custom ERC-20 verification in CreateInvoice/GenerateLink, updates invoice pages to resolve token metadata by chain, updates dependencies, and rewrites README guidance.

Changes

Cohort / File(s) Summary
Docs refresh
README.md
Rewrites overview, setup, ETC deployment, env vars; restructures content and terminology; no code changes.
Dependencies update
frontend/package.json
Adds Radix UI components, react-window; bumps framer-motion and react-slot.
Token picker feature
frontend/src/components/TokenPicker.jsx
New modal-based token picker with search, loading/error/empty states, optional custom-token action; exports TokenPicker and ToggleSwitch.
UI primitives (new)
frontend/src/components/ui/avatar.jsx, .../ui/dialog.jsx, .../ui/separator.jsx, .../ui/copyButton.jsx
Adds Avatar, Dialog suite, Separator, and CopyButton components used by TokenPicker and pages.
UI badge simplification
frontend/src/components/ui/badge.jsx
Replaces variant-based Badge with minimal span; removes badgeVariants and prop forwarding.
Token data hooks (new)
frontend/src/hooks/useTokenList.js, frontend/src/hooks/useTokenSearch.js
Adds chain-aware token fetching with caching and a debounced, indexed search with pagination.
Pages: create/send/receive/link
frontend/src/page/CreateInvoice.jsx, .../GenerateLink.jsx, .../ReceivedInvoice.jsx, .../SentInvoice.jsx
Integrate TokenPicker and hooks; add custom ERC-20 verification flow; enrich invoices with token metadata; update UI, notes, and network handling.
Preset removal
frontend/src/utils/erc20_token.js
Removes exported TOKEN_PRESETS dataset.
Carousel regression
frontend/src/components/TokenCrousel.jsx
Commented-out import of TOKEN_PRESETS while still referenced, making TOKEN_PRESETS undefined.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CreateInvoice as Create/Generate Pages
  participant TokenPicker
  participant useTokenList
  participant useTokenSearch

  User->>CreateInvoice: Open token selector
  CreateInvoice->>TokenPicker: Render trigger
  User->>TokenPicker: Click trigger
  TokenPicker->>useTokenList: fetch(chainId)
  useTokenList-->>TokenPicker: { tokens, loading/error }
  TokenPicker->>useTokenSearch: index(tokens), setQuery(q)
  useTokenSearch-->>TokenPicker: filteredTokens
  User->>TokenPicker: Select token
  TokenPicker-->>CreateInvoice: onSelect(token)
  CreateInvoice-->>User: Token set in form
Loading
sequenceDiagram
  participant User
  participant Page as Create/Generate Pages
  participant Provider as BrowserProvider/ethers
  participant ERC20 as ERC20 Contract

  User->>Page: Enter contract address (custom token)
  Page->>Page: tokenVerificationState = verifying
  Page->>Provider: getSigner()
  Provider-->>Page: signer
  Page->>ERC20: symbol(), name(), decimals()
  ERC20-->>Page: metadata or error
  alt success
    Page->>Page: set verifiedToken + state=success
  else error
    Page->>Page: set state=error
  end
  Page-->>User: Show verified info or error banner
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • adityabhattad2021

Poem

I hop through lists where tokens gleam,
A picker pops—like minty dream.
Presets gone, the chain’s our guide,
Verify, then stride with pride.
Clip-clop keys, addresses sing—
Invoices dance on on-chain spring.
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

⚠️ Outside diff range comments (7)
frontend/src/components/TokenCrousel.jsx (1)

4-9: Fix runtime ReferenceError: migrate off TOKEN_PRESETS.

The import was removed but duplicatedTokens still references TOKEN_PRESETS, which will crash at render. Switch to the new token list source and memoize the doubled array; add a null-guard for the ref in effect.

-import { useEffect, useRef } from "react";
+import { useEffect, useRef, useMemo } from "react";
+import { useTokenList } from "@/hooks/useTokenList";
@@
-  const duplicatedTokens = [...TOKEN_PRESETS, ...TOKEN_PRESETS]; // Double the tokens for seamless loop
+  const { tokens = [] } = useTokenList();
+  const duplicatedTokens = useMemo(() => [...tokens, ...tokens], [tokens]); // Double for seamless loop
@@
-    const carousel = carouselRef.current;
+    const carousel = carouselRef.current;
+    if (!carousel) return;
frontend/src/page/SentInvoice.jsx (2)

422-426: formatAddress can throw on undefined/short input

Guard against undefined/short strings to avoid runtime errors.

-const formatAddress = (address) => {
-  return `${address.substring(0, 10)}...${address.substring(
-    address.length - 10
-  )}`;
-};
+const formatAddress = (addr = "") => {
+  if (!addr || addr.length < 15) return addr || "—";
+  return `${addr.slice(0, 10)}...${addr.slice(-10)}`;
+};

903-904: Hard-coded “Chain: Sepolia Testnet”

This contradicts the README’s ETC guidance. Consider deriving from current network and/or env.

-<p>Chain: Sepolia Testnet</p>
+<p>Chain: {network?.name || (chainId === 61 ? "Ethereum Classic" : "Unknown")}</p>
frontend/src/page/ReceivedInvoice.jsx (1)

153-160: Fix BigInt/number comparison for chainId (ethers v6).

provider.getNetwork() returns chainId as bigint. Using loose != 11155111 is brittle. Compare as Number(...) or to a 11155111n literal.

-        if (network.chainId != 11155111) {
+        if (Number(network.chainId) !== 11155111) {
frontend/src/page/CreateInvoice.jsx (3)

20-27: Wrong Badge import breaks UI.

Badge here should be the UI component, not a Lucide icon. Import from your UI lib and remove from lucide-react.

-import {
-  Badge,
-  CalendarIcon,
-  CheckCircle2,
-  Coins,
-  Loader2,
-  PlusIcon,
-  XCircle,
-} from "lucide-react";
+import {
+  CalendarIcon,
+  CheckCircle2,
+  Coins,
+  Loader2,
+  PlusIcon,
+  XCircle,
+} from "lucide-react";
+import { Badge } from "@/components/ui/badge";

97-116: Leftover TOKEN_PRESETS causes runtime ReferenceError and wrong decimals.

This block references a removed constant and doesn't resolve token decimals, which are required later for parseUnits(...).

-      } else {
-        const preselectedToken = TOKEN_PRESETS.find(
-          (token) =>
-            token.address.toLowerCase() === urlTokenAddress.toLowerCase()
-        );
-        if (preselectedToken) {
-          setSelectedToken(preselectedToken);
-          setUseCustomToken(false);
-        } else {
-          setUseCustomToken(true);
-          setCustomTokenAddress(urlTokenAddress);
-          verifyToken(urlTokenAddress);
-        }
-      }
+      } else {
+        // Resolve token metadata from chain to capture proper decimals
+        const resolve = async () => {
+          try {
+            const provider = new BrowserProvider(
+              typeof window !== "undefined" && window.ethereum
+                ? window.ethereum
+                : walletClient
+            );
+            const contract = new ethers.Contract(urlTokenAddress, ERC20_ABI, provider);
+            const [symbol, name, decimals] = await Promise.all([
+              contract.symbol().catch(() => "UNKNOWN"),
+              contract.name().catch(() => "Unknown Token"),
+              contract.decimals().catch(() => 18),
+            ]);
+            setSelectedToken({
+              address: urlTokenAddress,
+              symbol,
+              name,
+              decimals: Number(decimals),
+              logo: "/tokenImages/generic.png",
+            });
+            setUseCustomToken(false);
+          } catch {
+            // Fallback minimal metadata; user can re-pick token
+            setSelectedToken({
+              address: urlTokenAddress,
+              symbol: "TOKEN",
+              name: "Unknown Token",
+              decimals: 18,
+              logo: "/tokenImages/generic.png",
+            });
+            setUseCustomToken(false);
+          }
+        };
+        resolve();
+      }

228-244: Guard against missing paymentToken before encoding amounts.

If the user hasn't selected/verified a token, paymentToken can be null, causing failures or NaNs in parseUnits(...).

-      const paymentToken = useCustomToken ? verifiedToken : selectedToken;
+      const paymentToken = useCustomToken ? verifiedToken : selectedToken;
+      if (!paymentToken || !paymentToken.address || paymentToken.decimals == null) {
+        alert("Please select or verify a payment token before creating the invoice.");
+        setLoading(false);
+        return;
+      }

Also applies to: 345-351

🧹 Nitpick comments (36)
frontend/src/components/TokenCrousel.jsx (2)

6-6: Align filename with component name.

The file is TokenCrousel.jsx but the component is TokenCarousel. Please rename the file to prevent import mistakes.


64-69: Avoid hard-coding native-token detection to a zero address.

The new token list may flag native assets differently across chains. Prefer a semantic flag (e.g., token.isNative) with the address check as a fallback.

-                  {token.address ===
-                    "0x0000000000000000000000000000000000000000" && (
+                  {(token.isNative ||
+                    token.address === "0x0000000000000000000000000000000000000000") && (
                     <div className="absolute -bottom-1 -right-1 bg-green-500 rounded-full p-0.5">
                       <SiEthereum className="text-white text-xs" />
                     </div>
                   )}
frontend/src/components/ui/avatar.jsx (1)

1-1: Provide sane defaults for a11y and robust image fallback.

Default alt to an empty string and add a safe onError fallback to prevent broken images without requiring every caller to pass it.

-const Avatar = ({ src, alt, className = "", children, onError }) => (
+const Avatar = ({ src, alt = "", className = "", children, onError }) => (
@@
-      <img
+      <img
         src={src}
         alt={alt}
         className="w-full h-full object-cover rounded-full"
-        onError={onError}
+        onError={
+          onError ??
+          ((e) => {
+            e.currentTarget.onerror = null;
+            e.currentTarget.src = "/tokenImages/default.png";
+          })
+        }
       />

Also applies to: 6-11

frontend/src/components/ui/copyButton.jsx (1)

1-3: Harden copy UX: handle insecure contexts, empty input, and timer cleanup.

Add a fallback for environments without navigator.clipboard, disable the button when textToCopy is falsy, and clear the timeout on unmount to avoid leaks.

-import { useState } from "react";
+import { useEffect, useRef, useState } from "react";
@@
 const CopyButton = ({ textToCopy, className = "" }) => {
   const [copied, setCopied] = useState(false);
+  const timeoutRef = useRef(null);
 
-  const handleCopy = async (e) => {
+  const handleCopy = async (e) => {
     e.stopPropagation();
     try {
-      await navigator.clipboard.writeText(textToCopy);
+      if (navigator.clipboard && window.isSecureContext) {
+        await navigator.clipboard.writeText(textToCopy ?? "");
+      } else {
+        // Fallback: temporary textarea
+        const ta = document.createElement("textarea");
+        ta.value = textToCopy ?? "";
+        ta.style.position = "fixed";
+        ta.style.opacity = "0";
+        document.body.appendChild(ta);
+        ta.select();
+        document.execCommand("copy");
+        document.body.removeChild(ta);
+      }
       setCopied(true);
-      setTimeout(() => setCopied(false), 500);
+      timeoutRef.current = setTimeout(() => setCopied(false), 500);
     } catch (err) {
       console.error("Failed to copy:", err);
     }
   };
+
+  useEffect(() => {
+    return () => {
+      if (timeoutRef.current) clearTimeout(timeoutRef.current);
+    };
+  }, []);
@@
     <button
       type="button"
       onClick={handleCopy}
       className={`inline-flex items-center gap-1 px-2 py-1 text-xs rounded hover:bg-gray-100 transition-colors ${className}`}
       title="Copy address"
+      disabled={!textToCopy}
     >

Also applies to: 5-7, 8-17, 19-38

frontend/src/components/ui/separator.jsx (1)

14-18: Optional: rely on Radix data-orientation instead of branching

Removes the ternary and lets CSS handle orientation via data attributes.

Apply this diff:

-      "shrink-0 bg-border",
-      orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
+      "shrink-0 bg-border data-[orientation=horizontal]:h-[1px] data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-[1px]",
frontend/src/hooks/useTokenSearch.js (4)

21-27: Constrain prefix index growth

Indexing every prefix of long names/symbols can bloat memory/time. Cap the prefix length.

Apply this diff:

-    for (let i = 1; i <= symbol.length; i++) {
+    for (let i = 1; i <= Math.min(symbol.length, 12); i++) {
@@
-    for (let i = 1; i <= name.length; i++) {
+    for (let i = 1; i <= Math.min(name.length, 12); i++) {

Also applies to: 30-36


38-40: Leverage exact address map before substring scan

Use O(1) exact match first; then fall back to includes-scan for partial queries.

Apply this diff:

     // Address index
     indexes.byAddress.set(address, token);
@@
-    // Check address matches
+    // Exact address match
+    const exactAddress = indexes.byAddress.get(lowerQuery);
+    if (exactAddress) {
+      results.add(exactAddress);
+    }
+
+    // Check partial address matches
     if (lowerQuery.length >= 2) {
       // Only search addresses for queries >= 2 chars
       for (const [address, token] of indexes.byAddress.entries()) {
         if (address.includes(lowerQuery)) {
           results.add(token);
         }
       }
     }

Also applies to: 90-98


61-63: Reset pagination when tokens change

Prevents stale pages when a new token set arrives.

Apply this diff:

   // Create search indexes
   const indexes = useMemo(() => createSearchIndexes(tokens), [tokens]);
 
+  // Reset page when the underlying dataset changes
+  useEffect(() => {
+    setPage(1)
+  }, [tokens])

46-49: Consider removing unused loading/error

They are never set here. Either wire them to upstream fetch state or drop them from this hook’s API to avoid confusion.

Also applies to: 109-117

README.md (5)

2-2: Anchor should use id, not name

GitHub anchors target id, so the "back to top" link won’t work reliably.

-<div name="readme-top"></div>
+<div id="readme-top"></div>

70-75: Format shell commands and avoid bare URL warning

Fence the commands to fix readability and markdownlint MD034.

-2. **Clone your fork**
-
-git clone https://github.com/yourusername/Chainvoice.git  
-cd Chainvoice
+2. **Clone your fork**
+```bash
+git clone https://github.com/yourusername/Chainvoice.git
+cd Chainvoice
+```

78-91: Frontend steps should be fenced as code

Render commands as code blocks for consistency.

-1. **Navigate to frontend directory**  
-cd frontend
-
-2. **Install dependencies**  
-npm install
-
-3. **Start development server**  
-npm run dev
-
-4. **Open application**
-Navigate to `http://localhost:5173` in your browser
+1. **Navigate to frontend directory**
+```bash
+cd frontend
+```
+2. **Install dependencies**
+```bash
+npm install
+```
+3. **Start development server**
+```bash
+npm run dev
+```
+4. **Open application**
+Visit http://localhost:5173 in your browser.

96-104: Fix numbering and fence test commands

The list numbering skips “2”; also render commands as code.

-1. **Navigate to contracts directory**  
-cd contracts
-
-3. **Run test suite**  
-forge test
-
-4. **Run tests with verbosity (optional)**  
-forge test -vvv
+1. **Navigate to contracts directory**
+```bash
+cd contracts
+```
+2. **Run test suite**
+```bash
+forge test
+```
+3. **Run tests with verbosity (optional)**
+```bash
+forge test -vvv
+```

119-131: Deployment command should be a single fenced block

Current wrapping splits the command. Use a code block.

-4. **Deploy to Ethereum Classic**  
-forge create contracts/src/Chainvoice.sol:Chainvoice
---rpc-url $ETC_RPC_URL
---private-key $PRIVATE_KEY
---broadcast
+4. **Deploy to Ethereum Classic**
+```bash
+forge create contracts/src/Chainvoice.sol:Chainvoice \
+  --rpc-url "$ETC_RPC_URL" \
+  --private-key "$PRIVATE_KEY" \
+  --broadcast
+```
frontend/src/page/SentInvoice.jsx (4)

65-65: chainId handling

If address is on Sepolia only, consider enforcing chainId 11155111 in one place (config) and removing the “|| 1” fallback to avoid silently querying mainnet token lists elsewhere.

-const { address, isConnected, chainId } = useAccount();
+const { address, isConnected, chainId } = useAccount(); // prefer explicit chain gating below

155-161: Coerce chainId to number when comparing

Avoid BigInt/number pitfalls from ethers providers.

-if (network.chainId != 11155111) {
+if (Number(network.chainId) !== 11155111) {

274-323: On-chain token metadata fallback: minor hardening

Good fallback. Consider wrapping provider-dependent calls with a short timeout or race to avoid long UI stalls on slow RPCs; also guard decimals Number() when it’s already a number.

-const [symbol, name, decimals] = await Promise.all([
+const [symbol, name, decimals] = await Promise.allSettled([
   tokenContract.symbol().catch(() => parsed.paymentToken.symbol || "UNKNOWN"),
   tokenContract.name().catch(() => parsed.paymentToken.name || "Unknown Token"),
   tokenContract.decimals().catch(() => parsed.paymentToken.decimals || 18),
-]);
+]).then(results => results.map(r => (r.status === "fulfilled" ? r.value : undefined)));
...
-  decimals: Number(decimals),
+  decimals: Number(decimals ?? 18),

406-420: Network switch UX

Good use of wallet_switchEthereumChain. Optionally, add chain add fallback (wallet_addEthereumChain) if Sepolia isn’t present.

    } catch (error) {
      console.error("Network switch failed:", error);
-      alert("Failed to switch network. Please switch to Sepolia manually.");
+      // Fallback: suggest adding the chain if not available
+      try {
+        await window.ethereum.request({
+          method: "wallet_addEthereumChain",
+          params: [{
+            chainId: "0xaa36a7",
+            chainName: "Sepolia",
+            nativeCurrency: { name: "Sepolia ETH", symbol: "SEP", decimals: 18 },
+            rpcUrls: ["https://rpc.sepolia.org"],
+            blockExplorerUrls: ["https://sepolia.etherscan.io"],
+          }],
+        });
+      } catch {
+        alert("Failed to switch/add Sepolia. Please switch manually.");
+      }
frontend/src/hooks/useTokenList.js (5)

12-16: Minor formatting and intent

Add spacing consistency and clarify mapping comments.

 export const ChainIdToName = {
-  1: "ethereum",
-  61:"ethereum-classic",
-  11155111: "sepolia", // For demo purposes
+  1: "ethereum",
+  61: "ethereum-classic",
+  11155111: "sepolia", // Testnet
 };

34-39: Surface guidance in error for testnets

Provide a short actionable hint (e.g., “Use the custom token flow”).

- setError(`Please manually input the token's contract address instead.`);
+ setError(`Testnet detected. Please use the "Custom Token" option and input the token's contract address.`);

62-69: Include decimals (and address) in normalized tokens

Downstream code expects decimals/logo; include decimals if present to reduce on-chain calls.

-const transformedData = data.map((token) => ({
-  contract_address: token.contract_address || token.address,
-  symbol: token.symbol,
-  name: token.name,
-  image: token.image || token.logo || "/tokenImages/generic.png",
-}));
+const transformedData = data.map((token) => ({
+  contract_address: token.contract_address || token.address,
+  address: token.address || token.contract_address, // keep both for compatibility
+  symbol: token.symbol,
+  name: token.name,
+  decimals: token.decimals ?? undefined,
+  image: token.image || token.logo || "/tokenImages/generic.png",
+}));

26-33: Set loading=false when returning from cache

Avoid transient spinners if callers check loading.

 if (tokenCache[chainId]) {
   setTokens(tokenCache[chainId]);
+  setLoading(false);
   return;
 }

48-55: Tiny robustness improvement

Add a short fetch timeout to prevent hanging on slow GitHub responses.

-const response = await fetch(dataUrl, {
+const controller = new AbortController();
+const t = setTimeout(() => controller.abort(), 10_000);
+const response = await fetch(dataUrl, {
   headers: { Accept: "application/json" },
-});
+  signal: controller.signal,
+});
+clearTimeout(t);
frontend/src/page/ReceivedInvoice.jsx (3)

340-342: Re-run fetch when network changes.

The effect doesn't depend on chainId. Add it to ensure invoices refresh after a successful network switch.

-  }, [walletClient, litReady, address, tokens]);
+  }, [walletClient, litReady, address, chainId, tokens]);

660-663: Use e.currentTarget in onError handlers.

More robust than e.target and avoids edge cases in React event handling.

-                                    onError={(e) => {
-                                      e.target.src = "/tokenImages/generic.png";
-                                    }}
+                                    onError={(e) => {
+                                      e.currentTarget.src = "/tokenImages/generic.png";
+                                    }}
-                        onError={(e) => {
-                          e.target.src = "/tokenImages/generic.png";
-                        }}
+                        onError={(e) => {
+                          e.currentTarget.src = "/tokenImages/generic.png";
+                        }}

Also applies to: 952-955


272-320: Avoid N sequential on-chain metadata calls; batch or cache.

Fetching ERC-20 metadata per invoice, sequentially, will scale poorly. Consider batching via Promise.all with a per-tick concurrency cap and cache token-address→metadata.

If helpful, I can provide a small in-file LRU cache for token metadata.

frontend/src/page/CreateInvoice.jsx (1)

196-221: Prefer walletClient when available for token verification; debounce input.

Using window.ethereum is fine, but walletClient is already scoped. Also, verifying on every keystroke can spam RPC.

Happy to share a small debounced verifyToken wrapper if you'd like.

frontend/src/page/GenerateLink.jsx (2)

273-288: Debounce custom-token verification to reduce RPC spam.

Verification fires on every character change. Add a short debounce (300–500ms) and cancel in-flight calls on new input.

I can provide a minimal useDebouncedEffect helper if useful.


211-234: Populate accurate decimals when selecting from picker (future-proofing).

You set decimals: 18. While link generation doesn’t use decimals, downstream prefill flows may. Consider resolving decimals here too for consistency.

I can mirror the CreateInvoice approach for resolving decimals post-select.

frontend/src/components/TokenPicker.jsx (7)

140-143: Prevent potential image onError loops

If the fallback also fails, this can recurse. Null the handler before swapping src.

-        onError={(e) => {
-          e.currentTarget.src = "/tokenImages/generic.png";
-        }}
+        onError={(e) => {
+          e.currentTarget.onerror = null;
+          e.currentTarget.src = "/tokenImages/generic.png";
+        }}

And similarly for the selected token avatar:

-              onError={(e) => {
-                e.currentTarget.src = "/tokenImages/generic.png";
-              }}
+              onError={(e) => {
+                e.currentTarget.onerror = null;
+                e.currentTarget.src = "/tokenImages/generic.png";
+              }}

Also applies to: 241-243


21-47: Add dialog a11y and ESC-to-close

Improve accessibility and UX by labeling the dialog and supporting Escape to close.

 const Modal = ({ isOpen, onClose, children }) => {
   useEffect(() => {
     if (isOpen) {
       document.body.style.overflow = "hidden";
     } else {
       document.body.style.overflow = "unset";
     }
     return () => {
       document.body.style.overflow = "unset";
     };
   }, [isOpen]);
 
+  useEffect(() => {
+    if (!isOpen) return;
+    const onKeyDown = (e) => {
+      if (e.key === "Escape") onClose();
+    };
+    document.addEventListener("keydown", onKeyDown);
+    return () => document.removeEventListener("keydown", onKeyDown);
+  }, [isOpen, onClose]);
 
   if (!isOpen) return null;
 
   return (
-    <div className="fixed inset-0 z-50 flex items-center justify-center">
+    <div
+      className="fixed inset-0 z-50 flex items-center justify-center"
+      role="dialog"
+      aria-modal="true"
+      aria-labelledby="token-picker-title"
+    >
       <div
         className="fixed inset-0 bg-black/50 backdrop-blur-sm"
         onClick={onClose}
       />

Label the title:

-            <h2 className="text-xl font-semibold text-gray-900">
+            <h2 id="token-picker-title" className="text-xl font-semibold text-gray-900">
               Select Token
             </h2>

Also applies to: 269-271


273-279: Add accessible labels to icon-only controls

Screen readers need labels for the close and clear buttons.

-            <button
+            <button
               type="button"
               onClick={() => setOpen(false)}
-              className="ml-auto p-1 hover:bg-gray-100 rounded-full transition-colors"
+              aria-label="Close"
+              className="ml-auto p-1 hover:bg-gray-100 rounded-full transition-colors"
             >
-              {query && (
+              {query && (
                 <button
                   type="button"
                   onClick={() => setQuery("")}
-                  className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-gray-100 rounded-full transition-colors"
+                  aria-label="Clear search"
+                  className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-gray-100 rounded-full transition-colors"
                 >

Also applies to: 291-299


221-232: Expose dialog state on trigger button

Advertise the popup relationship for assistive tech.

-      <Button
+      <Button
         type="button"
         variant="outline"
         disabled={disabled}
         onClick={() => setOpen(true)}
+        aria-haspopup="dialog"
+        aria-expanded={open}
         className={cn(

160-163: Guard address display

Avoid potential crashes if contract_address is missing.

-          <span className="text-gray-500 text-sm font-mono truncate">
-            {token.contract_address.slice(0, 8)}...
-            {token.contract_address.slice(-6)}
-          </span>
+          <span className="text-gray-500 text-sm font-mono truncate">
+            {token.contract_address
+              ? `${token.contract_address.slice(0, 8)}...${token.contract_address.slice(-6)}`
+              : "—"}
+          </span>

60-76: Mark toggle as a switch for a11y

Expose semantics and state; improves SR and testing.

-    <button
+    <button
       type="button"
       onClick={() => onChange(!enabled)}
+      role="switch"
+      aria-checked={enabled}
+      aria-label={`${leftLabel} / ${rightLabel}`}
       className={cn(

303-313: Consider list virtualization or incremental loading

Large token sets will re-render many rows. Consider react-window/react-virtual or IntersectionObserver to call useTokenSearch.loadMore() as the user scrolls. Keeps memory/paint costs low.

Also applies to: 339-353

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 51e1aa5 and 7754f2b.

📒 Files selected for processing (16)
  • README.md (1 hunks)
  • frontend/package.json (3 hunks)
  • frontend/src/components/TokenCrousel.jsx (1 hunks)
  • frontend/src/components/TokenPicker.jsx (1 hunks)
  • frontend/src/components/ui/avatar.jsx (1 hunks)
  • frontend/src/components/ui/badge.jsx (1 hunks)
  • frontend/src/components/ui/copyButton.jsx (1 hunks)
  • frontend/src/components/ui/dialog.jsx (1 hunks)
  • frontend/src/components/ui/separator.jsx (1 hunks)
  • frontend/src/hooks/useTokenList.js (1 hunks)
  • frontend/src/hooks/useTokenSearch.js (1 hunks)
  • frontend/src/page/CreateInvoice.jsx (7 hunks)
  • frontend/src/page/GenerateLink.jsx (8 hunks)
  • frontend/src/page/ReceivedInvoice.jsx (14 hunks)
  • frontend/src/page/SentInvoice.jsx (12 hunks)
  • frontend/src/utils/erc20_token.js (0 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/utils/erc20_token.js
🧰 Additional context used
🧬 Code graph analysis (9)
frontend/src/components/ui/dialog.jsx (1)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/hooks/useTokenList.js (2)
frontend/src/page/GenerateLink.jsx (1)
  • useTokenList (38-38)
frontend/src/page/ReceivedInvoice.jsx (1)
  • useTokenList (71-71)
frontend/src/page/SentInvoice.jsx (3)
frontend/src/page/ReceivedInvoice.jsx (4)
  • useTokenList (71-71)
  • getTokenInfo (83-91)
  • getTokenLogo (94-102)
  • getTokenDecimals (105-108)
frontend/src/hooks/useTokenList.js (1)
  • tokens (19-19)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
frontend/src/page/CreateInvoice.jsx (6)
frontend/src/page/GenerateLink.jsx (2)
  • loading (46-46)
  • verifyToken (104-124)
frontend/src/components/TokenPicker.jsx (3)
  • ToggleSwitch (50-84)
  • ToggleSwitch (50-84)
  • TokenPicker (175-382)
frontend/src/components/ui/label.jsx (1)
  • Label (11-13)
frontend/src/components/ui/input.jsx (1)
  • Input (5-16)
frontend/src/components/ui/badge.jsx (1)
  • Badge (1-7)
frontend/src/components/ui/copyButton.jsx (1)
  • CopyButton (5-39)
frontend/src/components/ui/separator.jsx (1)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/hooks/useTokenSearch.js (2)
frontend/src/hooks/useTokenList.js (3)
  • tokens (19-19)
  • loading (20-20)
  • error (21-21)
frontend/src/components/TokenPicker.jsx (1)
  • filteredTokens (193-193)
frontend/src/components/TokenPicker.jsx (8)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/hooks/useTokenSearch.js (3)
  • query (48-48)
  • filteredTokens (64-101)
  • useTokenSearch (45-118)
frontend/src/components/ui/avatar.jsx (1)
  • Avatar (1-18)
frontend/src/components/ui/badge.jsx (1)
  • Badge (1-7)
frontend/src/components/ui/copyButton.jsx (1)
  • CopyButton (5-39)
frontend/src/hooks/useTokenList.js (2)
  • useTokenList (18-86)
  • tokens (19-19)
frontend/src/components/ui/button.jsx (1)
  • Button (37-45)
frontend/src/components/ui/input.jsx (1)
  • Input (5-16)
frontend/src/page/GenerateLink.jsx (3)
frontend/src/components/ui/copyButton.jsx (2)
  • copied (6-6)
  • CopyButton (5-39)
frontend/src/hooks/useTokenList.js (3)
  • useTokenList (18-86)
  • loading (20-20)
  • tokens (19-19)
frontend/src/components/TokenPicker.jsx (3)
  • ToggleSwitch (50-84)
  • ToggleSwitch (50-84)
  • TokenPicker (175-382)
frontend/src/page/ReceivedInvoice.jsx (3)
frontend/src/page/SentInvoice.jsx (5)
  • useTokenList (79-79)
  • getTokenInfo (82-90)
  • getTokenLogo (93-101)
  • getTokenDecimals (104-107)
  • error (69-69)
frontend/src/hooks/useTokenList.js (3)
  • useTokenList (18-86)
  • tokens (19-19)
  • error (21-21)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
🪛 LanguageTool
README.md

[grammar] ~37-~37: There might be a mistake here.
Context: ...ries. ## Table of Contents - Overview - Features - [Project Structur...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...nts - Overview - Features - Project Structure -...

(QB_NEW_EN)


[grammar] ~39-~39: There might be a mistake here.
Context: ...eatures](#features) - Project Structure - Getting Started - [Fr...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...](#project-structure) - Getting Started - Frontend Setup - [Smar...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...ted](#getting-started) - Frontend Setup - [Smart Contract Testing](#smart-contract-...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...rontend-setup) - Smart Contract Testing - [Deploy to Ethereum Classic](#deploy-to-e...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...t-testing) - Deploy to Ethereum Classic - [Environment Variables](#environment-vari...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...hereum-classic) - Environment Variables - [Community and Support](#community-and-su...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...ation ## Project Structure Chainvoice/ ├── frontend/ # Web application (UI/UX, ...

(QB_NEW_EN)


[grammar] ~63-~63: There might be a mistake here.
Context: ... application (UI/UX, wallet integration) ├── contracts/ # Solidity smart contract...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...y smart contracts (core invoicing logic) ├── docs/ # Documentation and guides └...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...) ├── docs/ # Documentation and guides └── README.md # This file ## Getting St...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...//github.com/yourusername/Chainvoice.git cd Chainvoice ## Frontend Setup 1. **N...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...voice ## Frontend Setup 1. Navigate to frontend directory cd frontend 2. ...

(QB_NEW_EN)


[grammar] ~89-~89: There might be a mistake here.
Context: ... npm run dev 4. Open application Navigate to http://localhost:5173 in y...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...y.sh/) must be installed 1. Navigate to contracts directory cd contracts 3...

(QB_NEW_EN)


[grammar] ~102-~102: There might be a mistake here.
Context: ...4. Run tests with verbosity (optional) forge test -vvv ## Deploy to Ethereum ...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...y to Ethereum Classic ### Prerequisites - Foundry install...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...undry](https://getfoundry.sh/) installed - Wallet funded with ETC - ETC RPC URL (e....

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ....sh/) installed - Wallet funded with ETC - ETC RPC URL (e.g., Rivet, Ankr, Chainsta...

(QB_NEW_EN)


[grammar] ~116-~116: There might be a mistake here.
Context: ...cp contracts/.env.example contracts/.env Edit contracts/.env with your actual val...

(QB_NEW_EN)


[grammar] ~120-~120: There might be a mistake here.
Context: ... 2. Compile contracts cd contracts forge build 3. **Load environment vari...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ... contracts/src/Chainvoice.sol:Chainvoice --rpc-url $ETC_RPC_URL --private-key $PR...

(QB_NEW_EN)


[grammar] ~128-~128: There might be a mistake here.
Context: ...ce.sol:Chainvoice --rpc-url $ETC_RPC_URL --private-key $PRIVATE_KEY --broadcast ...

(QB_NEW_EN)


[grammar] ~134-~134: There might be a mistake here.
Context: ... cp frontend/.env.example frontend/.env Edit frontend/.env and set: VITE_CONTR...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ...ntend/.env Edit frontend/.env and set: VITE_CONTRACT_ADDRESS=your_deployed_cont...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ...ntend development server** cd frontend npm run dev ## Environment Variables ...

(QB_NEW_EN)


[grammar] ~144-~144: There might be a mistake here.
Context: ...Frontend Configuration (frontend/.env) VITE_CONTRACT_ADDRESS=your_deployed_cont...

(QB_NEW_EN)


[grammar] ~145-~145: There might be a mistake here.
Context: ...RESS=your_deployed_contract_address_here VITE_CHAIN_ID=61 VITE_RPC_URL=your_etc...

(QB_NEW_EN)


[grammar] ~146-~146: There might be a mistake here.
Context: ...contract_address_here VITE_CHAIN_ID=61 VITE_RPC_URL=your_etc_rpc_url_here ##...

(QB_NEW_EN)


[grammar] ~149-~149: There might be a mistake here.
Context: ...ntracts Configuration (contracts/.env) PRIVATE_KEY=your_private_key_here ETC_...

(QB_NEW_EN)


[grammar] ~150-~150: There might be a mistake here.
Context: ...nv`) PRIVATE_KEY=your_private_key_here ETC_RPC_URL=your_etc_rpc_endpoint_here ...

(QB_NEW_EN)


[grammar] ~151-~151: There might be a mistake here.
Context: ... ETC_RPC_URL=your_etc_rpc_endpoint_here ETHERSCAN_API_KEY=your_etherscan_api_key...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: ...: - Telegram - t.me/StabilityNexus - Discord - [discord.gg/YzDKeEfWtS](http...

(QB_NEW_EN)


[grammar] ~161-~161: There might be a mistake here.
Context: ...) - Discord - discord.gg/YzDKeEfWtS - X (Twitter) - [@StabilityNexus](https:...

(QB_NEW_EN)


[grammar] ~162-~162: There might be a mistake here.
Context: ...tS) - X (Twitter) - @StabilityNexus - Medium - [news.stability.nexus](https:...

(QB_NEW_EN)


[grammar] ~163-~163: There might be a mistake here.
Context: ...us) - Medium - news.stability.nexus - LinkedIn - [linkedin.com/company/stabi...

(QB_NEW_EN)


[grammar] ~164-~164: There might be a mistake here.
Context: ... - linkedin.com/company/stability-nexus - YouTube - [youtube.com/@StabilityNexus...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
README.md

73-73: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (15)
frontend/package.json (1)

21-28: Validate dependency compatibility (FM v12, Radix, react-window) and bundle impact.

Ensure the app compiles and tree-shakes correctly with framer-motion@^12.23.12, new Radix UI packages, and react-window. Watch for peer dep warnings and unexpected bundle growth.

Also applies to: 37-38, 50-51

frontend/src/components/ui/badge.jsx (1)

1-9: Remove legacy variant shim—no usages found.
A repo-wide search across .js/.jsx/.ts/.tsx files returned zero occurrences of badgeVariants or <Badge variant=; no backward‐compatibility shim is needed.

frontend/src/components/ui/separator.jsx (1)

6-20: LGTM — solid Radix wrapper

Ref-forwarding, default props, and class override order look correct. API shape matches Radix’s expectations.

frontend/src/hooks/useTokenSearch.js (1)

72-76: Exact symbol uniqueness assumption

bySymbol returns only one token; if multiple tokens share a symbol on a chain, others won’t appear as “exact” results. Confirm this is intended.

frontend/src/components/ui/dialog.jsx (2)

26-44: LGTM — accessible dialog composition

Portal + overlay + content setup, focusable close button with sr-only label, and cn integration all look good.


2-3: Dependencies and animation utilities verified

All required packages (@radix-ui/react-dialog, lucide-react, tailwindcss-animate, and @radix-ui/react-separator) are present in frontend/package.json and the animate plugin is registered in tailwind.config.js; no further action needed.

frontend/src/page/SentInvoice.jsx (7)

49-49: Toast usage: ensure ToastContainer is mounted

You import react-toastify; confirm a is rendered at app root to display toasts.


78-90: Token lookup helpers look good

The find-by-address (with contract_address/address) is appropriate for mixed sources.


103-107: Decimals fallback is reasonable

Keeps UI resilient when token list lacks decimals.


331-337: Error message is fine; consider surfacing reason in dev

Keep user-friendly text, but log the original error (already done).


344-344: Dependency on tokens

Including tokens in the effect is correct to re-enrich invoices when token data updates.


590-598: Token logo fallback LGTM

onError fallback to generic token image is correct.


863-871: Drawer logo fallback LGTM

Consistent with table row image handling.

frontend/src/hooks/useTokenList.js (1)

85-86: Return shape is good

Exposing { tokens, loading, error } matches hook conventions.

frontend/src/page/GenerateLink.jsx (1)

48-61: Default token selection logic looks good.

Nice fallback to ETH or first token; handles empty lists cleanly.

@adityabhattad2021 adityabhattad2021 merged commit 139a9ce into StabilityNexus:main Sep 3, 2025
1 check passed
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