Skip to content

fix(core): load wallet & network images on React Native - #574

Merged
ignaciosantise merged 4 commits into
developfrom
fix/fetch-image-blob-arraybuffer
Jul 13, 2026
Merged

fix(core): load wallet & network images on React Native#574
ignaciosantise merged 4 commits into
developfrom
fix/fetch-image-blob-arraybuffer

Conversation

@ignaciosantise

Copy link
Copy Markdown
Collaborator

Problem

Wallet and network logos in the connect modal never load on React Native — the list shows only placeholder icons on both iOS and Android (wallet names load fine, since those come from plain JSON).

Root cause is in FetchUtil.fetchImage (packages/core/src/utils/FetchUtil.ts), which builds the image data URL with response.blob() + FileReader.readAsDataURL:

const blob = await response.blob();      // ← throws on RN for binary bodies
const reader = new FileReader();
reader.readAsDataURL(blob);

On React Native, fetch(...).blob() for a binary (image) response throws:

Error: Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported

RN's Blob implementation can't be constructed from an ArrayBuffer body, so the promise rejects, fetchImage returns undefined, and AssetUtil.getWalletImage falls back to the placeholder. This is the only .blob() usage in the codebase.

Observed with Expo SDK 56 / React Native 0.85.3 (New Architecture), @reown/appkit-*@2.0.5. The image fetch itself succeeds (HTTP 200, content-type: image/webp); only the blob() step fails.

Fix

Read the response as an ArrayBuffer and base64-encode it into the data URL, avoiding Blob/FileReader entirely:

const arrayBuffer = await response.arrayBuffer();
const contentType = response.headers.get('content-type') ?? 'image/png';
return `data:${contentType};base64,${FetchUtil._arrayBufferToBase64(arrayBuffer)}`;

The base64 encoder is dependency-free — React Native guarantees neither a global Buffer nor btoa, so relying on either would be fragile for a library.

Tests

Adds unit tests for fetchImage (ArrayBuffer → base64 data URL, default content-type, blob() not called, error → undefined). Full packages/core suite passes:

Tests:       17 passed, 17 total

Verification

Patched into a multichain AppKit + wagmi app on Expo SDK 56 / RN 0.85.3:

  • Android: wallet logos (Binance, MetaMask, SafePal, Trust Wallet, …) render; zero blob errors.
  • iOS: zero blob errors after the fix.

🤖 Generated with Claude Code

FetchUtil.fetchImage built its data URL with response.blob() + FileReader,
but on React Native fetch(...).blob() throws "Creating blobs from 'ArrayBuffer'
and 'ArrayBufferView' are not supported" for binary responses, so wallet and
network images silently failed to load. Read the bytes via response.arrayBuffer()
and base64-encode them into the data URL instead (dependency-free encoder, since
RN guarantees neither Buffer nor btoa).

Adds unit tests for fetchImage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c300bde

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@reown/appkit-react-native Patch
@reown/appkit-bitcoin-react-native Patch
@reown/appkit-coinbase-react-native Patch
@reown/appkit-common-react-native Patch
@reown/appkit-core-react-native Patch
@reown/appkit-ethers-react-native Patch
@reown/appkit-solana-react-native Patch
@reown/appkit-ui-react-native Patch
@reown/appkit-wagmi-react-native Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@claude

claude Bot commented Jul 6, 2026

Copy link
Copy Markdown

Claude finished @ignaciosantise's task in 2m 42s —— View job


Code Review

  • Gather context (read changed files)
  • Check for breaking changes — none found
  • Analyze issues
Found 1 issue(s)

Issue 1: fetchImage encodes error-response bodies as image data URLs

ID: fetchutil-fetchimage-ok-check-a3f1
File: packages/core/src/utils/FetchUtil.ts:89
Severity: MEDIUM
Category: correctness

Context:

  • Pattern: fetchImage calls response.arrayBuffer() without first checking response.ok. processResponse (used by get/post/etc.) does guard with response.ok; fetchImage is inconsistent.
  • Risk: For 4xx/5xx responses the error body is base64-encoded and returned as a data URL (e.g. data:application/json;base64,...) instead of undefined. The catch block never fires because arrayBuffer() itself succeeds on non-ok responses.
  • Impact: Callers receive a syntactically valid but semantically garbage data URL. The RN Image component attempts (and silently fails) to render it rather than falling back to the placeholder as intended.
  • Trigger: Any image URL returning a non-2xx status (CDN 404, rate-limit 429, etc.).

Recommendation:

const response = await fetch(url, { headers });
if (!response.ok) {
  return undefined;
}
const arrayBuffer = await response.arrayBuffer();

Fix this →

Comment thread packages/core/src/utils/FetchUtil.ts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ignaciosantise
ignaciosantise marked this pull request as ready for review July 13, 2026 17:45
Copilot AI review requested due to automatic review settings July 13, 2026 17:45
Match the release convention of bumping every published package on each
release, consistent with the other changeset on this branch and prior
releases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Fixes React Native wallet/network logo loading by replacing response.blob() + FileReader (which fails on RN for binary responses) with response.arrayBuffer() and a dependency-free base64 data URL encoder, plus adds unit tests and a changeset.

