Update GitHub Actions workflow for Vite React deployment from subfolder#12
Update GitHub Actions workflow for Vite React deployment from subfolder#12kumawatkaran523 wants to merge 47 commits intoStabilityNexus:mainfrom
Conversation
47cfb25 to
35c1996
Compare
WalkthroughReplaces GitHub Pages workflow for a Vite React frontend in ./frontend. Extends Chainvoice.sol with batch invoice creation and payment, new errors/events, and fee/treasury management. Frontend adds routes and pages for batch creation and batch payment, enhances ReceivedInvoice with batch flows, and updates TokenCarousel to use a dynamic token list. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant FE as Frontend (CreateInvoicesBatch)
participant Lit as Lit Protocol
participant C as Chainvoice Contract
U->>FE: Enter invoices, select token, dates
FE->>Lit: Encrypt per-invoice payloads (ACL)
Lit-->>FE: Encrypted payloads + hashes
FE->>C: createInvoicesBatch(tos, amounts, token, payloads, hashes)
C-->>FE: emit InvoiceBatchCreated(ids)
FE-->>U: Navigate to sent / show success
sequenceDiagram
autonumber
participant U as User
participant FE as Frontend (BatchPayment/Received)
participant ERC20 as ERC20 Token
participant C as Chainvoice Contract
U->>FE: Select invoices (same token)
FE->>FE: Validate batch size, totals, fees
alt ERC20 token
FE->>ERC20: Check allowance
alt Insufficient
FE->>ERC20: approve(Chainvoice, total)
ERC20-->>FE: Approval tx mined
end
FE->>C: payInvoicesBatch(invoiceIds)
else Native
FE->>C: payInvoicesBatch(invoiceIds) with value
end
C-->>FE: emit InvoiceBatchPaid + InvoicePaid[]
FE-->>U: Update UI statuses, toasts
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/TokenCrousel.jsx (1)
51-55: Use stable, unique key: token.contract_address (not token.address).Hook useTokenList returns contract_address. Using token.address risks undefined keys and re-mounting.
- key={`${token.address}-${index}`} + key={`${token.contract_address}-${index}`}
🧹 Nitpick comments (21)
frontend/src/components/TokenCrousel.jsx (2)
71-76: Zero-address check uses the wrong field.Compare against token.contract_address (lowercased) to avoid false negatives.
- {token.address === - "0x0000000000000000000000000000000000000000" && ( + {token.contract_address?.toLowerCase() === + "0x0000000000000000000000000000000000000000" && (
19-26: Guard animation when content width is 0 to prevent tight RAF loops.When tokens is empty, position wraps every frame. Add early return.
useEffect(() => { - const carousel = carouselRef.current; + const carousel = carouselRef.current; + if (!carousel) return; + if (!tokens?.length) return; @@ - }, []); + }, [tokens?.length]);Also applies to: 45-45
frontend/src/page/Home.jsx (1)
15-15: Remove unused imports (CreditCard, Layers3, PlusCircle) if not used.Keeps bundle lean and avoids linter noise.
frontend/src/App.jsx (1)
36-37: Unused import: BatchPayment.Either wire the route or remove the import.
-import BatchPayment from "./page/BatchPayment"; // New import neededOptionally add a route later:
+<Route path="batch-payment" element={<BatchPayment />} />.github/workflows/deploy.yml (3)
57-57: Cache dependency path should cover both npm and yarn.Current setting always points to package-lock.json, so yarn installs won’t benefit.
- cache-dependency-path: frontend/package-lock.json + cache-dependency-path: | + frontend/package-lock.json + frontend/yarn.lock
37-46: Consider pnpm detection and aligning setup-node cache with detected manager.Broaden detection and ensure consistency across steps.
if [ -f "${{ github.workspace }}/frontend/yarn.lock" ]; then echo "manager=yarn" >> $GITHUB_OUTPUT echo "command=install" >> $GITHUB_OUTPUT echo "runner=yarn" >> $GITHUB_OUTPUT exit 0 + elif [ -f "${{ github.workspace }}/frontend/pnpm-lock.yaml" ]; then + echo "manager=pnpm" >> $GITHUB_OUTPUT + echo "command=install" >> $GITHUB_OUTPUT + echo "runner=pnpm" >> $GITHUB_OUTPUT + exit 0 elif [ -f "${{ github.workspace }}/frontend/package.json" ]; thenAlso update cache key patterns to include config/assets for better cache busting:
- key: ${{ runner.os }}-vite-${{ hashFiles('frontend/package-lock.json', 'frontend/yarn.lock') }}-${{ hashFiles('frontend/**.[jt]s', 'frontend/**.[jt]sx') }} + key: ${{ runner.os }}-vite-${{ hashFiles('frontend/package-lock.json', 'frontend/yarn.lock', 'frontend/pnpm-lock.yaml') }}-${{ hashFiles('frontend/**.[jt]s', 'frontend/**.[jt]sx', 'frontend/**.css', 'frontend/**.html', 'frontend/vite.config.*') }}
66-73: Caching dist is optional and may be unnecessary.Since you always build before upload, caching frontend/dist rarely helps and can waste cache space.
- frontend/dist - frontend/node_modules/.vite + frontend/node_modules/.vitefrontend/src/page/CreateInvoicesBatch.jsx (2)
268-274: Use imported Contract directly (avoid ethers.Contract) and handle bad addresses.Also validate token address before constructing the contract.
- const contract = new ethers.Contract(address, ERC20_ABI, provider); + if (!ethers.isAddress(address)) throw new Error("Invalid token address"); + const contract = new Contract(address, ERC20_ABI, provider);
39-41: Toast library mismatch with the rest of the app (react-hot-toast vs react-toastify).App.jsx uses react-hot-toast Toaster; this file uses react-toastify. Unify to one library.
-import { toast } from "react-toastify"; -import "react-toastify/dist/ReactToastify.css"; +import { toast } from "react-hot-toast";frontend/src/page/BatchPayment.jsx (6)
816-820: Null-guard address formatting to avoid runtime crashesformatAddress assumes a non-empty string. If invoice.user?.address is undefined/null, this will throw during render.
Apply:
-const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; -}; +const formatAddress = (addr) => { + if (!addr || typeof addr !== "string" || addr.length < 12) return addr || ""; + return `${addr.slice(0, 10)}...${addr.slice(-10)}`; +};
800-814: Harden network switching and use consistent UX (toast vs alert)Guard for missing window.ethereum and prefer toast over alert for consistency with the rest of the UI.
const switchNetwork = async () => { try { setNetworkLoading(true); - await window.ethereum.request({ + if (!window?.ethereum) { + toast.error("No injected wallet found. Please switch to Sepolia manually."); + return; + } + await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: "0xaa36a7" }], // Sepolia chain ID }); setError(null); + toast.success("Successfully switched to Sepolia network!"); } catch (error) { console.error("Network switch failed:", error); - alert("Failed to switch network. Please switch to Sepolia manually."); + toast.error("Failed to switch network. Please switch to Sepolia manually in your wallet."); } finally { setNetworkLoading(false); } };
728-731: Replace blocking alerts with non-blocking toastsKeep feedback patterns consistent and non-intrusive.
- 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! Paid with ${tokenSymbol}`); ... - await tx.wait(); - alert("Payment successful in ETH!"); + await tx.wait(); + toast.success("Payment successful! Paid with ETH");Also applies to: 737-749
169-217: Balance pre-checks may use stale fee; pass on-chain fee explicitlycheckPaymentCapability multiplies by component state fee, which may be 0 or stale if the fetch hasn’t completed. Fetch fee from the contract once and pass it in.
-const checkPaymentCapability = async (group, signer) => { +const checkPaymentCapability = async (group, signer, feePerInvoice) => { const { tokenAddress, symbol, invoices, totalAmount } = group; const userAddress = await signer.getAddress(); if (tokenAddress === ethers.ZeroAddress) { // Check ETH balance const balance = await signer.provider.getBalance(userAddress); - const totalFee = BigInt(fee) * BigInt(invoices.length); + const totalFee = BigInt(feePerInvoice) * BigInt(invoices.length); const totalRequired = ethers.parseUnits(totalAmount.toString(), 18) + totalFee; ... } else { // Check ERC20 balance ... // Check ETH balance for fees const ethBalance = await signer.provider.getBalance(userAddress); - const totalFee = BigInt(fee) * BigInt(invoices.length); + const totalFee = BigInt(feePerInvoice) * BigInt(invoices.length); if (ethBalance < totalFee) { ... } } };And in handleBatchPayment before the pre-check loop:
- const grouped = getGroupedInvoices(); + const grouped = getGroupedInvoices(); + const feePerInvoice = await contract.fee(); // PRE-CHECK ALL BALANCES BEFORE ANY TRANSACTIONS toast.info("Checking balances..."); const errors = []; for (const [tokenKey, group] of grouped.entries()) { try { - await checkPaymentCapability(group, signer); + await checkPaymentCapability(group, signer, feePerInvoice); } catch (error) { errors.push(`${group.symbol}: ${error.message}`); } }Also applies to: 547-567
346-351: Prefer explicit BigInt-safe comparison for chainIdethers v6 returns chainId as BigInt. Use Number(...) or a BigInt literal to avoid implicit coercion pitfalls.
- if (network.chainId != 11155111) { + if (Number(network.chainId) !== 11155111) {
73-101: DRY: Extract shared invoice/token helpers into a reusable modulegetTokenInfo/getTokenSymbol, detectBatchFromMetadata, getGroupedInvoices, handlePrint, and switchNetwork duplicate logic in ReceivedInvoice.jsx. Centralize into a shared util to reduce drift.
Would you like me to factor these into src/lib/invoices.tsx (or utils) and wire both screens to it?
Also applies to: 102-166, 276-306, 772-799, 800-814
frontend/src/page/ReceivedInvoice.jsx (3)
871-875: Null-guard address formatting to avoid runtime crashesSame risk as in BatchPayment: invoice.user?.address can be falsy.
-const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; -}; +const formatAddress = (addr) => { + if (!addr || typeof addr !== "string" || addr.length < 12) return addr || ""; + return `${addr.slice(0, 10)}...${addr.slice(-10)}`; +};
631-637: Make chainId comparison explicit (ethers v6 returns BigInt)Avoid implicit coercion; use Number(...) or 11155111n.
-if (network.chainId != 11155111) { +if (Number(network.chainId) !== 11155111) {
236-274: Ensure fee used in balance checks is currentcheckBalance relies on state fee which may be unset/stale; fetch from contract and pass it explicitly to avoid false positives/negatives.
-const checkBalance = async (tokenAddress, amount, symbol, signer) => { +const checkBalance = async (tokenAddress, amount, symbol, signer, feeOverride) => { + const effectiveFee = feeOverride ?? fee; const userAddress = await signer.getAddress(); if (tokenAddress === ethers.ZeroAddress) { const balance = await signer.provider.getBalance(userAddress); - const totalRequired = ethers.parseUnits(amount.toString(), 18) + BigInt(fee); + const totalRequired = ethers.parseUnits(amount.toString(), 18) + BigInt(effectiveFee); ... } else { ... - if (ethBalance < BigInt(fee)) { - const requiredEthFee = ethers.formatEther(fee); + if (ethBalance < BigInt(effectiveFee)) { + const requiredEthFee = ethers.formatEther(effectiveFee); ... } } };Use it where invoked:
- await checkBalance(tokenAddress, amountDue, tokenSymbol, signer); + const feeOnChain = await contract.fee(); + await checkBalance(tokenAddress, amountDue, tokenSymbol, signer, feeOnChain);And before the batch pre-check loop:
- toast.info("Checking balances..."); + toast.info("Checking balances..."); + const feeOnChain = await contract.fee(); for (const [tokenKey, group] of grouped.entries()) { try { - await checkBalance(group.tokenAddress, group.totalAmount, group.symbol, signer); + await checkBalance(group.tokenAddress, group.totalAmount, group.symbol, signer, feeOnChain);Also applies to: 398-406, 487-507
contracts/src/Chainvoice.sol (3)
84-101: Enforce positive amount in single-invoice creation (parity with batch create)createInvoice does not validate amountDue > 0, while createInvoicesBatch does. Zero-amount invoices would still require paying the fee, which is likely undesirable/inconsistent.
function createInvoice( address to, uint256 amountDue, address tokenAddress, string memory encryptedInvoiceData, string memory encryptedHash ) external { require(to != address(0), "Recipient address is zero"); require(to != msg.sender, "Self-invoicing not allowed"); + require(amountDue > 0, "Amount zero");
73-81: Avoid duplicated ERC20 validation; use the helper or remove itYou added _isERC20 but still inline the staticcall checks in both create paths. Either use _isERC20(tokenAddress) to centralize validation or drop the helper to reduce dead code.
Example:
- 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"); - } + if (tokenAddress != address(0)) { + require(_isERC20(tokenAddress), "Not an ERC20 token"); + }And same for createInvoicesBatch.
Also applies to: 94-101, 141-148
385-395: Anyone can withdraw to treasury (intentional?)withdrawFees isn’t onlyOwner. Funds always go to treasuryAddress, so this isn’t theft-prone, but it allows grief-triggered withdrawals at arbitrary times. Confirm this is intentional; otherwise gate with onlyOwner.
-function withdrawFees() external { +function withdrawFees() external onlyOwner {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.github/workflows/deploy.yml(2 hunks)contracts/src/Chainvoice.sol(5 hunks)frontend/src/App.jsx(2 hunks)frontend/src/components/TokenCrousel.jsx(2 hunks)frontend/src/page/BatchPayment.jsx(1 hunks)frontend/src/page/CreateInvoicesBatch.jsx(1 hunks)frontend/src/page/Home.jsx(4 hunks)frontend/src/page/ReceivedInvoice.jsx(19 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/page/BatchPayment.jsx (1)
frontend/src/page/ReceivedInvoice.jsx (24)
page(72-72)walletClient(74-74)receivedInvoices(77-77)selectedInvoices(91-91)fee(78-78)error(79-79)drawerState(96-99)getTokenInfo(175-182)getTokenSymbol(184-187)detectBatchFromMetadata(189-199)findBatchSuggestions(201-234)selectBatchSuggestion(339-343)payEntireBatch(345-364)handleBatchPayment(469-590)handleSelectInvoice(313-326)handleSelectAll(328-333)unpaidInvoices(882-884)handleClearAll(335-337)getGroupedInvoices(276-302)grouped(886-886)payInvoice(367-466)toggleDrawer(819-831)handlePrint(833-850)switchNetwork(852-869)
frontend/src/components/TokenCrousel.jsx (1)
frontend/src/hooks/useTokenList.js (2)
useTokenList(18-86)tokens(19-19)
frontend/src/page/CreateInvoicesBatch.jsx (10)
frontend/src/page/BatchPayment.jsx (6)
walletClient(39-39)useAccount(40-40)loading(41-41)litClientRef(48-48)showWalletAlert(51-51)error(46-46)frontend/src/page/ReceivedInvoice.jsx (6)
walletClient(74-74)useAccount(75-75)loading(76-76)litClientRef(81-81)showWalletAlert(84-84)error(79-79)frontend/src/components/WalletConnectionAlert.jsx (1)
WalletConnectionAlert(7-56)frontend/src/components/ui/label.jsx (1)
Label(11-13)frontend/src/components/ui/button.jsx (1)
Button(37-45)frontend/src/components/ui/popover.jsx (3)
Popover(6-6)PopoverTrigger(8-8)PopoverContent(12-24)frontend/src/components/ui/input.jsx (1)
Input(5-16)frontend/src/components/TokenPicker.jsx (1)
TokenPicker(175-382)frontend/src/components/ui/copyButton.jsx (1)
CopyButton(5-39)frontend/src/lib/utils.js (1)
cn(4-6)
frontend/src/page/ReceivedInvoice.jsx (2)
frontend/src/page/SentInvoice.jsx (11)
columns(51-58)drawerState(346-349)error(69-69)getTokenInfo(82-90)fee(68-68)walletClient(63-63)switchNetwork(406-420)showWalletAlert(76-76)paymentLoading(72-72)toggleDrawer(351-363)handlePrint(365-376)frontend/src/page/BatchPayment.jsx (27)
selectedInvoices(43-43)batchLoading(44-44)batchSuggestions(53-53)drawerState(56-59)error(46-46)getTokenInfo(74-81)getTokenSymbol(84-87)detectBatchFromMetadata(90-100)findBatchSuggestions(103-167)fee(45-45)getGroupedInvoices(277-305)grouped(831-831)receivedInvoices(42-42)handleSelectInvoice(250-263)handleSelectAll(265-270)unpaidInvoices(827-829)handleClearAll(272-274)selectBatchSuggestion(220-224)payEntireBatch(227-247)handleBatchPayment(540-678)payInvoice(681-770)walletClient(39-39)switchNetwork(800-814)showWalletAlert(51-51)paymentLoading(49-49)toggleDrawer(773-785)handlePrint(787-798)
🔇 Additional comments (8)
frontend/src/page/Home.jsx (1)
111-112: Template string is fine; current concatenation is OK.No action needed; just noting style change is harmless.
frontend/src/App.jsx (1)
89-92: Route wiring for batch creation looks good.The new /dashboard/batch-invoice route renders CreateInvoicesBatch.
frontend/src/page/CreateInvoicesBatch.jsx (2)
400-424: Lit config: chain hardcoded to "ethereum" and sessionSigs unused.
- Consider mapping the active chain to Lit’s expected chain name.
- If encryptString requires sessionSigs, pass them; otherwise drop the unused call.
Do you intend to target only Ethereum mainnet for access control? If not, map chainId to the appropriate Lit chain string and either pass sessionSigs to encryptString or remove the dead code.
Also applies to: 434-466
739-773: Token selection UX is solid; custom‑token verification flow reads well.Nice feedback and copy actions.
Also applies to: 795-811, 821-851, 853-867
frontend/src/page/ReceivedInvoice.jsx (3)
112-172: Unified error parsing looks solidNice defensive normalization of common provider/contract error shapes. This should reduce noisy messages.
819-869: Good UX polish on network switching and error snackbarSnackbar pattern and toasts are consistent and accessible; props and autoHide are reasonable.
Also applies to: 930-967
748-795: Invoice drawer: safe defaults for token visualsGraceful logo fallback and clear fee/total display. LGTM.
Also applies to: 1796-1841
contracts/src/Chainvoice.sol (1)
246-318: Batch payment CEI and invariants look correct
- Token-homogeneous enforcement (MixedTokenBatch).
- All-or-nothing semantics with effects-before-interactions and nonReentrant.
- Correct native vs ERC20 fee/value handling.
If you want, I can generate Foundry/Hardhat tests to cover:
- Mixed token array reverts with MixedTokenBatch.
- ERC20 insufficient allowance reverts with InsufficientAllowance.
- Native incorrect msg.value reverts with IncorrectNativeValue.
- Max batch size boundary (50/51).
Summary by CodeRabbit
New Features
Improvements
Chores