Skip to content

Fix static-analyzer backend loading and refactor Ledger entrypoints for future HWW support#68

Merged
landabaso merged 6 commits intomainfrom
bundler-fix
Mar 30, 2026
Merged

Fix static-analyzer backend loading and refactor Ledger entrypoints for future HWW support#68
landabaso merged 6 commits intomainfrom
bundler-fix

Conversation

@landabaso
Copy link
Copy Markdown
Member

Some static analyzers used by bundlers followed the code too aggressively and assumed some backend-specific paths could be needed, even when those require() branches would never be hit at runtime. That could still make users hit missing peer dependency problems even when they only wanted one backend (bitcoinjs or scure).

This started from #66 and from the discussion in #67.

While looking at that issue, I decided to also fix the Ledger side even if the original report did not complain about it directly. The reason is that the same kind of static-analysis problem could also appear later when users imported signer helpers.

In addition, I also fixed the complementary problem in the other direction: not only scure users could accidentally pull bitcoinjs-related paths, but bitcoinjs users could also still reach scure/noble-related paths in some flows. This PR tries to clean both directions.

The solution proposed in #67 moved in the right direction, but it also made the public API break in places where I would rather keep compatibility, especially for @bitcoinerlab/descriptors, which is the package most users actually depend on.

Main idea

The code now tries to separate three things much more clearly:

  • the core implementation
  • the package APIs users import directly: @bitcoinerlab/descriptors/@bitcoinerlab/descriptors-scure
  • the compatibility layer we still keep for old 3.x users

This removes a lot of hidden backend coupling, reduces the need for runtime require() tricks and makes the code easier to evolve later.

What this PR covers

1. Fix the original static-analysis / bundler issue without breaking @bitcoinerlab/descriptors

  • DescriptorsFactory in core does not call any adapter's code implicitly
  • when a conversion is needed, the code now uses the currently selected BitcoinLib backend instead of trying to load helper modules on the fly
  • backend-specific require() chains were removed and replaced with clearer explicit entrypoints or internal wiring

This work was applied not only to the exact path reported in #66, but also to the Ledger-related paths and to the reverse case where bitcoinjs users could still end up pulling scure/noble-related code.

At the same time, @bitcoinerlab/descriptors still keeps the old compatibility behavior for existing users.

So the main public contract stays working, while the internals become much cleaner.

2. Refactor Ledger around explicit entrypoints

This is a big part of the PR.

Ledger is no longer treated as a loose bag of helpers mixed with internal policy logic. It is now separated into:

  • public Ledger entrypoints
  • client-related internals
  • policy-related internals

This matters not only for static analyzers and bundlers, but also for the future architecture.

I put a lot of emphasis here because I think this is the right preparation work for future hardware wallet support. Ledger should be one explicit path, with a clean public surface and separate internal logic. That way, when we later add support for other HWWs, we do not need to untangle public and internal concerns again.

In other words: this PR is not just a bundler fix. It also prepares the project structure for the next stage of hardware wallet support.

3. Prefer architecture over lazy loading

The goal here was not just "make static analyzers / bundlers stop failing".

The goal was to reduce the reasons why the code needed lazy loading in the first place.

So instead of keeping more require() and hidden conditional loading in core, this PR moves the code toward:

  • explicit backend entrypoints
  • explicit Ledger entrypoints
  • smaller public barrels
  • better internal separation

There is still one local require() kept on purpose in the deprecated root compat path of @bitcoinerlab/descriptors, but that is now only there for backwards compatibility and to avoid forcing the Ledger peer dependency on non-Ledger users of the root package.

4. Put real effort into backwards compatibility

This PR does not treat compatibility as an afterthought.

I intentionally kept @bitcoinerlab/descriptors working for old users even while moving core and the newer package shapes to a cleaner API.

That means:

  • old DescriptorsFactory(...) usage is still supported on @bitcoinerlab/descriptors
  • old root Ledger access is still supported on @bitcoinerlab/descriptors
  • old ledgerManager.ecc flows are still normalized in the compat layer

But all of that compatibility code is now clearly marked as temporary and deprecated, so the next major version can remove it much more easily.

Deprecations introduced in this PR

These deprecations are mainly about the preset package @bitcoinerlab/descriptors.

Deprecated there:

  • root DescriptorsFactory() / DescriptorsFactory(ecc) / DescriptorsFactory(...)
  • root Ledger exports such as:
    • ledger
    • keyExpressionLedger
    • signers.signLedger
    • signers.signInputLedger
    • scriptExpressions.*Ledger
  • root LedgerState and LedgerManager types

New code should instead use:

  • the preset top-level exports directly (Output, signers, scriptExpressions, etc.)
  • @bitcoinerlab/descriptors/ledger for Ledger
  • @bitcoinerlab/descriptors-scure/ledger for scure + Ledger

Also, the advanced custom ecc initialization path on @bitcoinerlab/descriptors is now documented as temporary. It is kept only for 3.x compatibility and is planned to stop being a public preset-package API in the next major release.

DescriptorsFactory itself is not deprecated in core, because core still needs it internally. But it is no longer presented as a normal end-user API.

Notes about package compatibility

This PR puts most of the compatibility effort into @bitcoinerlab/descriptors, because that is the main user-facing contract we want to preserve.

At the same time, @bitcoinerlab/descriptors-core and @bitcoinerlab/descriptors-scure are moved to the cleaner, newer API shape sooner. That keeps the long-term architecture cleaner instead of carrying legacy behavior everywhere.

Detailed changes

  • core DescriptorsFactory now accepts only an explicit BitcoinLib
  • backend-specific hidden loading paths were removed from core hot paths
  • applyPR2137 was moved out of src/adapters/ into src/bitcoinjsHdPatch.ts, which matches its real role better
  • Ledger internals were split into:
    • src/ledger/client.ts
    • src/ledger/policies.ts
    • src/ledger/index.ts as the public Ledger barrel
  • public Ledger entrypoints now expose a cleaner API, while internal policy helpers stay internal
  • package wrappers were moved to TypeScript sources generated on build
  • generated docs now expose Ledger as its own module, while keeping the main API at the top level
  • tests were updated to initialize the active backend explicitly where needed and to use the parent Ledger barrel in the public cases

Closes #66.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

descriptors-core: static require('bitcoinjs-lib') breaks bundlers in non-bitcoinjs environments

1 participant