Skip to content

Conversation

@kumawatkaran523
Copy link
Contributor

@kumawatkaran523 kumawatkaran523 commented Aug 22, 2025

Implement the ability to generate invoice links with pre-filled data

image image image

Summary by CodeRabbit

  • New Features
    • Added “Generate Link” page to create sharable, prefilled invoice links.
    • Revamped invoice creation: wallet connection prompt, URL-based prefill, searchable token picker with custom ERC‑20 verification, and secure on-chain submission.
    • Enhanced Sent and Received views: richer tables, status chips, token logos, detailed invoice drawer, print/download, and cancellation.
    • Navigation updates: improved active highlighting and new route for Generate Link.
    • Refined toast notifications with clearer styling and timing.
    • Introduced curated token presets for faster selection and testnet support.

@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

Routes updated to use page-based components for CreateInvoice and new GenerateLink. Added wallet-connection alert, token presets, token verification, and URL-prefill flows. Reworked invoice creation to ERC-20-aware, Lit-encrypted on-chain submission. Navbar/Home navigation updated. Sent/Received invoice pages gain wallet gating and richer detail drawers. Toaster customized.

Changes

Cohort / File(s) Summary
App routing & notifications
frontend/src/App.jsx
Switched CreateInvoice import path to page/*, added GenerateLink route, customized Toaster options, and commented out explicit initialChain in RainbowKitProvider.
Component removal
frontend/src/components/CreateInvoice.jsx
Removed legacy CreateInvoice component and its Lit/contract flow implementation.
Navigation updates
frontend/src/components/Navbar.jsx, frontend/src/page/Home.jsx
Simplified redirect logic, added active-route helper, replaced navItems with appItems, introduced Generate Link menu and a restructured Home layout with a left rail.
Wallet connection UX
frontend/src/components/WalletConnectionAlert.jsx
Added a dismissible wallet-connection modal component with motion animations.
Invoice creation (new page)
frontend/src/page/CreateInvoice.jsx
Rewrote CreateInvoice: wallet gating, URL prefill, advanced token picker with ERC-20 verification, itemized invoice, Lit-based encryption, and token-aware on-chain createInvoice call.
Prefill link generator (new page)
frontend/src/page/GenerateLink.jsx
New page to generate prefilled /dashboard/create links; includes token presets, custom token verification, and copy functionality.
Invoices lists & drawers
frontend/src/page/SentInvoice.jsx, frontend/src/page/ReceivedInvoice.jsx
Added wallet alerts, enhanced tables, detailed right-side drawers with token info, dates, items, totals, and actions (cancel/pay/print).
Token presets
frontend/src/utils/erc20_token.js
Added TOKEN_PRESETS dataset listing ERC-20 tokens with metadata (name, symbol, address, decimals, logo).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant W as Wallet (wagmi)
  participant UI as CreateInvoice Page
  participant Lit as Lit Protocol
  participant C as Chainvoice Contract

  U->>UI: Open /dashboard/create (optional URL params)
  UI->>W: Check connection
  alt Not connected
    UI-->>U: Show WalletConnectionAlert
    U->>W: Connect wallet
    W-->>UI: isConnected=true, address
  end

  UI->>UI: Prefill client/token from URL
  U->>UI: Select token (preset/custom) and fill invoice
  alt Custom token
    UI->>W: Read ERC-20 (symbol/name/decimals)
    W-->>UI: Verification result
  end

  U->>UI: Submit invoice
  UI->>Lit: Connect + get session sigs
  UI->>Lit: Encrypt invoice payload (ACL: sender OR client)
  Lit-->>UI: Ciphertext + data hash
  UI->>C: createInvoice(client, amount, token, ciphertext, hash)
  C-->>UI: Tx receipt
  UI-->>U: Navigate to /dashboard/sent
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant W as Wallet (wagmi)
  participant GL as GenerateLink Page

  U->>GL: Open /dashboard/generate-link
  GL->>W: Check connection
  alt Not connected
    GL-->>U: Show WalletConnectionAlert
    U->>W: Connect wallet
    W-->>GL: isConnected=true
  end

  U->>GL: Choose token or enter custom address
  alt Custom token
    GL->>W: Verify ERC-20 (read symbol/decimals)
    W-->>GL: Verified token metadata
  end

  U->>GL: Enter clientAddress, amount, description
  GL-->>U: Generate link with query params
  U->>GL: Copy link to clipboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • adityabhattad2021

Poem

In burrows of code I twitch my nose,
Tokens and links in tidy rows.
I hop through routes with careful care,
Encrypting carrots I must share.
Wallets connected, invoices sing—
With one swift bounce, I copy the link. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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 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: 29

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/App.jsx (1)

27-32: Replace WalletConnect placeholder with an env var (otherwise connects will fail).

Using "YOUR_PROJECT_ID" will break WalletConnect-based connectors in production and most test setups. Read from an env var instead.

Apply this diff:

 export const config = getDefaultConfig({
   appName: "My RainbowKit App",
-  projectId: "YOUR_PROJECT_ID",
+  projectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID,
   chains: AllChains,
   ssr: true,
 });

Additionally (outside this hunk), add a runtime check near the imports:

// Add near the top of the file
if (!import.meta.env.VITE_WALLETCONNECT_PROJECT_ID) {
  // Prefer toast/error reporting if available
  console.warn("VITE_WALLETCONNECT_PROJECT_ID is not set; wallet connections may fail.");
}
🧹 Nitpick comments (39)
frontend/src/page/Treasure.jsx (10)

31-31: Input state type is fine, but consider intent-revealing naming

newFee is stored as a string (correct for passing to ethers.parseUnits). Consider renaming to feeInput for clarity since fee is the on-chain value.


63-89: Strengthen address validation and avoid no-op transactions

  • Trim user input before validation.
  • Use checksum formatting when persisting/displaying.
  • Short-circuit if the new address equals the current one to avoid a wasted transaction and gas.

Apply this diff within this block:

-  if (!ethers.isAddress(newTreasuryAddress)) {
+  const addr = newTreasuryAddress.trim();
+  if (!ethers.isAddress(addr)) {
     alert("Please enter a valid Ethereum address");
     return;
   }
   try {
     if (!walletClient) return;
     setLoading((prev) => ({ ...prev, setAddress: true }));
@@
-  const tx = await contract.setTreasuryAddress(newTreasuryAddress);
+  // Avoid no-op txs
+  if (treasuryAddress && ethers.isAddress(treasuryAddress) && ethers.getAddress(treasuryAddress) === ethers.getAddress(addr)) {
+    alert("New treasury address is the same as the current one.");
+    return;
+  }
+  const checksum = ethers.getAddress(addr);
+  const tx = await contract.setTreasuryAddress(checksum);
   await tx.wait();
-  setTreasuryAddress(newTreasuryAddress);
-  setNewTreasuryAddress("");
+  setTreasuryAddress(checksum);
+  setNewTreasuryAddress("");

And pre-validate at the button level (see lines 256-258 suggestion).


91-113: Improve error messaging for withdraw and guard contract address

  • Surface clearer revert reasons (Ownable, Paused, etc.) by inspecting nested error fields used by popular wallets/providers.
  • Optional: Guard for a missing VITE_CONTRACT_ADDRESS to fail-fast with a helpful message.

Apply this minimal improvement within this block:

-  } catch (error) {
-    console.error("Error withdrawing collection:", error);
-    alert(error.message || "Failed to withdraw funds");
+  } catch (error) {
+    console.error("Error withdrawing collection:", error);
+    const msg =
+      error?.info?.error?.message ||
+      error?.shortMessage ||
+      error?.cause?.message ||
+      error?.message ||
+      "Failed to withdraw funds";
+    alert(msg);

Optionally extract a small helper near the top of the file:

// place above the component
const getReadableError = (error) =>
  error?.info?.error?.message ||
  error?.shortMessage ||
  error?.cause?.message ||
  error?.message ||
  null;

115-144: Harden fee update validation (range, decimals) and error reporting

  • Disallow negative values; optionally disallow zero if business rules require a non-zero fee.
  • Validate up to 18 decimals to match wei precision and provide a clear message before hitting the contract.
  • Use the trimmed value for parseUnits.
  • Improve error surface.

Apply this diff in place:

-  if (!newFee || isNaN(newFee)) {
-    alert("Please enter a valid fee amount");
-    return;
-  }
+  const feeStr = (newFee ?? "").trim();
+  if (feeStr === "") {
+    alert("Please enter a fee amount");
+    return;
+  }
+  if (Number(feeStr) < 0) {
+    alert("Fee cannot be negative");
+    return;
+  }
+  // Up to 18 decimals (ETH precision)
+  if (!/^\d+(?:\.\d{1,18})?$/.test(feeStr)) {
+    alert("Invalid precision: use up to 18 decimal places");
+    return;
+  }
@@
-  const tx = await contract.setFeeAmount(
-    ethers.parseUnits(newFee, "ether")
-  );
+  const tx = await contract.setFeeAmount(
+    ethers.parseUnits(feeStr, "ether")
+  );
@@
-} catch (error) {
-  console.error("Error updating fee:", error);
-  alert(error.message || "Failed to update fee");
+} catch (error) {
+  console.error("Error updating fee:", error);
+  const msg =
+    error?.info?.error?.message ||
+    error?.shortMessage ||
+    error?.cause?.message ||
+    error?.message ||
+    "Failed to update fee";
+  alert(msg);

249-271: Pre-validate address at the button level for better UX

Disable the Update Address button until the input is a valid EVM address. This avoids needless clicks and alerts.

Apply this diff:

-  <Button
-    onClick={handleSetTreasuryAddress}
-    disabled={loading.setAddress || !newTreasuryAddress}
+  <Button
+    onClick={handleSetTreasuryAddress}
+    disabled={loading.setAddress || !ethers.isAddress(newTreasuryAddress.trim())}
     className="w-full bg-green-600 hover:bg-green-700 h-11"
   >

283-289: Make the fee input friendlier and harder to mis-enter

  • Prevent accidental negative/scroll changes.
  • Hint allowed precision to the browser.
  • Keep passing strings to state (good).

Apply this diff:

-  <Input
-    type="number"
-    placeholder={`Current: ${fee} ETH`}
-    value={newFee}
-    onChange={(e) => setNewFee(e.target.value)}
-    className="bg-gray-800 border-gray-700 text-white font-mono"
-  />
+  <Input
+    type="number"
+    inputMode="decimal"
+    min="0"
+    step="0.000000000000000001"
+    pattern="^\d+(\.\d{1,18})?$"
+    placeholder={`Current: ${fee} ETH`}
+    value={newFee}
+    onChange={(e) => setNewFee(e.target.value)}
+    onWheel={(e) => e.currentTarget.blur()}
+    className="bg-gray-800 border-gray-700 text-white font-mono"
+    aria-label="Transaction fee in ETH (up to 18 decimals)"
+  />

291-294: Disable Update Fee unless the input is valid

Mirror the validation logic at the button level to save user clicks.

Apply this diff:

-  <Button
-    onClick={handleUpdateFee}
-    disabled={loading.feeUpdate || !newFee}
+  <Button
+    onClick={handleUpdateFee}
+    disabled={
+      loading.feeUpdate ||
+      (newFee.trim() === "") ||
+      Number(newFee) < 0 ||
+      !/^\d+(?:\.\d{1,18})?$/.test(newFee.trim())
+    }
     className="w-full bg-green-600 hover:bg-green-700 h-11"
   >

309-327: Withdraw button label and enablement are fine; minor naming nit

The UI correctly disables when balance is 0 and refreshes post-withdraw. Minor: variables/file use “Treasure” while the UI says “Treasury.” Consider renaming to Treasury for consistency across the codebase.


1-18: Scope check: file changes don’t reflect “Pre-filled invoice links”

This component focuses on treasury administration (address, fee, withdraw). If the PR’s primary objective is “Pre-filled invoice links,” confirm whether those changes are in other files or if the PR scope broadened.

I can review/link the pre-filled invoice link generator and its routing if you point me to the relevant files.


115-144: Reduce duplication with a shared getContract helper (optional)

You repeat provider/signer/contract wiring in three handlers. A tiny helper improves readability and reduces surface for mistakes (like a future address/ABI change).

Add this utility near the top of the file and adopt it in the handlers:

// place above the component, or inside the component if you need walletClient from state/hook
const CONTRACT_ADDRESS = import.meta.env.VITE_CONTRACT_ADDRESS;

async function getContract(walletClient) {
  if (!walletClient) throw new Error("Wallet not connected");
  if (!CONTRACT_ADDRESS) throw new Error("Contract address is not configured (VITE_CONTRACT_ADDRESS)");
  const provider = new BrowserProvider(walletClient);
  const signer = await provider.getSigner();
  return new Contract(CONTRACT_ADDRESS, ChainvoiceABI, signer);
}

Then replace the repeated blocks with:

const contract = await getContract(walletClient);

Also applies to: 63-89, 91-113

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

9-9: Sticky footer regression: re-add min-h-screen on wrapper

Dropping min-h-screen breaks the "sticky footer" pattern (Footer has mt-auto but the container no longer stretches to viewport height). On short pages the footer will float mid-screen.

Apply:

-    <div className="flex flex-col ">
+    <div className="min-h-screen flex flex-col">

Optional: consider min-h-dvh to better handle mobile dynamic viewport heights.

frontend/src/components/Footer.jsx (1)

13-15: Commented-out copyright: either remove dead code or gate via config

The JSX comment disables the copyright block entirely. If this is intentional due to the new footer on Landing, remove the dead code; otherwise, make it conditional via a prop or site config.

Example:

-                    {/* <p className="text-sm my-3">
-                        &copy; {new Date().getFullYear()} Chainvoice. All rights reserved.
-                    </p> */}
+                    {showCopyright && (
+                      <p className="text-sm my-3">
+                        &copy; {new Date().getFullYear()} Chainvoice. All rights reserved.
+                      </p>
+                    )}
frontend/src/components/TokenIntegrationRequest.jsx (1)

1-16: Optional: pre-fill the request with context (token address)

You already accept address; consider appending it as a query param so users submit a pre-filled request.

Example:

-function TokenIntegrationRequest({ address }) {
+function TokenIntegrationRequest({ address }) {
   return (
     <div className="text-xs text-gray-500 pt-2">
       Using this token frequently?{" "}
       <a
-        href="https://stability.nexus/"
+        href={`https://stability.nexus/?token=${encodeURIComponent(address ?? "")}`}
         target="_blank"
         rel="noopener noreferrer"
         className="text-blue-500 hover:underline"
       >
         Request adding to default list
       </a>
     </div>
   );
 }

If the URL differs, I can wire this to your actual intake form.

frontend/src/utils/erc20_token.js (1)

8-716: Optional: add chainId and checksum addresses

Some entries are non-mainnet or wrappers; consider adding chainId and converting all addresses to EIP‑55 checksummed casing at build time to avoid copy/paste errors and improve UX.

frontend/src/components/Navbar.jsx (2)

70-89: Clean up commented-out code

The commented-out navItems array should be removed if it's no longer needed. Keeping dead code makes the codebase harder to maintain.

Remove lines 70-89 if this navigation structure is no longer used.


176-201: Simplify the Dashboard navigation logic

The conditional rendering for the Dashboard item adds unnecessary complexity. Consider extracting this logic to make the code more maintainable.

Extract the navigation logic into a helper function:

+const getNavigationPath = (item) => {
+  if (item.path === "/dashboard" && location.pathname === "/dashboard") {
+    return "/dashboard/create";
+  }
+  return item.path;
+};

 {isConnected &&
   appItems.map((item) => (
     <motion.div
       key={item.name}
       whileHover={{ y: -2 }}
       whileTap={{ scale: 0.95 }}
     >
-      {item?.path === "/dashboard" ? (
-        <Link
-          to="/dashboard/create"
-          className={`flex items-center px-4 py-2 rounded-lg transition-colors ${
-            isActive(item.path)
-              ? "text-green-400 font-medium"
-              : "text-white hover:text-green-400"
-          }`}
-        >
-          <span className="mr-2">{item.icon}</span>
-          {item.name}
-        </Link>
-      ) : (
-        <Link
-          to={item.path}
-          className={`flex items-center px-4 py-2 rounded-lg transition-colors ${
-            isActive(item.path)
-              ? "text-green-400 font-medium"
-              : "text-white hover:text-green-400"
-          }`}
-        >
-          <span className="mr-2">{item.icon}</span>
-          {item.name}
-        </Link>
-      )}
+      <Link
+        to={getNavigationPath(item)}
+        className={`flex items-center px-4 py-2 rounded-lg transition-colors ${
+          isActive(item.path)
+            ? "text-green-400 font-medium"
+            : "text-white hover:text-green-400"
+        }`}
+      >
+        <span className="mr-2">{item.icon}</span>
+        {item.name}
+      </Link>
     </motion.div>
   ))}
frontend/src/page/Landing.jsx (1)

305-345: Non-functional footer links

All footer links use href="#" which doesn't provide any functionality. Consider either implementing proper navigation or removing these links if they're not ready.

Either implement proper routing or add a "Coming Soon" indicator to set proper user expectations.

contracts/src/Chainvoice.sol (2)

4-15: Consider using OpenZeppelin's IERC20 interface

Instead of defining a custom IERC20 interface, consider importing from OpenZeppelin for better standardization and to include all standard ERC20 functions.

-interface IERC20 {
-    function transferFrom(
-        address sender,
-        address recipient,
-        uint256 amount
-    ) external returns (bool);
-    function balanceOf(address account) external view returns (uint256);
-    function allowance(
-        address owner,
-        address spender
-    ) external view returns (uint256);
-}
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

196-206: Redundant balance and allowance checks

The function checks both balance and allowance twice for the same condition, which is inefficient.

Optimize the checks:

 } else {
+    uint256 balance = IERC20(invoice.tokenAddress).balanceOf(payer);
+    uint256 allowance = IERC20(invoice.tokenAddress).allowance(payer, address(this));
     return (
-        IERC20(invoice.tokenAddress).balanceOf(payer) >=
-            invoice.amountDue &&
-            IERC20(invoice.tokenAddress).allowance(
-                payer,
-                address(this)
-            ) >=
-            invoice.amountDue,
-        IERC20(invoice.tokenAddress).balanceOf(payer),
-        IERC20(invoice.tokenAddress).allowance(payer, address(this))
+        balance >= invoice.amountDue && allowance >= invoice.amountDue,
+        balance,
+        allowance
     );
 }
