Skip to content

feat: implement IPFS caching and update NFT image retrieval logic - #295

Merged
dadamu merged 1 commit into
mainfrom
feat/add-ipfs-cache
Jul 9, 2026
Merged

feat: implement IPFS caching and update NFT image retrieval logic#295
dadamu merged 1 commit into
mainfrom
feat/add-ipfs-cache

Conversation

@dadamu

@dadamu dadamu commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Pull Request Checklist

Before Submission


Description


Additional Notes


By submitting this pull request, I confirm that:

  • My contribution is made under the Business Source License
  • I grant Forbole Technology Limited a perpetual, worldwide, non-exclusive license to use my contribution under the
    project's
    license terms
  • I have the authority to make this contribution and license it appropriately

Summary by CodeRabbit

  • New Features

    • Improved NFT image loading across the app, with broader support for metadata image sources and IPFS-based links.
    • Added more reliable caching for immutable metadata and IPFS content to speed up repeat views.
  • Bug Fixes

    • Updated NFT screens, dashboard items, and transfer details to use the best available image URL instead of relying on a single field.
    • Prevented missing collection data from causing broken NFT detail fetches.

Copilot AI review requested due to automatic review settings July 9, 2026 07:20
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

.coderabbit.yml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized keys: "labels", "include_paths", "exclude_paths", "filters", "review", "pull_request", "limits", "commands", "messages"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds a getNftImageUrl helper in lib/nft/erc721.ts that resolves an NFT image from metadata.image, media_url, or image_url with IPFS-to-HTTP conversion, and wires it into three display components. Also introduces lib/cache/ipfsCache.ts for permanent immutable/IPFS JSON caching, used by useKRC721Details.

Changes

NFT image resolution and caching

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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts with fetchImmutable() and fetchIPFS() helpers backed by WXT storage.
  • Adds getNftImageUrl() to prefer raw metadata.image (and fall back to media_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.

Comment thread lib/cache/ipfsCache.ts
Comment on lines +11 to +13
async function writeCache<T>(key: string, data: T): Promise<void> {
await storage.setItem(`${PREFIX}${key}`, data);
}
Comment thread lib/cache/ipfsCache.ts
Comment on lines +26 to +37
// 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;
}
Comment thread lib/nft/erc721.ts
Comment on lines +33 to +35
const raw = asset?.metadata?.image ?? asset?.media_url ?? asset?.image_url;
if (!raw) return undefined;
return raw.startsWith("ipfs://") ? convertIPFStoHTTP(raw) : raw;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/cache/ipfsCache.ts (1)

15-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Validate fetched API payloads with Zod per coding guidelines.

Both functions cast the JSON response as T without 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 in useKRC721Details.

🛡️ 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

📥 Commits

Reviewing files that changed from the base of the PR and between c48bdf7 and 4da173e.

📒 Files selected for processing (6)
  • components/dashboard/Erc721Item.tsx
  • components/screens/ERC721.tsx
  • components/send/evm/erc721-transfer/Erc721TransferDetails.tsx
  • hooks/krc721/useKRC721.ts
  • lib/cache/ipfsCache.ts
  • lib/nft/erc721.ts

Comment thread lib/cache/ipfsCache.ts
Comment on lines +15 to +24
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

@dadamu
dadamu merged commit e61f9e0 into main Jul 9, 2026
3 checks passed
@dadamu
dadamu deleted the feat/add-ipfs-cache branch July 9, 2026 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants