hardhat 2.15.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/builtin-tasks/compile.js +25 -29
  2. package/builtin-tasks/compile.js.map +1 -1
  3. package/builtin-tasks/node.js +1 -2
  4. package/builtin-tasks/node.js.map +1 -1
  5. package/console.sol +3 -4
  6. package/internal/cli/analytics.d.ts +6 -6
  7. package/internal/cli/analytics.d.ts.map +1 -1
  8. package/internal/cli/analytics.js +43 -115
  9. package/internal/cli/analytics.js.map +1 -1
  10. package/internal/cli/cli.js +6 -4
  11. package/internal/cli/cli.js.map +1 -1
  12. package/internal/cli/project-creation.d.ts.map +1 -1
  13. package/internal/cli/project-creation.js +2 -1
  14. package/internal/cli/project-creation.js.map +1 -1
  15. package/internal/context.d.ts +3 -3
  16. package/internal/context.d.ts.map +1 -1
  17. package/internal/context.js +2 -2
  18. package/internal/context.js.map +1 -1
  19. package/internal/core/config/config-env.d.ts +16 -1
  20. package/internal/core/config/config-env.d.ts.map +1 -1
  21. package/internal/core/config/config-env.js +21 -3
  22. package/internal/core/config/config-env.js.map +1 -1
  23. package/internal/core/errors-list.d.ts +7 -0
  24. package/internal/core/errors-list.d.ts.map +1 -1
  25. package/internal/core/errors-list.js +8 -0
  26. package/internal/core/errors-list.js.map +1 -1
  27. package/internal/core/providers/construction.d.ts +3 -3
  28. package/internal/core/providers/construction.d.ts.map +1 -1
  29. package/internal/core/providers/construction.js +13 -5
  30. package/internal/core/providers/construction.js.map +1 -1
  31. package/internal/core/providers/lazy-initialization.d.ts +44 -0
  32. package/internal/core/providers/lazy-initialization.d.ts.map +1 -0
  33. package/internal/core/providers/lazy-initialization.js +137 -0
  34. package/internal/core/providers/lazy-initialization.js.map +1 -0
  35. package/internal/core/providers/wrapper.d.ts +10 -0
  36. package/internal/core/providers/wrapper.d.ts.map +1 -1
  37. package/internal/core/providers/wrapper.js +10 -0
  38. package/internal/core/providers/wrapper.js.map +1 -1
  39. package/internal/core/runtime-environment.d.ts +5 -4
  40. package/internal/core/runtime-environment.d.ts.map +1 -1
  41. package/internal/core/runtime-environment.js +9 -8
  42. package/internal/core/runtime-environment.js.map +1 -1
  43. package/internal/lib/hardhat-lib.d.ts.map +1 -1
  44. package/internal/lib/hardhat-lib.js +1 -1
  45. package/internal/lib/hardhat-lib.js.map +1 -1
  46. package/package.json +1 -3
  47. package/plugins.d.ts +1 -0
  48. package/plugins.d.ts.map +1 -1
  49. package/plugins.js +3 -1
  50. package/plugins.js.map +1 -1
  51. package/register.js +1 -1
  52. package/register.js.map +1 -1
  53. package/src/builtin-tasks/compile.ts +53 -53
  54. package/src/builtin-tasks/node.ts +2 -5
  55. package/src/internal/cli/analytics.ts +82 -134
  56. package/src/internal/cli/cli.ts +7 -4
  57. package/src/internal/cli/project-creation.ts +4 -1
  58. package/src/internal/context.ts +5 -2
  59. package/src/internal/core/config/config-env.ts +21 -2
  60. package/src/internal/core/errors-list.ts +9 -0
  61. package/src/internal/core/providers/construction.ts +26 -10
  62. package/src/internal/core/providers/lazy-initialization.ts +178 -0
  63. package/src/internal/core/providers/wrapper.ts +10 -0
  64. package/src/internal/core/runtime-environment.ts +15 -12
  65. package/src/internal/lib/hardhat-lib.ts +3 -2
  66. package/src/plugins.ts +1 -0
  67. package/src/register.ts +3 -2
  68. package/src/types/runtime.ts +11 -1
  69. package/types/runtime.d.ts +6 -1
  70. package/types/runtime.d.ts.map +1 -1
  71. package/internal/core/config/extenders.d.ts +0 -7
  72. package/internal/core/config/extenders.d.ts.map +0 -1
  73. package/internal/core/config/extenders.js +0 -16
  74. package/internal/core/config/extenders.js.map +0 -1
  75. package/src/internal/core/config/extenders.ts +0 -13
