Describe the bug
Provider.create()'s EIP-6963 wallet-announcement code guards on window, CustomEvent, and crypto.randomUUID being defined before calling mipd's announceProvider, but doesn't check for window.dispatchEvent/window.addEventListener themselves:
https://github.com/tempoxyz/accounts/blob/main/src/core/Provider.ts#L1821-L1841
if (
typeof window !== 'undefined' &&
typeof CustomEvent !== 'undefined' &&
typeof crypto.randomUUID === 'function'
) {
const rdns = adapter.rdns ?? `com.${(adapter.name ?? 'Injected Wallet').toLowerCase().replace(/\s+/g, '')}`
if (!announced.has(rdns)) {
announced.add(rdns)
announceProvider({
info: { icon: adapter.icon ?? defaultIcon, name: adapter.name ?? 'Injected Wallet', rdns, uuid: crypto.randomUUID() },
provider,
} as never)
}
}
mipd's announceProvider unconditionally calls window.dispatchEvent(...) and window.addEventListener(...):
https://github.com/wevm/mipd/blob/main/src/utils.ts
In environments that provide a partial window-like global (e.g. React Native, where some setups alias global.window = global and polyfill CustomEvent/crypto.randomUUID for other unrelated reasons, but have no DOM event dispatching), all three guard conditions pass, announceProvider gets called, and window.dispatchEvent throws:
TypeError: undefined is not a function
This crashes Provider.create() entirely — not just the EIP-6963 announcement — since the throw is unhandled at the call site, taking down whatever adapter (webAuthn, tempoWallet, dangerous_secp256k1, etc.) was being set up.
Confirmed via runtime introspection in the affected environment:
typeof window // "object"
typeof window.dispatchEvent // "undefined" <-- crashes here
typeof window.addEventListener // "undefined"
typeof CustomEvent // "function" (polyfilled)
typeof crypto.randomUUID // "function" (polyfilled)
Reproduction
- In any environment where
window exists (possibly via global.window = global or similar) and CustomEvent/crypto.randomUUID are polyfilled, but window.dispatchEvent/addEventListener are not implemented (this is a natural state for React Native — there is no DOM, so nothing provides real event dispatching even when some other library or shim sets global.window).
- Call
Provider.create({ adapter, ... }) for any adapter.
Provider.create throws TypeError: undefined is not a function from inside announceProvider/window.dispatchEvent, rather than either skipping the (browser-only, EIP-6963-specific) announcement or throwing a clear error.
Expected behavior
The guard before calling announceProvider should also check that window.dispatchEvent (and ideally addEventListener) are actually functions, e.g.:
if (
typeof window !== 'undefined' &&
typeof window.dispatchEvent === 'function' &&
typeof CustomEvent !== 'undefined' &&
typeof crypto.randomUUID === 'function'
) { ... }
EIP-6963 injected-provider discovery is a browser-extension-wallet concept with no equivalent in non-browser environments; skipping the announcement there (rather than crashing) seems like the correct behavior regardless of which adapter is in use — this isn't specific to any one connector/adapter, since the check lives in the shared Provider.create() path.
Environment
accounts@0.15.2 (current latest) — confirmed the same guard is unchanged on main as of this issue.
- Reached via a React Native (Expo) app where
window is aliased to globalThis (a common RN pattern) and CustomEvent/crypto.randomUUID are polyfilled for unrelated reasons (WebAuthn/crypto needs), but no DOM event APIs exist.
Describe the bug
Provider.create()'s EIP-6963 wallet-announcement code guards onwindow,CustomEvent, andcrypto.randomUUIDbeing defined before callingmipd'sannounceProvider, but doesn't check forwindow.dispatchEvent/window.addEventListenerthemselves:https://github.com/tempoxyz/accounts/blob/main/src/core/Provider.ts#L1821-L1841
mipd'sannounceProviderunconditionally callswindow.dispatchEvent(...)andwindow.addEventListener(...):https://github.com/wevm/mipd/blob/main/src/utils.ts
In environments that provide a partial
window-like global (e.g. React Native, where some setups aliasglobal.window = globaland polyfillCustomEvent/crypto.randomUUIDfor other unrelated reasons, but have no DOM event dispatching), all three guard conditions pass,announceProvidergets called, andwindow.dispatchEventthrows:This crashes
Provider.create()entirely — not just the EIP-6963 announcement — since the throw is unhandled at the call site, taking down whatever adapter (webAuthn,tempoWallet,dangerous_secp256k1, etc.) was being set up.Confirmed via runtime introspection in the affected environment:
Reproduction
windowexists (possibly viaglobal.window = globalor similar) andCustomEvent/crypto.randomUUIDare polyfilled, butwindow.dispatchEvent/addEventListenerare not implemented (this is a natural state for React Native — there is no DOM, so nothing provides real event dispatching even when some other library or shim setsglobal.window).Provider.create({ adapter, ... })for any adapter.Provider.createthrowsTypeError: undefined is not a functionfrom insideannounceProvider/window.dispatchEvent, rather than either skipping the (browser-only, EIP-6963-specific) announcement or throwing a clear error.Expected behavior
The guard before calling
announceProvidershould also check thatwindow.dispatchEvent(and ideallyaddEventListener) are actually functions, e.g.:EIP-6963 injected-provider discovery is a browser-extension-wallet concept with no equivalent in non-browser environments; skipping the announcement there (rather than crashing) seems like the correct behavior regardless of which adapter is in use — this isn't specific to any one connector/adapter, since the check lives in the shared
Provider.create()path.Environment
accounts@0.15.2(current latest) — confirmed the same guard is unchanged onmainas of this issue.windowis aliased toglobalThis(a common RN pattern) andCustomEvent/crypto.randomUUIDare polyfilled for unrelated reasons (WebAuthn/crypto needs), but no DOM event APIs exist.