First: read what the loader tells you
Two places carry the diagnosis:- Your
onErrorprop receives aManifestErrorwith areason. - The console — on every successful load the loader prints a provenance line:
[layerswap/widget-js] widget <version> (<sha>, built <timestamp>). If you don’t see it, the widget never finished loading.
By ManifestError reason
There is no other reason value — if you’re matching on error strings, these five are the complete set.
Content Security Policy
If your app ships a CSP, the widget needs the CDN origin inscript-src and connect-src, the Layerswap API in connect-src, and 'unsafe-inline' in style-src (styles inject at runtime).
The production origin is https://cdn.layerswap.io. Copy the complete policy from Widget delivery and security.
Widget renders an alert instead of the UI
“Only one Layerswap widget can be live per page” — you mounted two (any combination of the Swap Widget and Deposit Widget). Render one at a time. In the JavaScript package the secondmountWidget call throws instead.
It loads, then my dev server breaks on hot reload
Remounting is safe and is the recovery path for transient failures — each mount fetches fresh. But mixing@layerswap/widget-react and @layerswap/widget-js on one page throws at init; use one loader per page.
The same browser wallet appears twice
wagmi v2 discovers EIP-6963 wallets automatically. If your host config also declares a genericinjected() connector, reconnect can restore two connectors for the same extension. Remove the generic connector and rely on discovery, or set multiInjectedProviderDiscovery: false when you intentionally own discovery.
Changes cause avoidable provider rebuilds
Keepconfig, callbacks, walletDefaults, and walletProvidersConfig stable by hoisting or memoizing them. New object identities can rebuild provider lists and trigger additional renders; they do not normally remount the loader.
Still stuck
Grab the console provenance line (or its absence), theManifestError.reason, and your CSP header, and ask in the developer community.