-
Notifications
You must be signed in to change notification settings - Fork 32
Multi-Token (ERC20) Payment Support and Invoice Cancellation #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds per-invoice ERC‑20 token support, invoice cancellation, and payment-status queries to the Chainvoice contract; updates ABIs; and integrates token-aware creation, ERC‑20/native payments, cancellation, Lit encryption/decryption, token UI components, and multiple frontend UI/UX changes. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant FE as Frontend (CreateInvoice)
participant Lit as Lit Protocol
participant SC as Chainvoice.sol
U->>FE: Enter invoice data + select token
FE->>Lit: SIWE auth & encrypt invoice JSON
Lit-->>FE: Ciphertext + hash
FE->>SC: createInvoice(to, amount, tokenAddress, ciphertext, hash)
SC-->>FE: Tx receipt (InvoiceCreated)
FE-->>U: Navigate to dashboard
sequenceDiagram
participant P as Payer
participant FE as Frontend (ReceivedInvoice)
participant T as ERC20 Token
participant SC as Chainvoice.sol
P->>FE: Initiate payment
alt ERC20 payment
FE->>T: allowance(payer, Chainvoice)
alt insufficient allowance
FE->>T: approve(Chainvoice, amount)
end
FE->>SC: payInvoice(invoiceId) with msg.value = fee (ETH)
else Native ETH
FE->>SC: payInvoice(invoiceId) with msg.value = amountDue + fee
end
SC-->>FE: Tx receipt (InvoicePaid)
FE-->>P: Update UI/status
sequenceDiagram
participant S as Sender
participant FE as Frontend (SentInvoice)
participant SC as Chainvoice.sol
S->>FE: Cancel invoice
FE->>SC: cancelInvoice(invoiceId)
SC-->>FE: Tx receipt (InvoiceCancelled)
FE-->>S: Refresh list/status
sequenceDiagram
participant FE as Frontend
participant SC as Chainvoice.sol
participant T as ERC20 Token
FE->>SC: getPaymentStatus(invoiceId, payer)
alt Native
SC-->>FE: canPay, payerBalance, MAX_UINT allowance
else ERC20
SC->>T: staticcall balanceOf/allowance
SC-->>FE: canPay, payerBalance, allowanceAmount
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
♻️ Duplicate comments (1)
frontend/src/page/SentInvoice.jsx (1)
221-234: Extract duplicated token enrichment logic.This is the same token enrichment logic as in
ReceivedInvoice.jsx. As suggested earlier, extract this to a shared utility function to avoid duplication.
🧹 Nitpick comments (18)
frontend/src/utils/erc20_token.js (2)
1-7: Consider uncommenting or removing the Chainvoice token entry.The commented-out Chainvoice token at the top suggests this might be needed for the platform. Either include it in the active token list or remove the dead code.
8-709: Consider optimizations for large static data.This large token array will increase bundle size. Consider:
- Loading token data asynchronously or from an API
- Implementing lazy loading for logos
- Adding network/chain identification since addresses are mainnet-specific
- Validating token addresses are properly checksummed
frontend/src/components/TokenIntegrationRequest.jsx (2)
1-1: Remove unused prop or implement its usage.The
addressprop is declared but never used within the component. Either remove it or implement its intended functionality.-function TokenIntegrationRequest({ address }) { +function TokenIntegrationRequest() {
6-6: Consider making the URL configurable.The hardcoded URL should be made configurable through props or environment variables for better maintainability.
- href="https://stability.nexus/" + href={requestUrl || "https://stability.nexus/"}frontend/src/components/TokenSelector.jsx (3)
286-288: Implement token verification functionality.The verification button has placeholder logic that needs to be implemented for proper token validation.
Do you want me to generate the token verification implementation that validates ERC-20 contract addresses and fetches token metadata?
onClick={() => { - /* Add verification logic here */ + verifyCustomToken(customTokenAddress); }}
206-206: Remove commented code.Commented code should be removed to maintain code cleanliness.
<img - // src={`/tokenImages/${selectedToken.symbol.toLowerCase()}.png`} src={selectedToken.logo}
14-19: Consider moving popular tokens to configuration.The hardcoded POPULAR_TOKENS array should be moved to a configuration file for better maintainability.
Consider moving this to
frontend/src/utils/erc20_token.jsalongside TOKEN_PRESETS.frontend/src/components/TokenCrousel.jsx (2)
6-6: Fix filename typo.The filename has a typo:
TokenCrousel.jsxshould beTokenCarousel.jsxto match the component name.
48-77: Consider memoizing the duplicated tokens array.The tokens duplication happens on every render. Consider using useMemo for better performance.
+import { useEffect, useRef, useMemo } from "react"; const TokenCarousel = () => { const carouselRef = useRef(); - const duplicatedTokens = [...TOKEN_PRESETS, ...TOKEN_PRESETS]; + const duplicatedTokens = useMemo(() => [...TOKEN_PRESETS, ...TOKEN_PRESETS], []);frontend/src/page/Home.jsx (1)
19-38: Consider extracting menu configuration to separate file.The menuItems array could be moved to a separate configuration file for better maintainability and potential reuse.
+// config/menuItems.js +export const MENU_ITEMS = [ + { + text: "Sent Requests", + icon: <MailOutlineIcon />, + route: "sent", + color: "#4ade80", + }, + // ... other items +];frontend/src/App.jsx (1)
54-68: Consider adding error boundaries for better error handlingThe routing structure looks good, but consider wrapping routes in error boundaries to handle component errors gracefully.
Would you like me to generate an ErrorBoundary component that can wrap your routes to provide better error handling and user feedback when components fail to render?
frontend/src/components/Navbar.jsx (2)
78-97: Remove commented out codeThere are large blocks of commented code that should be removed to keep the codebase clean.
Remove the commented
navItemsarray and the commented navigation sections in both desktop and mobile views. If this code might be needed later, it should be preserved in version control history rather than as comments.Also applies to: 161-176, 270-287
43-46: Consider making navigation on disconnect optionalAutomatically navigating users to the home page when they disconnect their wallet might be disruptive, especially if they're viewing public content.
if (!address) { - navigate("/"); + // Only navigate away from protected routes + if (location.pathname.startsWith("/dashboard") || location.pathname === "/treasure") { + navigate("/"); + } setHasConnected(false); }frontend/src/page/Landing.jsx (1)
305-322: Footer links need implementationAll footer links are currently placeholders with
href="#". These should either be implemented with actual navigation/URLs or removed if not needed.Would you like me to help implement proper routing for these footer links or create a footer configuration object that maps to actual routes and external URLs?
Also applies to: 329-346, 353-376
frontend/src/page/ReceivedInvoice.jsx (2)
105-111: Use strict equality and consider extracting chain ID.Use strict equality operator and consider extracting the chain ID to a constant for better maintainability.
+ const SEPOLIA_CHAIN_ID = 11155111; + - if (network.chainId != 11155111) { + if (network.chainId !== SEPOLIA_CHAIN_ID) {
224-237: Consider extracting token enrichment to a utility function.This token enrichment logic is duplicated in
SentInvoice.jsx. Consider extracting it to a shared utility function for better maintainability.Create a utility function in a shared utils file:
// utils/tokenUtils.js export const enrichTokenWithPresets = (paymentToken) => { if (!paymentToken?.address) return paymentToken; const tokenInfo = TOKEN_PRESETS.find( (t) => t.address.toLowerCase() === paymentToken.address.toLowerCase() ); if (tokenInfo) { return { ...paymentToken, logo: tokenInfo.logo, decimals: tokenInfo.decimals, }; } return paymentToken; };Then use it here:
- if (parsed.paymentToken?.address) { - const tokenInfo = TOKEN_PRESETS.find( - (t) => - t.address.toLowerCase() === - parsed.paymentToken.address.toLowerCase() - ); - if (tokenInfo) { - parsed.paymentToken = { - ...parsed.paymentToken, - logo: tokenInfo.logo, - decimals: tokenInfo.decimals, - }; - } - } + parsed.paymentToken = enrichTokenWithPresets(parsed.paymentToken);contracts/src/Chainvoice.sol (1)
74-80: Good ERC20 validation, but consider additional checks.The token validation correctly checks if the address is a contract and has a
balanceOffunction. Consider also validating the decimals to ensure it's within a reasonable range (typically 0-18).Add decimals validation:
if (tokenAddress != address(0)) { require(tokenAddress.code.length > 0, "Not a contract address"); (bool success,) = tokenAddress.staticcall( abi.encodeWithSignature("balanceOf(address)", address(this)) ); require(success, "Not an ERC20 token"); // Validate decimals (bool decSuccess, bytes memory decData) = tokenAddress.staticcall( abi.encodeWithSignature("decimals()") ); if (decSuccess && decData.length > 0) { uint8 decimals = abi.decode(decData, (uint8)); require(decimals <= 18, "Token decimals too high"); } }frontend/src/page/SentInvoice.jsx (1)
104-110: Use strict equality for network check.Same as in
ReceivedInvoice.jsx, use strict equality operator for consistency.- if (network.chainId != 11155111) { + if (network.chainId !== 11155111) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
frontend/public/dashboard.pngis excluded by!**/*.pngfrontend/public/lit-protocol-diagram.pngis excluded by!**/*.pngfrontend/public/lit-protocol-logo.pngis excluded by!**/*.pngfrontend/public/logo.pngis excluded by!**/*.pngfrontend/public/token-select.pngis excluded by!**/*.pngfrontend/public/tokenImages/cin.pngis excluded by!**/*.pngfrontend/public/tokenImages/eth.pngis excluded by!**/*.pngfrontend/public/tokenImages/generic.pngis excluded by!**/*.png
📒 Files selected for processing (20)
contracts/src/Chainvoice.sol(6 hunks)frontend/package.json(2 hunks)frontend/src/App.jsx(1 hunks)frontend/src/components/CreateInvoice.jsx(0 hunks)frontend/src/components/Footer.jsx(1 hunks)frontend/src/components/Navbar.jsx(1 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/ui/select.jsx(1 hunks)frontend/src/contractsABI/ChainvoiceABI.js(8 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/Home.jsx(1 hunks)frontend/src/page/Landing.jsx(1 hunks)frontend/src/page/ReceivedInvoice.jsx(4 hunks)frontend/src/page/SentInvoice.jsx(4 hunks)frontend/src/page/Treasure.jsx(2 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 (5)
frontend/src/page/Home.jsx (2)
frontend/src/components/Navbar.jsx (2)
navigate(17-17)location(18-18)frontend/src/page/CreateInvoice.jsx (1)
navigate(58-58)
frontend/src/components/ui/select.jsx (1)
frontend/src/lib/utils.js (1)
cn(4-6)
frontend/src/components/Navbar.jsx (4)
frontend/src/page/SentInvoice.jsx (1)
useAccount(53-53)frontend/src/page/ReceivedInvoice.jsx (1)
useAccount(53-53)frontend/src/page/About.jsx (1)
useAccount(7-7)frontend/src/page/Home.jsx (2)
navigate(16-16)location(17-17)
frontend/src/page/Landing.jsx (1)
frontend/src/components/TokenCrousel.jsx (1)
TokenCarousel(6-82)
frontend/src/page/SentInvoice.jsx (4)
frontend/src/page/ReceivedInvoice.jsx (20)
columns(40-47)page(50-50)rowsPerPage(51-51)walletClient(52-52)useAccount(53-53)loading(54-54)fee(56-56)error(57-57)litReady(58-58)litClientRef(59-59)paymentLoading(60-60)networkLoading(61-61)handleChangePage(63-65)handleChangeRowsPerPage(67-70)drawerState(347-350)switchNetwork(379-393)formatAddress(395-399)formatDate(401-404)toggleDrawer(352-364)handlePrint(366-377)frontend/src/page/CreateInvoice.jsx (4)
walletClient(52-52)useAccount(53-53)loading(57-57)litClientRef(59-59)frontend/src/components/Navbar.jsx (1)
useAccount(16-16)frontend/src/page/Working.jsx (2)
toggleDrawer(11-21)handlePrint(23-48)
🔇 Additional comments (16)
frontend/package.json (1)
23-23: LGTM! Dependencies align with new UI features.The new dependencies support the enhanced UI components for multi-token payment support:
@radix-ui/react-selectfor accessible token selection dropdownsframer-motionfor smooth animations in navigation and componentsreact-hot-toastfor user notification systemreact-iconsfor consistent iconographyAll versions are recent and appropriate for production use.
Also applies to: 34-34, 42-43
frontend/src/components/Footer.jsx (1)
13-15: Verify that removing the copyright notice is intentional.The copyright notice has been commented out. Confirm this aligns with legal/branding requirements for the platform.
frontend/src/contractsABI/ERC20_ABI.js (1)
1-10: LGTM! Standard and complete ERC20 ABI.The ABI includes all essential ERC20 functions needed for token interactions:
- Transfer operations (transfer, transferFrom, approve)
- Balance queries (balanceOf, allowance)
- Metadata access (name, symbol, decimals)
The human-readable format is appropriate for ethers.js integration.
frontend/src/page/Applayout.jsx (1)
8-16: LGTM! Improved semantic structure and styling.The refactoring enhances the layout with:
- Semantic
<main>element for better accessibility- Simplified structure by removing unnecessary fragment
- Appropriate flex layout with
flex-1for content area- Proper spacing with
pt-20(likely for fixed navbar clearance)The removal of
h-fullallows content to determine the natural height.frontend/src/components/TokenCrousel.jsx (1)
10-42: Excellent animation implementation with proper cleanup.The infinite scroll animation is well-implemented with proper event listeners cleanup, pause on hover functionality, and smooth transitions.
frontend/src/components/ui/select.jsx (3)
13-27: Excellent use of React.forwardRef and component composition.The SelectTrigger implementation properly forwards refs and uses consistent styling patterns with the cn utility function.
50-72: Well-structured SelectContent with accessibility features.The component properly implements Radix UI Portal pattern with scroll buttons and responsive positioning. The animation classes provide smooth user experience.
108-119: Complete and consistent component exports.All necessary Select components are properly exported, providing a comprehensive API for the component library.
frontend/src/page/Home.jsx (2)
48-125: Excellent responsive design implementation.The responsive layout with proper Material UI breakpoints, smooth transitions, and accessible navigation patterns creates a great user experience.
84-100: Great dynamic styling based on route state.The conditional styling based on location.pathname provides clear visual feedback for navigation state with smooth transitions and color coding.
frontend/src/contractsABI/ChainvoiceABI.js (1)
34-38: ABI updates for ERC20 token support look correctThe ABI has been properly updated to support multi-token payments:
- Added
tokenAddressparameter tocreateInvoicefunction- Updated
InvoiceDetailsstruct withtokenAddressfield- Extended events to include token information
- Added
getPaymentStatusfunction for payment verificationThese changes correctly reflect the smart contract modifications for ERC20 token support.
Also applies to: 102-106, 197-201, 258-262, 314-318, 480-484, 517-521
frontend/src/page/ReceivedInvoice.jsx (1)
258-345: Well-structured multi-token payment implementation.The payment function correctly handles both native ETH and ERC20 token payments with proper allowance checks, approval flow, and error handling. The separation of fee handling (always in ETH) from the payment amount is implemented correctly.
frontend/src/page/CreateInvoice.jsx (2)
94-108: Excellent handling of precision in calculations.The total amount calculation correctly uses BigInt arithmetic with ethers.js utilities to avoid floating-point precision issues. The division by
parseUnits("1", 18)properly cancels the double scaling when multiplying quantities.
169-187: Well-implemented token verification.The token verification correctly validates ERC20 contracts by calling standard methods with proper error handling and fallback values. Good use of
Promise.allfor efficiency.contracts/src/Chainvoice.sol (2)
102-139: Well-structured payment logic with proper separation of concerns.The payment function correctly handles both native and ERC20 token payments. The design choice to always collect fees in native tokens simplifies the implementation and ensures consistent fee collection. The allowance check and event emission are properly implemented.
141-163: Useful view function for payment status checks.The
getPaymentStatusfunction provides valuable information for the frontend to determine payment capability. Good handling of the allowance concept for native tokens.Consider adding a check for invoice payment status to avoid unnecessary checks for already paid invoices:
require(!invoice.isPaid, "Invoice already paid");
There was a problem hiding this 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
🔭 Outside diff range comments (1)
contracts/src/Chainvoice.sol (1)
124-173: Harden pay flow: set state before external calls and consider nonReentrantAlthough reentrancy into
payInvoiceis currently constrained bymsg.sender == invoice.to, best practice is to follow Checks-Effects-Interactions and/or use a reentrancy guard.
- Move
invoice.isPaid = true;before the external call toinvoice.from(state change will revert if transfer fails).- Consider adding OpenZeppelin
ReentrancyGuardand markingpayInvoice/withdrawFeesasnonReentrant.- For ERC-20 transfers, prefer
SafeERC20.safeTransferFrom.Apply this diff to move the effect before interactions:
if (invoice.tokenAddress == address(0)) { // Native token (ETH) payment require( msg.value == invoice.amountDue + fee, "Incorrect payment amount" ); - accumulatedFees += fee; - - uint256 amountToSender = msg.value - fee; - (bool sent, ) = payable(invoice.from).call{value: amountToSender}( - "" - ); - require(sent, "Transfer failed"); + // Effects before interactions + invoice.isPaid = true; + accumulatedFees += fee; + + uint256 amountToSender = msg.value - fee; // equals amountDue + (bool sent, ) = payable(invoice.from).call{value: amountToSender}(""); + require(sent, "Transfer failed"); } else { // ERC20 token payment require(msg.value == fee, "Must pay fee in native token"); require( IERC20(invoice.tokenAddress).allowance( msg.sender, address(this) ) >= invoice.amountDue, "Insufficient allowance" ); - accumulatedFees += fee; - bool transferSuccess = IERC20(invoice.tokenAddress).transferFrom( + // Effects before interactions + invoice.isPaid = true; + accumulatedFees += fee; + bool transferSuccess = IERC20(invoice.tokenAddress).transferFrom( msg.sender, invoice.from, invoice.amountDue ); require(transferSuccess, "Token transfer failed"); } - invoice.isPaid = true; emit InvoicePaid( invoiceId, invoice.from, invoice.to, invoice.amountDue, invoice.tokenAddress );
🧹 Nitpick comments (6)
contracts/src/Chainvoice.sol (3)
82-89: ERC-20 validation can be made more robustThe
staticcalltobalanceOf(address)is a lightweight sanity check, but it can pass for contracts that merely implement that selector. Consider a stricter check:
- Use a
try/catchonIERC20(tokenAddress).balanceOf(address(this))to guarantee the call decodes properly.- Optionally, also
tryreadingallowanceto ensure both views decode correctly.Example:
try IERC20(tokenAddress).balanceOf(address(this)) returns (uint256) {} catch { revert("Not an ERC20 token"); }
175-207: getPaymentStatus behavior is sensible; minor note on native allowancesReturning
type(uint256).maxfor native allowance is a pragmatic sentinel. Document this in the ABI/README so the frontend doesn’t attempt arithmetic with it unnecessarily.
239-246: Emit an event when fee/treasury is updatedConsider emitting
FeeUpdated(uint256 oldFee, uint256 newFee)andTreasuryUpdated(address oldTreasury, address newTreasury)to ease off-chain indexing and audits.frontend/src/page/SentInvoice.jsx (3)
312-326: Add wallet_addEthereumChain fallback for unconfigured SepoliaIf Sepolia isn’t added,
wallet_switchEthereumChainreturns code 4902. Add a fallback to prompt chain addition.Suggested change:
try { setNetworkLoading(true); - await window.ethereum.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: "0xaa36a7" }], // Sepolia chain ID - }); + try { + await window.ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: "0xaa36a7" }], + }); + } catch (err) { + if (err?.code === 4902) { + await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: "0xaa36a7", + chainName: "Sepolia", + nativeCurrency: { name: "Sepolia Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: ["https://rpc.sepolia.org"], + blockExplorerUrls: ["https://sepolia.etherscan.io"], + }, + ], + }); + } else { + throw err; + } + } setError(null);
539-547: Inconsistent date sources (timestamp vs issueDate)Tooltip uses
invoice.timestampwhile the visible date usesissueDate. Unless both are guaranteed present and consistent, pick one canonical source (likelyissueDate) or gracefully fallback.Example:
- title={new Date(invoice.timestamp * 1000).toLocaleString()} + title={new Date(Number(drawerState.selectedInvoice?.issueDate ?? Date.now())).toLocaleString()}
120-134: Contract address must be present; consider early guardIf
import.meta.env.VITE_CONTRACT_ADDRESSis missing, calls will fail at runtime. Add a guard and user-facing error.Example:
- const contract = new Contract( - import.meta.env.VITE_CONTRACT_ADDRESS, + const contractAddr = import.meta.env.VITE_CONTRACT_ADDRESS; + if (!contractAddr) { + setError("Contract address is not configured."); + setLoading(false); + return; + } + const contract = new Contract( + contractAddr, ChainvoiceABI, signer );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
contracts/src/Chainvoice.sol(6 hunks)frontend/src/contractsABI/ChainvoiceABI.js(10 hunks)frontend/src/page/SentInvoice.jsx(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/page/SentInvoice.jsx (2)
frontend/src/page/ReceivedInvoice.jsx (8)
columns(40-47)page(50-50)fee(56-56)error(57-57)litReady(58-58)litClientRef(59-59)networkLoading(61-61)switchNetwork(379-393)frontend/src/contractsABI/ChainvoiceABI.js (2)
ChainvoiceABI(1-589)ChainvoiceABI(1-589)
🔇 Additional comments (10)
contracts/src/Chainvoice.sol (2)
111-122: Cancellation flow looks correctProper authorization and state checks are in place; event emission is included. No issues.
248-257: Confirm intention: anyone can trigger fee withdrawal to treasury
withdrawFeesis intentionally callable by anyone but sends funds totreasuryAddress. This is a common pattern, but please confirm this is intended. If not, gate withonlyOwner.frontend/src/contractsABI/ChainvoiceABI.js (7)
20-32: ABI addition for cancelInvoice matches contract signature
cancelInvoice(uint256)nonpayable aligns with the Solidity implementation. Good.
47-52: createInvoice input signature matches on-chain (tokenAddress added)The parameter order and types are consistent with the contract. Good.
115-129: Struct shape update (tokenAddress, isCancelled) is consistent
InvoiceDetailstuple now includestokenAddressandisCancelledin the correct positions. Ensure frontend index accessors are updated accordingly.
145-178: getPaymentStatus ABI matches contract (canPay, payerBalance, allowanceAmount)This will enable richer UX for payment readiness checks. Looks good.
490-520: InvoiceCancelled event definition matches on-chainFields and indexed parameters align with the Solidity event. Good.
543-549: InvoiceCreated event extended with tokenAddressMatches contract changes. Good.
580-586: InvoicePaid event extended with tokenAddressMatches contract changes. Good.
frontend/src/page/SentInvoice.jsx (1)
510-536: Status chip logic now works once isCancelled is propagatedAfter fixing parsing to set
isCancelled, this UI section becomes correct. No further changes needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🔭 Outside diff range comments (1)
frontend/package.json (1)
23-47: Action required: remove duplicate toast lib and wire the chosen providerShort: package.json contains both react-hot-toast and react-toastify. App.jsx already mounts react-hot-toast's , but ReceivedInvoice.jsx imports react-toastify's toast without any ToastContainer in the repo — and SentInvoice.jsx calls toast.* but doesn't import toast. This will lead to missing/inconsistent toasts. Standardize on one lib and fix imports/providers.
Files to inspect/fix:
- frontend/package.json — contains both "react-hot-toast" and "react-toastify".
- frontend/src/App.jsx — imports { Toaster } from "react-hot-toast" and mounts at app root.
- frontend/src/page/ReceivedInvoice.jsx — imports { toast } from "react-toastify" and the react-toastify CSS (lines ~26-27) and calls toast.error(...) (lines ~341-348). No is present anywhere.
- frontend/src/page/SentInvoice.jsx — calls toast.success / toast.error (lines ~316/319) but the file does not import toast.
Recommended fixes (pick one):
Option A — keep react-hot-toast (recommended since App already mounts it)
- Replace in ReceivedInvoice.jsx:
- Remove:
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";- Add:
import { toast } from "react-hot-toast";- Ensure SentInvoice.jsx imports toast if missing:
- Add at top: import { toast } from "react-hot-toast";
- Remove "react-toastify" from frontend/package.json and run your package manager to update lockfile.
Option B — keep react-toastify
- In App.jsx replace the react-hot-toast with react-toastify's (and import it), or add a root-level .
- Update SentInvoice.jsx to import { toast } from "react-toastify" (if you keep usage there).
- Remove "react-hot-toast" from frontend/package.json and update lockfile.
Minimal code snippets (Option A):
- ReceivedInvoice.jsx: replace
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
with
import { toast } from "react-hot-toast";- SentInvoice.jsx: add at top if missing
import { toast } from "react-hot-toast";Please implement one of the options and run the app to confirm toasts appear consistently across pages.
♻️ Duplicate comments (6)
frontend/src/page/SentInvoice.jsx (5)
873-883: Total computation mixes floats/units — use integer math and format for displayAdding parseFloat(amountDue) with parseFloat(ethers.formatUnits(fee)) loses precision and mixes units. Prefer base units + proper formatting.
Example (ETH-only display path):
- {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" - ? `${( - parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - ).toFixed(6)} ETH` + {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" + ? (() => { + const dueWei = ethers.parseUnits( + String(drawerState.selectedInvoice.amountDue), + 18 + ); + const totalWei = dueWei + fee; + return `${ethers.formatUnits(totalWei, 18)} ETH`; + })()For ERC-20, keep showing “amount + fee ETH” but ensure amountDue is formatted using token decimals when known.
111-119: Fix BigInt vs Number comparison for chainId (ethers v6)getNetwork().chainId is a BigInt in ethers v6; comparing to a Number throws a TypeError.
Apply:
- if (network.chainId != 11155111) { + if (network.chainId !== 11155111n) {Alternatively:
- if (network.chainId != 11155111) { + if (Number(network.chainId) !== 11155111) {
164-164: Don’t atob() the ciphertext — pass base64 string to LitdecryptToString expects the base64-encoded ciphertext string. Decoding with atob corrupts the data.
Apply:
- const ciphertext = atob(encryptedStringBase64); + const ciphertext = encryptedStringBase64; // already base64
316-323: Undefined ‘toast’ — import and ensure providertoast.success/error is used but not imported, causing runtime errors.
Add at the top of this file (or a shared layout):
import { toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css";Ensure is rendered once at the app root. If you intend to use react-hot-toast instead, replace calls with from 'react-hot-toast' and mount at the root.
340-344: Guard against undefined/short addresses in formatterformatAddress assumes a non-empty string and will throw on undefined. Also simplify with slice.
Apply:
- const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; - }; + const formatAddress = (addr) => { + if (!addr) return ""; + const s = String(addr); + if (s.length <= 20) return s; + return `${s.slice(0, 10)}...${s.slice(-10)}`; + };frontend/src/page/ReceivedInvoice.jsx (1)
133-137: Setter name is corrected — LGTMUsing setReceivedInvoice matches the state variable receivedInvoices.
🧹 Nitpick comments (5)
frontend/src/page/SentInvoice.jsx (3)
563-583: Disable cancel button while processing to prevent duplicate submissionsYou already track per-invoice loading; wire it to the button’s disabled state.
Apply:
- <IconButton + <IconButton + disabled={!!paymentLoading[invoice.id]} size="small" onClick={() => { setInvoiceToCancel(invoice); setCancelConfirmOpen(true); }}
333-335: Prefer toast over alert for consistent UXStay consistent with your toast system instead of alert().
Apply:
- alert("Failed to switch network. Please switch to Sepolia manually."); + toast.error("Failed to switch network. Please switch to Sepolia manually.");Note: Requires toast import as suggested above.
936-949: Nit: fix dialog button copyMinor grammar/spacing improvement.
Apply:
- <Button onClick={() => setCancelConfirmOpen(false)}> - {" "} - No,Keep Invoice Active + <Button onClick={() => setCancelConfirmOpen(false)}> + No, Keep Invoice Active </Button>frontend/src/page/ReceivedInvoice.jsx (2)
386-400: Prefer toast over alert for network switch errorsAlign with your toast system for consistent UX and theming.
Apply:
- alert("Failed to switch network. Please switch to Sepolia manually."); + toast.error("Failed to switch network. Please switch to Sepolia manually.");
965-973: Total computation mixes floats/units — use integer math and formatAdding parseFloat(amountDue) to parseFloat(formatUnits(fee)) can be lossy. Use BigInt units and format only for display.
Example:
- {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" - ? `${( - parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - ).toFixed(6)} ETH` + {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" + ? (() => { + const dueWei = ethers.parseUnits( + String(drawerState.selectedInvoice.amountDue), + 18 + ); + const totalWei = dueWei + fee; + return `${ethers.formatUnits(totalWei, 18)} ETH`; + })()
📜 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 settings in your CodeRabbit configuration.
📒 Files selected for processing (3)
frontend/package.json(2 hunks)frontend/src/page/ReceivedInvoice.jsx(4 hunks)frontend/src/page/SentInvoice.jsx(6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
frontend/src/page/SentInvoice.jsx (2)
frontend/src/page/ReceivedInvoice.jsx (7)
columns(45-52)page(55-55)fee(61-61)error(62-62)switchNetwork(386-400)formatAddress(402-406)formatDate(408-411)frontend/src/contractsABI/ChainvoiceABI.js (2)
ChainvoiceABI(1-589)ChainvoiceABI(1-589)
frontend/src/page/ReceivedInvoice.jsx (8)
frontend/src/page/SentInvoice.jsx (12)
columns(49-56)page(59-59)walletClient(61-61)useAccount(62-62)loading(63-63)fee(65-65)error(66-66)drawerState(266-269)switchNetwork(324-338)formatAddress(340-344)formatDate(346-349)handlePrint(285-296)frontend/src/page/Treasure.jsx (3)
walletClient(22-22)loading(23-28)fee(21-21)frontend/src/page/CreateInvoice.jsx (4)
walletClient(52-52)useAccount(53-53)loading(57-57)issueDate(56-56)frontend/src/components/Navbar.jsx (1)
useAccount(16-16)frontend/src/page/About.jsx (2)
useAccount(7-7)loading(9-9)frontend/src/contractsABI/ChainvoiceABI.js (2)
ChainvoiceABI(1-589)ChainvoiceABI(1-589)frontend/src/utils/erc20_token.js (2)
TOKEN_PRESETS(8-709)TOKEN_PRESETS(8-709)frontend/src/contractsABI/ERC20_ABI.js (2)
ERC20_ABI(1-10)ERC20_ABI(1-10)
🔇 Additional comments (4)
frontend/src/page/SentInvoice.jsx (1)
149-156: Correct InvoiceDetails indexing — LGTMIndices reflect the updated struct: id(0), from(1), to(2), isPaid(5), isCancelled(6), encryptedData(7), dataHash(8).
frontend/src/page/ReceivedInvoice.jsx (3)
614-624: Use issueDate for tooltip; avoid undefined ‘timestamp’ NaNinvoice.timestamp isn’t guaranteed; use the already-present issueDate.
Apply:
- <Tooltip - title={new Date( - invoice.timestamp * 1000 - ).toLocaleString()} - > + <Tooltip + title={new Date(invoice.issueDate).toLocaleString()} + >If timestamp is required, ensure it’s set in the decrypted payload first.
25-44: Nice: ERC-20 ABI and token presets integrationBringing in ERC20_ABI and TOKEN_PRESETS unlocks the approval/payment and richer token display flows. The enrichment with logo/decimals is handled cleanly.
26-28: Confirm a single is mounted — manual verification requiredI ran
rg -n '<ToastContainer' -A 2against the repo and found no matches. Since frontend/src/page/ReceivedInvoice.jsx importstoast, please confirm a single root-level is mounted (e.g., App.jsx or index.jsx) to avoid duplicate containers/memory leaks.Files/steps to check:
- File importing toast: frontend/src/page/ReceivedInvoice.jsx (lines 26–28).
- Run locally to locate provider/usages:
- rg -n "ToastContainer" -S
- rg -n "react-toastify" -S
- rg -n "toast(" -S
Reply with the location of the mounted or ask me to run a broader search in the repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (14)
frontend/src/page/SentInvoice.jsx (5)
113-119: Fix BigInt vs Number comparison for chainId (ethers v6)getNetwork().chainId is BigInt in ethers v6; comparing to a Number can throw. Use a BigInt literal or cast to Number.
- if (network.chainId != 11155111) { + if (network.chainId !== 11155111n) {Alternatively:
- if (network.chainId != 11155111) { + if (Number(network.chainId) !== 11155111) {
164-166: Don’t atob() the ciphertext — pass base64 string to LitBase64-decoding corrupts input for Lit decryption. Pass the base64 string directly.
- const ciphertext = atob(encryptedStringBase64); + const ciphertext = encryptedStringBase64; // already base64No other changes needed; decryptToString currently passes ciphertext variable correctly.
Also applies to: 217-226
25-41: Import toast used in cancel flowtoast.success/error is used but not imported; this will crash at runtime.
import { createSiweMessageWithRecaps, generateAuthSig, LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; +import { toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css";
552-559: Fix tooltip date source — invoice.timestamp is undefinedTitle uses invoice.timestamp leading to “Invalid Date”. Use the available issueDate.
- <Tooltip - title={new Date( - invoice.timestamp * 1000 - ).toLocaleString()} - > + <Tooltip + title={new Date(invoice.issueDate).toLocaleString()} + >
340-344: Guard against undefined/short addresses in formatterCurrent implementation throws when address is undefined or too short.
- const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; - }; + const formatAddress = (addr) => { + if (!addr) return ""; + const s = String(addr); + if (s.length <= 20) return s; + return `${s.slice(0, 10)}...${s.slice(-10)}`; + };frontend/src/page/ReceivedInvoice.jsx (5)
110-116: Fix BigInt vs Number comparison for chainId (ethers v6)getNetwork().chainId is BigInt in ethers v6; comparing to a Number can throw. Use a BigInt literal or cast.
- if (network.chainId != 11155111) { + if (network.chainId !== 11155111n) {Or:
- if (network.chainId != 11155111) { + if (Number(network.chainId) !== 11155111) {
167-167: Don’t atob() the ciphertext — pass base64 string to LitBase64-decoding the ciphertext breaks Lit decryption.
- const ciphertext = atob(encryptedStringBase64); + const ciphertext = encryptedStringBase64; // already base64Also applies to: 218-226
611-618: Status fallback shows Cancelled incorrectly — should be Pending/UnpaidThe else branch duplicates Cancelled; unpaid invoices should show Pending.
- ) : ( - <Chip - icon={<CancelIcon />} - label="Cancelled" - color="error" - size="small" - variant="outlined" - /> - )} + ) : ( + <Chip + icon={<UnpaidIcon />} + label="Pending" + color="warning" + size="small" + variant="outlined" + /> + )}
652-660: Pass ZeroAddress for native ETH to avoid undefined tokenAddressFor ETH invoices, paymentToken.address may be undefined, causing isAddress() to fail.
- payInvoice( - invoice.id, - invoice.amountDue, - invoice.paymentToken.address - ) + payInvoice( + invoice.id, + invoice.amountDue, + invoice.paymentToken?.address ?? ethers.ZeroAddress + )
410-414: Guard against undefined/short addresses in formatterAvoid substring on possibly undefined values; handle short strings.
- const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; - }; + const formatAddress = (addr) => { + if (!addr) return ""; + const s = String(addr); + if (s.length <= 20) return s; + return `${s.slice(0, 10)}...${s.slice(-10)}`; + };frontend/src/page/Treasure.jsx (4)
41-44: Hoist and validate contract address; replace direct env usageAvoid repeating
import.meta.env.VITE_CONTRACT_ADDRESSand add a single validated constant. Prevents runtime errors when the env var is missing and improves consistency.Apply this diff to replace direct usages:
- const contract = new Contract( - import.meta.env.VITE_CONTRACT_ADDRESS, - ChainvoiceABI, - signer - ); + const contract = new Contract(CONTRACT_ADDRESS, ChainvoiceABI, signer);Add this at the top of the file (after imports):
// Add near imports const CONTRACT_ADDRESS = import.meta.env.VITE_CONTRACT_ADDRESS; if (!CONTRACT_ADDRESS) { console.error("VITE_CONTRACT_ADDRESS is not defined in environment variables"); }Also applies to: 73-77, 97-101, 125-129
63-89: Replace alert() with toasts and improve UX for address updatesReplace blocking
alert()s with non-blocking toasts; the app already uses react-hot-toast elsewhere.Add import:
-import { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; +import toast from "react-hot-toast";Update the handler:
const handleSetTreasuryAddress = async () => { if (!ethers.isAddress(newTreasuryAddress)) { - alert("Please enter a valid Ethereum address"); + toast.error("Please enter a valid Ethereum address"); return; } try { if (!walletClient) return; @@ await tx.wait(); setTreasuryAddress(newTreasuryAddress); setNewTreasuryAddress(""); - alert("Treasury address updated successfully!"); + toast.success("Treasury address updated successfully"); } catch (error) { console.error("Error setting treasury address:", error); - alert(error.message || "Failed to update treasury address"); + toast.error(error?.shortMessage || error?.message || "Failed to update treasury address"); } finally { setLoading((prev) => ({ ...prev, setAddress: false })); } };
91-113: Add a confirmation step and replace alerts in withdrawal flowWithdrawing funds should require user confirmation. Also replace
alert()with toasts.const handleWithdrawCollection = async () => { + const confirmWithdraw = window.confirm( + `Are you sure you want to withdraw ${treasureAmount} ETH to the treasury address?` + ); + if (!confirmWithdraw) return; try { if (!walletClient) return; @@ const newAmt = await contract.accumulatedFees(); setTreasureAmount(ethers.formatUnits(newAmt)); - alert("Funds withdrawn successfully!"); + toast.success("Funds withdrawn successfully"); } catch (error) { console.error("Error withdrawing collection:", error); - alert(error.message || "Failed to withdraw funds"); + toast.error(error?.shortMessage || error?.message || "Failed to withdraw funds"); } finally { setLoading((prev) => ({ ...prev, withdraw: false })); } };
115-144: Strengthen fee validation and switch alerts to toastsEnhance input validation to prevent negative/absurd fees and replace
alert()with toasts. Keeps UX consistent and avoids accidental high-fee settings.const handleUpdateFee = async () => { - if (!newFee || isNaN(newFee)) { - alert("Please enter a valid fee amount"); - return; - } + const feeValue = parseFloat(newFee); + if (!newFee || !Number.isFinite(feeValue) || feeValue < 0) { + toast.error("Please enter a valid positive fee amount"); + return; + } + // Optional safety guard; adjust threshold to your business rules + if (feeValue > 1) { + toast.error("Fee amount seems too high. Please confirm the value."); + return; + } try { if (!walletClient) return; setLoading((prev) => ({ ...prev, feeUpdate: true })); @@ - const tx = await contract.setFeeAmount( - ethers.parseUnits(newFee, "ether") - ); + const tx = await contract.setFeeAmount(ethers.parseUnits(newFee, "ether")); await tx.wait(); const updatedFee = await contract.fee(); setFee(ethers.formatUnits(updatedFee)); setNewFee(""); - alert("Fee updated successfully!"); + toast.success("Fee updated successfully"); } catch (error) { console.error("Error updating fee:", error); - alert(error.message || "Failed to update fee"); + toast.error(error?.shortMessage || error?.message || "Failed to update fee"); } finally { setLoading((prev) => ({ ...prev, feeUpdate: false })); } };
🧹 Nitpick comments (11)
frontend/src/page/SentInvoice.jsx (2)
872-881: Avoid floating-point math for totals; compute with base unitsMixing parseFloat with formatUnits is lossy. Prefer BigInt arithmetic and format only for display. For ETH, parse amountDue with 18 decimals; for ERC-20, keep fee (ETH) separate.
Example approach:
- For ETH: const totalWei = ethers.parseUnits(amountDue, 18) + fee; display ethers.formatUnits(totalWei, 18).
- For ERC-20: display
${amountDue} TOKEN + ${ethers.formatUnits(fee)} ETH.If you want, I can provide a small helper to format totals safely using BigInt.
258-258: Nit: remove stale state log in finallyconsole.log(sentInvoices) logs the previous state due to closure. Consider moving logs to where state is updated or remove.
frontend/src/page/ReceivedInvoice.jsx (3)
623-631: Fix tooltip date source — invoice.timestamp is undefinedUsing invoice.timestamp yields “Invalid Date”. Use issueDate instead.
- <Tooltip - title={new Date( - invoice.timestamp * 1000 - ).toLocaleString()} - > + <Tooltip + title={new Date(invoice.issueDate).toLocaleString()} + >
318-321: Prefer toast over alert for UX consistencyUse non-blocking toasts (already imported) for approval and success messages.
- alert( - `Approval for ${tokenSymbol} completed! Now processing payment...` - ); + toast.success(`Approved ${tokenSymbol}. 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!");Also applies to: 328-339
970-979: Avoid floating-point math for totals; compute with base unitsSame concern as SentInvoice: parseFloat with formatUnits is lossy. Prefer BigInt arithmetic for ETH totals and show “+ fee ETH” for ERC-20.
I can provide a small helper to compute and format totals safely if helpful.
frontend/src/page/Treasure.jsx (6)
284-289: Allow decimal fee input with step/min attributesType=number defaults to integer step=1 on some browsers. Permit decimals and disallow negatives.
- <Input - type="number" + <Input + type="number" + step="any" + min="0" placeholder={`Current: ${fee} ETH`} value={newFee} onChange={(e) => setNewFee(e.target.value)} className="bg-gray-800 border-gray-700 text-white font-mono" />
316-318: Avoid implicit string-to-number coercion in disabled check
treasureAmountis a string fromformatUnits. Be explicit to improve readability and prevent edge-case bugs.- disabled={loading.withdraw || treasureAmount <= 0} + disabled={loading.withdraw || Number(treasureAmount) <= 0}Optional: Track the raw wei value in state to avoid floating point comparisons altogether.
// Example const [treasuryAmountWei, setTreasuryAmountWei] = useState(0n); // on fetch: setTreasuryAmountWei(amt); // disabled={loading.withdraw || treasuryAmountWei === 0n}
39-44: DRY up contract instantiation with a helperThe provider/signer/contract boilerplate is repeated four times. Extracting it reduces errors and centralizes address/ABI changes.
Add once inside the component:
import { useCallback } from "react"; const getContract = useCallback(async () => { if (!walletClient) throw new Error("Wallet not connected"); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); return new Contract(CONTRACT_ADDRESS, ChainvoiceABI, signer); }, [walletClient]);Then replace occurrences:
-const provider = new BrowserProvider(walletClient); -const signer = await provider.getSigner(); -const contract = new Contract(CONTRACT_ADDRESS, ChainvoiceABI, signer); +const contract = await getContract();Also applies to: 71-77, 95-101, 123-129
188-201: Reconcile Treasury UI with multi-token (ERC20) feesThe UI labels balances/fees as “ETH” and reads a single
accumulatedFeesandfee(). With ERC20 support, fees may accrue per-token and require separate withdraws.
- Confirm the contract interface for token fees (e.g.,
accumulatedFees(address token),withdrawTokenFees(address token)).- Consider extending the UI with a token selector and per-token balances/withdrawals.
- If ETH-only fees remain, clarify in copy and code comments.
Would you like a mock of a token-aware Treasury panel?
19-21: Align naming: “Treasure” → “Treasury”Current naming mixes “Treasure” (component/file) with “Treasury” (UI text). Rename for clarity and consistency.
- File: Treasure.jsx → Treasury.jsx
- Component: Treasure → Treasury
- State: treasureAmount → treasuryAmount
Also applies to: 31-31
183-184: Format displayed ETH values to a readable precisionRaw
formatUnitsstrings can be long. Trim to a few decimals for UI polish.Example helper:
const formatEth = (val) => Number(val).toLocaleString(undefined, { maximumFractionDigits: 6 }); /* Usage */ {`${formatEth(treasureAmount)} ETH`} {`${formatEth(fee)} ETH`} `Withdraw ${formatEth(treasureAmount)} ETH`Also applies to: 197-198, 324-325
📜 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 settings in your CodeRabbit configuration.
📒 Files selected for processing (4)
frontend/src/components/Navbar.jsx(5 hunks)frontend/src/page/ReceivedInvoice.jsx(5 hunks)frontend/src/page/SentInvoice.jsx(6 hunks)frontend/src/page/Treasure.jsx(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/Navbar.jsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
frontend/src/page/SentInvoice.jsx (6)
frontend/src/page/ReceivedInvoice.jsx (7)
columns(45-52)page(55-55)walletClient(57-57)useAccount(58-58)loading(59-59)fee(61-61)litClientRef(64-64)frontend/src/page/Treasure.jsx (3)
walletClient(22-22)loading(23-28)fee(21-21)frontend/src/page/CreateInvoice.jsx (4)
walletClient(52-52)useAccount(53-53)loading(57-57)litClientRef(59-59)frontend/src/components/Navbar.jsx (1)
useAccount(16-16)frontend/src/page/About.jsx (2)
useAccount(7-7)loading(9-9)frontend/src/contractsABI/ChainvoiceABI.js (2)
ChainvoiceABI(1-589)ChainvoiceABI(1-589)
frontend/src/page/Treasure.jsx (4)
frontend/src/page/CreateInvoice.jsx (2)
walletClient(52-52)loading(57-57)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/ReceivedInvoice.jsx (6)
frontend/src/page/SentInvoice.jsx (6)
columns(49-56)page(59-59)useAccount(62-62)loading(63-63)fee(65-65)handlePrint(285-296)frontend/src/components/Navbar.jsx (1)
useAccount(16-16)frontend/src/page/About.jsx (2)
useAccount(7-7)loading(9-9)frontend/src/utils/erc20_token.js (2)
TOKEN_PRESETS(8-709)TOKEN_PRESETS(8-709)frontend/src/contractsABI/ERC20_ABI.js (2)
ERC20_ABI(1-10)ERC20_ABI(1-10)frontend/src/page/Working.jsx (1)
handlePrint(23-48)
🔇 Additional comments (1)
frontend/src/page/Treasure.jsx (1)
38-39: Verify BrowserProvider compatibility with wagmi's walletClient (v2)
new BrowserProvider(walletClient)assumeswalletClientis an EIP-1193 provider. Depending on wagmi version, you may needwalletClient.transport?.provideror a fallback towindow.ethereum.If you encounter runtime/type errors, this pattern is safer:
const eip1193 = walletClient?.transport?.provider ?? window.ethereum; const provider = new BrowserProvider(eip1193); const signer = await provider.getSigner();Please verify on the targeted wagmi/ethers versions and across popular wallets (MetaMask, RainbowKit connectors).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
frontend/src/page/ReceivedInvoice.jsx (3)
110-116: Fix BigInt vs Number chainId comparison (ethers v6).
getNetwork().chainIdis BigInt in ethers v6. Comparing to a Number risks subtle bugs and makes intent unclear. Use a BigInt literal or coerce to Number.Apply one of the following diffs:
- if (network.chainId != 11155111) { + if (network.chainId !== 11155111n) {or
- if (network.chainId != 11155111) { + if (Number(network.chainId) !== 11155111) {
167-167: Don’t atob() the ciphertext — pass base64 string to Lit.
atob(encryptedStringBase64)corrupts the payload expected by Lit; Lit expects the ciphertext as base64.Apply:
- const ciphertext = atob(encryptedStringBase64); + const ciphertext = encryptedStringBase64; // already base64
410-414: Guard against undefined/short addresses in formatter.Current implementation will throw for falsy or short inputs.
Apply:
- const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; - }; + const formatAddress = (addr) => { + if (!addr) return ""; + const s = String(addr); + if (s.length <= 20) return s; + return `${s.slice(0, 10)}...${s.slice(-10)}`; + };
🧹 Nitpick comments (8)
frontend/src/page/ReceivedInvoice.jsx (8)
241-246: Harden token metadata merge; handledecimalvsdecimalsand missing fields.Some presets use
decimal(singular). Preserve existing fields and provide sensible fallbacks.Apply:
- if (tokenInfo) { - parsed.paymentToken = { - ...parsed.paymentToken, - logo: tokenInfo.logo, - decimals: tokenInfo.decimals, - }; - } + if (tokenInfo) { + parsed.paymentToken = { + ...parsed.paymentToken, + logo: tokenInfo.logo ?? parsed.paymentToken.logo, + decimals: + tokenInfo.decimals ?? + tokenInfo.decimal ?? + parsed.paymentToken.decimals ?? + 18, + symbol: tokenInfo.symbol ?? parsed.paymentToken.symbol, + name: tokenInfo.name ?? parsed.paymentToken.name, + }; + }
318-321: Replace blocking alert with non-blocking toast for approval flow.Alerts block UI and are inconsistent with toast-based error handling.
Apply:
- await approveTx.wait(); - alert( - `Approval for ${tokenSymbol} completed! Now processing payment...` - ); + await approveTx.wait(); + toast.info(`Approval for ${tokenSymbol} completed! Now processing payment...`);
327-339: Use toast for payment success messages (both ERC20 and ETH paths).Keep feedback consistent and non-blocking.
Apply:
- await tx.wait(); - alert(`Payment successful in ${tokenSymbol}!`); + await tx.wait(); + toast.success(`Payment successful in ${tokenSymbol}!`);and
- await tx.wait(); - alert("Payment successful in ETH!"); + await tx.wait(); + toast.success("Payment successful in ETH!");
404-405: Use toast for network switch failure; avoid blocking alert.Aligns with the rest of your UX.
Apply:
- console.error("Network switch failed:", error); - alert("Failed to switch network. Please switch to Sepolia manually."); + console.error("Network switch failed:", error); + toast.error("Failed to switch network. Please switch to Sepolia manually.");
130-131: Remove noisy console log or guard behind a debug flag.Avoid logging raw on-chain/encrypted data in production.
Apply:
- console.log("Raw invoices data:", res); + // console.debug("Raw invoices data:", res);
880-888: Use the shared formatter for token address display.The current slicing uses hard-coded indices and may produce uneven truncation. Reuse
formatAddressfor consistency and safety.Apply:
- {drawerState.selectedInvoice.paymentToken?.address - ? `${drawerState.selectedInvoice.paymentToken.address.substring( - 0, - 10 - )}......${drawerState.selectedInvoice.paymentToken.address.substring( - 33 - )}` - : "Native Currency"} + {drawerState.selectedInvoice.paymentToken?.address + ? formatAddress(drawerState.selectedInvoice.paymentToken.address) + : "Native Currency"}
570-589: Amount column: provide a fallback symbol for native ETH.If
paymentToken?.symbolis undefined for native payments, the amount renders without a symbol.Apply:
- {invoice.amountDue}{" "} - {invoice.paymentToken?.symbol} + {invoice.amountDue}{" "} + {invoice.paymentToken?.symbol ?? "ETH"}
395-401: Guard against missingwindow.ethereum.Prevent runtime errors on non-EIP-1193 environments.
Apply:
- await window.ethereum.request({ + if (!window?.ethereum?.request) { + toast.error("No Ethereum provider found. Please install MetaMask."); + return; + } + await window.ethereum.request({
📜 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 settings in your CodeRabbit configuration.
📒 Files selected for processing (2)
frontend/src/page/ReceivedInvoice.jsx(5 hunks)frontend/src/page/SentInvoice.jsx(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/page/SentInvoice.jsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/page/ReceivedInvoice.jsx (4)
frontend/src/components/Navbar.jsx (1)
useAccount(16-16)frontend/src/contractsABI/ChainvoiceABI.js (2)
ChainvoiceABI(1-589)ChainvoiceABI(1-589)frontend/src/utils/erc20_token.js (2)
TOKEN_PRESETS(8-709)TOKEN_PRESETS(8-709)frontend/src/contractsABI/ERC20_ABI.js (2)
ERC20_ABI(1-10)ERC20_ABI(1-10)
🔇 Additional comments (1)
frontend/src/page/ReceivedInvoice.jsx (1)
654-659: LGTM: Default ZeroAddress for native ETH payment.Good catch defaulting to
ethers.ZeroAddresswhen token address is absent. This avoidsisAddressfailures and cleanly distinguishes native from ERC-20.
There was a problem hiding this 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
♻️ Duplicate comments (4)
frontend/src/page/ReceivedInvoice.jsx (4)
110-116: Fix BigInt vs Number comparison for chainId (ethers v6).getNetwork().chainId is a BigInt in ethers v6; comparing to a Number will throw. Use a BigInt literal or coerce safely.
Apply:
- if (network.chainId != 11155111) { + if (network?.chainId !== 11155111n) {Alternatively (if you prefer coercion):
- if (network.chainId != 11155111) { + if (Number(network?.chainId) !== 11155111) {
167-167: Don’t atob() the ciphertext — pass base64 string to Lit.Base64-decoding the ciphertext breaks Lit decryption. Lit expects the base64 string as-is.
Apply:
- const ciphertext = atob(encryptedStringBase64); + const ciphertext = encryptedStringBase64; // already base64-encodedAlso applies to: 218-227
394-408: After network switch, refresh state or listen to chain changes.Switching chains clears the error, but the UI may remain stale. Trigger a refresh or subscribe to chain changes.
Minimal fix (inline):
const switchNetwork = async () => { try { setNetworkLoading(true); + if (!window?.ethereum) { + toast.error("No Ethereum provider found"); + return; + } await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: "0xaa36a7" }], // Sepolia chain ID }); setError(null); + // Ensure UI reflects the new network immediately + window.location.reload();Preferred (add a chainChanged listener and refetch instead of reloading). Because
fetchReceivedInvoicesis scoped inside an effect, wrap it with useCallback first, then add:// Add near other hooks (outside effects) const fetchReceivedInvoices = useCallback(async () => { // move existing body from inside the effect here }, [walletClient, address, litReady]); useEffect(() => { if (!walletClient || !address || !litReady) return; fetchReceivedInvoices(); }, [walletClient, litReady, address, fetchReceivedInvoices]); useEffect(() => { if (!window?.ethereum) return; const handler = () => fetchReceivedInvoices(); window.ethereum.on("chainChanged", handler); return () => { window.ethereum.removeListener("chainChanged", handler); }; }, [fetchReceivedInvoices]);
410-414: Guard against undefined/short addresses in formatter.Apply:
- const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; - }; + const formatAddress = (addr) => { + if (!addr) return ""; + const s = String(addr); + if (s.length <= 20) return s; + return `${s.slice(0, 10)}...${s.slice(-10)}`; + };
🧹 Nitpick comments (5)
frontend/src/page/ReceivedInvoice.jsx (5)
318-321: Replace blocking alerts with non-blocking toasts for consistent UX.Leverage react-toastify (already imported) instead of alert() to avoid blocking modals and maintain a consistent notification style.
Apply:
- await approveTx.wait(); - alert( - `Approval for ${tokenSymbol} completed! Now processing payment...` - ); + await approveTx.wait(); + toast.info(`Approval for ${tokenSymbol} completed. 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!");Also applies to: 328-339
416-419: Make date formatting robust for epoch seconds/strings.issueDate may be epoch seconds (10-digit) or a numeric/string. Current code can produce “Invalid Date”.
Apply:
- const formatDate = (issueDate) => { - const date = new Date(issueDate); - return date.toLocaleString(); - }; + const formatDate = (value) => { + if (!value) return ""; + let date; + const n = typeof value === "string" ? Number(value) : value; + if (Number.isFinite(n)) { + // Heuristic: treat 10-digit epoch seconds as seconds + date = new Date(n < 1e12 ? n * 1000 : n); + } else { + date = new Date(value); + } + return isNaN(date.getTime()) ? "" : date.toLocaleString(); + };
874-882: Use formatAddress here instead of hard-coded substrings.The current slicing assumes fixed length and uses a magic index (33). Reuse the formatter for consistency and safety.
Apply:
- {drawerState.selectedInvoice.paymentToken?.address - ? `${drawerState.selectedInvoice.paymentToken.address.substring( - 0, - 10 - )}......${drawerState.selectedInvoice.paymentToken.address.substring( - 33 - )}` - : "Native Currency"} + {drawerState.selectedInvoice.paymentToken?.address + ? formatAddress( + drawerState.selectedInvoice.paymentToken.address + ) + : "Native Currency"}
259-262: Surface fetch/decrypt failures to the user (not just console).Errors during fetch/decrypt are swallowed from the UX perspective. Provide feedback so users know to retry or switch networks.
Apply:
- } catch (error) { - console.error("Fetch error:", error); + } catch (error) { + console.error("Fetch error:", error); + toast.error("Failed to load invoices. Please try again.");
18-18: Avoid deep import path for LitdecryptToString.Importing from an internal path can break across versions/bundlers. Prefer the package entrypoint.
Apply:
-import { decryptToString } from "@lit-protocol/encryption/src/lib/encryption.js"; +import { decryptToString } from "@lit-protocol/encryption";
📜 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 settings in your CodeRabbit configuration.
📒 Files selected for processing (1)
frontend/src/page/ReceivedInvoice.jsx(5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/page/ReceivedInvoice.jsx (3)
frontend/src/contractsABI/ChainvoiceABI.js (2)
ChainvoiceABI(1-589)ChainvoiceABI(1-589)frontend/src/utils/erc20_token.js (2)
TOKEN_PRESETS(8-709)TOKEN_PRESETS(8-709)frontend/src/contractsABI/ERC20_ABI.js (2)
ERC20_ABI(1-10)ERC20_ABI(1-10)




Summary by CodeRabbit
New Features
Refactor/UX
Chores