Changes:

  • Update FetchUtil.fetchImage to build data:<content-type>;base64,... from Response.arrayBuffer().
  • Add _arrayBufferToBase64 helper to avoid relying on Blob, FileReader, Buffer, or btoa.
  • Add Jest coverage for success, missing content-type, avoiding .blob(), and error cases; include a patch changeset.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/core/src/utils/FetchUtil.ts Switch image loading to ArrayBuffer→base64 data URLs for RN compatibility; add encoder helper.
packages/core/src/tests/utils/FetchUtil.test.ts Add unit tests validating the new ArrayBuffer-based implementation and error handling.
.changeset/fix-fetch-image-blob-arraybuffer.md Document the patch release and the RN-specific fix rationale.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/utils/FetchUtil.ts
@ignaciosantise

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown

Claude finished @ignaciosantise's task in 1m 21s —— View job


Code Review

🔄 Incremental Review: 2 issues remain / 1 newly flagged

  • Gather context
  • Read changed files
  • Check previous issue status
  • Analyze for new issues

Found 3 issue(s)

Issue 1: fetchImage still missing response.ok guard

ID: fetchutil-fetchimage-ok-check-a3f1
File: packages/core/src/utils/FetchUtil.ts:89
Severity: MEDIUM
Category: correctness

Context:

  • Pattern: fetchImage calls response.arrayBuffer() at line 89 with no prior response.ok check. The PR author acknowledged this would be fixed, but the guard was not added.
  • Risk: A 4xx/5xx response body (e.g. JSON error, HTML 404 page) is base64-encoded and returned as a valid-looking data URL like data:application/json;base64,.... The catch block at line 93 never fires because arrayBuffer() itself succeeds on non-ok responses.
  • Impact: RN's Image component silently fails to render the bogus data URL rather than falling back to the placeholder icon.
  • Trigger: Any CDN 404, rate-limited 429, or transient 5xx on a wallet/network image URL.

Recommendation:

const response = await fetch(url, { headers });
if (!response.ok) {
  return undefined;
}
const arrayBuffer = await response.arrayBuffer();

Fix this →


Issue 2: fetchImage tests mock missing ok: true — will break when guard is added

ID: fetchutil-test-mock-ok-missing-9e2c
File: packages/core/src/tests/utils/FetchUtil.test.ts:118
Severity: LOW
Category: test_coverage

Context:

  • Pattern: All three success-path fetchImage mocks omit ok: true. Once the response.ok guard from Issue 1 is added, response.ok evaluates to undefined (falsy) and every mock returns undefined instead of the data URL — three tests flip from pass to fail with no apparent reason.
  • Risk: Silent regression when the guard is applied.
  • Impact: Tests give false confidence before the guard; then fail confusingly after.
  • Trigger: Adding if (!response.ok) return undefined without updating the mocks.

Recommendation: Add ok: true to the three success mocks and a new failure-case test:

global.fetch = jest.fn().mockResolvedValue({
  ok: true,
  arrayBuffer: async () => bytes.buffer,
  headers: { get: () => 'image/png' }
}) as unknown as typeof fetch;

// new test
it('should return undefined for non-ok responses', async () => {
  global.fetch = jest.fn().mockResolvedValue({
    ok: false,
    arrayBuffer: async () => new Uint8Array([]).buffer,
    headers: { get: () => 'application/json' }
  }) as unknown as typeof fetch;
  const result = await fetchUtil.fetchImage('/getWalletImage/1');
  expect(result).toBeUndefined();
});

Issue 3: _arrayBufferToBase64 uses string concatenation in a hot loop

ID: fetchutil-arraybuffer-concat-perf-b71a
File: packages/core/src/utils/FetchUtil.ts:110
Severity: LOW
Category: performance

Context:

  • Pattern: base64 += chars[...] is called 4× per 3-byte chunk inside the loop (lines 110–113), allocating a new string on every iteration.
  • Risk: For typical wallet/network images (5–50 KB = ~6700–66700 iterations), the JS engine creates thousands of intermediate strings on the RN JS thread before GC reclaims them. Copilot flagged this in the review.
  • Impact: Extra GC pressure when the wallet list loads many images in parallel; more noticeable on lower-end Android devices.
  • Trigger: Any wallet list load with >~10 images.

Recommendation: Accumulate into an array and join once:

const parts: string[] = [];
// inside loop:
parts.push(chars[byte0 >> 2]!);
parts.push(chars[...]!);
// ...
return parts.join('');

Fix this →


Previously reported issue fetchutil-fetchimage-ok-check-a3f1 is still open — the response.ok guard was acknowledged by @ignaciosantise but not yet applied.

Comment thread packages/core/src/__tests__/utils/FetchUtil.test.ts
Comment thread packages/core/src/utils/FetchUtil.ts Outdated
- Return undefined for non-2xx responses so error bodies aren't
  base64-encoded into a bogus data URL that RN's Image silently fails to
  render instead of falling back to the placeholder.
- Accumulate base64 output into an array and join once to avoid
  per-iteration string allocations / GC pressure on the RN JS thread.
- Add ok: true to the fetchImage success mocks and a non-ok test case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ignaciosantise
ignaciosantise merged commit eaf99a7 into develop Jul 13, 2026
9 checks passed
@ignaciosantise
ignaciosantise deleted the fix/fetch-image-blob-arraybuffer branch July 13, 2026 18:47
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants