feat: implement IPFS caching and update NFT image retrieval logic - #295
Conversation
|
Note
|
| Layer / File(s) | Summary |
|---|---|
getNftImageUrl helper and type update lib/nft/erc721.ts |
Adds optional metadata.image field and getNftImageUrl(asset?) which prefers metadata.image, falls back to media_url/image_url, and converts ipfs:// URLs via convertIPFStoHTTP. |
Component image URL wiring components/dashboard/Erc721Item.tsx, components/screens/ERC721.tsx, components/send/evm/erc721-transfer/Erc721TransferDetails.tsx |
Replaces direct image_url reads with getNftImageUrl(asset/data) for image src/imageUrl props. |
IPFS/immutable cache module lib/cache/ipfsCache.ts |
New module with readCache/writeCache storage helpers and exported fetchImmutable<T>(url) and fetchIPFS<T>(ipfsUrl) that cache fetched JSON permanently, throwing on non-OK responses. |
KRC721 hook integration hooks/krc721/useKRC721.ts |
useKRC721Details now uses fetchImmutable for collection metadata and fetchIPFS for NFT details instead of fetcher plus convertIPFStoHTTP, with SWR data typed as `KRC721DetailsResponse |
Estimated code review effort: 2 (Simple) | ~12 minutes
Sequence Diagram(s)
sequenceDiagram
participant useKRC721Details
participant fetchImmutable
participant fetchIPFS
participant Storage
participant IPFSGateway
useKRC721Details->>fetchImmutable: request collection metadata (url)
fetchImmutable->>Storage: readCache(url)
alt cache hit
Storage-->>fetchImmutable: cached data
else cache miss
fetchImmutable->>IPFSGateway: fetch(url)
IPFSGateway-->>fetchImmutable: JSON response
fetchImmutable->>Storage: writeCache(url, data)
end
fetchImmutable-->>useKRC721Details: collection data (buri)
useKRC721Details->>fetchIPFS: request NFT details (buri/id)
fetchIPFS->>Storage: readCache(ipfsUrl)
alt cache hit
Storage-->>fetchIPFS: cached data
else cache miss
fetchIPFS->>IPFSGateway: fetch(convertedHttpUrl)
IPFSGateway-->>fetchIPFS: JSON response
fetchIPFS->>Storage: writeCache(ipfsUrl, data)
end
fetchIPFS-->>useKRC721Details: NFT details
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title accurately summarizes the two main changes: IPFS caching and updated NFT image retrieval logic. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
✨ Finishing Touches
📝 Generate docstrings
- Create stacked PR
- Commit on current branch
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
feat/add-ipfs-cache
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
There was a problem hiding this comment.
Pull request overview
This PR adds a persistent IPFS/immutable-JSON cache layer and centralizes ERC-721 NFT image URL selection so the extension can control IPFS gateway usage (and avoid relying on Blockscout’s rewritten image URLs).
Changes:
- Introduces
lib/cache/ipfsCache.tswithfetchImmutable()andfetchIPFS()helpers backed by WXT storage. - Adds
getNftImageUrl()to prefer rawmetadata.image(and fall back tomedia_url/image_url) with IPFS → HTTP conversion. - Updates ERC-721 UI components and KRC-721 details hook to use the new helpers.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/nft/erc721.ts | Adds metadata.image / media_url support and a helper to resolve ERC-721 image URLs with IPFS gateway conversion. |
| lib/cache/ipfsCache.ts | Adds permanent local-storage caching helpers for immutable JSON and IPFS JSON fetches. |
| hooks/krc721/useKRC721.ts | Switches KRC-721 metadata fetching to cached fetch helpers (immutable indexer lookup + IPFS metadata fetch). |
| components/send/evm/erc721-transfer/Erc721TransferDetails.tsx | Uses centralized ERC-721 image URL helper for transfer preview rendering. |
| components/screens/ERC721.tsx | Uses centralized ERC-721 image URL helper for the ERC-721 details screen image. |
| components/dashboard/Erc721Item.tsx | Uses centralized ERC-721 image URL helper for dashboard ERC-721 item rendering. |
| async function writeCache<T>(key: string, data: T): Promise<void> { | ||
| await storage.setItem(`${PREFIX}${key}`, data); | ||
| } |
| // Same as fetchImmutable, but keyed by the original ipfs:// URL so the cache survives a gateway change. | ||
| export async function fetchIPFS<T>(ipfsUrl: string): Promise<T> { | ||
| const cached = await readCache<T>(ipfsUrl); | ||
| if (cached !== null) return cached; | ||
|
|
||
| const httpUrl = convertIPFStoHTTP(ipfsUrl); | ||
| const res = await fetch(httpUrl); | ||
| if (!res.ok) throw new Error(`HTTP ${res.status}: ${httpUrl}`); | ||
| const data = (await res.json()) as T; | ||
| await writeCache(ipfsUrl, data); | ||
| return data; | ||
| } |
| const raw = asset?.metadata?.image ?? asset?.media_url ?? asset?.image_url; | ||
| if (!raw) return undefined; | ||
| return raw.startsWith("ipfs://") ? convertIPFStoHTTP(raw) : raw; |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/cache/ipfsCache.ts (1)
15-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winValidate fetched API payloads with Zod per coding guidelines.
Both functions cast the JSON response as
Twithout runtime validation. Per coding guidelines, "Use Zod for validation of all API payloads and message schemas." Consider accepting an optional Zod schema parameter so callers can validate at the fetch boundary, or validate at the call site inuseKRC721Details.🛡️ Optional schema parameter approach
+import { z } from "zod"; + -export async function fetchImmutable<T>(url: string): Promise<T> { +export async function fetchImmutable<T>( + url: string, + schema?: z.ZodType<T>, +): Promise<T> { const cached = await readCache<T>(url); if (cached !== null) return cached; - const res = await fetch(url); + const res = await fetchWithTimeout(url); if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`); - const data = (await res.json()) as T; + const json = await res.json(); + const data = schema ? schema.parse(json) : (json as T); await writeCache(url, data); return data; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/cache/ipfsCache.ts` around lines 15 - 37, Both fetchImmutable and fetchIPFS currently trust res.json() by casting to T, so add runtime validation with Zod at the fetch boundary. Update these helpers to accept an optional schema parameter (or ensure the caller validates immediately in useKRC721Details), and parse the fetched JSON through that schema before caching/returning it. Use the existing fetchImmutable and fetchIPFS symbols as the main touchpoints so all API payloads are validated consistently.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/cache/ipfsCache.ts`:
- Around line 15-24: `fetchImmutable` and `fetchIPFS` are using bare `fetch()`
and can hang indefinitely on slow or unresponsive IPFS gateways. Add a shared
`fetchWithTimeout` helper that wraps the request with an abort/timeout, then
update both `fetchImmutable` and `fetchIPFS` to use it so callers get a bounded
failure instead of an endless loading state.
---
Nitpick comments:
In `@lib/cache/ipfsCache.ts`:
- Around line 15-37: Both fetchImmutable and fetchIPFS currently trust
res.json() by casting to T, so add runtime validation with Zod at the fetch
boundary. Update these helpers to accept an optional schema parameter (or ensure
the caller validates immediately in useKRC721Details), and parse the fetched
JSON through that schema before caching/returning it. Use the existing
fetchImmutable and fetchIPFS symbols as the main touchpoints so all API payloads
are validated consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: b62e8440-a889-4479-b185-50141af310a6
📒 Files selected for processing (6)
components/dashboard/Erc721Item.tsxcomponents/screens/ERC721.tsxcomponents/send/evm/erc721-transfer/Erc721TransferDetails.tsxhooks/krc721/useKRC721.tslib/cache/ipfsCache.tslib/nft/erc721.ts
| export async function fetchImmutable<T>(url: string): Promise<T> { | ||
| const cached = await readCache<T>(url); | ||
| if (cached !== null) return cached; | ||
|
|
||
| const res = await fetch(url); | ||
| if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`); | ||
| const data = (await res.json()) as T; | ||
| await writeCache(url, data); | ||
| return data; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add fetch timeout to prevent indefinite hangs on slow IPFS gateways.
Both fetchImmutable and fetchIPFS use bare fetch() without a timeout. IPFS gateways can be slow or unresponsive, causing indefinite loading states in SWR consumers with no fallback.
⏱️ Proposed fix: add a `fetchWithTimeout` helper
+async function fetchWithTimeout(url: string, ms = 15000): Promise<Response> {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), ms);
+ try {
+ return await fetch(url, { signal: controller.signal });
+ } finally {
+ clearTimeout(timeout);
+ }
+}
+
export async function fetchImmutable<T>(url: string): Promise<T> {
const cached = await readCache<T>(url);
if (cached !== null) return cached;
- const res = await fetch(url);
+ const res = await fetchWithTimeout(url);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`);And apply the same change in fetchIPFS:
const httpUrl = convertIPFStoHTTP(ipfsUrl);
- const res = await fetch(httpUrl);
+ const res = await fetchWithTimeout(httpUrl);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${httpUrl}`);Also applies to: 27-37
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/cache/ipfsCache.ts` around lines 15 - 24, `fetchImmutable` and
`fetchIPFS` are using bare `fetch()` and can hang indefinitely on slow or
unresponsive IPFS gateways. Add a shared `fetchWithTimeout` helper that wraps
the request with an abort/timeout, then update both `fetchImmutable` and
`fetchIPFS` to use it so callers get a bounded failure instead of an endless
loading state.
Pull Request Checklist
Before Submission
Description
Additional Notes
By submitting this pull request, I confirm that:
project's
license terms
Summary by CodeRabbit
New Features
Bug Fixes