frontend/src/App.jsx (1)

25-25: Limit configured chains to the ones you actually support to reduce bundle size and SSR overhead.

[...Object.values(chains)] registers every chain (including many you won’t use), bloating the config. Prefer an explicit list.

Apply this diff:

-const AllChains = [...Object.values(chains), citreaTestnet];
+const AllChains = [chains.mainnet, citreaTestnet]; // adjust to the minimal set you support
frontend/src/page/Home.jsx (1)

90-106: Avoid substring matching for selected state; use path-segment aware check.

includes(item.route) can produce false positives (e.g., "sent" matching "present"). Prefer matching the /dashboard/${route} prefix.

Apply this diff:

- selected={location.pathname.includes(item.route)}
+ selected={location.pathname.startsWith(`/dashboard/${item.route}`)}
frontend/src/components/TokenSelector.jsx (2)

3-3: Use the app’s Label component for consistent styling.

Importing from Radix directly bypasses your ui/label variants.

Apply this diff:

-import { Label } from "@radix-ui/react-label";
+import { Label } from "./ui/label";

21-33: Component is self-contained but currently “write-only” (no way to lift selection up).

If this is intended to be shared, expose value, onChange, and an optional onVerifyToken(address) to allow parents to control state and plug verification. Otherwise, document that it’s demo-only.

I can convert this to a controlled component with minimal API: { value, onChange, onVerifyToken }.

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

54-57: Consolidate useAccount usage and remove duplication.

You’re calling useAccount() twice and then accessing account.address repeatedly.

Apply this diff:

-const { data: walletClient } = useWalletClient();
-const { isConnected } = useAccount();
-const account = useAccount();
+const { data: walletClient } = useWalletClient();
+const { address, isConnected } = useAccount();

Then replace account?.address/account.address with address elsewhere (see suggested diffs below).


1088-1100: Render the computed amount (BigInt → string), not a separate float recomputation.

Prevents rounding drift and keeps UI aligned with the reducer’s total.

Apply this diff:

-                    <Input
+                    <Input
                       type="text"
                       placeholder="0.00"
                       className="w-full bg-gray-50 border-gray-300 text-gray-700 py-2"
                       name="amount"
                       disabled
-                      value={
-                        (parseFloat(itemData[index].qty) || 0) *
-                          (parseFloat(itemData[index].unitPrice) || 0) -
-                        (parseFloat(itemData[index].discount) || 0) +
-                        (parseFloat(itemData[index].tax) || 0)
-                      }
+                      value={itemData[index].amount || "0.00"}
                     />

27-27: Minor: import startOfDay and compare dates without time to avoid disabling “today.”

date < new Date() disables earlier hours today. Use startOfDay.

Apply this diff:

-import { format } from "date-fns";
+import { format, startOfDay } from "date-fns";

And update the calendar disabled prop (see next comment).


498-499: Disable past dates correctly (not earlier hours of the same day).