@@ -109,13 +109,10 @@ subtask(TASK_NODE_GET_PROVIDER)
109
109
  let provider = network.provider;
110
110
 
111
111
  if (network.name !== HARDHAT_NETWORK_NAME) {
112
- const networkConfig = config.networks[HARDHAT_NETWORK_NAME];
113
-
114
112
  log(`Creating hardhat provider for JSON-RPC server`);
115
- provider = createProvider(
113
+ provider = await createProvider(
114
+ config,
116
115
  HARDHAT_NETWORK_NAME,
117
- networkConfig,
118
- config.paths,
119
116
  artifacts
120
117
  );
121
118
  }
@@ -3,10 +3,8 @@ import type { request as RequestT } from "undici";
3
3
  import AbortController from "abort-controller";
4
4
  import debug from "debug";
5
5
  import os from "os";
6
- import qs from "qs";
7
6
  import { v4 as uuid } from "uuid";
8
7
 
9
- import * as builtinTaskNames from "../../builtin-tasks/task-names";
10
8
  import { isLocalDev } from "../core/execution-mode";
11
9
  import { isRunningOnCiServer } from "../util/ci-detection";
12
10
  import {
@@ -19,37 +17,46 @@ import { getPackageJson } from "../util/packageInfo";
19
17
 
20
18
  const log = debug("hardhat:core:analytics");
21
19
 
22
- // VERY IMPORTANT:
23
- // The documentation doesn't say so, but the user-agent parameter is required (ua).
24
- // If you don't send it, you won't get an error or anything, Google will *silently* drop your hit.
25
- //
26
- // https://stackoverflow.com/questions/27357954/google-analytics-measurement-protocol-not-working
27
- interface RawAnalytics {
28
- v: "1";
29
- tid: string;
30
- cid: string;
31
- dp: string;
32
- dh: string;
33
- t: string;
34
- ua: string;
35
- cs: string;
36
- cm: string;
37
- cd1: string;
38
- cd2: string;
39
- cd3: string;
20
+ /* eslint-disable @typescript-eslint/naming-convention */
21
+ interface AnalyticsPayload {
22
+ client_id: string;
23
+ user_id: string;
24
+ user_properties: {
25
+ projectId: {
26
+ value?: string;
27
+ };
28
+ userType: {
29
+ value?: string;
30
+ };
31
+ hardhatVersion: {
32
+ value?: string;
33
+ };
34
+ operatingSystem: {
35
+ value?: string;
36
+ };
37
+ nodeVersion: {
38
+ value?: string;
39
+ };
40
+ };
41
+ events: Array<{
42
+ name: string;
43
+ params: {
44
+ engagement_time_msec: string;
45
+ session_id: string;
46
+ };
47
+ }>;
40
48
  }
49
+ /* eslint-enable @typescript-eslint/naming-convention */
41
50
 
42
51
  type AbortAnalytics = () => void;
43
52
 
44
- const googleAnalyticsUrl = "https://www.google-analytics.com/collect";
45
-
46
53
  export class Analytics {
47
54
  public static async getInstance(telemetryConsent: boolean | undefined) {
48
- const analytics: Analytics = new Analytics({
49
- clientId: await getClientId(),
55
+ const analytics: Analytics = new Analytics(
56
+ await getClientId(),
50
57
  telemetryConsent,
51
- userType: getUserType(),
52
- });
58
+ getUserType()
59
+ );
53
60
 
54
61
  return analytics;
55
62
  }
@@ -57,22 +64,22 @@ export class Analytics {
57
64
  private readonly _clientId: string;
58
65
  private readonly _enabled: boolean;
59
66
  private readonly _userType: string;
60
- // Hardhat's tracking id. I guess there's no other choice than keeping it here.
61
- private readonly _trackingId: string = "UA-117668706-3";
62
-
63
- private constructor({
64
- clientId,
65
- telemetryConsent,
66
- userType,
67
- }: {
68
- clientId: string;
69
- telemetryConsent: boolean | undefined;
70
- userType: string;
71
- }) {
67
+ private readonly _analyticsUrl: string =
68
+ "https://www.google-analytics.com/mp/collect";
69
+ private readonly _apiSecret: string = "fQ5joCsDRTOp55wX8a2cVw";
70
+ private readonly _measurementId: string = "G-8LQ007N2QJ";
71
+ private _sessionId: string;
72
+
73
+ private constructor(
74
+ clientId: string,
75
+ telemetryConsent: boolean | undefined,
76
+ userType: string
77
+ ) {
72
78
  this._clientId = clientId;
73
79
  this._enabled =
74
80
  !isLocalDev() && !isRunningOnCiServer() && telemetryConsent === true;
75
81
  this._userType = userType;
82
+ this._sessionId = Math.random().toString();
76
83
  }
77
84
 
78
85
  /**
@@ -83,106 +90,70 @@ export class Analytics {
83
90
  *
84
91
  * Trying to abort a successfully completed request is a no-op, so it's always safe to call it.
85
92
  *
86
- * @param taskName The name of the task to be logged
87
- *
88
93
  * @returns The abort function
89
94
  */
90
- public async sendTaskHit(
91
- taskName: string
92
- ): Promise<[AbortAnalytics, Promise<void>]> {
93
- if (this._isABuiltinTaskName(taskName)) {
94
- taskName = "builtin";
95
- } else {
96
- taskName = "custom";
97
- }
98
-
95
+ public async sendTaskHit(): Promise<[AbortAnalytics, Promise<void>]> {
99
96
  if (!this._enabled) {
100
97
  return [() => {}, Promise.resolve()];
101
98
  }
102
99
 
103
- return this._sendHit(await this._taskHit(taskName));
100
+ return this._sendHit(await this._buildTaskHitPayload());
104
101
  }
105
102
 
106
- private _isABuiltinTaskName(taskName: string) {
107
- return Object.values<string>(builtinTaskNames).includes(taskName);
108
- }
109
-
110
- private async _taskHit(taskName: string): Promise<RawAnalytics> {
103
+ private async _buildTaskHitPayload(): Promise<AnalyticsPayload> {
111
104
  return {
112
- // Measurement protocol version.
113
- v: "1",
114
-
115
- // Hit type, we're only using pageviews for now.
116
- t: "pageview",
117
-
118
- // Hardhat's tracking Id.
119
- tid: this._trackingId,
120
-
121
- // Client Id.
122
- cid: this._clientId,
123
-
124
- // Document path, must start with a '/'.
125
- dp: `/task/${taskName}`,
126
-
127
- // Host name.
128
- dh: "cli.hardhat.org",
129
-
130
- // User agent, must be present.
131
- // We use it to inform Node version used and OS.
132
- // Example:
133
- // Node/v8.12.0 (Darwin 17.7.0)
134
- ua: getUserAgent(),
135
-
136
- // We're using the following values (Campaign source, Campaign medium) to track
137
- // whether the user is a Developer or CI, as Custom Dimensions are not working for us atm.
138
- cs: this._userType,
139
- cm: "User Type",
140
-
141
- // We're using custom dimensions for tracking different user projects, and user types (Developer/CI).
142
- //
143
- // See the following link for docs on these paremeters:
144
- // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#pr_cd_
145
- //
146
- // See the following link for setting up our custom dimensions in the Google Analytics dashboard
147
- // https://support.google.com/tagmanager/answer/6164990
148
- //
149
- // Custom dimension 1: Project Id
150
- cd1: "hardhat-project",
151
- // Custom dimension 2: User type
152
- // Possible values: "CI", "Developer".
153
- cd2: this._userType,
154
- // Custom dimension 3: Hardhat Version
155
- // Example: "Hardhat 1.0.0".
156
- cd3: await getHardhatVersion(),
105
+ client_id: this._clientId,
106
+ user_id: this._clientId,
107
+ user_properties: {
108
+ projectId: { value: "hardhat-project" },
109
+ userType: { value: this._userType },
110
+ hardhatVersion: { value: await getHardhatVersion() },
111
+ operatingSystem: { value: os.platform() },
112
+ nodeVersion: { value: process.version },
113
+ },
114
+ events: [
115
+ {
116
+ name: "task",
117
+ params: {
118
+ // From the GA docs: amount of time someone spends with your web
119
+ // page in focus or app screen in the foreground
120
+ // The parameter has no use for our app, but it's required in order
121
+ // for user activity to display in standard reports like Realtime
122
+ engagement_time_msec: "10000",
123
+ session_id: this._sessionId,
124
+ },
125
+ },
126
+ ],
157
127
  };
158
128
  }
159
129
 
160
- private _sendHit(hit: RawAnalytics): [AbortAnalytics, Promise<void>] {
130
+ private _sendHit(payload: AnalyticsPayload): [AbortAnalytics, Promise<void>] {
161
131
  const { request } = require("undici") as { request: typeof RequestT };
162
-
163
- log(`Sending hit for ${hit.dp}`);
132
+ const eventName = payload.events[0].name;
133
+ log(`Sending hit for ${eventName}`);
164
134
 
165
135
  const controller = new AbortController();
166
136
 
167
137
  const abortAnalytics = () => {
168
- log(`Aborting hit for ${JSON.stringify(hit.dp)}`);
138
+ log(`Aborting hit for ${eventName}`);
169
139
 
170
140
  controller.abort();
171
141
  };
172
142
 
173
- const hitPayload = qs.stringify(hit);
143
+ log(`Hit payload: ${JSON.stringify(payload)}`);
174
144
 
175
- log(`Hit payload: ${JSON.stringify(hit)}`);
176
-
177
- const hitPromise = request(googleAnalyticsUrl, {
178
- body: hitPayload,
145
+ const hitPromise = request(this._analyticsUrl, {
146
+ query: {
147
+ api_secret: this._apiSecret,
148
+ measurement_id: this._measurementId,
149
+ },
150
+ body: JSON.stringify(payload),
179
151
  method: "POST",
180
152
  signal: controller.signal,
181
153
  })
182
154
  .then(() => {
183
- log(`Hit for ${JSON.stringify(hit.dp)} sent successfully`);
155
+ log(`Hit for ${eventName} sent successfully`);
184
156
  })
185
- // We're not really interested in handling failed analytics requests
186
157
  .catch(() => {
187
158
  log("Hit request failed");
188
159
  });
@@ -214,29 +185,6 @@ function getUserType(): string {
214
185
  return isRunningOnCiServer() ? "CI" : "Developer";
215
186
  }
216
187
 
217
- /**
218
- * At the moment, we couldn't find a reliably way to report the OS () in Node,
219
- * as the versions reported by the various `os` APIs (`os.platform()`, `os.type()`, etc)
220
- * return values different to those expected by Google Analytics
221
- * We decided to take the compromise of just reporting the OS Platform (OSX/Linux/Windows) for now (version information is bogus for now).
222
- */
223
- function getOperatingSystem(): string {
224
- switch (os.type()) {
225
- case "Windows_NT":
226
- return "(Windows NT 6.1; Win64; x64)";
227
- case "Darwin":
228
- return "(Macintosh; Intel Mac OS X 10_13_6)";
229
- case "Linux":
230
- return "(X11; Linux x86_64)";
231
- default:
232
- return "(Unknown)";
233
- }
234
- }
235
-
236
- function getUserAgent(): string {
237
- return `Node/${process.version} ${getOperatingSystem()}`;
238
- }
239
-
240
188
  async function getHardhatVersion(): Promise<string> {
241
189
  const { version } = await getPackageJson();
242
190
 
@@ -212,7 +212,8 @@ async function main() {
212
212
  telemetryConsent === undefined &&
213
213
  !isHelpCommand &&
214
214
  !isRunningOnCiServer() &&
215
- process.stdout.isTTY === true
215
+ process.stdout.isTTY === true &&
216
+ process.env.HARDHAT_DISABLE_TELEMETRY_PROMPT !== "true"
216
217
  ) {
217
218
  telemetryConsent = await confirmTelemetryConsent();
218
219
 
@@ -228,10 +229,11 @@ async function main() {
228
229
  Reporter.setEnabled(true);
229
230
  }
230
231
 
231
- const envExtenders = ctx.extendersManager.getExtenders();
232
+ const envExtenders = ctx.environmentExtenders;
233
+ const providerExtenders = ctx.providerExtenders;
232
234
  const taskDefinitions = ctx.tasksDSL.getTaskDefinitions();
233
235
 
234
- const [abortAnalytics, hitPromise] = await analytics.sendTaskHit(taskName);
236
+ const [abortAnalytics, hitPromise] = await analytics.sendTaskHit();
235
237
 
236
238
  let taskArguments: TaskArguments;
237
239
 
@@ -266,7 +268,8 @@ async function main() {
266
268
  taskDefinitions,
267
269
  envExtenders,
268
270
  ctx.experimentalHardhatNetworkMessageTraceHooks,
269
- userConfig
271
+ userConfig,
272
+ providerExtenders
270
273
  );
271
274
 
272
275
  ctx.setHardhatRuntimeEnvironment(env);
@@ -403,7 +403,10 @@ export async function createProject() {
403
403
  await addGitIgnore(projectRoot);
404
404
  }
405
405
 
406
- if (hasConsentedTelemetry() === undefined) {
406
+ if (
407
+ process.env.HARDHAT_DISABLE_TELEMETRY_PROMPT !== "true" &&
408
+ hasConsentedTelemetry() === undefined
409
+ ) {
407
410
  const telemetryConsent = await confirmTelemetryConsent();
408
411
 
409
412
  if (telemetryConsent !== undefined) {
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  ConfigExtender,
3
+ EnvironmentExtender,
3
4
  ExperimentalHardhatNetworkMessageTraceHook,
4
5
  HardhatRuntimeEnvironment,
6
+ ProviderExtender,
5
7
  } from "../types";
6
8
 
7
- import { ExtenderManager } from "./core/config/extenders";
8
9
  import { assertHardhatInvariant, HardhatError } from "./core/errors";
9
10
  import { ERRORS } from "./core/errors-list";
10
11
  import { TasksDSL } from "./core/tasks/dsl";
@@ -45,8 +46,10 @@ export class HardhatContext {
45
46
  }
46
47
 
47
48
  public readonly tasksDSL = new TasksDSL();
48
- public readonly extendersManager = new ExtenderManager();
49
+ public readonly environmentExtenders: EnvironmentExtender[] = [];
49
50
  public environment?: HardhatRuntimeEnvironment;
51
+ public readonly providerExtenders: ProviderExtender[] = [];
52
+
50
53
  public readonly configExtenders: ConfigExtender[] = [];
51
54
 
52
55
  // NOTE: This is experimental and will be removed. Please contact our team if
@@ -4,6 +4,7 @@ import {
4
4
  ConfigurableTaskDefinition,
5
5
  EnvironmentExtender,
6
6
  ExperimentalHardhatNetworkMessageTraceHook,
7
+ ProviderExtender,
7
8
  TaskArguments,
8
9
  } from "../../../types";
9
10
  import { HardhatContext } from "../../context";
@@ -126,15 +127,33 @@ export const types = argumentTypes;
126
127
  */
127
128
  export function extendEnvironment(extender: EnvironmentExtender) {
128
129
  const ctx = HardhatContext.getHardhatContext();
129
- const extenderManager = ctx.extendersManager;
130
- extenderManager.add(extender);
130
+ ctx.environmentExtenders.push(extender);
131
131
  }
132
132
 
133
+ /**
134
+ * Register a config extender what will be run after the
135
+ * Hardhat Runtime Environment is initialized.
136
+ *
137
+ * @param extender A function that receives the resolved config
138
+ * to be modified and the config provided by the user
139
+ */
133
140
  export function extendConfig(extender: ConfigExtender) {
134
141
  const ctx = HardhatContext.getHardhatContext();
135
142
  ctx.configExtenders.push(extender);
136
143
  }
137
144
 
145
+ /**
146
+ * Register a provider extender what will be run after the
147
+ * Hardhat Runtime Environment is initialized.
148
+ *
149
+ * @param extender A function that receives the current provider
150
+ * and returns a new one.
151
+ */
152
+ export function extendProvider(extender: ProviderExtender) {
153
+ const ctx = HardhatContext.getHardhatContext();
154
+ ctx.providerExtenders.push(extender);
155
+ }
156
+
138
157
  // NOTE: This is experimental and will be removed. Please contact our team
139
158
  // if you are planning to use it.
140
159
  export function experimentalAddHardhatNetworkMessageTraceHook(
@@ -269,6 +269,15 @@ Rename the file to use the .cjs to fix this problem.`,
269
269
  description: `Your project is an ESM project (you have "type": "module" set in your package.json) and you are trying to initialize a TypeScript project. This is not supported yet.`,
270
270
  shouldBeReported: false,
271
271
  },
272
+ UNINITIALIZED_PROVIDER: {
273
+ number: 21,
274
+ message:
275
+ "You tried to access an uninitialized provider. To initialize the provider, make sure you first call `.init()` or any method that hits a node like request, send or sendAsync.",
276
+ title: "Uninitialized provider",
277
+ description: `You tried to access an uninitialized provider. This is most likely caused by using the internal wrapped provider directly before using it to send a request or initializing it.
278
+ To initialize the provider, make sure you first call \`.init()\` or any method that hits a node like request, send or sendAsync.`,
279
+ shouldBeReported: true,
280
+ },
272
281
  },
273
282
  NETWORK: {
274
283
  CONFIG_NOT_FOUND: {
@@ -3,12 +3,13 @@ import type {
3
3
  BoundExperimentalHardhatNetworkMessageTraceHook,
4
4
  EIP1193Provider,
5
5
  EthereumProvider,
6
+ HardhatConfig,
6
7
  HardhatNetworkConfig,
7
8
  HDAccountsUserConfig,
8
9
  HttpNetworkAccountsUserConfig,
9
10
  HttpNetworkConfig,
10
11
  NetworkConfig,
11
- ProjectPathsConfig,
12
+ ProviderExtender,
12
13
  } from "../../../types";
13
14
 
14
15
  import type {
@@ -46,14 +47,16 @@ function importProvider<ModuleT, ProviderNameT extends keyof ModuleT>(
46
47
  return mod[name];
47
48
  }
48
49
 
49
- export function createProvider(
50
+ export async function createProvider(
51
+ config: HardhatConfig,
50
52
  networkName: string,
51
- networkConfig: NetworkConfig,
52
- paths?: ProjectPathsConfig,
53
53
  artifacts?: Artifacts,
54
- experimentalHardhatNetworkMessageTraceHooks: BoundExperimentalHardhatNetworkMessageTraceHook[] = []
55
- ): EthereumProvider {
54
+ experimentalHardhatNetworkMessageTraceHooks: BoundExperimentalHardhatNetworkMessageTraceHook[] = [],
55
+ extenders: ProviderExtender[] = []
56
+ ): Promise<EthereumProvider> {
56
57
  let eip1193Provider: EIP1193Provider;
58
+ const networkConfig = config.networks[networkName];
59
+ const paths = config.paths;
57
60
 
58
61
  if (networkName === HARDHAT_NETWORK_NAME) {
59
62
  const hardhatNetConfig = networkConfig as HardhatNetworkConfig;
@@ -132,7 +135,17 @@ export function createProvider(
132
135
  );
133
136
  }
134
137
 
135
- const wrappedProvider = applyProviderWrappers(eip1193Provider, networkConfig);
138
+ let wrappedProvider = eip1193Provider;
139
+
140
+ for (const extender of extenders) {
141
+ wrappedProvider = await extender(wrappedProvider, config, networkName);
142
+ }
143
+
144
+ wrappedProvider = applyProviderWrappers(
145
+ wrappedProvider,
146
+ networkConfig,
147
+ extenders
148
+ );
136
149
 
137
150
  const BackwardsCompatibilityProviderAdapter = importProvider<
138
151
  typeof import("./backwards-compatibility"),
@@ -144,7 +157,8 @@ export function createProvider(
144
157
 
145
158
  export function applyProviderWrappers(
146
159
  provider: EIP1193Provider,
147
- netConfig: Partial<NetworkConfig>
160
+ netConfig: Partial<NetworkConfig>,
161
+ extenders: ProviderExtender[]
148
162
  ): EIP1193Provider {
149
163
  // These dependencies are lazy-loaded because they are really big.
150
164
  const LocalAccountsProvider = importProvider<
@@ -226,8 +240,10 @@ export function applyProviderWrappers(
226
240
  // fields, as the missing ones will be resolved there.
227
241
  //
228
242
  // Hardhat Network handles this in a more performant way, so we don't use
229
- // the AutomaticGasPriceProvider for it.
230
- if (isResolvedHttpNetworkConfig(netConfig)) {
243
+ // the AutomaticGasPriceProvider for it unless there are provider extenders.
244
+ // The reason for this is that some extenders (like hardhat-ledger's) might
245
+ // do the signing themselves, and that needs the gas price to be set.
246
+ if (isResolvedHttpNetworkConfig(netConfig) || extenders.length > 0) {
231
247
  provider = new AutomaticGasPriceProvider(provider);
232
248
  }
233
249
  } else {