-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Workaround @ledgerhq/errors issue #15967 #8147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@nomicfoundation/hardhat-ledger": patch | ||
| --- | ||
|
|
||
| Workaround `@ledgerhq/errors` issue #15967 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @file This file exists to workaround an issue in `@ledgerhq/errors`, because | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * its ESM build is broken. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * See: https://github.com/LedgerHQ/ledger-live/issues/15967 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * While we can pin the version of `@ledgerhq/errors` that we use directly, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * `createRequire` to `require` it, instead of `import`ing it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Given that the rest of the packages can also `import` `@ledgerhq/errors`, we | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * need to `require` every ledger package to be safe, otherwise Node will fail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * if we mix `import` and `require` of the same package. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type * as LedgerErrorsT from "@ledgerhq/errors"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type * as EvmToolsLibIndexT from "@ledgerhq/evm-tools/lib/index"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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"]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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"]; | |
| 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"]; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,17 @@ | ||||||||||||||||||
| import type { LedgerHandler as LedgerHandlerT } from "../handler.js"; | ||||||||||||||||||
| import type { HookContext, NetworkHooks } from "hardhat/types/hooks"; | ||||||||||||||||||
| import type { ChainType, NetworkConnection } from "hardhat/types/network"; | ||||||||||||||||||
| import type { JsonRpcRequest, JsonRpcResponse } from "hardhat/types/providers"; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; | ||||||||||||||||||
| import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization"; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { LedgerHandler } from "../handler.js"; | ||||||||||||||||||
| import { isFailedJsonRpcResponse, isJsonRpcResponse } from "../rpc-helpers.js"; | ||||||||||||||||||
|
|
||||||||||||||||||
| // 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; | ||||||||||||||||||
|
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; | |
| 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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| import type Eth from "@ledgerhq/hw-app-eth"; | ||
| import type { LedgerEthTransactionResolution } from "@ledgerhq/hw-app-eth/lib/services/types.js"; | ||
| import type { EIP712Message } from "@ledgerhq/types-live"; | ||
| import type { | ||
| EIP712Message, | ||
| Eth, | ||
| LedgerEthTransactionResolution, | ||
| } from "../../src/internal/cjs-imports.js"; | ||
|
|
||
| import assert from "node:assert/strict"; | ||
|
|
||
|
|
@@ -84,7 +86,7 @@ export interface MockCallState { | |
|
|
||
| export function getEthMocked( | ||
| methodsConfig: MethodsConfig, | ||
| ): [typeof Eth.default, Map<string, MockCallState>] { | ||
| ): [typeof Eth, Map<string, MockCallState>] { | ||
| const calls = new Map<string, MockCallState>(); | ||
|
Comment on lines
87
to
90
|
||
|
|
||
| for (const method of [ | ||
|
|
@@ -269,7 +271,7 @@ export function getEthMocked( | |
|
|
||
| return this.#methodsConfig.signTransaction.result; | ||
| } | ||
| } as unknown as typeof Eth.default, | ||
| } as unknown as typeof Eth, | ||
| calls, | ||
| ]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,22 @@ | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * @file See the comment in `src/internal/cjs-imports.ts` | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * This is an equivalent file, but with test-only imports. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import type { TransportT as TransportNodeHidT } from "../../src/internal/cjs-imports.js"; | ||||||||||||||||||||||||||||
| import type TransportT from "@ledgerhq/hw-transport"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { createRequire } from "node:module"; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const require = createRequire(import.meta.url); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| type BaseTransportClass = (typeof TransportT)["default"]; | ||||||||||||||||||||||||||||
|
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"]; | |
| import { createRequire } from "node:module"; | |
| const require = createRequire(import.meta.url); | |
| type BaseTransportClass = (typeof import("@ledgerhq/hw-transport"))["default"]; |
Copilot
AI
Apr 15, 2026
There was a problem hiding this comment.
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.
| export { TransportNodeHidT }; | |
| export type { TransportNodeHidT }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,29 @@ | ||
| import type TransportNodeHid from "@ledgerhq/hw-transport-node-hid"; | ||
|
|
||
| import Transport from "@ledgerhq/hw-transport"; | ||
|
|
||
| /** | ||
| * Mock implementation of the `TransportNodeHid` class from `@ledgerhq/hw-transport-node-hid`. | ||
| * This mock initializes a new Transport instance and exposes only the create method, which is the sole method used by hardhat-ledger. | ||
| * This mock initializes a new base `Transport` instance (from `@ledgerhq/hw-transport`) and | ||
| * exposes only the create method, which is the sole method used by hardhat-ledger. | ||
| */ | ||
|
|
||
| import type { TransportNodeHidT } from "./tests-cjs-imports.js"; | ||
|
|
||
| import { BaseTransport } from "./tests-cjs-imports.js"; | ||
|
|
||
| export interface TransportMockState { | ||
| createCount: number; | ||
| } | ||
|
|
||
| export function getTransportNodeHidMock( | ||
| state?: TransportMockState, | ||
| ): typeof TransportNodeHid.default { | ||
| ): TransportNodeHidT { | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- this is a mock for testing purpose | ||
| const transportNodeHid = { | ||
| create: () => { | ||
| if (state !== undefined) { | ||
| state.createCount++; | ||
| } | ||
| return new Transport.default(); | ||
| return new BaseTransport(); | ||
| }, | ||
| } as unknown as typeof TransportNodeHid.default; | ||
| } as unknown as TransportNodeHidT; | ||
|
|
||
| return transportNodeHid; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,16 +1,9 @@ | ||||||
| import type Eth from "@ledgerhq/hw-app-eth"; | ||||||
| import type { Eth } from "../../src/internal/cjs-imports.js"; | ||||||
|
|
||||||
| import assert from "node:assert/strict"; | ||||||
| import path from "node:path"; | ||||||
| import { after, before, beforeEach, describe, it } from "node:test"; | ||||||
|
|
||||||
| import { | ||||||
| DisconnectedDevice, | ||||||
| DisconnectedDeviceDuringOperation, | ||||||
| LockedDeviceError, | ||||||
| TransportStatusError, | ||||||
| } from "@ledgerhq/errors"; | ||||||
| import { TransportError } from "@ledgerhq/hw-transport"; | ||||||
| import { | ||||||
| assertHardhatInvariant, | ||||||
| HardhatError, | ||||||
|
|
@@ -27,6 +20,13 @@ import { | |||||
| } from "@nomicfoundation/hardhat-utils/fs"; | ||||||
| import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex"; | ||||||
|
|
||||||
| import { | ||||||
| DisconnectedDevice, | ||||||
| DisconnectedDeviceDuringOperation, | ||||||
| LockedDeviceError, | ||||||
| TransportError, | ||||||
| TransportStatusError, | ||||||
| } from "../../src/internal/cjs-imports.js"; | ||||||
| import { LedgerHandler } from "../../src/internal/handler.js"; | ||||||
| import { createJsonRpcRequest } from "../helpers/create-json-rpc-request.js"; | ||||||
| import { mockedDisplayInfo } from "../helpers/display-info-mock.js"; | ||||||
|
|
@@ -107,7 +107,7 @@ const signature = | |||||
|
|
||||||
| describe("LedgerHandler", () => { | ||||||
| let ethereumMockedProvider: EthereumMockedProvider; | ||||||
| let eth: typeof Eth.default; | ||||||
| let eth: typeof Eth; | ||||||
|
||||||
| let eth: typeof Eth; | |
| let eth: Eth; |
There was a problem hiding this comment.
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”.