Apply this diff:

-                  disabled={(date) => date < new Date()}
+                  disabled={(date) => date < startOfDay(new Date())}

512-516: Use the consolidated address from useAccount.

Follow-up to the earlier refactor; keeps the dependency surface small.

Apply this diff:

-              <Input
-                value={account?.address}
+              <Input
+                value={address}
                 className="w-full mb-4 bg-gray-50 border-gray-300 text-gray-500"
                 readOnly
                 name="userAddress"
               />

105-105: Remove debug log.

Avoid leaking addresses in console in production.

Apply this diff:

-    console.log("account address : ", account.address);
+    // console.debug("account address:", address);
frontend/src/page/GenerateLink.jsx (3)

363-371: Add image fallback for selected token logo (consistent with lists).

The list items already use a fallback to a generic image on error; replicate here to avoid broken images.

Apply this diff:

-                            <img
-                              src={selectedToken.logo}
-                              alt={selectedToken.name}
-                              width={32}
-                              height={32}
-                              className="object-contain"
-                            />
+                            <img
+                              src={selectedToken.logo}
+                              alt={selectedToken.name}
+                              width={32}
+                              height={32}
+                              className="object-contain"
+                              onError={(e) => {
+                                e.currentTarget.src = "/tokenImages/generic.png";
+                              }}
+                            />

70-75: Minor UX: allow amount/description in the link even when no token is selected.

Currently, when no token is chosen you drop back to only clientAddress. Including amount/description improves the “prefilled” value of the link.

Apply this diff:

-    if (!tokenToUse) {
-      return `${window.location.origin}/dashboard/create?clientAddress=${address || ""}`;
-    }
+    if (!tokenToUse) {
+      const params = new URLSearchParams({ clientAddress: address || "" });
+      if (amount) params.append("amount", amount);
+      if (description) params.append("description", description);
+      return `${window.location.origin}/dashboard/create?${params.toString()}`;
+    }

Also applies to: 83-91


127-131: Remove unused message prop from WalletConnectionAlert

The WalletConnectionAlert component is defined as:

const WalletConnectionAlert = ({ show, onDismiss }) => {
  
}

It does not accept or render a message prop, so passing it is a no-op and adds confusion. Please remove the message attribute from every usage.

Files and locations to update:

  • frontend/src/page/SentInvoice.jsx (line ~360)
  • frontend/src/page/ReceivedInvoice.jsx (line ~429)
  • frontend/src/page/CreateInvoice.jsx (line ~406)
  • frontend/src/page/GenerateLink.jsx (line ~127)

Apply the following change in each file:

-          message="Connect your wallet to create and manage invoices"
frontend/src/page/ReceivedInvoice.jsx (4)

111-118: Prefer window.ethereum for BrowserProvider; walletClient may not be EIP‑1193.

This mirrors CreateInvoice and avoids subtle provider mismatches.

Apply this diff:

-        const provider = new BrowserProvider(walletClient);
+        const provider = new BrowserProvider(window.ethereum);
         const signer = await provider.getSigner();
         const network = await provider.getNetwork();

If you intentionally use walletClient, please confirm it satisfies EIP‑1193 in your wagmi version.


115-118: Be explicit about chainId type to avoid coercion pitfalls.

getNetwork().chainId is a BigInt in ethers v6. Compare with a BigInt literal to be precise.

Apply this diff:

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

323-346: Use toasts instead of blocking alerts for approvals/payments.

Elsewhere you already use react-toastify for error cases. Replace alert() with non-blocking toasts for consistent UX.

Apply this diff:

-          await approveTx.wait();
-          alert(
-            `Approval for ${tokenSymbol} completed! Now processing payment...`
-          );
+          await approveTx.wait();
+          toast.success(`Approval for ${tokenSymbol} completed! Now processing payment...`);
@@
-        await tx.wait();
-        alert(`Payment successful in ${tokenSymbol}!`);
+        await tx.wait();
+        toast.success(`Payment successful in ${tokenSymbol}!`);
@@
-        await tx.wait();
-        alert("Payment successful in ETH!");
+        await tx.wait();
+        toast.success("Payment successful in ETH!");

429-433: WalletConnectionAlert message prop likely unused.

See component signature; remove the prop here or extend the component to render custom messaging.

-        <WalletConnectionAlert
-          show={showWalletAlert}
-          message="Connect your wallet to create and manage invoices"
-          onDismiss={() => setShowWalletAlert(false)}
-        />
+        <WalletConnectionAlert show={showWalletAlert} onDismiss={() => setShowWalletAlert(false)} />
frontend/src/page/SentInvoice.jsx (3)

115-125: Use window.ethereum for BrowserProvider and compare chainId as BigInt.

Align provider usage with other pages and avoid type coercion.

Apply this diff:

-        const provider = new BrowserProvider(walletClient);
+        const provider = new BrowserProvider(window.ethereum);
         const signer = await provider.getSigner();
