Update README with stability standard#10
Update README with stability standard#10adityabhattad2021 merged 43 commits intoStabilityNexus:mainfrom
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.
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