Skip to content

Workaround @ledgerhq/errors issue #15967#8147

Merged
alcuadrado merged 3 commits intomainfrom
ledger-workaround
Apr 16, 2026
Merged

Workaround @ledgerhq/errors issue #15967#8147
alcuadrado merged 3 commits intomainfrom
ledger-workaround

Conversation

@alcuadrado
Copy link
Copy Markdown
Member

@alcuadrado alcuadrado commented Apr 15, 2026

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 15, 2026

🦋 Changeset detected

Latest commit: f632aea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@nomicfoundation/hardhat-ledger Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@alcuadrado alcuadrado added no docs needed This PR doesn't require links to documentation no peer bump needed labels Apr 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a CJS-import shim to work around @ledgerhq/errors ESM breakage (LedgerHQ/ledger-live#15967) and reduces Ledger package load impact by switching to lazy-loading where appropriate.

Changes:

  • Add src/internal/cjs-imports.ts (and a test-only equivalent) to load LedgerHQ dependencies via createRequire.
  • Update handler implementation and tests to consume LedgerHQ types/values from the new CJS-import shim.
  • Lazy-load LedgerHandler in the network hook handler to avoid Ledger package load overhead when unused.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/hardhat-ledger/src/internal/cjs-imports.ts New CJS import shim for LedgerHQ deps to avoid broken ESM builds.
packages/hardhat-ledger/src/internal/handler.ts Switch LedgerHQ imports/types to the shim and adjust constructor/instance typing.
packages/hardhat-ledger/src/internal/hook-handlers/network.ts Lazy-load LedgerHandler to avoid loading Ledger deps unless needed.
packages/hardhat-ledger/test/internal/handler.ts Update tests to import LedgerHQ error/types via the shim.
packages/hardhat-ledger/test/helpers/eth-mocked.ts Update Eth mock typings to align with the shim.
packages/hardhat-ledger/test/helpers/tests-cjs-imports.ts New test-only require-based import shim for mocks.
packages/hardhat-ledger/test/helpers/transport-node-hid-mock.ts Update transport mock to use the test shim and base transport.
.changeset/gold-chefs-read.md Patch changeset for the workaround release.