-        const network = await provider.getNetwork();
+        const network = await provider.getNetwork();
@@
-        if (network.chainId != 11155111) {
+        if (network.chainId !== 11155111n) {

359-365: WalletConnectionAlert message prop likely unused.

Mirror the fix applied in other pages.

-        <WalletConnectionAlert
-          show={showWalletAlert}
-          message="Connect your wallet to create and manage invoices"
-          onDismiss={() => setShowWalletAlert(false)}
-        />
+        <WalletConnectionAlert show={showWalletAlert} onDismiss={() => setShowWalletAlert(false)} />

25-26: Nit: Unused imports/state.

ERC20_ABI and paymentLoading are only used for cancellation spinner? If the ERC20 flow isn’t used here, consider removing to keep the surface lean.

If unused, remove:

- import { ERC20_ABI } from "@/contractsABI/ERC20_ABI";

and any dead state related to payments not used in this page.

Also applies to: 70-74

📜 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 3c43534 and 2670801.

⛔ Files ignored due to path filters (7)
  • frontend/public/dashboard.png is excluded by !**/*.png
  • frontend/public/lit-protocol-diagram.png is excluded by !**/*.png
  • frontend/public/lit-protocol-logo.png is excluded by !**/*.png
  • frontend/public/token-select.png is excluded by !**/*.png
  • frontend/public/tokenImages/cin.png is excluded by !**/*.png
  • frontend/public/tokenImages/eth.png is excluded by !**/*.png
  • frontend/public/tokenImages/generic.png is excluded by !**/*.png
📒 Files selected for processing (22)
  • contracts/src/Chainvoice.sol (6 hunks)
  • frontend/package.json (2 hunks)
  • frontend/src/App.jsx (3 hunks)
  • frontend/src/components/CreateInvoice.jsx (0 hunks)
  • frontend/src/components/Footer.jsx (1 hunks)
  • frontend/src/components/Navbar.jsx (6 hunks)
  • frontend/src/components/TokenCrousel.jsx (1 hunks)
  • frontend/src/components/TokenIntegrationRequest.jsx (1 hunks)
  • frontend/src/components/TokenSelector.jsx (1 hunks)
  • frontend/src/components/WalletConnectionAlert.jsx (1 hunks)
  • frontend/src/components/ui/select.jsx (1 hunks)
  • frontend/src/contractsABI/ChainvoiceABI.js (10 hunks)
  • frontend/src/contractsABI/ERC20_ABI.js (1 hunks)
  • frontend/src/page/Applayout.jsx (1 hunks)
  • frontend/src/page/CreateInvoice.jsx (1 hunks)
  • frontend/src/page/GenerateLink.jsx (1 hunks)
  • frontend/src/page/Home.jsx (3 hunks)
  • frontend/src/page/Landing.jsx (1 hunks)
  • frontend/src/page/ReceivedInvoice.jsx (5 hunks)
  • frontend/src/page/SentInvoice.jsx (5 hunks)
  • frontend/src/page/Treasure.jsx (4 hunks)
  • frontend/src/utils/erc20_token.js (1 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/components/CreateInvoice.jsx
🧰 Additional context used
🧬 Code graph analysis (11)
frontend/src/page/ReceivedInvoice.jsx (4)
frontend/src/page/SentInvoice.jsx (19)
  • columns (50-57)
  • page (60-60)
  • rowsPerPage (61-61)
  • walletClient (62-62)
  • useAccount (63-63)
  • loading (64-64)
  • fee (66-66)
  • error (67-67)
  • litReady (68-68)
  • litClientRef (69-69)
  • paymentLoading (70-70)
  • networkLoading (71-71)
  • showWalletAlert (74-74)
  • drawerState (272-275)
  • switchNetwork (330-344)
  • formatAddress (346-350)
  • formatDate (352-355)
  • toggleDrawer (277-289)
  • handlePrint (291-302)
frontend/src/utils/erc20_token.js (2)
  • TOKEN_PRESETS (8-716)
  • TOKEN_PRESETS (8-716)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
frontend/src/components/WalletConnectionAlert.jsx (1)
  • WalletConnectionAlert (7-56)
frontend/src/components/ui/select.jsx (1)
frontend/src/lib/utils.js (1)
  • cn (4-6)
frontend/src/App.jsx (1)
frontend/src/page/GenerateLink.jsx (1)
  • GenerateLink (30-545)
frontend/src/page/Landing.jsx (1)
frontend/src/components/TokenCrousel.jsx (1)
  • TokenCarousel (6-82)
frontend/src/page/CreateInvoice.jsx (5)
frontend/src/page/GenerateLink.jsx (2)
  • walletClient (32-32)
  • verifyToken (103-123)
frontend/src/utils/erc20_token.js (2)
  • TOKEN_PRESETS (8-716)
  • TOKEN_PRESETS (8-716)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
frontend/src/contractsABI/ChainvoiceABI.js (2)
  • ChainvoiceABI (1-589)
  • ChainvoiceABI (1-589)
frontend/src/components/WalletConnectionAlert.jsx (1)
  • WalletConnectionAlert (7-56)
frontend/src/page/GenerateLink.jsx (3)
frontend/src/page/CreateInvoice.jsx (4)
  • walletClient (54-54)
  • tokenVerificationState (73-73)
  • verifiedToken (74-74)
  • verifyToken (212-235)
frontend/src/utils/erc20_token.js (2)
  • TOKEN_PRESETS (8-716)
  • TOKEN_PRESETS (8-716)
frontend/src/components/WalletConnectionAlert.jsx (1)
  • WalletConnectionAlert (7-56)
frontend/src/components/TokenSelector.jsx (5)
frontend/src/utils/erc20_token.js (2)
  • TOKEN_PRESETS (8-716)
  • TOKEN_PRESETS (8-716)
frontend/src/components/ui/label.jsx (1)
  • Label (11-13)
frontend/src/components/ui/select.jsx (5)
  • Select (7-7)
  • SelectTrigger (13-26)
  • SelectValue (11-11)
  • SelectContent (50-71)
  • SelectItem (82-97)
frontend/src/components/ui/input.jsx (1)
  • Input (5-16)
frontend/src/components/ui/button.jsx (1)
  • Button (37-45)
frontend/src/page/Home.jsx (2)
frontend/src/page/CreateInvoice.jsx (1)
  • navigate (60-60)
frontend/src/components/Navbar.jsx (2)
  • navigate (17-17)
  • location (18-18)
frontend/src/components/Navbar.jsx (1)
frontend/src/page/Home.jsx (1)
  • location (18-18)
frontend/src/page/Treasure.jsx (3)
frontend/src/contractsABI/ChainvoiceABI.js (2)
  • ChainvoiceABI (1-589)
  • ChainvoiceABI (1-589)
frontend/src/components/ui/input.jsx (1)
  • Input (5-16)
frontend/src/components/ui/button.jsx (1)
  • Button (37-45)
frontend/src/page/SentInvoice.jsx (3)
frontend/src/page/ReceivedInvoice.jsx (19)
  • columns (46-53)
  • page (56-56)
  • rowsPerPage (57-57)
  • walletClient (58-58)
  • useAccount (59-59)
  • loading (60-60)
  • fee (62-62)
  • error (63-63)
  • litReady (64-64)
  • litClientRef (65-65)
  • paymentLoading (66-66)
  • networkLoading (67-67)
  • showWalletAlert (68-68)
  • drawerState (367-370)
  • switchNetwork (399-413)
  • formatAddress (415-419)
  • formatDate (421-424)
  • toggleDrawer (372-384)
  • handlePrint (386-397)
frontend/src/contractsABI/ChainvoiceABI.js (2)
  • ChainvoiceABI (1-589)
  • ChainvoiceABI (1-589)
frontend/src/components/WalletConnectionAlert.jsx (1)
  • WalletConnectionAlert (7-56)
🔇 Additional comments (20)
frontend/src/page/Treasure.jsx (5)

13-17: Icon updates and framer-motion import look good

No issues spotted with the icon name changes or the framer-motion import. Tree-shakeable and consistent with the rest of the file.


23-28: Loading state extension is consistent

Adding the feeUpdate flag aligns with your async handling pattern for other actions. Good separation of concerns.


148-156: UI motion wrappers are fine

Animations are subtle and won’t block interactions. Nice touch.


173-205: Admin-only notice is good; consider gating controls when not owner

You already state “Admin Access Only.” If you have an owner check in-app, you can proactively disable/hide the controls to reduce revert attempts and save users gas.

Would you like me to propose a small hook that checks contract.owner() against the connected account and gates the controls?


188-199: Confirm fee denomination across payment types

UI labels the fee as ETH, and setFeeAmount uses wei (parseUnits with "ether"). If invoices can be in ERC‑20 tokens, confirm that the fee is always denominated in the native token (ETH) and not per-token units.

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

11-11: Verify top padding vs Navbar height

Padding reduced to pt-20. Ensure Navbar’s actual rendered height still prevents content from sliding under it across breakpoints.

frontend/src/utils/erc20_token.js (1)

17-22: Zero address native ETH: ensure consumers special-case it (no ERC‑20 calls)

The zero address is often used to represent native ETH. Verify that selection, verification, and amount-formatting flows don’t attempt ERC‑20 contract calls (symbol/decimals/balanceOf/allowance) for this entry.

I can scan CreateInvoice/GenerateLink/TokenSelector for such calls if you want a verification script.

frontend/src/components/Navbar.jsx (1)

24-32: LGTM! Improved active route detection

The enhanced isActive function now correctly handles both standard paths and hash-based anchor navigation, ensuring proper highlighting for nested routes.

frontend/package.json (1)

23-23: LGTM! New UI dependencies added appropriately

The addition of @radix-ui/react-select and react-toastify aligns well with the new features for token selection and user notifications.

Also applies to: 46-46

frontend/src/App.jsx (3)

68-69: Confirm intended default network; initialChain is commented out.

With initialChain commented, RainbowKit defaults may not match your intended flow (e.g., citreaTestnet). If the prefills/UX assumes a specific chain, set it explicitly.

Would you like me to wire initialChain={citreaTestnet} and add a "Wrong network" guard in Create/Generate pages?


40-63: Toast config looks solid and consistent.

Nice accessibility and theming choices; status-specific icon colors are a good touch.


83-87: Route wiring for Generate Link is correct.

Nested under /dashboard, consistent with the left-rail navigation.

frontend/src/contractsABI/ERC20_ABI.js (1)

1-10: Minimal ERC‑20 ABI is accurate and fits the current usage.

Contains the read functions you call (name/symbol/decimals) and standard transfers/approve. No issues spotted.

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

39-44: Nav entry for Generate Link fits the pattern and styling.

Name/icon/route align with App routes; color coding consistent.


64-82: Drawer container styling is clear; no elevation/border keeps the left rail clean.

Good use of Paper overrides to make it layout-friendly.

frontend/src/components/TokenSelector.jsx (1)

97-131: Popular/All filtering UX is good; image fallback protects from broken logos.

Nice details (fallback to generic.png, duplicates avoided).

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

352-353: Confirm ciphertext encoding; btoa may throw for non‑latin1 data.

If ciphertext is a Uint8Array (common), btoa will throw. Convert via String.fromCharCode(...bytes) or use a base64 helper.

I can swap to a safe conversion:

// Safe Uint8Array → base64
const toBase64 = (u8) => btoa(String.fromCharCode(...u8));
const encryptedStringBase64 = ArrayBuffer.isView(ciphertext)
  ? toBase64(ciphertext)
  : (typeof ciphertext === "string" ? ciphertext : String(ciphertext));

Would you like me to apply this change?


1000-1019: Note rendering is good; for custom tokens, show note only after successful verification.

Current branch already does this; good UX detail.

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

50-71: LGTM — solid, accessible Radix Select wrapper.

Good use of forwardRef, portals, and Tailwind classes. Animations and viewport sizing align with Radix patterns.

frontend/src/contractsABI/ChainvoiceABI.js (1)

20-33: ABI updates look coherent and align with UI flows.

Token support, cancellation, and getPaymentStatus additions mirror the frontend changes. Field ordering matches how indexes are accessed across pages.

Also applies to: 145-178, 490-521, 543-551, 579-588

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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/page/CreateInvoice.jsx (1)

353-367: Be explicit about contract address existence and pass normalized decimals to parseUnits.

createInvoice will throw if VITE_CONTRACT_ADDRESS is unset; parseUnits can misbehave if decimals are undefined.

Apply this diff:

-      const encryptedStringBase64 = btoa(ciphertext);
+      const encryptedStringBase64 = btoa(ciphertext);
 
-      const contract = new Contract(
-        import.meta.env.VITE_CONTRACT_ADDRESS,
+      const contractAddress = import.meta.env.VITE_CONTRACT_ADDRESS;
+      if (!contractAddress) {
+        throw new Error("VITE_CONTRACT_ADDRESS is not configured");
+      }
+      const contract = new Contract(
+        contractAddress,
         ChainvoiceABI,
         signer
       );
 
-      const tx = await contract.createInvoice(
+      const decimals =
+        Number(paymentToken.decimals ?? paymentToken.decimal ?? 18);
+      const tx = await contract.createInvoice(
         data.clientAddress,
-        ethers.parseUnits(totalAmountDue.toString(), paymentToken.decimals),
+        ethers.parseUnits(totalAmountDue.toString(), decimals),
         paymentToken.address,
         encryptedStringBase64,
         dataToEncryptHash
       );
♻️ Duplicate comments (7)
frontend/src/page/CreateInvoice.jsx (5)

135-148: “TAX(%)” is treated as a flat amount; compute as a percentage and make computed BigInt the single source of truth for display.

Totals are wrong because tax is added as an absolute number. Compute percentage tax per line item and use the BigInt-derived amount for the read-only Amount input.

Apply these diffs:

  1. Totals useEffect (tax as percent):
   useEffect(() => {
     const total = itemData.reduce((sum, item) => {
       const qty = parseUnits(item.qty || "0", 18);
       const unitPrice = parseUnits(item.unitPrice || "0", 18);
       const discount = parseUnits(item.discount || "0", 18);
-      const tax = parseUnits(item.tax || "0", 18);
       const lineTotal = (qty * unitPrice) / parseUnits("1", 18);
-      const adjusted = lineTotal - discount + tax;
+      const taxBps = BigInt(Math.round(parseFloat(item.tax || "0") * 100)); // basis points (2dp)
+      const taxAmount = (lineTotal * taxBps) / 10000n;
+      const adjusted = lineTotal - discount + taxAmount;
 
       return sum + adjusted;
     }, 0n);
 
     setTotalAmountDue(formatUnits(total, 18));
   }, [itemData]);
  1. Line-item recomputation (keep in sync and write into item.amount):
         if (
           name === "qty" ||
           name === "unitPrice" ||
           name === "discount" ||
           name === "tax"
         ) {
           const qty = parseUnits(updatedItem.qty || "0", 18);
           const unitPrice = parseUnits(updatedItem.unitPrice || "0", 18);
           const discount = parseUnits(updatedItem.discount || "0", 18);
-          const tax = parseUnits(updatedItem.tax || "0", 18);
-
           const lineTotal = (qty * unitPrice) / parseUnits("1", 18);
-          const finalAmount = lineTotal - discount + tax;
+          const taxBps = BigInt(Math.round(parseFloat(updatedItem.tax || "0") * 100));
+          const taxAmount = (lineTotal * taxBps) / 10000n;
+          const finalAmount = lineTotal - discount + taxAmount;
 
           updatedItem.amount = formatUnits(finalAmount, 18);
         }
  1. Amount input shows computed amount:
-                    <Input
+                    <Input
                       type="text"
                       placeholder="0.00"
                       className="w-full bg-gray-50 border-gray-300 text-gray-700 py-2"
                       name="amount"
                       disabled
-                      value={
-                        (parseFloat(itemData[index].qty) || 0) *
-                          (parseFloat(itemData[index].unitPrice) || 0) -
-                        (parseFloat(itemData[index].discount) || 0) +
-                        (parseFloat(itemData[index].tax) || 0)
-                      }
+                      value={itemData[index].amount}
                     />

Also applies to: 168-196, 1080-1101


212-236: Token verification should prefer the connected wallet provider; fall back to window.ethereum.

This avoids breaking on WalletConnect/embedded providers and aligns with GenerateLink.jsx.

Apply this diff:

-  const verifyToken = async (address) => {
-    setTokenVerificationState("verifying");
+  const verifyToken = async (addr) => {
+    if (!ethers.isAddress(addr)) {
+      setTokenVerificationState("idle");
+      setVerifiedToken(null);
+      return;
+    }
+    setTokenVerificationState("verifying");
 
     try {
-      if (typeof window !== "undefined" && window.ethereum) {
-        const provider = new BrowserProvider(window.ethereum);
-        const contract = new ethers.Contract(address, ERC20_ABI, provider);
-
-        const [symbol, name, decimals] = await Promise.all([
-          contract.symbol().catch(() => "UNKNOWN"),
-          contract.name().catch(() => "Unknown Token"),
-          contract.decimals().catch(() => 18),
-        ]);
-
-        setVerifiedToken({ address, symbol, name, decimals });
-        setTokenVerificationState("success");
-      } else {
-        console.error("No Ethereum provider found");
-        setTokenVerificationState("error");
-      }
+      const provider = walletClient
+        ? new BrowserProvider(walletClient)
+        : (typeof window !== "undefined" && window.ethereum)
+          ? new BrowserProvider(window.ethereum)
+          : null;
+      if (!provider) throw new Error("No EIP-1193 provider found");
+      const contract = new Contract(addr, ERC20_ABI, provider);
+      const [symbol, name, decimals] = await Promise.all([
+        contract.symbol().catch(() => "UNKNOWN"),
+        contract.name().catch(() => "Unknown Token"),
+        contract.decimals().catch(() => 18),
+      ]);
+      setVerifiedToken({ address: addr, symbol, name, decimals });
+      setTokenVerificationState("success");
     } catch (error) {
       console.error("Verification failed:", error);
       setTokenVerificationState("error");
     }
   };

238-260: Guard against invalid client address and unverified/invalid token before encrypting/calling the contract.

Without this, paymentToken may be null/invalid and cause runtime errors or failed transactions.

Apply this diff:

   const createInvoiceRequest = async (data) => {
     if (!isConnected || !walletClient) {
       alert("Please connect your wallet");
       return;
     }
 
     try {
       setLoading(true);
-      const provider = new BrowserProvider(walletClient);
-      const signer = await provider.getSigner();
-
-      const paymentToken = useCustomToken ? verifiedToken : selectedToken;
+      // Resolve and validate token and client addresses early
+      const paymentToken = useCustomToken ? verifiedToken : selectedToken;
+      if (!ethers.isAddress(data.clientAddress)) {
+        alert("Invalid client address");
+        setLoading(false);
+        return;
+      }
+      if (!ethers.isAddress(paymentToken?.address)) {
+        alert("Invalid or missing payment token address");
+        setLoading(false);
+        return;
+      }
+      if (useCustomToken && !verifiedToken) {
+        alert("Please verify the custom token before creating the invoice");
+        setLoading(false);
+        return;
+      }
+      const provider = new BrowserProvider(walletClient);
+      const signer = await provider.getSigner();

251-260: Normalize decimals: handle presets that use decimal vs decimals.

Some presets use decimal. Number(paymentToken.decimals) can become NaN, breaking payloads and parseUnits later.

Apply this diff:

         paymentToken: {
           address: paymentToken.address,
           symbol: paymentToken.symbol,
-          decimals: Number(paymentToken.decimals),
+          decimals: Number(paymentToken.decimals ?? paymentToken.decimal ?? 18),
         },

103-134: Prefill link ignores amount/description; also remove debug log.

The PR objective mentions “Pre‑filled invoice links,” but only clientAddress/tokenAddress are applied. amount and description from the URL should prefill the first line item. Also drop the debug console log.

Apply this diff:

   useEffect(() => {
-    console.log("account address : ", account.address);
     const urlClientAddress = searchParams.get("clientAddress");
     const urlTokenAddress = searchParams.get("tokenAddress");
     const isCustomFromURL = searchParams.get("customToken") === "true";
+    const urlAmount = searchParams.get("amount");
+    const urlDescription = searchParams.get("description");
 
     if (urlClientAddress) {
       setClientAddress(urlClientAddress);
     }
 
+    // Prefill first line item from URL, defaulting qty=1
+    if (urlAmount || urlDescription) {
+      setItemData((prev) => {
+        const base =
+          prev[0] ??
+          {
+            description: "",
+            qty: "",
+            unitPrice: "",
+            discount: "",
+            tax: "",
+            amount: "",
+          };
+        return [
+          {
+            ...base,
+            description: urlDescription ?? base.description,
+            qty: base.qty || "1",
+            unitPrice: urlAmount ?? base.unitPrice,
+          },
+          ...prev.slice(1),
+        ];
+      });
+    }
frontend/src/page/SentInvoice.jsx (2)

322-326: Missing toast import causes runtime ReferenceError.

toast.success / toast.error are used but not imported in this file.

Apply this diff near the import block:

+import { toast } from "react-toastify";
+import "react-toastify/dist/ReactToastify.css";

347-351: formatAddress can crash on undefined addresses. Guard inside the helper.

Same issue as in ReceivedInvoice.

Apply this diff:

-const formatAddress = (address) => {
-    return `${address.substring(0, 10)}...${address.substring(
-      address.length - 10
-    )}`;
-  };
+const formatAddress = (address) => {
+  if (!address) return "—";
+  const start = address.slice(0, 10);
+  const end = address.slice(-10);
+  return `${start}...${end}`;
+};
🧹 Nitpick comments (9)
frontend/src/page/CreateInvoice.jsx (8)

113-131: Validate tokenAddress from URL before verification.

If tokenAddress is malformed, verifyToken runs and immediately errors. Guard with ethers.isAddress and only call verifyToken for valid addresses; otherwise fall back to custom-token mode without verification.

Apply this diff:

-    if (urlTokenAddress) {
+    if (urlTokenAddress) {
+      const isValidTokenAddr = ethers.isAddress(urlTokenAddress);
       if (isCustomFromURL) {
         setUseCustomToken(true);
-        setCustomTokenAddress(urlTokenAddress);
-        verifyToken(urlTokenAddress);
+        setCustomTokenAddress(urlTokenAddress);
+        if (isValidTokenAddr) verifyToken(urlTokenAddress);
       } 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);
+          if (isValidTokenAddr) verifyToken(urlTokenAddress);
         }
       }
     }

1062-1087: Use numeric inputs for unit fields and constrain step.

unitPrice/discount/tax are monetary/percentage values; type="number" with inputMode/step improves UX and reduces parsing errors.

Apply this diff:

-                    <Input
-                      type="text"
+                    <Input
+                      type="number"
+                      inputMode="decimal"
+                      step="0.01"
                       placeholder="0"
                       className="w-full border-gray-300 text-black py-2"
                       name="unitPrice"
                       onChange={(e) => handleItemData(e, index)}
                     />
...
-                    <Input
-                      type="text"
+                    <Input
+                      type="number"
+                      inputMode="decimal"
+                      step="0.01"
                       placeholder="0"
                       className="w-full border-gray-300 text-black py-2"
                       name="discount"
                       onChange={(e) => handleItemData(e, index)}
                     />
...
-                    <Input
-                      type="text"
+                    <Input
+                      type="number"
+                      inputMode="decimal"
+                      step="0.01"
                       placeholder="0"
                       className="w-full border-gray-300 text-black py-2"
                       name="tax"
                       onChange={(e) => handleItemData(e, index)}
                     />

Also applies to: 1071-1077, 1080-1086


31-39: Avoid deep import from @lit-protocol/encryption internals.

Importing from src/lib/encryption.js is brittle. Use the package’s public entrypoint for stability.

Apply this diff:

-import { encryptString } from "@lit-protocol/encryption/src/lib/encryption.js";
+import { encryptString } from "@lit-protocol/encryption";

407-411: WalletConnectionAlert does not accept a message prop.

The component ignores message; either add support to the component or drop the prop here to avoid confusion.

Apply this diff to drop the unused prop:

         <WalletConnectionAlert
           show={showWalletAlert}
-          message="Connect your wallet to create and manage invoices"
           onDismiss={() => setShowWalletAlert(false)}
         />

82-89: Reduce duplication: centralize TESTNET_TOKEN list.

This TESTNET_TOKEN address is also declared in GenerateLink.jsx and utils. Prefer a single source (e.g., utils/erc20_token.js) to avoid drift.

Would you like a follow-up PR to expose a constants export from utils/erc20_token.js and reuse it across pages?


361-371: Success path UX: navigate immediately after receipt or show link to tx.

You already await tx.wait(); delaying 4 seconds after confirmation can feel sluggish. Consider navigating immediately and/or surfacing the transaction hash.

Example:

-      const receipt = await tx.wait();
-      setTimeout(() => navigate("/dashboard/sent"), 4000);
+      const receipt = await tx.wait();
+      navigate("/dashboard/sent", { state: { txHash: receipt?.hash } });

716-733: When switching tokens, clear customTokenAddress and verification state deterministically.

You’re doing this for preset selection; also consider resetting itemData amounts if token decimals differ to avoid misleading totals display.

I can provide a helper that recalculates totals when token decimals change; want me to add it?


1001-1021: Note text: guard for undefined symbol while custom token is verifying.

Brief flicker can occur; small UX polish.

Apply this diff:

-                        Payments will be processed in {verifiedToken?.symbol}.
+                        Payments will be processed in {verifiedToken?.symbol ?? "the selected token"}.
frontend/src/page/SentInvoice.jsx (1)

810-817: Address truncation logic could be improved.

The manual substring logic for token addresses could benefit from using the existing formatAddress helper for consistency.

Apply this diff to use the existing helper:

-                    <p className="text-xs text-gray-600">
-                      {drawerState.selectedInvoice.paymentToken?.address
-                        ? `${drawerState.selectedInvoice.paymentToken.address.substring(
-                            0,
-                            10
-                          )}......${drawerState.selectedInvoice.paymentToken.address.substring(
-                            33
-                          )}`
-                        : "Native Currency"}
-                    </p>
+                    <p className="text-xs text-gray-600">
+                      {drawerState.selectedInvoice.paymentToken?.address
+                        ? formatAddress(drawerState.selectedInvoice.paymentToken.address)
+                        : "Native Currency"}
+                    </p>
📜 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 dffd379 and 8d67011.

📒 Files selected for processing (3)
  • frontend/src/page/CreateInvoice.jsx (9 hunks)
  • frontend/src/page/ReceivedInvoice.jsx (5 hunks)
  • frontend/src/page/SentInvoice.jsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/page/ReceivedInvoice.jsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/page/SentInvoice.jsx (2)
frontend/src/page/ReceivedInvoice.jsx (7)
  • error (63-63)
  • switchNetwork (398-412)
  • page (56-56)
  • formatAddress (414-418)
  • formatDate (420-423)
  • toggleDrawer (371-383)
  • drawerState (366-369)
frontend/src/components/WalletConnectionAlert.jsx (1)
  • WalletConnectionAlert (7-56)
frontend/src/page/CreateInvoice.jsx (4)
frontend/src/page/GenerateLink.jsx (3)
  • showWalletAlert (36-36)
  • TESTNET_TOKEN (61-61)
  • verifyToken (103-123)
frontend/src/utils/erc20_token.js (2)
  • TOKEN_PRESETS (8-716)
  • TOKEN_PRESETS (8-716)
frontend/src/contractsABI/ERC20_ABI.js (2)
  • ERC20_ABI (1-10)
  • ERC20_ABI (1-10)
frontend/src/components/WalletConnectionAlert.jsx (1)
  • WalletConnectionAlert (7-56)
🔇 Additional comments (14)
frontend/src/page/CreateInvoice.jsx (1)

516-517: Avoid calling toString() on possibly undefined address.

account?.address?.toString() can throw if address is undefined. Since the form is gated by isConnected, use account.address directly or guard once.

Apply this diff:

-          address: account?.address.toString(),
+          address: account.address,

And keep the read-only input bound to account.address as you do already.

Also applies to: 261-269

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

48-48: LGTM! Clean wallet connection alert integration.

The WalletConnectionAlert import follows established patterns from other files and enhances the UX by providing wallet connection gating.


63-63: Wallet connection state management looks good.

The wallet connection state is properly derived from isConnected and updates appropriately when the connection status changes. This provides clear feedback to users about wallet requirements.

Also applies to: 75-75, 105-107


359-366: Well-implemented wallet connection alert banner.

The WalletConnectionAlert component is properly positioned and configured with appropriate props. The dismissal functionality allows users to hide the alert while maintaining the wallet connection requirement for actual functionality.


395-440: Enhanced loading and error states improve UX.

The updated loading skeleton with multiple rows and improved error display provide better user feedback. The empty state with descriptive messaging is also well-implemented.


478-502: Rich client information display enhances usability.

The client column with avatar, name, and email provides clear visual identification. The avatar generation using the first character of the first name is a nice touch.


504-510: Address formatting with tooltip provides good UX.

The truncated address display with a tooltip showing the full address on hover is user-friendly. However, ensure the formatAddress helper is fixed as noted in the duplicate comment.


513-532: Token information display is well-structured.

The amount column properly displays token logos when available and falls back to a generic icon. The formatting includes both amount and symbol for clarity.


535-561: Status indicators are clear and intuitive.

The status chips with appropriate icons (cancel, paid, pending) provide immediate visual feedback about invoice states. The color coding follows Material-UI conventions.


570-610: Action buttons are well-organized and conditional.

The cancel button is properly shown only for unpaid, non-cancelled invoices, and the view details button is always available. The styling and tooltips enhance usability.


639-732: Invoice drawer provides comprehensive details.

The drawer layout is well-structured with proper header information, status indicators, and a professional invoice appearance. The cancellation notice for cancelled invoices is particularly user-friendly.


734-825: From/Bill To and payment currency sections are professional.

The contact information layout is clean and readable. The payment currency section properly handles both ERC-20 tokens and native ETH, with appropriate fallbacks for missing token information.


845-909: Invoice table and totals calculation are comprehensive.

The items table is well-formatted and the totals section properly accounts for network fees. The ETH handling in total calculations correctly adds network fees for ETH payments while keeping them separate for ERC-20 tokens.


930-977: Cancellation dialog provides appropriate warnings.

The confirmation dialog properly warns users about the irreversible nature of cancellation and includes special handling for already-paid invoices. The alert components enhance user understanding.

@adityabhattad2021 adityabhattad2021 merged commit 23b6a31 into StabilityNexus:main Aug 22, 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