Describe the bug
The Tempo webAuthn/tempoWallet/dangerous_secp256k1 connectors in @wagmi/core's tempo module lazily load the peer-installed accounts package via:
async function getAccountsModule() {
return await import('accounts').catch(() => {
throw new Error('dependency "accounts" not found')
})
}
Chaining .catch() directly on the return value of a dynamic import() assumes that value is always a spec-compliant native Promise. That's not guaranteed in every bundling environment — notably under Metro (React Native's bundler), particularly with eager/non-lazy bundling (?lazy=false, which some setups force to work around unrelated issues), the object returned by a transformed import() call reliably has .then() but does not have a .catch() method.
The result is a very misleading failure: instead of surfacing "dependency accounts not found" (or succeeding, since accounts is installed and resolvable), the app crashes with:
TypeError: undefined is not a function
thrown from evaluating import('accounts').catch itself — before the import even settles, and before the intended catch handler's custom error message ever has a chance to run. This makes the connector effectively unusable in this class of environment, and the actual error message is unhelpful for diagnosing why.
Confirmed via runtime introspection in the affected environment:
const importResult = import('accounts')
typeof importResult // "object"
typeof importResult.then // "function"
typeof importResult.catch // "undefined" <-- crashes here
Link to Reproduction
No hosted repro (this reproduces specifically under Metro/React Native bundling, which isn't something new.wagmi.sh can host). Minimal repro steps below reproduce it in any React Native + Expo + Metro project.
Steps To Reproduce
- In a React Native (Expo) app, install
wagmi, @wagmi/core, and accounts, and configure a Tempo connector, e.g.:
import { createConfig } from 'wagmi'
import { webAuthn } from 'wagmi/tempo'
export const config = createConfig({
chains: [tempoModerato],
connectors: [webAuthn({ ceremony: someCeremony })],
transports: { [tempoModerato.id]: http() },
})
- Run the app on a real device/simulator via Metro (
expo run:ios / expo run:android), not a web/Node target.
- On startup,
createConfig → createStore → connector setup() → getProvider() → getAccountsModule() throws TypeError: undefined is not a function instead of resolving the accounts module (or throwing the intended, readable error).
Affected package(s)
@wagmi/core
Package version(s)
@wagmi/core@3.4.4 through the current latest @wagmi/core@3.6.3 — the code at packages/core/src/tempo/Connectors.ts's getAccountsModule is unchanged in this respect as of PR #5165 (which touched the same function for an unrelated Turbopack issue, #5144, but didn't change the .catch() chaining).
Current behavior
Chaining .catch() directly on import('accounts') throws TypeError: undefined is not a function in bundling environments (e.g. Metro/React Native, especially eager/non-lazy bundling) where the value returned by a transformed dynamic import() has .then() but no .catch() method — masking both the success path and the intended "dependency not found" error message.
Expected behavior
getAccountsModule (and any other import(...).catch(...) call sites in the Tempo connectors) should not assume the dynamic import() return value is a fully spec-compliant Promise. Wrapping in try { return await import('accounts') } catch { throw new Error('dependency "accounts" not found') } instead of chaining .catch() fixes this — await only requires .then(), which is present in all the environments observed (including Metro), and behaves identically to the current code in standard Node/browser/Vite/webpack/Turbopack environments where import() does return a real Promise.
Anything else?
Environment where this was diagnosed: Expo (React Native) app bundled by Metro, both with the default lazy bundling and with eager (lazy=false) bundling forced via a custom rewriteRequestUrl — reproduces in both, and is not specific to any particular Expo SDK/React Native version tested (confirmed across two consecutive SDK majors). Not a TypeScript type-checking issue — types report the code as correct; this is a runtime-only failure specific to how import() gets transformed by non-Node/browser bundlers.
Describe the bug
The Tempo
webAuthn/tempoWallet/dangerous_secp256k1connectors in@wagmi/core'stempomodule lazily load the peer-installedaccountspackage via:Chaining
.catch()directly on the return value of a dynamicimport()assumes that value is always a spec-compliant nativePromise. That's not guaranteed in every bundling environment — notably under Metro (React Native's bundler), particularly with eager/non-lazy bundling (?lazy=false, which some setups force to work around unrelated issues), the object returned by a transformedimport()call reliably has.then()but does not have a.catch()method.The result is a very misleading failure: instead of surfacing "dependency accounts not found" (or succeeding, since
accountsis installed and resolvable), the app crashes with:thrown from evaluating
import('accounts').catchitself — before the import even settles, and before the intendedcatchhandler's custom error message ever has a chance to run. This makes the connector effectively unusable in this class of environment, and the actual error message is unhelpful for diagnosing why.Confirmed via runtime introspection in the affected environment:
Link to Reproduction
No hosted repro (this reproduces specifically under Metro/React Native bundling, which isn't something new.wagmi.sh can host). Minimal repro steps below reproduce it in any React Native + Expo + Metro project.
Steps To Reproduce
wagmi,@wagmi/core, andaccounts, and configure a Tempo connector, e.g.:expo run:ios/expo run:android), not a web/Node target.createConfig→createStore→ connectorsetup()→getProvider()→getAccountsModule()throwsTypeError: undefined is not a functioninstead of resolving theaccountsmodule (or throwing the intended, readable error).Affected package(s)
@wagmi/core
Package version(s)
@wagmi/core@3.4.4through the current latest@wagmi/core@3.6.3— the code atpackages/core/src/tempo/Connectors.ts'sgetAccountsModuleis unchanged in this respect as of PR #5165 (which touched the same function for an unrelated Turbopack issue, #5144, but didn't change the.catch()chaining).Current behavior
Chaining
.catch()directly onimport('accounts')throwsTypeError: undefined is not a functionin bundling environments (e.g. Metro/React Native, especially eager/non-lazy bundling) where the value returned by a transformed dynamicimport()has.then()but no.catch()method — masking both the success path and the intended "dependency not found" error message.Expected behavior
getAccountsModule(and any otherimport(...).catch(...)call sites in the Tempo connectors) should not assume the dynamicimport()return value is a fully spec-compliantPromise. Wrapping intry { return await import('accounts') } catch { throw new Error('dependency "accounts" not found') }instead of chaining.catch()fixes this —awaitonly requires.then(), which is present in all the environments observed (including Metro), and behaves identically to the current code in standard Node/browser/Vite/webpack/Turbopack environments whereimport()does return a realPromise.Anything else?
Environment where this was diagnosed: Expo (React Native) app bundled by Metro, both with the default lazy bundling and with eager (
lazy=false) bundling forced via a customrewriteRequestUrl— reproduces in both, and is not specific to any particular Expo SDK/React Native version tested (confirmed across two consecutive SDK majors). Not a TypeScript type-checking issue — types report the code as correct; this is a runtime-only failure specific to howimport()gets transformed by non-Node/browser bundlers.