Conversation
Make the crate compile for the wasm32-unknown-unknown target so that uniffi-bindgen-react-native's `wasm2` flavor can generate browser and Node bindings from it, the same way it generates the React Native ones. Native targets are unchanged. On wasm32: - The blocking esplora, electrum, kyoto and rusqlite dependencies move to a `cfg(not(target_arch = "wasm32"))` table. The wasm32 table adds the async esplora client, `getrandom/js`, uniffi's single-threaded mode, `uniffi-runtime-wasm`, and the wasm-bindgen glue needed for a `setTimeout` based sleeper. - `resolver = "2"`, because resolver 1 unifies features across target tables: it pulled rusqlite into the wasm build and the reqwest error variant into the native one. - `EsploraClient` wraps `esplora_client::AsyncClient`. Every method keeps its name, arguments and result but is `async`. The constructor returns a `Result` because building the reqwest client can fail. Retry backoff sleeps through the host's global `setTimeout`, so it works in browsers, workers and Node. - `Persister` offers only `custom`; the SQLite constructors and the pre-v1 migration helper are native only. - `EsploraError` gains a `Reqwest` variant. It exists on every target so the generated API stays the same, but native never produces it. - The electrum and kyoto modules are not compiled. CI gets a job that runs `cargo check --locked` and clippy for the wasm32 target. Ubuntu's clang carries the wasm32 backend that secp256k1-sys needs; Apple's clang does not, so local builds on macOS must point `CC_wasm32_unknown_unknown` at an LLVM clang.
Ugarba202
approved these changes
Sep 13, 2026
Ugarba202
left a comment
There was a problem hiding this comment.
This is a massive step forward for the JS/web ecosystem!
I pulled the branch locally and ran through some checks:
- Dependency Isolation: Verified the
cfgfeature gating inCargo.toml. Theresolver = "2"addition does a great job at isolating the features. - Wasm Compilation: Ran
cargo clippy --target wasm32-unknown-unknownand it compiled flawlessly without any feature leaks. - Native Integrity: Ran the standard
cargo testsuite on the Native targets just to be extra safe, and everything passed perfectly. No regressions for existing mobile/native users.
The async translation for the Esplora client and the customsetTimeoutsleeper wrapper for the retry backoffs look really solid. Exposing theReqwesterror variant globally to keep the UniFFI API shape identical across platforms is also a smart tradeoff.
Everything looks great on my end. Thanks for putting this together!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Make the crate compile for
wasm32-unknown-unknown, so that a UniFFI binding generator such asuniffi-bindgen-react-native(wasm2flavor) can produce browser and Node bindings from the same API the native bindings use. Native targets are unchanged.How the wasm package gets built and published is still open. That discussion lives in this gist: https://gist.github.com/bennyhodl/e290b7de4ee8c746ee28f19695ab7bd2. Whatever is decided there, the
cfgsplits in this PR are required for any wasm build, becauserusqlite,minreq,bdk_electrumandbdk_kyotohave no wasm32 support and cannot be replaced from a downstream package.What changes:
Cargo.toml:rusqlite, the blocking Esplora client,bdk_electrumandbdk_kyotomove to acfg(not(target_arch = "wasm32"))table. A wasm32 table adds the async Esplora client,getrandom/js, UniFFI'swasm-unstable-single-threadedfeature,uniffi-runtime-wasm, andwasm-bindgen/js-sys/futures-channelfor the sleeper.resolver = "2"stops features from unifying across target tables.esplora.rs: on wasm32EsploraClientwrapsAsyncClient. Every method keeps its name, arguments and result but isasync. The constructor returns aResultbecause building the reqwest client can fail. Retry backoff sleeps through the host's globalsetTimeout, so it works in browsers, workers and Node. The request/update helpers are shared by both implementations.store.rs:Persister::customis available on every target. The SQLite constructors and the pre-v1 migration helper are native only.error.rs:EsploraErrorgains aReqwestvariant. It exists on every target so the generated API is identical, but native never produces it. Electrum, SQLite, migration and Kyoto conversions are native only.lib.rs: theelectrumandkyotomodules are not compiled on wasm32. Anextern crate uniffi_runtime_wasmline keeps the runtime exports from being dropped by the linker.wasmjob runscargo check --lockedandcargo clippy --lockedforwasm32-unknown-unknown.Notes to the reviewers
wasm-unstable-single-threadedis not a threading model choice. It only removes theSend + Syncbounds UniFFI requires on native, where foreign threads call in.wasm32-unknown-unknownhas no threads. ThePersistencetrait keeps itsSend + Syncbounds and compiles as is.secp256k1-sysneeds a C compiler with a wasm32 backend. Ubuntu's clang has one, so CI setsCC_wasm32_unknown_unknown=clang. Apple's clang does not, so on macOS point that variable at an LLVM clang (for example Homebrew'sllvm).cargo tree: native has noreqwestorwasm-bindgen, and wasm32 has norusqliteorminreq.proxyargument on the wasm32EsploraClientconstructor is kept for API parity and does nothing on that target.CHANGELOG.mdedit in this PR, following the release flow where entries come from thechangelog:label.Documentation
AsyncClient: https://docs.rs/esplora-client/0.12.3/esplora_client/async/struct.AsyncClient.htmlSleepertrait: https://docs.rs/esplora-client/0.12.3/esplora_client/async/trait.Sleeper.htmlEsploraAsyncExt: https://docs.rs/bdk_esplora/0.22.2/bdk_esplora/trait.EsploraAsyncExt.htmluniffi-runtime-wasm: https://docs.rs/uniffi-runtime-wasm/0.31.0-5/uniffi_runtime_wasm/wasm2reference: https://github.com/jhugman/uniffi-bindgen-react-native/tree/main/docs/src/reference/wasm2Changelog
changelog: addedChecklists
All Submissions:
cargo fmtandcargo clippybefore committingchangelog:*label (needs a maintainer:changelog: added)New Features: