-
Notifications
You must be signed in to change notification settings - Fork 12
Update README with stability standard #10
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
Update README with stability standard #10
Conversation
WalkthroughReplaces static TOKEN_PRESETS with a dynamic token-list and search flow, adds a TokenPicker component and related UI primitives, introduces hooks for fetching/searching tokens, integrates custom ERC-20 verification in CreateInvoice/GenerateLink, updates invoice pages to resolve token metadata by chain, updates dependencies, and rewrites README guidance. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CreateInvoice as Create/Generate Pages
participant TokenPicker
participant useTokenList
participant useTokenSearch
User->>CreateInvoice: Open token selector
CreateInvoice->>TokenPicker: Render trigger
User->>TokenPicker: Click trigger
TokenPicker->>useTokenList: fetch(chainId)
useTokenList-->>TokenPicker: { tokens, loading/error }
TokenPicker->>useTokenSearch: index(tokens), setQuery(q)
useTokenSearch-->>TokenPicker: filteredTokens
User->>TokenPicker: Select token
TokenPicker-->>CreateInvoice: onSelect(token)
CreateInvoice-->>User: Token set in form
sequenceDiagram
participant User
participant Page as Create/Generate Pages
participant Provider as BrowserProvider/ethers
participant ERC20 as ERC20 Contract
User->>Page: Enter contract address (custom token)
Page->>Page: tokenVerificationState = verifying
Page->>Provider: getSigner()
Provider-->>Page: signer
Page->>ERC20: symbol(), name(), decimals()
ERC20-->>Page: metadata or error
alt success
Page->>Page: set verifiedToken + state=success
else error
Page->>Page: set state=error
end
Page-->>User: Show verified info or error banner
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
✨ 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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
frontend/src/components/TokenCrousel.jsx (1)
4-9: Fix runtime ReferenceError: migrate off TOKEN_PRESETS.The import was removed but
duplicatedTokensstill referencesTOKEN_PRESETS, which will crash at render. Switch to the new token list source and memoize the doubled array; add a null-guard for the ref in effect.-import { useEffect, useRef } from "react"; +import { useEffect, useRef, useMemo } from "react"; +import { useTokenList } from "@/hooks/useTokenList"; @@ - const duplicatedTokens = [...TOKEN_PRESETS, ...TOKEN_PRESETS]; // Double the tokens for seamless loop + const { tokens = [] } = useTokenList(); + const duplicatedTokens = useMemo(() => [...tokens, ...tokens], [tokens]); // Double for seamless loop @@ - const carousel = carouselRef.current; + const carousel = carouselRef.current; + if (!carousel) return;frontend/src/page/SentInvoice.jsx (2)
422-426: formatAddress can throw on undefined/short inputGuard against undefined/short strings to avoid runtime errors.
-const formatAddress = (address) => { - return `${address.substring(0, 10)}...${address.substring( - address.length - 10 - )}`; -}; +const formatAddress = (addr = "") => { + if (!addr || addr.length < 15) return addr || "—"; + return `${addr.slice(0, 10)}...${addr.slice(-10)}`; +};
903-904: Hard-coded “Chain: Sepolia Testnet”This contradicts the README’s ETC guidance. Consider deriving from current network and/or env.
-<p>Chain: Sepolia Testnet</p> +<p>Chain: {network?.name || (chainId === 61 ? "Ethereum Classic" : "Unknown")}</p>frontend/src/page/ReceivedInvoice.jsx (1)
153-160: Fix BigInt/number comparison for chainId (ethers v6).
provider.getNetwork()returnschainIdasbigint. Using loose!= 11155111is brittle. Compare asNumber(...)or to a11155111nliteral.- if (network.chainId != 11155111) { + if (Number(network.chainId) !== 11155111) {frontend/src/page/CreateInvoice.jsx (3)
20-27: WrongBadgeimport breaks UI.
Badgehere should be the UI component, not a Lucide icon. Import from your UI lib and remove fromlucide-react.-import { - Badge, - CalendarIcon, - CheckCircle2, - Coins, - Loader2, - PlusIcon, - XCircle, -} from "lucide-react"; +import { + CalendarIcon, + CheckCircle2, + Coins, + Loader2, + PlusIcon, + XCircle, +} from "lucide-react"; +import { Badge } from "@/components/ui/badge";
97-116: LeftoverTOKEN_PRESETScauses runtime ReferenceError and wrong decimals.This block references a removed constant and doesn't resolve token decimals, which are required later for
parseUnits(...).- } else { - const preselectedToken = TOKEN_PRESETS.find( - (token) => - token.address.toLowerCase() === urlTokenAddress.toLowerCase() - ); - if (preselectedToken) { - setSelectedToken(preselectedToken); - setUseCustomToken(false); - } else { - setUseCustomToken(true); - setCustomTokenAddress(urlTokenAddress); - verifyToken(urlTokenAddress); - } - } + } else { + // Resolve token metadata from chain to capture proper decimals + const resolve = async () => { + try { + const provider = new BrowserProvider( + typeof window !== "undefined" && window.ethereum + ? window.ethereum + : walletClient + ); + const contract = new ethers.Contract(urlTokenAddress, ERC20_ABI, provider); + const [symbol, name, decimals] = await Promise.all([ + contract.symbol().catch(() => "UNKNOWN"), + contract.name().catch(() => "Unknown Token"), + contract.decimals().catch(() => 18), + ]); + setSelectedToken({ + address: urlTokenAddress, + symbol, + name, + decimals: Number(decimals), + logo: "/tokenImages/generic.png", + }); + setUseCustomToken(false); + } catch { + // Fallback minimal metadata; user can re-pick token + setSelectedToken({ + address: urlTokenAddress, + symbol: "TOKEN", + name: "Unknown Token", + decimals: 18, + logo: "/tokenImages/generic.png", + }); + setUseCustomToken(false); + } + }; + resolve(); + }
228-244: Guard against missingpaymentTokenbefore encoding amounts.If the user hasn't selected/verified a token,
paymentTokencan be null, causing failures or NaNs inparseUnits(...).- const paymentToken = useCustomToken ? verifiedToken : selectedToken; + const paymentToken = useCustomToken ? verifiedToken : selectedToken; + if (!paymentToken || !paymentToken.address || paymentToken.decimals == null) { + alert("Please select or verify a payment token before creating the invoice."); + setLoading(false); + return; + }Also applies to: 345-351
🧹 Nitpick comments (36)
frontend/src/components/TokenCrousel.jsx (2)
6-6: Align filename with component name.The file is
TokenCrousel.jsxbut the component isTokenCarousel. Please rename the file to prevent import mistakes.
64-69: Avoid hard-coding native-token detection to a zero address.The new token list may flag native assets differently across chains. Prefer a semantic flag (e.g.,
token.isNative) with the address check as a fallback.- {token.address === - "0x0000000000000000000000000000000000000000" && ( + {(token.isNative || + token.address === "0x0000000000000000000000000000000000000000") && ( <div className="absolute -bottom-1 -right-1 bg-green-500 rounded-full p-0.5"> <SiEthereum className="text-white text-xs" /> </div> )}frontend/src/components/ui/avatar.jsx (1)
1-1: Provide sane defaults for a11y and robust image fallback.Default
altto an empty string and add a safeonErrorfallback to prevent broken images without requiring every caller to pass it.-const Avatar = ({ src, alt, className = "", children, onError }) => ( +const Avatar = ({ src, alt = "", className = "", children, onError }) => ( @@ - <img + <img src={src} alt={alt} className="w-full h-full object-cover rounded-full" - onError={onError} + onError={ + onError ?? + ((e) => { + e.currentTarget.onerror = null; + e.currentTarget.src = "/tokenImages/default.png"; + }) + } />Also applies to: 6-11
frontend/src/components/ui/copyButton.jsx (1)
1-3: Harden copy UX: handle insecure contexts, empty input, and timer cleanup.Add a fallback for environments without
navigator.clipboard, disable the button whentextToCopyis falsy, and clear the timeout on unmount to avoid leaks.-import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; @@ const CopyButton = ({ textToCopy, className = "" }) => { const [copied, setCopied] = useState(false); + const timeoutRef = useRef(null); - const handleCopy = async (e) => { + const handleCopy = async (e) => { e.stopPropagation(); try { - await navigator.clipboard.writeText(textToCopy); + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(textToCopy ?? ""); + } else { + // Fallback: temporary textarea + const ta = document.createElement("textarea"); + ta.value = textToCopy ?? ""; + ta.style.position = "fixed"; + ta.style.opacity = "0"; + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy"); + document.body.removeChild(ta); + } setCopied(true); - setTimeout(() => setCopied(false), 500); + timeoutRef.current = setTimeout(() => setCopied(false), 500); } catch (err) { console.error("Failed to copy:", err); } }; + + useEffect(() => { + return () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }; + }, []); @@ <button type="button" onClick={handleCopy} className={`inline-flex items-center gap-1 px-2 py-1 text-xs rounded hover:bg-gray-100 transition-colors ${className}`} title="Copy address" + disabled={!textToCopy} >Also applies to: 5-7, 8-17, 19-38
frontend/src/components/ui/separator.jsx (1)
14-18: Optional: rely on Radix data-orientation instead of branchingRemoves the ternary and lets CSS handle orientation via data attributes.
Apply this diff:
- "shrink-0 bg-border", - orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", + "shrink-0 bg-border data-[orientation=horizontal]:h-[1px] data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-[1px]",frontend/src/hooks/useTokenSearch.js (4)
21-27: Constrain prefix index growthIndexing every prefix of long names/symbols can bloat memory/time. Cap the prefix length.
Apply this diff:
- for (let i = 1; i <= symbol.length; i++) { + for (let i = 1; i <= Math.min(symbol.length, 12); i++) { @@ - for (let i = 1; i <= name.length; i++) { + for (let i = 1; i <= Math.min(name.length, 12); i++) {Also applies to: 30-36
38-40: Leverage exact address map before substring scanUse O(1) exact match first; then fall back to includes-scan for partial queries.
Apply this diff:
// Address index indexes.byAddress.set(address, token); @@ - // Check address matches + // Exact address match + const exactAddress = indexes.byAddress.get(lowerQuery); + if (exactAddress) { + results.add(exactAddress); + } + + // Check partial address matches if (lowerQuery.length >= 2) { // Only search addresses for queries >= 2 chars for (const [address, token] of indexes.byAddress.entries()) { if (address.includes(lowerQuery)) { results.add(token); } } }Also applies to: 90-98
61-63: Reset pagination when tokens changePrevents stale pages when a new token set arrives.
Apply this diff:
// Create search indexes const indexes = useMemo(() => createSearchIndexes(tokens), [tokens]); + // Reset page when the underlying dataset changes + useEffect(() => { + setPage(1) + }, [tokens])
46-49: Consider removing unused loading/errorThey are never set here. Either wire them to upstream fetch state or drop them from this hook’s API to avoid confusion.
Also applies to: 109-117
README.md (5)
2-2: Anchor should use id, not nameGitHub anchors target id, so the "back to top" link won’t work reliably.
-<div name="readme-top"></div> +<div id="readme-top"></div>
70-75: Format shell commands and avoid bare URL warningFence the commands to fix readability and markdownlint MD034.
-2. **Clone your fork** - -git clone https://github.com/yourusername/Chainvoice.git -cd Chainvoice +2. **Clone your fork** +```bash +git clone https://github.com/yourusername/Chainvoice.git +cd Chainvoice +```
78-91: Frontend steps should be fenced as codeRender commands as code blocks for consistency.
-1. **Navigate to frontend directory** -cd frontend - -2. **Install dependencies** -npm install - -3. **Start development server** -npm run dev - -4. **Open application** -Navigate to `http://localhost:5173` in your browser +1. **Navigate to frontend directory** +```bash +cd frontend +``` +2. **Install dependencies** +```bash +npm install +``` +3. **Start development server** +```bash +npm run dev +``` +4. **Open application** +Visit http://localhost:5173 in your browser.
96-104: Fix numbering and fence test commandsThe list numbering skips “2”; also render commands as code.
-1. **Navigate to contracts directory** -cd contracts - -3. **Run test suite** -forge test - -4. **Run tests with verbosity (optional)** -forge test -vvv +1. **Navigate to contracts directory** +```bash +cd contracts +``` +2. **Run test suite** +```bash +forge test +``` +3. **Run tests with verbosity (optional)** +```bash +forge test -vvv +```
119-131: Deployment command should be a single fenced blockCurrent wrapping splits the command. Use a code block.
-4. **Deploy to Ethereum Classic** -forge create contracts/src/Chainvoice.sol:Chainvoice ---rpc-url $ETC_RPC_URL ---private-key $PRIVATE_KEY ---broadcast +4. **Deploy to Ethereum Classic** +```bash +forge create contracts/src/Chainvoice.sol:Chainvoice \ + --rpc-url "$ETC_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --broadcast +```frontend/src/page/SentInvoice.jsx (4)
65-65: chainId handlingIf address is on Sepolia only, consider enforcing chainId 11155111 in one place (config) and removing the “|| 1” fallback to avoid silently querying mainnet token lists elsewhere.
-const { address, isConnected, chainId } = useAccount(); +const { address, isConnected, chainId } = useAccount(); // prefer explicit chain gating below
155-161: Coerce chainId to number when comparingAvoid BigInt/number pitfalls from ethers providers.
-if (network.chainId != 11155111) { +if (Number(network.chainId) !== 11155111) {
274-323: On-chain token metadata fallback: minor hardeningGood fallback. Consider wrapping provider-dependent calls with a short timeout or race to avoid long UI stalls on slow RPCs; also guard decimals Number() when it’s already a number.
-const [symbol, name, decimals] = await Promise.all([ +const [symbol, name, decimals] = await Promise.allSettled([ tokenContract.symbol().catch(() => parsed.paymentToken.symbol || "UNKNOWN"), tokenContract.name().catch(() => parsed.paymentToken.name || "Unknown Token"), tokenContract.decimals().catch(() => parsed.paymentToken.decimals || 18), -]); +]).then(results => results.map(r => (r.status === "fulfilled" ? r.value : undefined))); ... - decimals: Number(decimals), + decimals: Number(decimals ?? 18),
406-420: Network switch UXGood use of wallet_switchEthereumChain. Optionally, add chain add fallback (wallet_addEthereumChain) if Sepolia isn’t present.
} catch (error) { console.error("Network switch failed:", error); - alert("Failed to switch network. Please switch to Sepolia manually."); + // Fallback: suggest adding the chain if not available + try { + await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ + chainId: "0xaa36a7", + chainName: "Sepolia", + nativeCurrency: { name: "Sepolia ETH", symbol: "SEP", decimals: 18 }, + rpcUrls: ["https://rpc.sepolia.org"], + blockExplorerUrls: ["https://sepolia.etherscan.io"], + }], + }); + } catch { + alert("Failed to switch/add Sepolia. Please switch manually."); + }frontend/src/hooks/useTokenList.js (5)
12-16: Minor formatting and intentAdd spacing consistency and clarify mapping comments.
export const ChainIdToName = { - 1: "ethereum", - 61:"ethereum-classic", - 11155111: "sepolia", // For demo purposes + 1: "ethereum", + 61: "ethereum-classic", + 11155111: "sepolia", // Testnet };
34-39: Surface guidance in error for testnetsProvide a short actionable hint (e.g., “Use the custom token flow”).
- setError(`Please manually input the token's contract address instead.`); + setError(`Testnet detected. Please use the "Custom Token" option and input the token's contract address.`);
62-69: Include decimals (and address) in normalized tokensDownstream code expects decimals/logo; include decimals if present to reduce on-chain calls.
-const transformedData = data.map((token) => ({ - contract_address: token.contract_address || token.address, - symbol: token.symbol, - name: token.name, - image: token.image || token.logo || "/tokenImages/generic.png", -})); +const transformedData = data.map((token) => ({ + contract_address: token.contract_address || token.address, + address: token.address || token.contract_address, // keep both for compatibility + symbol: token.symbol, + name: token.name, + decimals: token.decimals ?? undefined, + image: token.image || token.logo || "/tokenImages/generic.png", +}));
26-33: Set loading=false when returning from cacheAvoid transient spinners if callers check loading.
if (tokenCache[chainId]) { setTokens(tokenCache[chainId]); + setLoading(false); return; }
48-55: Tiny robustness improvementAdd a short fetch timeout to prevent hanging on slow GitHub responses.
-const response = await fetch(dataUrl, { +const controller = new AbortController(); +const t = setTimeout(() => controller.abort(), 10_000); +const response = await fetch(dataUrl, { headers: { Accept: "application/json" }, -}); + signal: controller.signal, +}); +clearTimeout(t);frontend/src/page/ReceivedInvoice.jsx (3)
340-342: Re-run fetch when network changes.The effect doesn't depend on
chainId. Add it to ensure invoices refresh after a successful network switch.- }, [walletClient, litReady, address, tokens]); + }, [walletClient, litReady, address, chainId, tokens]);
660-663: Usee.currentTargetinonErrorhandlers.More robust than
e.targetand avoids edge cases in React event handling.- onError={(e) => { - e.target.src = "/tokenImages/generic.png"; - }} + onError={(e) => { + e.currentTarget.src = "/tokenImages/generic.png"; + }}- onError={(e) => { - e.target.src = "/tokenImages/generic.png"; - }} + onError={(e) => { + e.currentTarget.src = "/tokenImages/generic.png"; + }}Also applies to: 952-955
272-320: Avoid N sequential on-chain metadata calls; batch or cache.Fetching ERC-20 metadata per invoice, sequentially, will scale poorly. Consider batching via
Promise.allwith a per-tick concurrency cap and cache token-address→metadata.If helpful, I can provide a small in-file LRU cache for token metadata.
frontend/src/page/CreateInvoice.jsx (1)
196-221: PreferwalletClientwhen available for token verification; debounce input.Using
window.ethereumis fine, butwalletClientis already scoped. Also, verifying on every keystroke can spam RPC.Happy to share a small debounced
verifyTokenwrapper if you'd like.frontend/src/page/GenerateLink.jsx (2)
273-288: Debounce custom-token verification to reduce RPC spam.Verification fires on every character change. Add a short debounce (300–500ms) and cancel in-flight calls on new input.
I can provide a minimal
useDebouncedEffecthelper if useful.
211-234: Populate accurate decimals when selecting from picker (future-proofing).You set
decimals: 18. While link generation doesn’t use decimals, downstream prefill flows may. Consider resolving decimals here too for consistency.I can mirror the
CreateInvoiceapproach for resolving decimals post-select.frontend/src/components/TokenPicker.jsx (7)
140-143: Prevent potential image onError loopsIf the fallback also fails, this can recurse. Null the handler before swapping src.
- onError={(e) => { - e.currentTarget.src = "/tokenImages/generic.png"; - }} + onError={(e) => { + e.currentTarget.onerror = null; + e.currentTarget.src = "/tokenImages/generic.png"; + }}And similarly for the selected token avatar:
- onError={(e) => { - e.currentTarget.src = "/tokenImages/generic.png"; - }} + onError={(e) => { + e.currentTarget.onerror = null; + e.currentTarget.src = "/tokenImages/generic.png"; + }}Also applies to: 241-243
21-47: Add dialog a11y and ESC-to-closeImprove accessibility and UX by labeling the dialog and supporting Escape to close.
const Modal = ({ isOpen, onClose, children }) => { useEffect(() => { if (isOpen) { document.body.style.overflow = "hidden"; } else { document.body.style.overflow = "unset"; } return () => { document.body.style.overflow = "unset"; }; }, [isOpen]); + useEffect(() => { + if (!isOpen) return; + const onKeyDown = (e) => { + if (e.key === "Escape") onClose(); + }; + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, [isOpen, onClose]); if (!isOpen) return null; return ( - <div className="fixed inset-0 z-50 flex items-center justify-center"> + <div + className="fixed inset-0 z-50 flex items-center justify-center" + role="dialog" + aria-modal="true" + aria-labelledby="token-picker-title" + > <div className="fixed inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />Label the title:
- <h2 className="text-xl font-semibold text-gray-900"> + <h2 id="token-picker-title" className="text-xl font-semibold text-gray-900"> Select Token </h2>Also applies to: 269-271
273-279: Add accessible labels to icon-only controlsScreen readers need labels for the close and clear buttons.
- <button + <button type="button" onClick={() => setOpen(false)} - className="ml-auto p-1 hover:bg-gray-100 rounded-full transition-colors" + aria-label="Close" + className="ml-auto p-1 hover:bg-gray-100 rounded-full transition-colors" >- {query && ( + {query && ( <button type="button" onClick={() => setQuery("")} - className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-gray-100 rounded-full transition-colors" + aria-label="Clear search" + className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-gray-100 rounded-full transition-colors" >Also applies to: 291-299
221-232: Expose dialog state on trigger buttonAdvertise the popup relationship for assistive tech.
- <Button + <Button type="button" variant="outline" disabled={disabled} onClick={() => setOpen(true)} + aria-haspopup="dialog" + aria-expanded={open} className={cn(
160-163: Guard address displayAvoid potential crashes if contract_address is missing.
- <span className="text-gray-500 text-sm font-mono truncate"> - {token.contract_address.slice(0, 8)}... - {token.contract_address.slice(-6)} - </span> + <span className="text-gray-500 text-sm font-mono truncate"> + {token.contract_address + ? `${token.contract_address.slice(0, 8)}...${token.contract_address.slice(-6)}` + : "—"} + </span>
60-76: Mark toggle as a switch for a11yExpose semantics and state; improves SR and testing.
- <button + <button type="button" onClick={() => onChange(!enabled)} + role="switch" + aria-checked={enabled} + aria-label={`${leftLabel} / ${rightLabel}`} className={cn(
303-313: Consider list virtualization or incremental loadingLarge token sets will re-render many rows. Consider react-window/react-virtual or IntersectionObserver to call useTokenSearch.loadMore() as the user scrolls. Keeps memory/paint costs low.
Also applies to: 339-353
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
README.md(1 hunks)frontend/package.json(3 hunks)frontend/src/components/TokenCrousel.jsx(1 hunks)frontend/src/components/TokenPicker.jsx(1 hunks)frontend/src/components/ui/avatar.jsx(1 hunks)frontend/src/components/ui/badge.jsx(1 hunks)frontend/src/components/ui/copyButton.jsx(1 hunks)frontend/src/components/ui/dialog.jsx(1 hunks)frontend/src/components/ui/separator.jsx(1 hunks)frontend/src/hooks/useTokenList.js(1 hunks)frontend/src/hooks/useTokenSearch.js(1 hunks)frontend/src/page/CreateInvoice.jsx(7 hunks)frontend/src/page/GenerateLink.jsx(8 hunks)frontend/src/page/ReceivedInvoice.jsx(14 hunks)frontend/src/page/SentInvoice.jsx(12 hunks)frontend/src/utils/erc20_token.js(0 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/utils/erc20_token.js
🧰 Additional context used
🧬 Code graph analysis (9)
frontend/src/components/ui/dialog.jsx (1)
frontend/src/lib/utils.js (1)
cn(4-6)
frontend/src/hooks/useTokenList.js (2)
frontend/src/page/GenerateLink.jsx (1)
useTokenList(38-38)frontend/src/page/ReceivedInvoice.jsx (1)
useTokenList(71-71)
frontend/src/page/SentInvoice.jsx (3)
frontend/src/page/ReceivedInvoice.jsx (4)
useTokenList(71-71)getTokenInfo(83-91)getTokenLogo(94-102)getTokenDecimals(105-108)frontend/src/hooks/useTokenList.js (1)
tokens(19-19)frontend/src/contractsABI/ERC20_ABI.js (2)
ERC20_ABI(1-10)ERC20_ABI(1-10)
frontend/src/page/CreateInvoice.jsx (6)
frontend/src/page/GenerateLink.jsx (2)
loading(46-46)verifyToken(104-124)frontend/src/components/TokenPicker.jsx (3)
ToggleSwitch(50-84)ToggleSwitch(50-84)TokenPicker(175-382)frontend/src/components/ui/label.jsx (1)
Label(11-13)frontend/src/components/ui/input.jsx (1)
Input(5-16)frontend/src/components/ui/badge.jsx (1)
Badge(1-7)frontend/src/components/ui/copyButton.jsx (1)
CopyButton(5-39)
frontend/src/components/ui/separator.jsx (1)
frontend/src/lib/utils.js (1)
cn(4-6)
frontend/src/hooks/useTokenSearch.js (2)
frontend/src/hooks/useTokenList.js (3)
tokens(19-19)loading(20-20)error(21-21)frontend/src/components/TokenPicker.jsx (1)
filteredTokens(193-193)
frontend/src/components/TokenPicker.jsx (8)
frontend/src/lib/utils.js (1)
cn(4-6)frontend/src/hooks/useTokenSearch.js (3)
query(48-48)filteredTokens(64-101)useTokenSearch(45-118)frontend/src/components/ui/avatar.jsx (1)
Avatar(1-18)frontend/src/components/ui/badge.jsx (1)
Badge(1-7)frontend/src/components/ui/copyButton.jsx (1)
CopyButton(5-39)frontend/src/hooks/useTokenList.js (2)
useTokenList(18-86)tokens(19-19)frontend/src/components/ui/button.jsx (1)
Button(37-45)frontend/src/components/ui/input.jsx (1)
Input(5-16)
frontend/src/page/GenerateLink.jsx (3)
frontend/src/components/ui/copyButton.jsx (2)
copied(6-6)CopyButton(5-39)frontend/src/hooks/useTokenList.js (3)
useTokenList(18-86)loading(20-20)tokens(19-19)frontend/src/components/TokenPicker.jsx (3)
ToggleSwitch(50-84)ToggleSwitch(50-84)TokenPicker(175-382)
frontend/src/page/ReceivedInvoice.jsx (3)
frontend/src/page/SentInvoice.jsx (5)
useTokenList(79-79)getTokenInfo(82-90)getTokenLogo(93-101)getTokenDecimals(104-107)error(69-69)frontend/src/hooks/useTokenList.js (3)
useTokenList(18-86)tokens(19-19)error(21-21)frontend/src/contractsABI/ERC20_ABI.js (2)
ERC20_ABI(1-10)ERC20_ABI(1-10)
🪛 LanguageTool
README.md
[grammar] ~37-~37: There might be a mistake here.
Context: ...ries. ## Table of Contents - Overview - Features - [Project Structur...
(QB_NEW_EN)
[grammar] ~38-~38: There might be a mistake here.
Context: ...nts - Overview - Features - Project Structure -...
(QB_NEW_EN)
[grammar] ~39-~39: There might be a mistake here.
Context: ...eatures](#features) - Project Structure - Getting Started - [Fr...
(QB_NEW_EN)
[grammar] ~40-~40: There might be a mistake here.
Context: ...](#project-structure) - Getting Started - Frontend Setup - [Smar...
(QB_NEW_EN)
[grammar] ~41-~41: There might be a mistake here.
Context: ...ted](#getting-started) - Frontend Setup - [Smart Contract Testing](#smart-contract-...
(QB_NEW_EN)
[grammar] ~42-~42: There might be a mistake here.
Context: ...rontend-setup) - Smart Contract Testing - [Deploy to Ethereum Classic](#deploy-to-e...
(QB_NEW_EN)
[grammar] ~43-~43: There might be a mistake here.
Context: ...t-testing) - Deploy to Ethereum Classic - [Environment Variables](#environment-vari...
(QB_NEW_EN)
[grammar] ~44-~44: There might be a mistake here.
Context: ...hereum-classic) - Environment Variables - [Community and Support](#community-and-su...
(QB_NEW_EN)
[grammar] ~62-~62: There might be a mistake here.
Context: ...ation ## Project Structure Chainvoice/ ├── frontend/ # Web application (UI/UX, ...
(QB_NEW_EN)
[grammar] ~63-~63: There might be a mistake here.
Context: ... application (UI/UX, wallet integration) ├── contracts/ # Solidity smart contract...
(QB_NEW_EN)
[grammar] ~64-~64: There might be a mistake here.
Context: ...y smart contracts (core invoicing logic) ├── docs/ # Documentation and guides └...
(QB_NEW_EN)
[grammar] ~65-~65: There might be a mistake here.
Context: ...) ├── docs/ # Documentation and guides └── README.md # This file ## Getting St...
(QB_NEW_EN)
[grammar] ~73-~73: There might be a mistake here.
Context: ...//github.com/yourusername/Chainvoice.git cd Chainvoice ## Frontend Setup 1. **N...
(QB_NEW_EN)
[grammar] ~78-~78: There might be a mistake here.
Context: ...voice ## Frontend Setup 1. Navigate to frontend directory cd frontend 2. ...
(QB_NEW_EN)
[grammar] ~89-~89: There might be a mistake here.
Context: ... npm run dev 4. Open application Navigate to http://localhost:5173 in y...
(QB_NEW_EN)
[grammar] ~96-~96: There might be a mistake here.
Context: ...y.sh/) must be installed 1. Navigate to contracts directory cd contracts 3...
(QB_NEW_EN)
[grammar] ~102-~102: There might be a mistake here.
Context: ...4. Run tests with verbosity (optional) forge test -vvv ## Deploy to Ethereum ...
(QB_NEW_EN)
[grammar] ~108-~108: There might be a mistake here.
Context: ...y to Ethereum Classic ### Prerequisites - Foundry install...
(QB_NEW_EN)
[grammar] ~109-~109: There might be a mistake here.
Context: ...undry](https://getfoundry.sh/) installed - Wallet funded with ETC - ETC RPC URL (e....
(QB_NEW_EN)
[grammar] ~110-~110: There might be a mistake here.
Context: ....sh/) installed - Wallet funded with ETC - ETC RPC URL (e.g., Rivet, Ankr, Chainsta...
(QB_NEW_EN)
[grammar] ~116-~116: There might be a mistake here.
Context: ...cp contracts/.env.example contracts/.env Edit contracts/.env with your actual val...
(QB_NEW_EN)
[grammar] ~120-~120: There might be a mistake here.
Context: ... 2. Compile contracts cd contracts forge build 3. **Load environment vari...
(QB_NEW_EN)
[grammar] ~127-~127: There might be a mistake here.
Context: ... contracts/src/Chainvoice.sol:Chainvoice --rpc-url $ETC_RPC_URL --private-key $PR...
(QB_NEW_EN)
[grammar] ~128-~128: There might be a mistake here.
Context: ...ce.sol:Chainvoice --rpc-url $ETC_RPC_URL --private-key $PRIVATE_KEY --broadcast ...
(QB_NEW_EN)
[grammar] ~134-~134: There might be a mistake here.
Context: ... cp frontend/.env.example frontend/.env Edit frontend/.env and set: VITE_CONTR...
(QB_NEW_EN)
[grammar] ~135-~135: There might be a mistake here.
Context: ...ntend/.env Edit frontend/.env and set: VITE_CONTRACT_ADDRESS=your_deployed_cont...
(QB_NEW_EN)
[grammar] ~139-~139: There might be a mistake here.
Context: ...ntend development server** cd frontend npm run dev ## Environment Variables ...
(QB_NEW_EN)
[grammar] ~144-~144: There might be a mistake here.
Context: ...Frontend Configuration (frontend/.env) VITE_CONTRACT_ADDRESS=your_deployed_cont...
(QB_NEW_EN)
[grammar] ~145-~145: There might be a mistake here.
Context: ...RESS=your_deployed_contract_address_here VITE_CHAIN_ID=61 VITE_RPC_URL=your_etc...
(QB_NEW_EN)
[grammar] ~146-~146: There might be a mistake here.
Context: ...contract_address_here VITE_CHAIN_ID=61 VITE_RPC_URL=your_etc_rpc_url_here ##...
(QB_NEW_EN)
[grammar] ~149-~149: There might be a mistake here.
Context: ...ntracts Configuration (contracts/.env) PRIVATE_KEY=your_private_key_here ETC_...
(QB_NEW_EN)
[grammar] ~150-~150: There might be a mistake here.
Context: ...nv`) PRIVATE_KEY=your_private_key_here ETC_RPC_URL=your_etc_rpc_endpoint_here ...
(QB_NEW_EN)
[grammar] ~151-~151: There might be a mistake here.
Context: ... ETC_RPC_URL=your_etc_rpc_endpoint_here ETHERSCAN_API_KEY=your_etherscan_api_key...
(QB_NEW_EN)
[grammar] ~160-~160: There might be a mistake here.
Context: ...: - Telegram - t.me/StabilityNexus - Discord - [discord.gg/YzDKeEfWtS](http...
(QB_NEW_EN)
[grammar] ~161-~161: There might be a mistake here.
Context: ...) - Discord - discord.gg/YzDKeEfWtS - X (Twitter) - [@StabilityNexus](https:...
(QB_NEW_EN)
[grammar] ~162-~162: There might be a mistake here.
Context: ...tS) - X (Twitter) - @StabilityNexus - Medium - [news.stability.nexus](https:...
(QB_NEW_EN)
[grammar] ~163-~163: There might be a mistake here.
Context: ...us) - Medium - news.stability.nexus - LinkedIn - [linkedin.com/company/stabi...
(QB_NEW_EN)
[grammar] ~164-~164: There might be a mistake here.
Context: ... - linkedin.com/company/stability-nexus - YouTube - [youtube.com/@StabilityNexus...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
README.md
73-73: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (15)
frontend/package.json (1)
21-28: Validate dependency compatibility (FM v12, Radix, react-window) and bundle impact.Ensure the app compiles and tree-shakes correctly with
framer-motion@^12.23.12, new Radix UI packages, andreact-window. Watch for peer dep warnings and unexpected bundle growth.Also applies to: 37-38, 50-51
frontend/src/components/ui/badge.jsx (1)
1-9: Remove legacy variant shim—no usages found.
A repo-wide search across .js/.jsx/.ts/.tsx files returned zero occurrences ofbadgeVariantsor<Badge variant=; no backward‐compatibility shim is needed.frontend/src/components/ui/separator.jsx (1)
6-20: LGTM — solid Radix wrapperRef-forwarding, default props, and class override order look correct. API shape matches Radix’s expectations.
frontend/src/hooks/useTokenSearch.js (1)
72-76: Exact symbol uniqueness assumptionbySymbol returns only one token; if multiple tokens share a symbol on a chain, others won’t appear as “exact” results. Confirm this is intended.
frontend/src/components/ui/dialog.jsx (2)
26-44: LGTM — accessible dialog compositionPortal + overlay + content setup, focusable close button with sr-only label, and cn integration all look good.
2-3: Dependencies and animation utilities verifiedAll required packages (@radix-ui/react-dialog, lucide-react, tailwindcss-animate, and @radix-ui/react-separator) are present in frontend/package.json and the animate plugin is registered in tailwind.config.js; no further action needed.
frontend/src/page/SentInvoice.jsx (7)
49-49: Toast usage: ensure ToastContainer is mountedYou import react-toastify; confirm a is rendered at app root to display toasts.
78-90: Token lookup helpers look goodThe find-by-address (with contract_address/address) is appropriate for mixed sources.
103-107: Decimals fallback is reasonableKeeps UI resilient when token list lacks decimals.
331-337: Error message is fine; consider surfacing reason in devKeep user-friendly text, but log the original error (already done).
344-344: Dependency on tokensIncluding tokens in the effect is correct to re-enrich invoices when token data updates.
590-598: Token logo fallback LGTMonError fallback to generic token image is correct.
863-871: Drawer logo fallback LGTMConsistent with table row image handling.
frontend/src/hooks/useTokenList.js (1)
85-86: Return shape is goodExposing { tokens, loading, error } matches hook conventions.
frontend/src/page/GenerateLink.jsx (1)
48-61: Default token selection logic looks good.Nice fallback to ETH or first token; handles empty lists cleanly.
Summary by CodeRabbit
New Features
Refactor
Documentation
Chores