fix: normalize accountsChanged payload in FarcasterProvider - #50
Conversation
Guard the host event boundary so malformed payloads (plain string, object with .accounts, or other non-array) are coerced to string[] before forwarding. Drops and warns on unrecognisable shapes to prevent Wagmi's onAccountsChanged from calling .map() on a non-array.
|
events were being forwarded raw to Wagmi's onAccountsChanged handler, which Root cause: FarcasterProvider blindly re-emitted all EIP-1193 events Fix: Added normalizeAccountsChangedPayload at the host event boundary |
Juminstock
left a comment
There was a problem hiding this comment.
Good direction overall. Two things that aren't coverable as inline comments:
Request path gap (line 77-79): handleWalletConnect casts the eth_requestAccounts response directly to Address[] and calls .map() on it. If Farcaster returns { accounts: [...] } from that method (the same non-standard shape this PR guards on the event side), .map() throws at runtime. Consider running that response through normalizeAccountsChangedPayload, or extracting a shared helper so both the event and request paths are protected.
Missing unit tests: normalizeAccountsChangedPayload is the core logic of this fix and has three branches, but no dedicated unit tests cover it. Each shape deserves its own test: plain string, valid string[], non-string array elements, { accounts: string[] }, { accounts: 'single-string' }, and an unrecognized shape. End-to-end forwarder tests won't catch element-type issues.
| } | ||
|
|
||
| function normalizeAccountsChangedPayload(payload: unknown): string[] | undefined { | ||
| if (Array.isArray(payload)) return payload as string[] |
There was a problem hiding this comment.
Array.isArray passes for any array regardless of element type, so a payload like [42, null] or [{ address: '0x...' }] clears this check and gets returned typed as string[]. Downstream callers that assume strings (.toLowerCase(), isAddress()) will throw at runtime with no warning.
Suggested fix:
if (Array.isArray(payload)) return payload.filter((v): v is string => typeof v === 'string')Apply the same .filter to line 123 (the .accounts branch).
| if (typeof payload === 'string') return [payload] | ||
| if (Array.isArray((payload as { accounts?: unknown }).accounts)) | ||
| return (payload as { accounts: string[] }).accounts | ||
| return undefined |
There was a problem hiding this comment.
{ accounts: '0xabc' } (a single address as a string, not an array) falls through all three branches and returns undefined here, silently dropping the event. Array.isArray(payload) is false (it's an object), typeof payload === 'string' is false, and Array.isArray(payload.accounts) is false because '0xabc' is not an array.
Suggested fix, add this branch before return undefined:
if (typeof (payload as { accounts?: unknown }).accounts === 'string')
return [(payload as { accounts: string }).accounts]
Guard the host event boundary so malformed payloads (plain
string, object with .accounts, or other non-array) are coerced to
string[] before forwarding. Drops and warns on unrecognisable shapes to
prevent Wagmi's onAccountsChanged from calling .map() on a non-array.