From d4519b060a9c8be69c90a4b412b9956de6c4bcdc Mon Sep 17 00:00:00 2001 From: Ezedike-Evan Date: Thu, 15 Oct 2026 13:48:06 +0100 Subject: [PATCH] Show Phantom's logo on the EVM list Phantom announces its icon with a leading newline, so the anchored data:image/https check rejected it and the modal fell back to a letter. Trim the value before checking; the allowlist itself is unchanged. --- apps/web/lib/wallets/evm.ts | 12 ++++++++++-- apps/web/lib/wallets/wallets.test.ts | 21 ++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/apps/web/lib/wallets/evm.ts b/apps/web/lib/wallets/evm.ts index 42c9e34..b969ca3 100644 --- a/apps/web/lib/wallets/evm.ts +++ b/apps/web/lib/wallets/evm.ts @@ -44,8 +44,16 @@ export function reduceAnnouncements( return next; } -const safeIcon = (icon: unknown): string | null => - typeof icon === "string" && /^(data:image\/|https:\/\/)/.test(icon) ? icon : null; +/** + * Trimmed first: Phantom announces its icon with a leading newline (`"\ndata:image/png;base64,..."`), + * which an anchored check rejects, so its logo fell back to a letter. The allowlist stays strict; + * only the whitespace around the value is ignored. + */ +export const safeIcon = (icon: unknown): string | null => { + if (typeof icon !== "string") return null; + const trimmed = icon.trim(); + return /^(data:image\/|https:\/\/)/.test(trimmed) ? trimmed : null; +}; export function evmEntries( announced: ReadonlyMap, diff --git a/apps/web/lib/wallets/wallets.test.ts b/apps/web/lib/wallets/wallets.test.ts index 13fe996..29ae95b 100644 --- a/apps/web/lib/wallets/wallets.test.ts +++ b/apps/web/lib/wallets/wallets.test.ts @@ -1,6 +1,13 @@ import type { Wallet, WalletAccount } from "@wallet-standard/base"; import { describe, expect, it } from "vitest"; -import { applyEvmEvent, connectEvm, evmEntries, parseChainId, reduceAnnouncements } from "./evm"; +import { + applyEvmEvent, + connectEvm, + evmEntries, + parseChainId, + reduceAnnouncements, + safeIcon, +} from "./evm"; import { isSolanaWallet, pickSolanaAccount, solanaEntries } from "./solana"; import { keepDetected, stellarEntries } from "./stellar"; import type { Connection, Eip1193Provider, WalletEntry } from "./types"; @@ -26,6 +33,18 @@ describe("EIP-6963", () => { m = reduceAnnouncements(m, { info: { rdns: "x", name: "X" } }); expect(m.size).toBe(0); }); + it("keeps an icon that has whitespace around it, as Phantom announces it", () => { + const m = reduceAnnouncements( + new Map(), + detail("app.phantom", "Phantom", "\ndata:image/png;base64,iVBORw0KGgo="), + ); + expect(evmEntries(m, null)[0]?.icon).toBe("data:image/png;base64,iVBORw0KGgo="); + expect(safeIcon(" https://x/y.png \n")).toBe("https://x/y.png"); + }); + it("still drops a script URL that is padded with whitespace", () => { + expect(safeIcon("\n javascript:alert(1)")).toBeNull(); + expect(safeIcon(42)).toBeNull(); + }); it("drops icons that are not data: images or https", () => { const m = reduceAnnouncements(new Map(), detail("evil", "Evil", "javascript:alert(1)")); expect(evmEntries(m, null)[0]?.icon).toBeNull();