Comment on lines 87 to 90
export function getEthMocked(
methodsConfig: MethodsConfig,
): [typeof Eth.default, Map<string, MockCallState>] {
): [typeof Eth, Map<string, MockCallState>] {
const calls = new Map<string, MockCallState>();
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eth is imported with import type, but the function return type and cast use typeof Eth (a value type query), which won’t typecheck. If you want the constructor type, use the imported type directly (e.g. [Eth, ...] / as unknown as Eth), or switch to a value import.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +35
import type HwAppEthT from "@ledgerhq/hw-app-eth";
import type HwTransportNodeHidT from "@ledgerhq/hw-transport-node-hid";
import type { EIP712Message } from "@ledgerhq/types-live";

import { createRequire } from "node:module";

const require = createRequire(import.meta.url);

// Note: `typeof HwAppEthT` / `typeof HwTransportNodeHidT` are the module
// namespace types. Indexing them with ["default"] narrows to the exported
// class constructor, while `typeof HwAppEthT.default` collapses back to the
// namespace type.
type EthClass = (typeof HwAppEthT)["default"];
type LedgerService = (typeof HwAppEthT)["ledgerService"];
type TransportNodeHidClass = (typeof HwTransportNodeHidT)["default"];
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HwAppEthT and HwTransportNodeHidT are imported with import type ... from ..., but then used in typeof HwAppEthT / typeof HwTransportNodeHidT type queries. typeof requires a value symbol, so this will fail to typecheck. Use a module type query instead (e.g. typeof import("@ledgerhq/hw-app-eth")) or switch to a namespace type import (import type * as HwAppEthT from ...) so you can safely index the module exports.

Suggested change
import type HwAppEthT from "@ledgerhq/hw-app-eth";
import type HwTransportNodeHidT from "@ledgerhq/hw-transport-node-hid";
import type { EIP712Message } from "@ledgerhq/types-live";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
// Note: `typeof HwAppEthT` / `typeof HwTransportNodeHidT` are the module
// namespace types. Indexing them with ["default"] narrows to the exported
// class constructor, while `typeof HwAppEthT.default` collapses back to the
// namespace type.
type EthClass = (typeof HwAppEthT)["default"];
type LedgerService = (typeof HwAppEthT)["ledgerService"];
type TransportNodeHidClass = (typeof HwTransportNodeHidT)["default"];
import type { EIP712Message } from "@ledgerhq/types-live";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
// Note: module type queries give us the module namespace types. Indexing them
// with ["default"] narrows to the exported class constructor.
type EthClass = (typeof import("@ledgerhq/hw-app-eth"))["default"];
type LedgerService = (typeof import("@ledgerhq/hw-app-eth"))["ledgerService"];
type TransportNodeHidClass =
(typeof import("@ledgerhq/hw-transport-node-hid"))["default"];

Copilot uses AI. Check for mistakes.
Comment thread packages/hardhat-ledger/src/internal/cjs-imports.ts Outdated
Comment on lines +8 to +14
import type TransportT from "@ledgerhq/hw-transport";

import { createRequire } from "node:module";

const require = createRequire(import.meta.url);

type BaseTransportClass = (typeof TransportT)["default"];
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TransportT is imported with import type, but then referenced in a typeof TransportT type query. This will fail to typecheck because typeof requires a value symbol. Consider replacing this with a module type query like type BaseTransportClass = (typeof import("@ledgerhq/hw-transport"))["default"]; (and remove the import type TransportT ...).

Suggested change
import type TransportT from "@ledgerhq/hw-transport";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
type BaseTransportClass = (typeof TransportT)["default"];
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
type BaseTransportClass = (typeof import("@ledgerhq/hw-transport"))["default"];

Copilot uses AI. Check for mistakes.
describe("LedgerHandler", () => {
let ethereumMockedProvider: EthereumMockedProvider;
let eth: typeof Eth.default;
let eth: typeof Eth;
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eth is imported with import type, but this line uses typeof Eth (a value type query). That combination won’t typecheck. Either import Eth as a value, or keep it type-only and use the imported type directly (e.g. let eth: Eth).

Suggested change
let eth: typeof Eth;
let eth: Eth;

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 15, 2026 13:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment on lines +11 to +13
// The ledger packages have been problematic in the past, leading to errors
// and slowdowns, even when not being used, so we lazy load them now.
let LedgerHandler: typeof LedgerHandlerT | undefined;
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LedgerHandlerT is imported with import type, so it’s a type-only symbol. Using typeof LedgerHandlerT here will not type-check (TypeScript typeof in a type position requires a value). Define a constructor type via typeof import("../handler.js").LedgerHandler (or a local type LedgerHandlerConstructor = typeof import("../handler.js")["LedgerHandler"]) and use that for the lazy-loaded variable instead.

Suggested change
// The ledger packages have been problematic in the past, leading to errors
// and slowdowns, even when not being used, so we lazy load them now.
let LedgerHandler: typeof LedgerHandlerT | undefined;
type LedgerHandlerConstructor = typeof import("../handler.js")["LedgerHandler"];
// The ledger packages have been problematic in the past, leading to errors
// and slowdowns, even when not being used, so we lazy load them now.
let LedgerHandler: LedgerHandlerConstructor | undefined;

Copilot uses AI. Check for mistakes.
const BaseTransport: BaseTransportClass = hwTransport.default;

export { BaseTransport };
export { TransportNodeHidT };
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a type-only import, but it’s being re-exported as a value (export { TransportNodeHidT }). This should be a type export (export type { TransportNodeHidT }) or the file won’t compile under TypeScript’s type-only import rules.

Suggested change
export { TransportNodeHidT };
export type { TransportNodeHidT };

Copilot uses AI. Check for mistakes.
* other ledger packages have different version ranges, and we can end up with
* multiple installations and still hit the error.
*
* The workaround consists on importing the CJS version of the package, by using
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammar: “consists on importing” should be “consists of importing”.

Suggested change
* The workaround consists on importing the CJS version of the package, by using
* The workaround consists of importing the CJS version of the package, by using

Copilot uses AI. Check for mistakes.
@alcuadrado
Copy link
Copy Markdown
Member Author

Let's ignore copilot here. This workaround is ugly, but we need it

@alcuadrado alcuadrado added this pull request to the merge queue Apr 16, 2026
Merged via the queue into main with commit 1ba2d0e Apr 16, 2026
40 checks passed
@alcuadrado alcuadrado deleted the ledger-workaround branch April 16, 2026 16:43
@github-actions github-actions Bot mentioned this pull request Apr 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no docs needed This PR doesn't require links to documentation no peer bump needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants