Conversation
WalkthroughAdds a complete NFT Picker implementation: spec, types, parsing utilities, React client components, Alchemy/6529 service integrations, client hooks with in-memory caches and virtualization, tests, demo page, docs, Next.js image domains, and a new dependency for virtualization. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as NftPicker UI
participant Hooks as useAlchemyClient
participant Cache as In‑memory Cache
participant API as Alchemy / 6529
participant V as Virtualizer
rect rgba(230,245,255,0.6)
User->>UI: search / paste contract / switch mode
UI->>Hooks: useCollectionSearch(query, hideSpam)
alt cache hit
Hooks->>Cache: lookup(query, hideSpam)
Cache-->>Hooks: cached suggestions
else cache miss
Hooks->>API: searchNftCollections(query)
API-->>Hooks: suggestions + spam flags
Hooks->>Cache: store(query, suggestions)
end
Hooks-->>UI: suggestions
UI->>V: render(visibleRows)
V-->>UI: visibleRows (lazy metadata requests)
end
rect rgba(240,255,240,0.6)
User->>UI: select contract / pick tokens / edit ranges / Select All
UI->>UI: parseTokenExpressionToBigints -> canonicalize ranges
UI->>Hooks: useTokenMetadataQuery(address, tokenIdsWindow)
Hooks->>API: getTokensMetadata(batched)
API-->>Hooks: token metadata
Hooks-->>UI: metadata
UI-->>User: onChange(NftPickerValue)
end
rect rgba(255,245,230,0.6)
API-->>Hooks: error | empty | spam flags
Hooks-->>UI: propagate errors / spam indicators
UI-->>User: ARIA live messages, toggles (show spam)
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
spec.md (1)
228-233: Promote “API specifics” to a proper heading.
markdownlint(MD036) warns that using bold here instead of a heading trips the “emphasis used as heading” rule. Please switch to an actual heading level (e.g.,### API specifics) to keep the linter green.Apply this diff:
-**API specifics** +### API specifics
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
spec.md(1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
spec.md
69-69: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
228-228: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
spec.md (1)
69-81: Add a language hint to this fenced block.
markdownlint(MD040) flagged this block for missing a language. Please annotate it (e.g.,```textor```plaintext) so linting and highlighting stay happy.[ suggest_optional_refactor ]
Apply this diff:-``` +```text /components/nft-picker/ NftPicker.tsx // client component (combobox + modes + selected list) NftPicker.types.ts // exported types @@ /services/6529api.ts // reuse existing fetch helpers</blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
spec.md(1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
spec.md
78-78: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
237-237: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
Signed-off-by: OpenAI Assistant <123456+openai-assistant@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
services/alchemy-api.ts (2)
490-497: Bug: wrong chainId passed during pagination recursion.Should pass the original chainId, not NEXTGEN_CHAIN_ID.
Apply:
- return getNftsForContractAndOwner( - NEXTGEN_CHAIN_ID, + return getNftsForContractAndOwner( + chainId, contract, owner, nfts, response.pageKey );
463-486: Add retry cap to avoid infinite recursion on API errors.The error path re-calls itself without limits. Add a bounded retry counter.
Apply:
-export async function getNftsForContractAndOwner( +export async function getNftsForContractAndOwner( chainId: number, contract: string, owner: string, nfts?: any[], - pageKey?: string + pageKey?: string, + retries: number = 0 ) { @@ - const response = await fetchLegacyUrl(url); + const response = await fetchLegacyUrl(url); if (response.error) { - return getNftsForContractAndOwner(chainId, contract, owner, nfts, pageKey); + if (retries >= 3) { + throw new Error("Failed to fetch NFTs for owner after retries"); + } + return getNftsForContractAndOwner(chainId, contract, owner, nfts, pageKey, retries + 1); }
♻️ Duplicate comments (1)
spec.md (1)
136-181: Unify the emitted output type with the union shown earlier.This section defines NftSelectionOutput as numbers‑only and drops outputMode/tokenIdsRaw, diverging from the union contract defined at Lines 13–27. Update this block to the same discriminated union.
Apply this replacement:
-export type NftSelectionOutput = { - contractAddress: `0x${string}`; - tokenIds: number[]; // empty = all -}; +export type NftSelectionOutput = + | { + contractAddress: `0x${string}`; + outputMode: 'number'; + tokenIds: number[]; + tokenIdsRaw: bigint[]; + } + | { + contractAddress: `0x${string}`; + outputMode: 'bigint'; + tokenIds: string[]; + tokenIdsRaw: bigint[]; + };
🧹 Nitpick comments (12)
services/6529api.ts (1)
38-58: Reorder init spread before method/body
To prevent callers from accidentally overriding the POST method or JSON body, move...initabovemethod: "POST"andbody: JSON.stringify(body)in thefetchoptions. No currentpostDatacalls passmethodorbodyin theirinitobject, so this is purely future-proofing.components/nft-picker/NftTokenList.tsx (3)
94-108: Avoid double scroll restoration (initialOffset + manual scrollTop).You set initialOffset on the virtualizer and also set container.scrollTop in effect. Pick one to prevent jumps.
Apply:
useEffect(() => { const container = scrollContainerRef.current; if (!container) { return; } - container.scrollTop = getPosition(VIRTUAL_SCROLL_KEY); const handleScroll = () => { setPosition(VIRTUAL_SCROLL_KEY, container.scrollTop); };
159-166: Reuse computed windowTokenIds; avoid O(R) per visible row.Resolve once via expandRangesWindow and index into it for each virtual row.
Apply:
- {virtualItems.map((virtualItem) => { - const tokenId = resolveTokenIdAtIndex(ranges, virtualItem.index); + {virtualItems.map((virtualItem) => { + const windowIndex = virtualItem.index - firstVisibleIndex; + const tokenId = windowTokenIds[windowIndex] ?? null; if (tokenId === null) { return null; }Also applies to: 116-126, 110-115
153-156: Prefer list semantics for simple lists (improves a11y).Use role="list" with role="listitem" (or ul/li) unless you need interactive grid semantics.
Also applies to: 176-177
components/nft-picker/useAlchemyClient.ts (3)
37-40: Add simple TTL GC to in-memory caches to prevent unbounded growth.Delete expired entries before reads. Keeps memory bounded during long sessions.
Apply:
const tokenCache = new Map<string, CacheEntry<TokenMetadata[]>>(); +function gcExpired<T>(map: Map<string, CacheEntry<T>>, now = Date.now()) { + for (const [k, v] of map) { + if (v.expires <= now) map.delete(k); + } +} @@ const result = useQuery({ @@ - queryFn: async ({ signal }) => { - const cacheKey = getSuggestionCacheKey( + queryFn: async ({ signal }) => { + const cacheKey = getSuggestionCacheKey( debouncedQuery, chain, hideSpam ); - const now = Date.now(); + const now = Date.now(); + gcExpired(suggestionCache, now); const cached = suggestionCache.get(cacheKey); @@ return useQuery({ @@ - queryFn: async ({ signal }) => { + queryFn: async ({ signal }) => { if (!normalizedAddress) { return null; } const cacheKey = getContractCacheKey(normalizedAddress, chain); - const now = Date.now(); + const now = Date.now(); + gcExpired(contractCache, now); const cached = contractCache.get(cacheKey); @@ return useQuery({ @@ - queryFn: async ({ signal }) => { + queryFn: async ({ signal }) => { if (!params) { return [] as TokenMetadata[]; } const cacheKey = getTokenCacheKey(params); - const now = Date.now(); + const now = Date.now(); + gcExpired(tokenCache, now); const cached = tokenCache.get(cacheKey);Also applies to: 101-126, 165-183, 226-244
192-203: Optional: normalise tokenIds for better cache hits.Sorting uniqueIds improves reuse across sliding windows; only do this if response order is not relied upon elsewhere.
56-60: Minor: remove extraneous$in token cache key.The literal “:$” and “:$$” separators are unusual; consider plain “:”.
app/demo/nft-picker/picker-client.tsx (1)
8-21: Remove unused state/helper or surface the selection.
stringifySelectionand theselectionbinding are unused, so TypeScript/ESLint (withnoUnusedLocals) will fail the build. Either drop them or render the selection in the demo.export default function DemoNftPicker() { - const [selection, setSelection] = useState<NftPickerSelection | null>(null); + const [selection, setSelection] = useState<NftPickerSelection | null>(null); + const renderedSelection = + selection === null + ? "No selection" + : stringifySelection(selection); return ( <div className="tw-@container tw-mx-auto tw-flex tw-max-w-3xl tw-flex-col tw-gap-4 tw-p-6"> - <NftPicker onChange={setSelection} allowAll allowRanges hideSpam className="tw-shadow-lg" /> + <NftPicker onChange={setSelection} allowAll allowRanges hideSpam className="tw-shadow-lg" /> + <pre className="tw-rounded tw-bg-neutral-900 tw-p-4 tw-text-sm">{renderedSelection}</pre> </div> ); }components/nft-picker/NftPicker.utils.ts (1)
173-182: Naming nit: function suggests merging ranges but returns expanded IDs.mergeAndSort only sorts and de-dupes; it doesn’t “merge” to ranges (that happens in toCanonicalRanges). Consider renaming to improve clarity (e.g., sortAndDedupeIds).
components/nft-picker/NftPicker.tsx (3)
3-6: Use FontAwesome instead of inline SVG (guideline).Replace the inline SVG in Select All with FontAwesome and add the import.
As per coding guidelines.
-import { useCallback, useEffect, useMemo, useRef, useState, useId } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState, useId } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCircle } from "@fortawesome/free-solid-svg-icons"; @@ - <svg - className="tw-h-5 tw-w-5" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" - aria-hidden="true" - > - <circle cx="12" cy="12" r="10" strokeWidth="2" /> - </svg> + <FontAwesomeIcon className="tw-h-5 tw-w-5" icon={faCircle} aria-hidden="true" />Also applies to: 684-694
82-85: Makechainreactive to prop/value changes.Freezing
chainin state prevents updates when the parent prop changes post‑mount. Derive it from props to keep queries aligned.- const initialChain = ensureChain(value?.chain ?? defaultValue?.chain ?? chainProp); - const [chain] = useState<SupportedChain>(initialChain); + const chain = useMemo( + () => ensureChain(value?.chain ?? defaultValue?.chain ?? chainProp), + [value?.chain, defaultValue?.chain, chainProp] + );Please confirm no callers rely on a one‑time initialization. If they do, add an explicit prop to lock the chain.
238-300: Large input ranges still parse via full expansion path.Even with a cap in utils, big ranges hit parseTokenExpressionToBigints and then canonicalize, risking heavy work. Prefer parsing directly to ranges and counting via arithmetic to avoid enumeration, then only expand via expandRangesWindow for visible rows.
If preferred, I can add a parseTokenExpressionToRanges helper and thread it through tokenPreview.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (20)
__tests__/components/nft-picker/NftPicker.utils.test.ts(1 hunks)__tests__/services/alchemy-api.test.ts(1 hunks)app/demo/nft-picker/page.tsx(1 hunks)app/demo/nft-picker/picker-client.tsx(1 hunks)components/nft-picker/AllTokensSelectedCard.tsx(1 hunks)components/nft-picker/NftContractHeader.tsx(1 hunks)components/nft-picker/NftEditRanges.tsx(1 hunks)components/nft-picker/NftPicker.tsx(1 hunks)components/nft-picker/NftPicker.types.ts(1 hunks)components/nft-picker/NftPicker.utils.ts(1 hunks)components/nft-picker/NftSuggestList.tsx(1 hunks)components/nft-picker/NftTokenList.tsx(1 hunks)components/nft-picker/useAlchemyClient.ts(1 hunks)components/react-query-wrapper/ReactQueryWrapper.tsx(1 hunks)docs/nft-picker-quick-checklist.md(1 hunks)next.config.mjs(1 hunks)package.json(1 hunks)services/6529api.ts(2 hunks)services/alchemy-api.ts(3 hunks)spec.md(1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before propsUse TypeScript for implementation code
Files:
components/nft-picker/NftTokenList.tsxcomponents/react-query-wrapper/ReactQueryWrapper.tsxapp/demo/nft-picker/picker-client.tsxcomponents/nft-picker/useAlchemyClient.tscomponents/nft-picker/NftContractHeader.tsx__tests__/components/nft-picker/NftPicker.utils.test.tscomponents/nft-picker/NftPicker.utils.tscomponents/nft-picker/NftPicker.tsxapp/demo/nft-picker/page.tsxcomponents/nft-picker/NftPicker.types.tscomponents/nft-picker/NftSuggestList.tsxcomponents/nft-picker/AllTokensSelectedCard.tsxcomponents/nft-picker/NftEditRanges.tsxservices/alchemy-api.tsservices/6529api.ts__tests__/services/alchemy-api.test.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for stylingUse React functional components with hooks
Files:
components/nft-picker/NftTokenList.tsxcomponents/react-query-wrapper/ReactQueryWrapper.tsxapp/demo/nft-picker/picker-client.tsxcomponents/nft-picker/NftContractHeader.tsxcomponents/nft-picker/NftPicker.tsxapp/demo/nft-picker/page.tsxcomponents/nft-picker/NftSuggestList.tsxcomponents/nft-picker/AllTokensSelectedCard.tsxcomponents/nft-picker/NftEditRanges.tsx
{app,pages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use NextJS features that match the current version
Files:
app/demo/nft-picker/picker-client.tsxapp/demo/nft-picker/page.tsx
**/{__tests__/**/*.{ts,tsx},*.test.tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/{__tests__/**/*.{ts,tsx},*.test.tsx}: Place tests in tests directories or alongside components as ComponentName.test.tsx
Mock external dependencies and APIs in tests
Files:
__tests__/components/nft-picker/NftPicker.utils.test.ts__tests__/services/alchemy-api.test.ts
__tests__/**
📄 CodeRabbit inference engine (tests/AGENTS.md)
Place Jest test suites under the
__tests__directory mirroring source folders (e.g., components, contexts, hooks, utils)
Files:
__tests__/components/nft-picker/NftPicker.utils.test.ts__tests__/services/alchemy-api.test.ts
__tests__/components/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (tests/AGENTS.md)
Use
@testing-library/reactand@testing-library/user-eventfor React component tests
Files:
__tests__/components/nft-picker/NftPicker.utils.test.ts
app/**/page.tsx
📄 CodeRabbit inference engine (AGENTS.md)
All new pages must be created inside the app/ directory
Files:
app/demo/nft-picker/page.tsx
app/**/{page,layout}.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Routes in app/ should export a generateMetadata function using getAppMetadata
Files:
app/demo/nft-picker/page.tsx
🧬 Code graph analysis (13)
components/nft-picker/NftTokenList.tsx (4)
components/nft-picker/NftPicker.types.ts (3)
SupportedChain(3-3)TokenRange(9-9)TokenMetadata(31-37)contexts/ScrollPositionContext.tsx (1)
useScrollPositionContext(36-40)components/nft-picker/NftPicker.utils.ts (2)
expandRangesWindow(247-284)formatCanonical(216-224)components/nft-picker/useAlchemyClient.ts (1)
useTokenMetadataQuery(186-244)
app/demo/nft-picker/picker-client.tsx (2)
components/nft-picker/NftPicker.types.ts (1)
NftPickerSelection(39-51)components/nft-picker/NftPicker.tsx (1)
NftPicker(66-755)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
SearchContractsResult(511-511)TokenMetadataParams(511-511)searchNftCollections(294-325)getContractOverview(327-374)getTokensMetadata(415-454)components/nft-picker/NftPicker.types.ts (4)
ContractOverview(26-29)TokenMetadata(31-37)SupportedChain(3-3)Suggestion(13-24)
components/nft-picker/NftContractHeader.tsx (1)
components/nft-picker/NftPicker.types.ts (1)
ContractOverview(26-29)
__tests__/components/nft-picker/NftPicker.utils.test.ts (2)
components/nft-picker/NftPicker.utils.ts (9)
parseTokenExpressionToBigints(114-164)mergeAndSort(173-182)toCanonicalRanges(184-202)fromCanonicalRanges(204-214)formatCanonical(216-224)tryToNumberArray(231-245)MAX_SAFE(7-7)bigintCompare(166-171)expandRangesWindow(247-284)components/nft-picker/NftPicker.types.ts (1)
TokenRange(9-9)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (3)
ParseError(77-82)TokenSelection(11-11)TokenRange(9-9)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (9)
SupportedChain(3-3)TokenSelection(11-11)TokenRange(9-9)Suggestion(13-24)ContractOverview(26-29)NftPickerProps(60-75)ParseError(77-82)TokenIdBigInt(7-7)NftPickerSelection(39-51)components/nft-picker/NftPicker.utils.ts (8)
toCanonicalRanges(184-202)formatCanonical(216-224)parseTokenExpressionToBigints(114-164)mergeAndSort(173-182)formatBigIntWithSeparators(226-229)fromCanonicalRanges(204-214)tryToNumberArray(231-245)MAX_SAFE(7-7)components/nft-picker/useAlchemyClient.ts (3)
useContractOverviewQuery(148-184)useCollectionSearch(92-146)primeContractCache(246-259)helpers/Helpers.ts (1)
isValidEthAddress(218-219)components/nft-picker/NftSuggestList.tsx (1)
NftSuggestList(39-175)components/nft-picker/NftContractHeader.tsx (1)
NftContractHeader(18-102)components/nft-picker/AllTokensSelectedCard.tsx (1)
AllTokensSelectedCard(11-45)components/nft-picker/NftEditRanges.tsx (1)
NftEditRanges(26-229)components/nft-picker/NftTokenList.tsx (1)
NftTokenList(70-223)
app/demo/nft-picker/page.tsx (2)
components/providers/metadata.ts (1)
getAppMetadata(41-72)app/demo/nft-picker/picker-client.tsx (1)
DemoNftPicker(15-23)
components/nft-picker/NftSuggestList.tsx (3)
components/nft-picker/NftPicker.types.ts (1)
Suggestion(13-24)hooks/useVirtualizedWaves.ts (1)
useVirtualizedWaves(14-74)components/distribution-plan-tool/common/DistributionPlanVerifiedIcon.tsx (1)
DistributionPlanVerifiedIcon(1-19)
components/nft-picker/NftEditRanges.tsx (3)
components/nft-picker/NftPicker.types.ts (2)
TokenRange(9-9)ParseError(77-82)components/nft-picker/NftPicker.utils.ts (2)
formatCanonical(216-224)formatBigIntWithSeparators(226-229)components/utils/icons/CopyIcon.tsx (1)
CopyIcon(1-21)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
SupportedChain(3-3)Suggestion(13-24)ContractOverview(26-29)TokenMetadata(31-37)helpers/Helpers.ts (1)
isValidEthAddress(218-219)services/6529api.ts (2)
fetchUrl(6-23)postData(38-58)
services/6529api.ts (2)
entities/IDBResponse.ts (1)
DBResponse(1-6)services/auth/auth.utils.ts (1)
getStagingAuth(77-79)
__tests__/services/alchemy-api.test.ts (2)
services/6529api.ts (2)
fetchUrl(6-23)postData(38-58)services/alchemy-api.ts (3)
searchNftCollections(294-325)getContractOverview(327-374)getTokensMetadata(415-454)
🪛 Biome (2.1.2)
components/nft-picker/NftContractHeader.tsx
[error] 27-27: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
[error] 31-31: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🪛 markdownlint-cli2 (0.18.1)
spec.md
78-78: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
237-237: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
docs/nft-picker-quick-checklist.md
30-30: Table column count
Expected: 4; Actual: 3; Too few cells, row will be missing data
(MD056, table-column-count)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
package.json (1)
64-64: LGTM! Virtualization dependency added correctly.The
@tanstack/react-virtualdependency supports the NFT picker's virtualized list rendering for large suggestion and token collections, as described in the spec.next.config.mjs (1)
123-125: LGTM! Image domains added for NFT metadata.The Seadn (OpenSea CDN) and Cloudinary domains enable Next.js image optimization for NFT images, contract logos, and metadata thumbnails used in the NFT picker components.
components/react-query-wrapper/ReactQueryWrapper.tsx (1)
75-77: LGTM! Query keys added for NFT picker cache management.The new
NFT_PICKER_SEARCH,NFT_PICKER_CONTRACT, andNFT_PICKER_TOKENSkeys establish distinct cache namespaces for collection searches, contract metadata, and token data, supporting the NFT picker's react-query integration.services/6529api.ts (1)
6-23: LGTM! fetchUrl correctly extended with RequestInit support.The updated signature allows callers to pass custom request options while properly handling header merging and authentication.
components/nft-picker/NftTokenList.tsx (2)
169-175: Stable keys require unique tokenIds. Ensure ranges are canonicalised/deduped.If overlapping ranges are allowed, duplicate keys can occur.
Would you confirm upstream canonicalisation/deduplication guarantees uniqueness? If not, I can add a quick normaliser before render.
180-186: Image domains whitelist.Next/Image needs all possible metadata.imageUrl domains allowed in next.config.
Please confirm domains are whitelisted for Alchemy gateways/CDNs used here.
| import CopyIcon from "@/components/utils/icons/CopyIcon"; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use FontAwesome icons in TSX components (project rule).
Replace the custom CopyIcon with FontAwesome to comply. As per coding guidelines.
Apply:
-import CopyIcon from "@/components/utils/icons/CopyIcon";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";
@@
- {copyStatus === "copied" ? (
- <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span>
- ) : (
- <>
- <CopyIcon />
- <span>Copy</span>
- </>
- )}
+ {copyStatus === "copied" ? (
+ <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300">
+ <FontAwesomeIcon icon={faCheck} />
+ <span>Copied</span>
+ </span>
+ ) : (
+ <span className="tw-inline-flex tw-items-center tw-gap-2">
+ <FontAwesomeIcon icon={faCopy} />
+ <span>Copy</span>
+ </span>
+ )}Also applies to: 146-153
🤖 Prompt for AI Agents
In components/nft-picker/NftEditRanges.tsx around lines 6-7 (and similarly lines
146-153), replace the custom CopyIcon usage with FontAwesome: remove the
CopyIcon import and add imports for FontAwesomeIcon from
@fortawesome/react-fontawesome and the faCopy icon from
@fortawesome/free-solid-svg-icons; then update the JSX where CopyIcon is
rendered to use <FontAwesomeIcon icon={faCopy} /> (preserve any className/props
for sizing/color), and update any TypeScript types/import statements accordingly
so the component compiles without the custom icon.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
components/nft-picker/NftEditRanges.tsx (1)
147-154: Use FontAwesome for the success state icon (project rule).Replace the unicode check with faCheck to comply with “Use FontAwesome for icons” in TSX components. As per coding guidelines.
-import { faCopy } from "@fortawesome/free-solid-svg-icons"; +import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons"; @@ - {copyStatus === "copied" ? ( - <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span> - ) : ( + {copyStatus === "copied" ? ( + <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300"> + <FontAwesomeIcon icon={faCheck} /> + <span>Copied</span> + </span> + ) : (Also applies to: 6-8
components/nft-picker/NftPicker.types.ts (1)
64-79: Props immutability looks good.NftPickerProps correctly uses readonly on all fields. As per coding guidelines.
components/nft-picker/NftPicker.tsx (1)
548-625: Previous concern resolved: allSelected discriminator added.The emitted payloads now include
allSelected: isAllin all branches (lines 587, 603, 613), allowing consumers to distinguish "all tokens selected" from "no tokens selected" even whentokenIdsis empty.
🧹 Nitpick comments (6)
components/nft-picker/NftEditRanges.tsx (1)
85-103: Improve legacy copy fallback reliability (iOS/WebKit edge).Set an explicit selection range before execCommand to increase success rates on iOS/Safari.
textarea.select(); + try { textarea.setSelectionRange(0, textarea.value.length); } catch {} const successful = document.execCommand("copy");components/nft-picker/useAlchemyClient.ts (2)
64-69: Make token cache key order‑insensitive for better hit rate.Sort the tokenIds when building the cache key so [1,2,3] and [3,2,1] share the entry. This does not affect returned data order; it only improves cache reuse.
-function getTokenCacheKey(params: TokenMetadataParams): string { - return `${params.chain ?? "ethereum"}:${params.address.toLowerCase()}:${params.tokenIds.join("|")}`; -} +function getTokenCacheKey(params: TokenMetadataParams): string { + const ids = [...params.tokenIds].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + return `${params.chain ?? "ethereum"}:${params.address.toLowerCase()}:${ids.join("|")}`; +}Also applies to: 202-213
79-86: Add readonly to hook param types (immutability rule).Mark fields readonly to match the repo convention for props/params in TS.
-type UseCollectionSearchParams = { - query: string; - chain?: SupportedChain; - hideSpam?: boolean; - debounceMs?: number; - enabled?: boolean; -}; +type UseCollectionSearchParams = { + readonly query: string; + readonly chain?: SupportedChain; + readonly hideSpam?: boolean; + readonly debounceMs?: number; + readonly enabled?: boolean; +}; @@ -type UseContractOverviewParams = { - address?: `0x${string}`; - chain?: SupportedChain; - enabled?: boolean; -}; +type UseContractOverviewParams = { + readonly address?: `0x${string}`; + readonly chain?: SupportedChain; + readonly enabled?: boolean; +}; @@ -type UseTokenMetadataParams = { - address?: `0x${string}`; - tokenIds: readonly string[]; - chain?: SupportedChain; - enabled?: boolean; -}; +type UseTokenMetadataParams = { + readonly address?: `0x${string}`; + readonly tokenIds: readonly string[]; + readonly chain?: SupportedChain; + readonly enabled?: boolean; +};Also applies to: 87-92, 93-99
components/nft-picker/NftPicker.types.ts (1)
39-54: Optional: consider readonly on selection payloads.If you want stronger guarantees for consumers, mark BaseSelection/NftPickerValue array fields as readonly to discourage mutation of emitted values.
-type BaseSelection = { +type BaseSelection = { contractAddress: `0x${string}`; allSelected: boolean; - tokenIdsRaw: TokenIdBigInt[]; + tokenIdsRaw: readonly TokenIdBigInt[]; }; @@ export type NftPickerValue = { chain: SupportedChain; contractAddress?: `0x${string}`; - selectedIds: TokenSelection; + selectedIds: Readonly<TokenSelection>; allSelected: boolean; };Also applies to: 57-62
services/alchemy-api.ts (1)
459-523: Add AbortSignal and retry backoff to legacy owner fetch.getNftsForContractAndOwner cannot be aborted and retries immediately; add an optional signal and basic backoff to avoid runaway retries and support cancellations.
-async function fetchLegacyUrl(url: string) { - const response = await fetch(url, legacyOptions); +async function fetchLegacyUrl(url: string, signal?: AbortSignal) { + const response = await fetch(url, { ...legacyOptions, signal }); return await response.json(); } -export async function getNftsForContractAndOwner( +export async function getNftsForContractAndOwner( chainId: number, contract: string, owner: string, nfts?: any[], - pageKey?: string, - retries = 0 + pageKey?: string, + retries = 0, + signal?: AbortSignal ) { @@ - const response = await fetchLegacyUrl(url); + const response = await fetchLegacyUrl(url, signal); if (response.error) { - if (retries >= MAX_GET_NFTS_RETRIES) { + if (retries >= MAX_GET_NFTS_RETRIES) { throw new Error("Failed to fetch NFTs for owner after retries"); } + await new Promise((r) => setTimeout(r, 250 * (retries + 1))); return getNftsForContractAndOwner( chainId, contract, owner, nfts, pageKey, - retries + 1 + retries + 1, + signal ); } @@ if (response.pageKey) { return getNftsForContractAndOwner( chainId, contract, owner, nfts, - response.pageKey + response.pageKey, + signal ); }components/nft-picker/NftPicker.tsx (1)
113-124: Effect dependency may cause extra runs.Including
onContractChangein the dependency array is correct per React rules, but since it's likely not memoized by the parent, this effect may run more often than necessary.Consider documenting that parent components should memoize the
onContractChangecallback withuseCallbackto prevent unnecessary re-initializations, or wrap the effect body in a conditional check to avoid redundant calls.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
__tests__/components/nft-picker/NftPicker.utils.test.ts(1 hunks)app/demo/nft-picker/picker-client.tsx(1 hunks)components/nft-picker/AllTokensSelectedCard.tsx(1 hunks)components/nft-picker/NftContractHeader.tsx(1 hunks)components/nft-picker/NftEditRanges.tsx(1 hunks)components/nft-picker/NftPicker.tsx(1 hunks)components/nft-picker/NftPicker.types.ts(1 hunks)components/nft-picker/NftPicker.utils.ts(1 hunks)components/nft-picker/NftTokenList.tsx(1 hunks)components/nft-picker/useAlchemyClient.ts(1 hunks)docs/nft-picker-quick-checklist.md(1 hunks)services/6529api.ts(2 hunks)services/alchemy-api.ts(4 hunks)spec.md(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- docs/nft-picker-quick-checklist.md
🚧 Files skipped from review as they are similar to previous changes (2)
- services/6529api.ts
- spec.md
👮 Files not reviewed due to content moderation or server errors (3)
- components/nft-picker/NftContractHeader.tsx
- components/nft-picker/NftTokenList.tsx
- components/nft-picker/AllTokensSelectedCard.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before propsUse TypeScript for implementation code
Files:
components/nft-picker/AllTokensSelectedCard.tsxcomponents/nft-picker/NftEditRanges.tsx__tests__/components/nft-picker/NftPicker.utils.test.tscomponents/nft-picker/NftPicker.types.tscomponents/nft-picker/NftTokenList.tsxcomponents/nft-picker/NftPicker.utils.tscomponents/nft-picker/NftContractHeader.tsxcomponents/nft-picker/useAlchemyClient.tsapp/demo/nft-picker/picker-client.tsxcomponents/nft-picker/NftPicker.tsxservices/alchemy-api.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for stylingUse React functional components with hooks
Files:
components/nft-picker/AllTokensSelectedCard.tsxcomponents/nft-picker/NftEditRanges.tsxcomponents/nft-picker/NftTokenList.tsxcomponents/nft-picker/NftContractHeader.tsxapp/demo/nft-picker/picker-client.tsxcomponents/nft-picker/NftPicker.tsx
**/{__tests__/**/*.{ts,tsx},*.test.tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/{__tests__/**/*.{ts,tsx},*.test.tsx}: Place tests in tests directories or alongside components as ComponentName.test.tsx
Mock external dependencies and APIs in tests
Files:
__tests__/components/nft-picker/NftPicker.utils.test.ts
__tests__/**
📄 CodeRabbit inference engine (tests/AGENTS.md)
Place Jest test suites under the
__tests__directory mirroring source folders (e.g., components, contexts, hooks, utils)
Files:
__tests__/components/nft-picker/NftPicker.utils.test.ts
__tests__/components/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (tests/AGENTS.md)
Use
@testing-library/reactand@testing-library/user-eventfor React component tests
Files:
__tests__/components/nft-picker/NftPicker.utils.test.ts
{app,pages}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
Use NextJS features that match the current version
Files:
app/demo/nft-picker/picker-client.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons
Applied to files:
components/nft-picker/AllTokensSelectedCard.tsxcomponents/nft-picker/NftEditRanges.tsx
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props
Applied to files:
components/nft-picker/AllTokensSelectedCard.tsxcomponents/nft-picker/NftPicker.types.ts
🧬 Code graph analysis (9)
components/nft-picker/NftEditRanges.tsx (2)
components/nft-picker/NftPicker.types.ts (2)
TokenRange(9-9)ParseError(81-87)components/nft-picker/NftPicker.utils.ts (2)
formatCanonical(374-382)formatBigIntWithSeparators(384-387)
__tests__/components/nft-picker/NftPicker.utils.test.ts (2)
components/nft-picker/NftPicker.utils.ts (12)
parseTokenExpressionToBigints(247-267)parseTokenExpressionToRanges(171-245)MAX_ENUMERATION(8-8)mergeAndSort(331-331)toCanonicalRanges(333-351)fromCanonicalRanges(353-372)isRangeTooLargeError(29-35)formatCanonical(374-382)tryToNumberArray(389-403)MAX_SAFE(7-7)bigintCompare(312-317)expandRangesWindow(405-442)components/nft-picker/NftPicker.types.ts (1)
TokenRange(9-9)
components/nft-picker/NftTokenList.tsx (4)
components/nft-picker/NftPicker.types.ts (3)
SupportedChain(3-3)TokenRange(9-9)TokenMetadata(31-37)contexts/ScrollPositionContext.tsx (1)
useScrollPositionContext(36-40)components/nft-picker/NftPicker.utils.ts (2)
expandRangesWindow(405-442)formatCanonical(374-382)components/nft-picker/useAlchemyClient.ts (1)
useTokenMetadataQuery(196-255)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (3)
ParseError(81-87)TokenRange(9-9)TokenSelection(11-11)
components/nft-picker/NftContractHeader.tsx (1)
components/nft-picker/NftPicker.types.ts (1)
ContractOverview(26-29)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
SearchContractsResult(523-523)TokenMetadataParams(523-523)searchNftCollections(295-326)getContractOverview(328-375)getTokensMetadata(416-455)components/nft-picker/NftPicker.types.ts (4)
ContractOverview(26-29)TokenMetadata(31-37)SupportedChain(3-3)Suggestion(13-24)
app/demo/nft-picker/picker-client.tsx (1)
components/nft-picker/NftPicker.types.ts (1)
NftPickerSelection(45-53)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (8)
SupportedChain(3-3)TokenSelection(11-11)TokenRange(9-9)Suggestion(13-24)ContractOverview(26-29)NftPickerProps(64-79)ParseError(81-87)NftPickerSelection(45-53)components/nft-picker/NftPicker.utils.ts (10)
toCanonicalRanges(333-351)formatCanonical(374-382)parseTokenExpressionToRanges(171-245)formatBigIntWithSeparators(384-387)mergeCanonicalRanges(269-280)removeTokenFromRanges(282-310)fromCanonicalRanges(353-372)isRangeTooLargeError(29-35)tryToNumberArray(389-403)MAX_SAFE(7-7)components/nft-picker/useAlchemyClient.ts (3)
useContractOverviewQuery(157-194)useCollectionSearch(100-155)primeContractCache(257-270)helpers/Helpers.ts (1)
isValidEthAddress(218-219)components/nft-picker/NftSuggestList.tsx (1)
NftSuggestList(39-175)components/nft-picker/NftContractHeader.tsx (1)
NftContractHeader(18-102)components/nft-picker/AllTokensSelectedCard.tsx (1)
AllTokensSelectedCard(12-46)components/nft-picker/NftEditRanges.tsx (1)
NftEditRanges(27-230)components/nft-picker/NftTokenList.tsx (1)
NftTokenList(55-211)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
SupportedChain(3-3)Suggestion(13-24)ContractOverview(26-29)TokenMetadata(31-37)helpers/Helpers.ts (1)
isValidEthAddress(218-219)services/6529api.ts (2)
fetchUrl(6-23)postData(38-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (25)
services/alchemy-api.ts (2)
244-293: Contract extraction is robust.Good normalization across OpenSea fields, safelist mapping, spam flags, and deployer checksum. LGTM.
416-455: Batching and normalization for token metadata look correct.Chunking by 100, passing AbortSignal, and tolerant parsing with image fallback are solid.
__tests__/components/nft-picker/NftPicker.utils.test.ts (1)
1-163: LGTM! Comprehensive test coverage.The test suite thoroughly validates utility functions with appropriate edge cases, error handling, and assertions. Test organization is clear and follows best practices.
app/demo/nft-picker/picker-client.tsx (1)
1-30: LGTM! Clean demo implementation.The component correctly handles BigInt serialization in JSON output and properly manages picker state. Implementation follows React best practices.
components/nft-picker/NftPicker.utils.ts (9)
7-35: LGTM! Range explosion safeguard implemented.The MAX_ENUMERATION limit and structured error handling effectively address the previous concern about unbounded range expansion. The type guard and error factory provide clear error contracts.
43-89: Tokenization logic handles edge cases correctly.The standalone dash merging (lines 70-84) properly guards against empty segments and correctly reconstructs range expressions from spaced input.
91-139: Robust token validation with clear error reporting.The function correctly rejects invalid formats and provides precise error positioning through the overrideStart parameter.
141-169: Canonicalization correctly merges overlapping and adjacent ranges.The sort-and-merge logic properly handles all edge cases including exact adjacency.
171-245: Comprehensive parsing with multi-level validation.The function validates individual ranges and aggregate totals against MAX_ENUMERATION, provides detailed parse errors, and handles malformed input gracefully.
247-267: Clean error transformation layer.Properly converts RangeTooLargeError to ParseError[] format for consistent error handling.
269-310: Token removal handles all split cases correctly.The function properly handles edge, middle, and single-token scenarios, with final canonicalization ensuring consistency.
312-403: Utility functions provide safe conversions and formatting.All conversion and formatting functions handle edge cases properly, including unsafe number conversions and range enumeration limits.
405-442: Virtualized windowing logic is correct.The function properly calculates token windows across ranges with correct offset handling and boundary checks.
components/nft-picker/NftPicker.tsx (6)
1-49: Clean imports and constants.All dependencies are appropriate and constants are well-defined. Complies with coding guidelines.
51-68: Helper functions are straightforward.Conversion and defaulting logic is clear and correct.
70-111: Component signature follows guidelines.Props are readonly, defaults are sensible, and initial state setup is clean.
195-546: Event handlers are well-structured.All handlers properly manage state updates and emit appropriate change events. The logic for selecting/deselecting all tokens correctly preserves previous ranges.
560-582: Error handling preserves user experience.The function properly catches range enumeration errors and updates UI state (parseErrors) without throwing, allowing the user to see feedback and correct the input.
634-784: Render structure is well-organized and accessible.The component properly uses conditional rendering, includes appropriate accessibility attributes, and follows the coding guidelines for TailwindCSS and FontAwesome icons.
components/nft-picker/NftContractHeader.tsx (2)
23-33: Hook ordering issue resolved.The conditional hook usage flagged in the previous review has been correctly addressed. The
useCallbackhooks now execute unconditionally before the early return, ensuring consistent hook ordering across all renders.
23-33: Hooks correctly placed before early return.The previous conditional hook issue has been resolved. The
useCallbackhooks now execute on every render before the early return, maintaining consistent hook order.components/nft-picker/NftTokenList.tsx (2)
39-49: BigInt overflow issue resolved.The previous concern about
Numberoverflow has been properly addressed. The function now accumulates totals usingBigIntarithmetic, compares against aBigIntcap, and only converts toNumberat the final return. This prevents precision loss and avoids extreme virtual container heights.
39-49: BigInt overflow safely handled.The previous issue with
Number(...)conversion and potential overflow has been resolved. The function now accumulates inBigInt, compares against aBigIntcap, and only converts toNumberwhen the total is safely below the threshold.components/nft-picker/AllTokensSelectedCard.tsx (2)
1-10: Previous review feedback addressed.Both issues from the prior review have been resolved:
- FontAwesome icons (
faCheckCircle) now replace Heroicons.- Props interface correctly declares all members as
readonly.
1-10: FontAwesome icons and readonly props correctly applied.The previous issues have been resolved:
- FontAwesome is now used instead of Heroicons (lines 1-2).
- Props are marked as
readonly(lines 8-9).
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
components/nft-picker/NftEditRanges.tsx (1)
6-7: Add faCheck icon import for consistent FontAwesome usage.The component uses a Unicode checkmark "✓" at line 153 for the copied state, but the coding guidelines require FontAwesome icons in TSX files. Import
faCheckfrom@fortawesome/free-solid-svg-iconsto replace the Unicode character.Apply this diff:
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCopy } from "@fortawesome/free-solid-svg-icons"; +import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";Then update line 153:
{copyStatus === "copied" ? ( - <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span> + <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300"> + <FontAwesomeIcon icon={faCheck} /> + <span>Copied!</span> + </span> ) : (As per coding guidelines
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
components/nft-picker/NftEditRanges.tsx(1 hunks)components/nft-picker/NftPicker.tsx(1 hunks)components/nft-picker/NftPicker.types.ts(1 hunks)components/nft-picker/NftPicker.utils.ts(1 hunks)components/nft-picker/useAlchemyClient.ts(1 hunks)services/alchemy-api.ts(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- components/nft-picker/NftPicker.types.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before propsUse TypeScript for implementation code
Files:
components/nft-picker/NftPicker.utils.tscomponents/nft-picker/useAlchemyClient.tscomponents/nft-picker/NftEditRanges.tsxservices/alchemy-api.tscomponents/nft-picker/NftPicker.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for stylingUse React functional components with hooks
Files:
components/nft-picker/NftEditRanges.tsxcomponents/nft-picker/NftPicker.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons
Applied to files:
components/nft-picker/NftEditRanges.tsx
🧬 Code graph analysis (5)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (4)
ParseError(81-87)TokenRange(9-9)TokenSelection(11-11)TokenIdBigInt(7-7)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
SearchContractsResult(570-570)TokenMetadataParams(570-570)searchNftCollections(295-326)getContractOverview(328-375)getTokensMetadata(416-455)components/nft-picker/NftPicker.types.ts (4)
ContractOverview(26-29)TokenMetadata(31-37)SupportedChain(3-3)Suggestion(13-24)
components/nft-picker/NftEditRanges.tsx (2)
components/nft-picker/NftPicker.types.ts (2)
TokenRange(9-9)ParseError(81-87)components/nft-picker/NftPicker.utils.ts (2)
formatCanonical(374-382)formatBigIntWithSeparators(384-387)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
SupportedChain(3-3)Suggestion(13-24)ContractOverview(26-29)TokenMetadata(31-37)helpers/Helpers.ts (1)
isValidEthAddress(218-219)services/6529api.ts (2)
fetchUrl(6-23)postData(38-58)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (8)
SupportedChain(3-3)TokenSelection(11-11)TokenRange(9-9)Suggestion(13-24)ContractOverview(26-29)NftPickerProps(64-79)ParseError(81-87)NftPickerSelection(45-53)components/nft-picker/NftPicker.utils.ts (10)
toCanonicalRanges(333-351)formatCanonical(374-382)parseTokenExpressionToRanges(171-245)formatBigIntWithSeparators(384-387)mergeCanonicalRanges(269-280)removeTokenFromRanges(282-310)fromCanonicalRanges(353-372)isRangeTooLargeError(29-35)tryToNumberArray(389-403)MAX_SAFE(7-7)components/nft-picker/useAlchemyClient.ts (3)
useContractOverviewQuery(156-193)useCollectionSearch(99-154)primeContractCache(256-269)helpers/Helpers.ts (1)
isValidEthAddress(218-219)components/nft-picker/NftSuggestList.tsx (1)
NftSuggestList(39-175)components/nft-picker/NftContractHeader.tsx (1)
NftContractHeader(18-102)components/nft-picker/AllTokensSelectedCard.tsx (1)
AllTokensSelectedCard(12-46)components/nft-picker/NftEditRanges.tsx (1)
NftEditRanges(27-235)components/nft-picker/NftTokenList.tsx (1)
NftTokenList(55-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (45)
components/nft-picker/NftEditRanges.tsx (7)
15-25: LGTM!Props interface correctly uses
readonlymodifier for all properties, as per coding guidelines.
27-56: LGTM!Component signature correctly destructures readonly props and memoizes expensive computations with appropriate dependencies.
57-69: LGTM!Copy status effects are well-structured with proper cleanup and reset behavior on canonical changes.
71-108: LGTM!Copy handler is robust with modern clipboard API primary path and comprehensive legacy fallback for older browsers. Proper error handling and cleanup throughout.
110-115: LGTM!Keyboard shortcut for Cmd/Ctrl+Enter to apply changes provides good UX for power users.
117-133: LGTM!Copy status UI logic and button visibility flags are clear and properly derived from state.
135-234: LGTM!JSX structure is well-organized with proper accessibility attributes, conditional rendering, and comprehensive error display. TailwindCSS is used consistently.
components/nft-picker/useAlchemyClient.ts (8)
1-31: LGTM!Imports and cache infrastructure are well-organized with reasonable TTL values for client-side caching.
64-66: LGTM!Token cache key generation is correct with proper ":" delimiters and sorted token IDs. The stray "$" issue mentioned in past reviews has been resolved.
69-76: LGTM!Debounce hook is a clean, standard implementation with proper cleanup.
78-97: LGTM!Parameter types correctly use
readonlymodifier and are well-structured.
99-154: LGTM!Collection search hook correctly implements debouncing, dual-layer caching (in-memory + react-query), and GC for expired entries.
156-193: LGTM!Contract overview query correctly normalizes addresses and implements consistent caching pattern.
195-254: LGTM!Token metadata query properly deduplicates IDs and uses
keepPreviousDatafor smooth UX during refetches.
256-269: LGTM!Cache priming utility enables optimistic UI updates by pre-populating contract cache from suggestions.
components/nft-picker/NftPicker.tsx (8)
1-48: LGTM!Imports and constants are well-organized. EMPTY_SELECTION constant provides a clear empty state reference.
51-68: LGTM!Helper functions are pure, well-scoped utilities that clarify component logic.
70-108: LGTM!Component properly handles controlled/uncontrolled modes with appropriate state initialization and refs for tracking hydration.
109-228: LGTM!Prop synchronization effects correctly handle both controlled and uncontrolled modes with appropriate guards and hydration tracking.
230-564: LGTM!Event handlers properly manage state transitions and call
emitChangewhen selection or contract changes occur.
583-661: LGTM!The
emitChangefunction correctly includes theallSelecteddiscriminator in all payloads and handles both number and bigint output modes with appropriate safety checks.
663-673: LGTM!This effect correctly handles the initial onChange emission for uncontrolled mode with defaultValue. The
pendingPropEmitRefguard prevents duplicate emissions that were mentioned in past reviews.
678-824: LGTM!JSX render is well-structured with proper accessibility attributes, conditional rendering based on state, and consistent TailwindCSS usage throughout.
components/nft-picker/NftPicker.utils.ts (12)
1-11: LGTM!Constants are well-defined.
MAX_ENUMERATIONprovides the necessary guard against explosive range expansion as noted in past reviews.
12-35: LGTM!Custom error type for range-too-large cases provides structured error handling with proper type guards.
37-89: LGTM!Tokenization logic correctly handles both comma and whitespace delimiters and intelligently merges segments around dash operators for range expressions.
91-99: LGTM!Error creation helper provides consistent ParseError objects with position information.
101-139: LGTM!Token value parsing correctly validates format, rejects signed values, and handles both hex and decimal representations.
141-169: LGTM!Range canonicalization correctly sorts and merges adjacent/overlapping ranges using standard algorithm.
171-245: LGTM!Expression parsing correctly guards against explosive range expansion with both per-range and total limits, addressing past review concerns.
247-267: LGTM!Expression-to-bigints parser correctly transforms range-too-large errors into ParseError format for consistent error handling.
269-310: LGTM!Range manipulation functions correctly handle merging and token removal with proper canonicalization.
312-372: LGTM!Sorting and conversion functions correctly handle bigint comparisons and enforce enumeration limits during range expansion.
374-403: LGTM!Formatting and conversion utilities provide clear output representations and safely handle bigint-to-number conversions with overflow detection.
405-442: LGTM!Windowed range expansion efficiently supports virtualization by only expanding the visible subset of ranges, avoiding full enumeration for large selections.
services/alchemy-api.ts (10)
2-12: LGTM!New imports are appropriate for the NFT API functionality and align with the existing codebase patterns.
15-127: LGTM!Type definitions comprehensively model Alchemy API responses with proper handling of optional fields and response shape variations.
129-293: LGTM!Helper functions robustly handle Alchemy API response variations and normalize data into consistent internal types.
295-326: LGTM!Collection search function correctly validates input, handles pagination, filters spam, and supports request cancellation via AbortSignal.
328-375: LGTM!Contract overview function properly validates addresses, normalizes data, and enriches suggestions with OpenSea metadata.
377-414: LGTM!Token metadata helpers correctly parse token IDs as bigints and normalize response data with proper error handling.
416-455: LGTM!Token metadata fetching correctly batches requests, validates inputs, and supports cancellation. Proper handling of batch responses and normalization.
457-504: LGTM!Legacy fetch utilities provide proper abort handling and delay mechanisms for retry logic.
506-568: LGTM!Legacy NFT fetching function correctly adds retry logic with exponential backoff and AbortSignal support for cancellation.
570-570: LGTM!Type exports provide clean public API surface for consumers of the Alchemy service.
| // TODO: remove once callers migrate to sortAndDedupIds. | ||
| export const mergeAndSort = sortAndDedupIds; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
TODO comment for deprecated alias.
The mergeAndSort alias is marked for removal once callers migrate to sortAndDedupIds. Consider tracking this as a tech debt item.
🏁 Script executed:
#!/bin/bash
# Check if mergeAndSort is still used in the codebase
rg -n '\bmergeAndSort\b' --type ts --type tsx -g '!**/NftPicker.utils.ts'Length of output: 27
🏁 Script executed:
#!/bin/bash
# Check if mergeAndSort is still used in the codebase, excluding NftPicker.utils.ts
rg -n '\bmergeAndSort\b' -g '*.ts' -g '*.tsx' -g '!components/nft-picker/NftPicker.utils.ts'Length of output: 257
Replace mergeAndSort references in tests with sortAndDedupIds and remove deprecated alias
Update the import in __tests__/components/nft-picker/NftPicker.utils.test.ts (around lines 9 and 101) to use sortAndDedupIds, then delete the export const mergeAndSort = sortAndDedupIds; alias in components/nft-picker/NftPicker.utils.ts (line 331).
🤖 Prompt for AI Agents
In components/nft-picker/NftPicker.utils.ts around lines 330-331, remove the
deprecated alias export "export const mergeAndSort = sortAndDedupIds;"; then
update the test file __tests__/components/nft-picker/NftPicker.utils.test.ts
(around lines 9 and 101) to import and reference sortAndDedupIds instead of
mergeAndSort, run the tests to ensure no other callers use mergeAndSort, and
delete the alias line from the utils file.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
components/nft-picker/NftEditRanges.tsx (1)
6-8: Use FontAwesome for the “copied” state checkmark for consistency.Import faCheck and render it instead of the raw ✓.
Apply:
-import { faCopy } from "@fortawesome/free-solid-svg-icons"; +import { faCopy, faCheck } from "@fortawesome/free-solid-svg-icons";- {copyStatus === "copied" ? ( - <span className="tw-font-semibold tw-text-emerald-300">✓ Copied!</span> - ) : ( + {copyStatus === "copied" ? ( + <span className="tw-inline-flex tw-items-center tw-gap-2 tw-font-semibold tw-text-emerald-300"> + <FontAwesomeIcon icon={faCheck} /> + <span>Copied</span> + </span> + ) : (Also applies to: 160-167
🧹 Nitpick comments (6)
components/gas-royalties/Royalties.tsx (1)
76-76: Optional: Remove redundant type annotation.The explicit
res: Royalty[]annotation is now redundant since TypeScript infers the type from the generic parameterfetchUrl<Royalty[]>. The code is correct as-is, but you could simplify:- fetchUrl<Royalty[]>(getUrlWithParams()).then((res: Royalty[]) => { + fetchUrl<Royalty[]>(getUrlWithParams()).then((res) => {components/gas-royalties/GasRoyalties.tsx (1)
90-91: Optional: Remove redundant type annotation.Similar to the Royalties component, the explicit
res: ApiArtistNameItem[]annotation is now redundant since TypeScript infers it fromfetchUrl<ApiArtistNameItem[]>.- fetchUrl<ApiArtistNameItem[]>(`${publicEnv.API_ENDPOINT}/api/${path}/artists_names`).then( - (res: ApiArtistNameItem[]) => { + fetchUrl<ApiArtistNameItem[]>(`${publicEnv.API_ENDPOINT}/api/${path}/artists_names`).then( + (res) => {__tests__/AGENTS.md (1)
61-134: Optional: Fix markdown heading structure.The linter flags several structural issues that would improve document navigation and tooling support. Consider promoting bold-text subsections to proper headings:
-### Complexity +## Complexity * Functions ≤ 15 cognitive complexity. * Extract deep ternaries (>3 levels). * Break down complex logic. -**Iteration** +### IterationApply similar changes to Array Access, Strings, Globals, Imports, Accessibility, Modern DOM, Error Handling, and Clarity sections (lines 84, 92, 99, 106, 112, 118, 124, 130) for consistent heading hierarchy.
components/nft-picker/NftTokenList.tsx (1)
35-36: Remove inline comment to satisfy TS/TSX guidelines.The repo’s TS/TSX guidelines forbid inline comments; this note violates that requirement. Please drop the comment or bake the intent into the constant name instead.
-// Cap prevents virtualization container heights from becoming extreme.As per coding guidelines
services/6529api.ts (2)
26-37: Replace recursive pagination with an iterative loop to avoid deep stacks.Large page counts can overflow the stack; use a loop.
Apply:
-export async function fetchAllPages(url: string, data?: any[]): Promise<any[]> { - let allData: any[] = []; - if (data) { - allData = data; - } - const response = await fetchUrl<DBResponse>(url); - allData = [...allData].concat(response.data); - if (response.next) { - return fetchAllPages(response.next, allData); - } - return allData; -} +export async function fetchAllPages(url: string, data: any[] = []): Promise<any[]> { + let allData = data; + let next: string | null = url; + while (next) { + const response = await fetchUrl<DBResponse>(next); + allData = allData.concat(response.data ?? []); + next = response.next ?? null; + } + return allData; +}
23-24: Harden JSON parsing for empty/non‑JSON responses.Avoid unhandled exceptions on 204/empty or HTML error bodies.
Apply:
- return (await res.json()) as T; + const text = await res.text(); + return (text ? (JSON.parse(text) as T) : (undefined as unknown as T));And similarly parse in postData/postFormData before returning response.
Also applies to: 55-60, 73-79
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
__tests__/AGENTS.md(1 hunks)components/gas-royalties/Gas.tsx(1 hunks)components/gas-royalties/GasRoyalties.tsx(1 hunks)components/gas-royalties/Royalties.tsx(1 hunks)components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx(1 hunks)components/nextGen/collections/collectionParts/mint/NextGenMint.tsx(1 hunks)components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx(1 hunks)components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx(1 hunks)components/nft-picker/AllTokensSelectedCard.tsx(1 hunks)components/nft-picker/NftEditRanges.tsx(1 hunks)components/nft-picker/NftPicker.tsx(1 hunks)components/nft-picker/NftPicker.types.ts(1 hunks)components/nft-picker/NftPicker.utils.ts(1 hunks)components/nft-picker/NftSuggestList.tsx(1 hunks)components/nft-picker/NftTokenList.tsx(1 hunks)components/nft-picker/useAlchemyClient.ts(1 hunks)contexts/SeizeSettingsContext.tsx(1 hunks)services/6529api.ts(1 hunks)services/alchemy-api.ts(4 hunks)spec.md(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- components/nft-picker/NftSuggestList.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before propsUse TypeScript for implementation code
Files:
components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsxcomponents/nextGen/collections/collectionParts/NextGenCollectionHeader.tsxcomponents/gas-royalties/Royalties.tsxservices/6529api.tscomponents/nextGen/collections/collectionParts/mint/NextGenMint.tsxcomponents/nft-picker/NftPicker.types.tscomponents/gas-royalties/GasRoyalties.tsxcomponents/gas-royalties/Gas.tsxcomponents/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsxcomponents/nft-picker/NftPicker.utils.tscomponents/nft-picker/NftEditRanges.tsxcomponents/nft-picker/NftPicker.tsxcontexts/SeizeSettingsContext.tsxcomponents/nft-picker/useAlchemyClient.tscomponents/nft-picker/NftTokenList.tsxservices/alchemy-api.tscomponents/nft-picker/AllTokensSelectedCard.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for stylingUse React functional components with hooks
Files:
components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsxcomponents/nextGen/collections/collectionParts/NextGenCollectionHeader.tsxcomponents/gas-royalties/Royalties.tsxcomponents/nextGen/collections/collectionParts/mint/NextGenMint.tsxcomponents/gas-royalties/GasRoyalties.tsxcomponents/gas-royalties/Gas.tsxcomponents/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsxcomponents/nft-picker/NftEditRanges.tsxcomponents/nft-picker/NftPicker.tsxcontexts/SeizeSettingsContext.tsxcomponents/nft-picker/NftTokenList.tsxcomponents/nft-picker/AllTokensSelectedCard.tsx
__tests__/**
📄 CodeRabbit inference engine (tests/AGENTS.md)
Place Jest test suites under the
__tests__directory mirroring source folders (e.g., components, contexts, hooks, utils)
Files:
__tests__/AGENTS.md
🧠 Learnings (3)
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.{ts,tsx} : Always add readonly before props
Applied to files:
components/nft-picker/NftPicker.types.tscomponents/nft-picker/AllTokensSelectedCard.tsx
📚 Learning: 2025-09-28T12:29:11.627Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: .cursorrules:0-0
Timestamp: 2025-09-28T12:29:11.627Z
Learning: Applies to **/*.tsx : Use FontAwesome for icons
Applied to files:
components/nft-picker/NftEditRanges.tsxcomponents/nft-picker/AllTokensSelectedCard.tsx
📚 Learning: 2025-09-28T12:33:30.941Z
Learnt from: CR
PR: 6529-Collections/6529seize-frontend#0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-09-28T12:33:30.941Z
Learning: Run `npm run test:cov:changed` for changed-file tests and coverage; use `npm run test` for full suite; ensure `npm run lint` and `npm run type-check` pass
Applied to files:
__tests__/AGENTS.md
🧬 Code graph analysis (15)
components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx (1)
services/6529api.ts (1)
fetchUrl(6-24)
components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx (1)
services/6529api.ts (1)
fetchUrl(6-24)
components/gas-royalties/Royalties.tsx (2)
services/6529api.ts (1)
fetchUrl(6-24)entities/IRoyalty.ts (1)
Royalty(1-11)
services/6529api.ts (3)
entities/IDBResponse.ts (1)
DBResponse(1-6)services/auth/auth.utils.ts (1)
getStagingAuth(77-79)constants.ts (1)
API_AUTH_COOKIE(39-39)
components/nextGen/collections/collectionParts/mint/NextGenMint.tsx (1)
services/6529api.ts (1)
fetchUrl(6-24)
components/gas-royalties/GasRoyalties.tsx (2)
services/6529api.ts (1)
fetchUrl(6-24)generated/models/ApiArtistNameItem.ts (1)
ApiArtistNameItem(15-41)
components/gas-royalties/Gas.tsx (2)
services/6529api.ts (1)
fetchUrl(6-24)entities/IGas.ts (1)
Gas(1-8)
components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx (1)
services/6529api.ts (1)
fetchUrl(6-24)
components/nft-picker/NftPicker.utils.ts (1)
components/nft-picker/NftPicker.types.ts (3)
ParseError(77-83)TokenRange(7-7)TokenSelection(9-9)
components/nft-picker/NftEditRanges.tsx (2)
components/nft-picker/NftPicker.types.ts (2)
TokenRange(7-7)ParseError(77-83)components/nft-picker/NftPicker.utils.ts (2)
formatCanonical(392-400)formatBigIntWithSeparators(402-421)
components/nft-picker/NftPicker.tsx (9)
components/nft-picker/NftPicker.types.ts (8)
SupportedChain(3-3)TokenSelection(9-9)TokenRange(7-7)Suggestion(11-22)ContractOverview(24-27)NftPickerProps(60-75)ParseError(77-83)NftPickerSelection(43-51)components/nft-picker/NftPicker.utils.ts (10)
toCanonicalRanges(351-369)formatCanonical(392-400)parseTokenExpressionToRanges(187-261)formatBigIntWithSeparators(402-421)mergeCanonicalRanges(284-295)removeTokenFromRanges(297-327)fromCanonicalRanges(371-390)isRangeTooLargeError(43-49)tryToNumberArray(423-437)MAX_SAFE(7-7)components/nft-picker/useAlchemyClient.ts (3)
useContractOverviewQuery(164-201)useCollectionSearch(107-162)primeContractCache(264-277)helpers/Helpers.ts (1)
isValidEthAddress(218-219)components/nft-picker/NftSuggestList.tsx (1)
NftSuggestList(39-191)components/nft-picker/NftContractHeader.tsx (1)
NftContractHeader(18-102)components/nft-picker/AllTokensSelectedCard.tsx (1)
AllTokensSelectedCard(12-44)components/nft-picker/NftEditRanges.tsx (1)
NftEditRanges(44-242)components/nft-picker/NftTokenList.tsx (1)
NftTokenList(55-216)
contexts/SeizeSettingsContext.tsx (2)
services/6529api.ts (1)
fetchUrl(6-24)generated/models/ApiSeizeSettings.ts (1)
ApiSeizeSettings(15-48)
components/nft-picker/useAlchemyClient.ts (2)
services/alchemy-api.ts (5)
SearchContractsResult(569-569)TokenMetadataParams(569-569)searchNftCollections(294-325)getContractOverview(327-374)getTokensMetadata(415-454)components/nft-picker/NftPicker.types.ts (4)
ContractOverview(24-27)TokenMetadata(29-35)SupportedChain(3-3)Suggestion(11-22)
components/nft-picker/NftTokenList.tsx (4)
components/nft-picker/NftPicker.types.ts (3)
SupportedChain(3-3)TokenRange(7-7)TokenMetadata(29-35)contexts/ScrollPositionContext.tsx (1)
useScrollPositionContext(36-40)components/nft-picker/NftPicker.utils.ts (2)
expandRangesWindow(439-479)formatCanonical(392-400)components/nft-picker/useAlchemyClient.ts (1)
useTokenMetadataQuery(203-262)
services/alchemy-api.ts (3)
components/nft-picker/NftPicker.types.ts (4)
SupportedChain(3-3)Suggestion(11-22)ContractOverview(24-27)TokenMetadata(29-35)helpers/Helpers.ts (1)
isValidEthAddress(218-219)services/6529api.ts (2)
fetchUrl(6-24)postData(39-60)
🪛 markdownlint-cli2 (0.18.1)
__tests__/AGENTS.md
61-61: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
69-69: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
84-84: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
92-92: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
99-99: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
106-106: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
112-112: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
118-118: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
124-124: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
130-130: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (64)
components/nextGen/collections/collectionParts/NextGenCollectionHeader.tsx (1)
94-94: LGTM - Type-safe fetch call.Adding the explicit
CollectionWithMerklegeneric parameter improves type safety and aligns with the updatedfetchUrl<T>signature.contexts/SeizeSettingsContext.tsx (1)
36-36: LGTM - Type-safe fetch call.Adding the explicit
ApiSeizeSettingsgeneric parameter improves type safety and aligns with the updatedfetchUrl<T>signature.components/nextGen/collections/collectionParts/mint/NextGenMintWidget.tsx (1)
150-150: LGTM - Type-safe fetch call.Adding the explicit
ProofResponse[]generic parameter improves type safety and correctly reflects the array response from the proofs endpoint.components/nextGen/collections/collectionParts/mint/NextGenMintBurnWidget.tsx (1)
141-141: LGTM - Type-safe fetch call.Adding the explicit
ProofResponsegeneric parameter improves type safety and correctly reflects the single-object response from the burn proofs endpoint.components/gas-royalties/Gas.tsx (1)
69-69: LGTM - Type-safe fetch call.Adding the explicit
Gas[]generic parameter improves type safety and aligns with the updatedfetchUrl<T>signature.components/nft-picker/AllTokensSelectedCard.tsx (1)
1-44: LGTM! Well-structured accessible component.The component follows all coding guidelines and best practices:
- Uses FontAwesome for icons as required
- TailwindCSS for styling
- Props properly marked as readonly
- Semantic HTML (
<output>element) for status display- Comprehensive accessibility with aria-labels
- Clean functional component with proper ref forwarding
components/nft-picker/NftPicker.types.ts (1)
1-106: LGTM! Comprehensive and well-structured type definitions.The type definitions follow TypeScript best practices:
- Proper use of branded types (
0x${string})- Discriminated unions for
NftPickerSelectionbased onoutputMode- Readonly modifiers on props as required
- Clear separation of concerns (base types, selections, metadata, props)
- Future-proof design (e.g.,
SupportedChainas union type ready for extension)components/nextGen/collections/collectionParts/mint/NextGenMint.tsx (1)
230-235: Typed fetchUrl usage looks good.Thanks for threading the
CollectionWithMerklegeneric throughfetchUrl; this tightens the typing and keeps the then-handler honest without changing runtime behavior.components/nft-picker/NftEditRanges.tsx (1)
89-127: Copy fallback is solid; minor polish only.Implementation handles Clipboard API and fallback well; no changes required.
components/nft-picker/useAlchemyClient.ts (3)
64-75: Stable token cache key and dedupe look good.Sorted, deduped keys prevent cache fragmentation. LGTM.
264-277: Nice: primeContractCache for optimistic UX.Preloading overview from suggestions is a good touch.
176-201: Approve React Query TTL configuration. staleTime and gcTime correctly use SUGGESTION_TTL, CONTRACT_TTL, and TOKEN_TTL respectively.services/6529api.ts (1)
6-9: fetchUrl/postData calls don’t pass custom headers—change is safe
All existing call sites invokefetchUrl,postData, orpostFormDatawith only URL (and body) arguments and never supply additional headers, so the updated implementations won’t affect their behavior.components/nft-picker/NftPicker.tsx (21)
1-1: "use client" directive is correct for Next.js App Router.This component uses client-side hooks and interactivity, so the directive is appropriate.
43-47: Constants are well-defined.The default values and BigInt constants are appropriately scoped and named.
51-56: Helper function logic is correct.The function correctly handles empty selections and delegates to
toCanonicalRanges.
70-92: Component signature and chain resolution look good.The props are correctly destructured with defaults, and the
chainvalue is properly memoized with fallback logic.
110-112: Prop sync effect is correct.Synchronizing the
hideSpamprop to local state allows for independent toggling while respecting parent updates.
121-132: Contract hydration effect handles edge cases well.The effect correctly updates
selectedContractonly when needed and notifies the parent viaonContractChange.
175-199: Value prop sync handles controlled mode correctly.The effect properly syncs the controlled
valueprop to internal state, includingallSelectedandpreviousRangesReffor restore logic.
201-225: DefaultValue hydration is correctly guarded.The effect only runs once for uncontrolled mode and properly sets
pendingPropEmitRefto delay the initial emit until the contract is loaded.
232-247: Suggestion selection logic is comprehensive.The handler correctly updates state, primes the cache, clears previous selections, and emits the change with an empty selection.
283-347: Token preview computation handles all edge cases.The memo correctly validates ranges-disabled mode, parses input, catches errors, and returns structured results.
349-353: Add-tokens guard is correct.The condition properly checks
allSelected, non-empty input, no errors, and positive count.
386-420: Helper state logic provides clear user feedback.The memo correctly handles all states: all-selected, empty input, errors, and success with appropriate tone and messaging.
445-458: addTokenRanges correctly merges and emits.The function properly merges ranges, clears
allSelected, resets errors, and emits the change.
510-522: Select-all flow preserves restore state.The handler correctly saves current ranges to
previousRangesReffor later restoration and focuses the deselect button.
524-542: Deselect-all restores previous ranges when available.The handler correctly checks for
previousRangesRefand restores it, or emits current ranges if none were saved.
585-615: Range-to-selection conversion handles errors gracefully.The function correctly catches
RangeTooLargeError, setsparseErrorsfor UI feedback, logs a warning, and returnsnullto prevent emission.
617-671: emitChange logic is comprehensive and correct.The function properly handles all cases: early return on null contract or selection errors, empty-selection payload, number-mode with unsafe-count check, and bigint-mode with conversion to strings. The
allSelecteddiscriminator is correctly included in all payloads.
673-683: Pending-emit effect is correctly guarded.The effect only emits when
pendingPropEmitRefis set and the contract matches, preventing duplicate emissions while handling the uncontrolled defaultValue initialization case.
688-722: Collection input section has proper accessibility.The input correctly uses ARIA attributes (
role="combobox",aria-expanded,aria-controls,aria-activedescendant) for screen-reader support and keyboard navigation.
761-776: Select All button properly integrates FontAwesome icon.The button correctly uses the
FontAwesomeIconcomponent per coding guidelines and includes proper accessibility attributes.
827-831: Unsafe count warning provides actionable guidance.The warning correctly appears only when
outputMode === "number"andunsafeCount > 0, informing users to switch modes or remove large IDs.components/nft-picker/NftPicker.utils.ts (15)
7-10: Constants are well-defined with safe limits.
MAX_SAFEandMAX_ENUMERATION(10,000) provide appropriate bounds to prevent memory exhaustion and UI freezes. The past review concern about unbounded range expansion has been addressed.
12-49: Error handling utilities are well-structured.The custom error types and factories provide clear, structured error information with proper type guards for downstream handling.
61-108: Tokenization logic handles hyphen ranges correctly.The function properly distinguishes between standalone hyphens (range operators) and hyphen-prefixed tokens, merging them correctly into range expressions.
120-147: Token value parsing is robust.The function correctly validates empty tokens, rejects signed values, accepts both hex and decimal formats, and returns BigInt.
149-185: Range canonicalization merges correctly.The function properly sorts ranges and merges adjacent or overlapping ones, producing a minimal canonical set.
187-261: Parsing enforces limits at both per-range and total levels.The function correctly validates individual range sizes and the total token count against
MAX_ENUMERATION, preventing memory exhaustion as flagged in past reviews.
263-282: Bigint parsing wraps range parsing with error conversion.The function correctly delegates to
parseTokenExpressionToRangesandfromCanonicalRanges, catching and re-throwingRangeTooLargeErroras aParseErrorfor UI consumption.
297-327: Token removal handles all range split cases.The function correctly handles tokens outside ranges, single-token ranges, boundary tokens, and mid-range tokens (splitting into two ranges).
336-346: Deduplication logic is correct.The function sorts by bigint comparison and removes consecutive duplicates.
351-369: Range conversion from IDs is efficient.The function correctly builds ranges by detecting consecutive IDs and grouping them.
371-390: Range expansion enforces limits correctly.The function checks both per-range and cumulative sizes against
MAX_ENUMERATION, throwingRangeTooLargeErrorwhen exceeded.
392-400: Canonical formatting is concise and readable.The function correctly formats single tokens and ranges, joining with commas.
402-421: Thousand-separator formatting is correct.The function properly handles negative numbers and inserts commas every three digits from the right.
423-437: Number array conversion tracks unsafe IDs.The function correctly converts safe IDs to numbers and counts IDs exceeding
MAX_SAFE_INTEGERfor the parent to handle.
439-479: Window expansion supports virtualization efficiently.The function correctly extracts a slice of token IDs from ranges for virtual scrolling, handling start index and count parameters.
services/alchemy-api.ts (15)
1-12: Imports are well-organized.The imports correctly bring in environment config, helpers, types, viem utilities, and the internal
fetchUrl/postDatafunctions.
14-119: Type definitions comprehensively model Alchemy API responses.The types correctly handle optional fields, unions, and nested structures for contract metadata, OpenSea metadata, and token metadata.
128-138: Network and query helpers are straightforward.The functions correctly map chains to network strings and validate non-empty queries.
157-176: Floor price parsing handles multiple formats.The function correctly handles direct numbers, nested objects with
ethfield (as number or string), and returnsnullfor invalid formats.
178-226: Image pickers implement fallback chains.Both
pickImageandpickThumbnailcorrectly try multiple sources in priority order, returning the first available URL ornull.
228-241: Address normalization validates and checksums.The function validates with
isValidEthAddress, checksums withviem.getAddress, and logs a warning on checksum failure while still returning the address. This is a reasonable fallback.
243-292: Contract extraction unifies multiple metadata sources.The function correctly aggregates data from
contractMetadata,openSeaMetadata, and base contract fields, normalizes addresses, and builds aSuggestionobject. Returningnullwhen no valid address is found is appropriate.
294-325: Collection search correctly filters spam.The function builds the Alchemy URL, fetches results, extracts contracts, and filters spam items when
hideSpamis true, returning both visible items and hidden count.
327-374: Contract overview fetches and maps metadata correctly.The function validates the address, builds the URL, fetches metadata, and constructs a
ContractOverviewwith description and banner from OpenSea metadata.
376-385: Token ID parsing handles hex and decimal formats.The function correctly trims whitespace and parses both hex (0x-prefixed) and decimal token IDs using
BigInt.
387-413: Token metadata normalization handles missing data gracefully.The function parses the token ID, picks an image from multiple sources, and returns
nullwith a warning if parsing fails.
415-454: Token metadata batching is efficient.The function correctly batches requests by
MAX_BATCH_SIZE(100), usespostDatafor each batch, and aggregates normalized results.
458-498: Abort utilities are correctly implemented.
createAbortErrorcreates proper error objects, anddelayWithAbortimplements abortable delays with cleanup listeners. These are essential for cancellable async operations.
505-567: NFT fetch function correctly adds retry and abort support.The modified function properly handles retries with exponential backoff (line 533:
250 * (retries + 1)ms), pagination, and abort signals. The recursive calls correctly propagate thesignalparameter.
530-542: Verify abort signal handling in getNftsForContractAndOwner retries.
- Internals: delayWithAbort and fetchLegacyUrl both respect and propagate the AbortSignal; the retry call forwards the same signal — aborts will reject with an AbortError and will not be swallowed by the retry logic.
- Outstanding: I did not find any external callers in the repo to confirm consumer handling — ensure all callsites catch AbortError (check error.name === 'AbortError' or instanceof DOMException) and treat it as a cancellation (do not record/display it as an unexpected error).
File: services/alchemy-api.ts (around lines 530–542).
| textarea.setSelectionRange(0, textarea.value.length); | ||
| } catch (error) { | ||
| /* no-op, best effort for iOS/WebKit */ | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Remove inline comment (TSX rule: no code comments).
Delete the in-line comment inside the try/catch.
Apply:
- try {
- textarea.setSelectionRange(0, textarea.value.length);
- } catch (error) {
- /* no-op, best effort for iOS/WebKit */
- }
+ try {
+ textarea.setSelectionRange(0, textarea.value.length);
+ } catch (error) {
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| textarea.setSelectionRange(0, textarea.value.length); | |
| } catch (error) { | |
| /* no-op, best effort for iOS/WebKit */ | |
| } | |
| try { | |
| textarea.setSelectionRange(0, textarea.value.length); | |
| } catch (error) { | |
| } |
🤖 Prompt for AI Agents
In components/nft-picker/NftEditRanges.tsx around lines 113 to 116, remove the
inline comment inside the catch block (the "/* no-op, best effort for iOS/WebKit
*/") so the catch block contains no comment; leave the empty catch body as-is
(or a minimal no-op expression if your lint rules require one) to satisfy the
TSX no-code-comments rule.
| /** @deprecated Use sortAndDedupIds instead. */ | ||
| export const mergeAndSort = sortAndDedupIds; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Deprecated alias should be removed after migration.
The mergeAndSort alias is marked deprecated. As per the past review comment, update test imports to use sortAndDedupIds and remove this alias.
Check if mergeAndSort is still referenced outside tests:
🏁 Script executed:
#!/bin/bash
rg -n '\bmergeAndSort\b' -g '*.ts' -g '*.tsx' -g '!components/nft-picker/NftPicker.utils.ts' -g '!**/__tests__/**'Length of output: 257
Remove deprecated mergeAndSort alias and update test references
- In tests/components/nft-picker/NftPicker.utils.test.ts (lines 9 and 101), replace all
mergeAndSortimports and calls withsortAndDedupIds. - In components/nft-picker/NftPicker.utils.ts (lines 348–349), delete the now-unused
export const mergeAndSort = sortAndDedupIds;alias.
🤖 Prompt for AI Agents
In components/nft-picker/NftPicker.utils.ts around lines 348 to 349, remove the
deprecated alias export "export const mergeAndSort = sortAndDedupIds;" so the
alias is no longer present; then update
__tests__/components/nft-picker/NftPicker.utils.test.ts (around lines 9 and 101)
to replace any imports and references of mergeAndSort with sortAndDedupIds
(update import names and call sites) and run tests to ensure no remaining
references to mergeAndSort remain.
| const baseHeaders = new Headers(init?.headers); | ||
| const apiAuth = getStagingAuth(); | ||
| if (apiAuth) { | ||
| headers = { "x-6529-auth": apiAuth }; | ||
| baseHeaders.set("x-6529-auth", apiAuth); | ||
| } | ||
| const headers = Object.fromEntries(baseHeaders.entries()); |
There was a problem hiding this comment.
Do not attach x-6529-auth to external URLs (leaks header to Alchemy; likely CORS failure).
Gate auth header injection to relative/internal URLs only.
Apply:
+const isRelativeUrl = (value: string): boolean =>
+ !/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value) && !value.startsWith("//");
+
export async function fetchUrl<T = DBResponse>(
url: string,
init?: RequestInit
): Promise<T> {
const baseHeaders = new Headers(init?.headers);
- const apiAuth = getStagingAuth();
+ const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
if (apiAuth) {
baseHeaders.set("x-6529-auth", apiAuth);
}Also mirror this in postData and postFormData (see below).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const baseHeaders = new Headers(init?.headers); | |
| const apiAuth = getStagingAuth(); | |
| if (apiAuth) { | |
| headers = { "x-6529-auth": apiAuth }; | |
| baseHeaders.set("x-6529-auth", apiAuth); | |
| } | |
| const headers = Object.fromEntries(baseHeaders.entries()); | |
| const isRelativeUrl = (value: string): boolean => | |
| !/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value) && !value.startsWith("//"); | |
| export async function fetchUrl<T = DBResponse>( | |
| url: string, | |
| init?: RequestInit | |
| ): Promise<T> { | |
| const baseHeaders = new Headers(init?.headers); | |
| const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null; | |
| if (apiAuth) { | |
| baseHeaders.set("x-6529-auth", apiAuth); | |
| } | |
| const headers = Object.fromEntries(baseHeaders.entries()); | |
| // ...rest of implementation | |
| } |
| export async function postData(url: string, body: any, init?: RequestInit) { | ||
| const baseHeaders = new Headers(init?.headers); | ||
| if (!baseHeaders.has("Content-Type")) { | ||
| baseHeaders.set("Content-Type", "application/json"); | ||
| } | ||
| const apiAuth = getStagingAuth(); | ||
| if (apiAuth) { | ||
| headers = { "x-6529-auth": apiAuth, "Content-Type": "application/json" }; | ||
| baseHeaders.set("x-6529-auth", apiAuth); | ||
| } | ||
| const headers = Object.fromEntries(baseHeaders.entries()); | ||
| const res = await fetch(url, { |
There was a problem hiding this comment.
Align 401 handling across POST helpers; also gate auth for POSTs.
postData/postFormData should clear the cookie on 401 (like fetchUrl) and avoid sending auth header to externals.
Apply:
export async function postData(url: string, body: any, init?: RequestInit) {
const baseHeaders = new Headers(init?.headers);
if (!baseHeaders.has("Content-Type")) {
baseHeaders.set("Content-Type", "application/json");
}
- const apiAuth = getStagingAuth();
+ const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
if (apiAuth) {
baseHeaders.set("x-6529-auth", apiAuth);
}
const headers = Object.fromEntries(baseHeaders.entries());
const res = await fetch(url, {
...init,
method: "POST",
body: JSON.stringify(body),
headers,
});
+ if (res.status === 401) {
+ Cookies.remove(API_AUTH_COOKIE);
+ } export async function postFormData(url: string, formData: FormData) {
- let headers: any = {};
- const apiAuth = getStagingAuth();
+ let headers: any = {};
+ const apiAuth = isRelativeUrl(url) ? getStagingAuth() : null;
if (apiAuth) {
headers = { "x-6529-auth": apiAuth };
}
const res = await fetch(url, {
method: "POST",
body: formData,
headers: headers,
});
+ if (res.status === 401) {
+ Cookies.remove(API_AUTH_COOKIE);
+ }Also applies to: 50-55, 62-67, 68-77
🤖 Prompt for AI Agents
In services/6529api.ts around lines 39-49 (also apply the same changes to lines
50-55, 62-67, 68-77): postData/postFormData currently always attaches
x-6529-auth and does not clear auth cookie on 401; update them to only set the
x-6529-auth header when the target URL is internal (e.g., same origin or matches
our API base) and after receiving the fetch response, if res.status === 401 call
the same cookie-clear routine used by fetchUrl (clearCookie or equivalent)
before returning/throwing; ensure headers construction gates auth header by
checking the URL, and add the 401 handling after the fetch so behavior matches
fetchUrl.
| <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded"> | ||
| {JSON.stringify(selection, null, 2)} | ||
| </pre> |
There was a problem hiding this comment.
Fix the demo JSON.stringify example to avoid runtime crash.
JSON.stringify throws TypeError: Do not know how to serialize a BigInt when run on the emitted selection because tokenIdsRaw is a bigint[]. The demo snippet will crash as soon as the picker emits a value, and anyone copying the spec example will hit the same failure. Please convert bigint fields before stringifying (e.g., via a replacer that calls .toString()), or render the value another way.
- <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
- {JSON.stringify(selection, null, 2)}
- </pre>
+ <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded">
+ {JSON.stringify(
+ selection,
+ (_, value) => (typeof value === "bigint" ? value.toString() : value),
+ 2
+ )}
+ </pre>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded"> | |
| {JSON.stringify(selection, null, 2)} | |
| </pre> | |
| <pre className="tw-mt-4 tw-text-xs tw-bg-iron-900 tw-border tw-border-iron-800 tw-p-3 tw-rounded"> | |
| {JSON.stringify( | |
| selection, | |
| (_, value) => (typeof value === "bigint" ? value.toString() : value), | |
| 2 | |
| )} | |
| </pre> |
🤖 Prompt for AI Agents
In spec.md around lines 474 to 476, the demo uses JSON.stringify(selection,
null, 2) which throws when selection contains BigInt (tokenIdsRaw: bigint[]);
fix by converting BigInt values before stringifying — either provide a replacer
to JSON.stringify that converts BigInt to string, or normalize the selection
object (map tokenIdsRaw elements to strings or add a safe-serializable copy) and
then call JSON.stringify on that sanitized object so the demo no longer crashes
at runtime.
|



Summary by CodeRabbit
New Features
New Components
Utilities
Documentation
Tests
Chores