hardhat 2.26.4 → 2.27.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.
@@ -1,5 +1,7 @@
1
1
  import type {
2
2
  Artifacts,
3
+ CompilerInput,
4
+ CompilerOutput,
3
5
  EIP1193Provider,
4
6
  EthSubscription,
5
7
  HardhatNetworkChainsConfig,
@@ -8,23 +10,39 @@ import type {
8
10
 
9
11
  import type {
10
12
  EdrContext,
13
+ LoggerConfig as EdrLoggerConfig,
11
14
  Provider as EdrProviderT,
12
15
  Response,
13
16
  SubscriptionEvent,
14
- HttpHeader,
15
17
  TracingConfigWithBuffers,
18
+ ProviderConfig,
19
+ SubscriptionConfig,
16
20
  } from "@nomicfoundation/edr";
21
+ import { privateToAddress } from "@ethereumjs/util";
22
+ import { ContractDecoder, precompileP256Verify } from "@nomicfoundation/edr";
17
23
  import picocolors from "picocolors";
18
24
  import debug from "debug";
19
25
  import { EventEmitter } from "events";
20
26
  import fsExtra from "fs-extra";
27
+ import * as t from "io-ts";
21
28
 
22
29
  import { requireNapiRsModule } from "../../../common/napi-rs";
23
30
  import {
24
31
  HARDHAT_NETWORK_RESET_EVENT,
25
32
  HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT,
26
33
  } from "../../constants";
34
+ import { numberToRpcQuantity } from "../../core/jsonrpc/types/base-types";
27
35
  import {
36
+ optionalRpcHardhatNetworkConfig,
37
+ RpcHardhatNetworkConfig,
38
+ } from "../../core/jsonrpc/types/input/hardhat-network";
39
+ import {
40
+ rpcCompilerInput,
41
+ rpcCompilerOutput,
42
+ } from "../../core/jsonrpc/types/input/solc";
43
+ import { validateParams } from "../../core/jsonrpc/types/input/validation";
44
+ import {
45
+ InternalError,
28
46
  InvalidArgumentsError,
29
47
  InvalidInputError,
30
48
  ProviderError,
@@ -50,6 +68,7 @@ import {
50
68
  ethereumjsIntervalMiningConfigToEdr,
51
69
  ethereumjsMempoolOrderToEdrMineOrdering,
52
70
  ethereumsjsHardforkToEdrSpecId,
71
+ httpHeadersToEdr,
53
72
  } from "./utils/convertToEdr";
54
73
  import { LoggerConfig, printLine, replaceLastLine } from "./modules/logger";
55
74
  import { MinimalEthereumJsVm, getMinimalEthereumJsVm } from "./vm/minimal-vm";
@@ -62,14 +81,19 @@ export const DEFAULT_COINBASE = "0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e";
62
81
  let _globalEdrContext: EdrContext | undefined;
63
82
 
64
83
  // Lazy initialize the global EDR context.
65
- export function getGlobalEdrContext(): EdrContext {
66
- const { EdrContext } = requireNapiRsModule(
67
- "@nomicfoundation/edr"
68
- ) as typeof import("@nomicfoundation/edr");
84
+ export async function getGlobalEdrContext(): Promise<EdrContext> {
85
+ const { EdrContext, GENERIC_CHAIN_TYPE, genericChainProviderFactory } =
86
+ requireNapiRsModule(
87
+ "@nomicfoundation/edr"
88
+ ) as typeof import("@nomicfoundation/edr");
69
89
 
70
90
  if (_globalEdrContext === undefined) {
71
91
  // Only one is allowed to exist
72
92
  _globalEdrContext = new EdrContext();
93
+ await _globalEdrContext.registerProviderFactory(
94
+ GENERIC_CHAIN_TYPE,
95
+ genericChainProviderFactory()
96
+ );
73
97
  }
74
98
 
75
99
  return _globalEdrContext;
@@ -119,11 +143,14 @@ export class EdrProviderWrapper
119
143
  private _callOverrideCallback?: CallOverrideCallback;
120
144
 
121
145
  private constructor(
122
- private readonly _provider: EdrProviderT,
146
+ private _provider: EdrProviderT,
147
+ private readonly _providerConfig: ProviderConfig,
148
+ private readonly _loggerConfig: EdrLoggerConfig,
123
149
  // we add this for backwards-compatibility with plugins like solidity-coverage
124
- private readonly _node: {
150
+ private _node: {
125
151
  _vm: MinimalEthereumJsVm;
126
- }
152
+ },
153
+ private readonly _subscriptionConfig: SubscriptionConfig
127
154
  ) {
128
155
  super();
129
156
  }
@@ -133,35 +160,43 @@ export class EdrProviderWrapper
133
160
  loggerConfig: LoggerConfig,
134
161
  tracingConfig?: TracingConfigWithBuffers
135
162
  ): Promise<EdrProviderWrapper> {
136
- const { Provider } = requireNapiRsModule(
137
- "@nomicfoundation/edr"
138
- ) as typeof import("@nomicfoundation/edr");
163
+ const { GENERIC_CHAIN_TYPE, l1GenesisState, l1HardforkFromString } =
164
+ requireNapiRsModule(
165
+ "@nomicfoundation/edr"
166
+ ) as typeof import("@nomicfoundation/edr");
139
167
 
140
168
  const coinbase = config.coinbase ?? DEFAULT_COINBASE;
141
169
 
142
170
  let fork;
143
171
  if (config.forkConfig !== undefined) {
144
- let httpHeaders: HttpHeader[] | undefined;
145
- if (config.forkConfig.httpHeaders !== undefined) {
146
- httpHeaders = [];
147
-
148
- for (const [name, value] of Object.entries(
149
- config.forkConfig.httpHeaders
150
- )) {
151
- httpHeaders.push({
152
- name,
153
- value,
154
- });
155
- }
156
- }
157
-
158
172
  fork = {
159
- jsonRpcUrl: config.forkConfig.jsonRpcUrl,
160
173
  blockNumber:
161
174
  config.forkConfig.blockNumber !== undefined
162
175
  ? BigInt(config.forkConfig.blockNumber)
163
176
  : undefined,
164
- httpHeaders,
177
+ cacheDir: config.forkCachePath,
178
+ chainOverrides: Array.from(
179
+ config.chains,
180
+ ([chainId, hardforkConfig]) => {
181
+ return {
182
+ chainId: BigInt(chainId),
183
+ name: "Unknown",
184
+ hardforks: Array.from(
185
+ hardforkConfig.hardforkHistory,
186
+ ([hardfork, blockNumber]) => {
187
+ return {
188
+ condition: { blockNumber: BigInt(blockNumber) },
189
+ hardfork: ethereumsjsHardforkToEdrSpecId(
190
+ getHardforkName(hardfork)
191
+ ),
192
+ };
193
+ }
194
+ ),
195
+ };
196
+ }
197
+ ),
198
+ httpHeaders: httpHeadersToEdr(config.forkConfig.httpHeaders),
199
+ url: config.forkConfig.jsonRpcUrl,
165
200
  };
166
201
  }
167
202
 
@@ -179,80 +214,107 @@ export class EdrProviderWrapper
179
214
 
180
215
  const hardforkName = getHardforkName(config.hardfork);
181
216
 
182
- const provider = await Provider.withConfig(
183
- getGlobalEdrContext(),
184
- {
185
- allowBlocksWithSameTimestamp:
186
- config.allowBlocksWithSameTimestamp ?? false,
187
- allowUnlimitedContractSize: config.allowUnlimitedContractSize,
188
- bailOnCallFailure: config.throwOnCallFailures,
189
- bailOnTransactionFailure: config.throwOnTransactionFailures,
190
- blockGasLimit: BigInt(config.blockGasLimit),
191
- chainId: BigInt(config.chainId),
192
- chains: Array.from(config.chains, ([chainId, hardforkConfig]) => {
193
- return {
194
- chainId: BigInt(chainId),
195
- hardforks: Array.from(
196
- hardforkConfig.hardforkHistory,
197
- ([hardfork, blockNumber]) => {
198
- return {
199
- blockNumber: BigInt(blockNumber),
200
- specId: ethereumsjsHardforkToEdrSpecId(
201
- getHardforkName(hardfork)
202
- ),
203
- };
204
- }
205
- ),
206
- };
207
- }),
208
- cacheDir: config.forkCachePath,
209
- coinbase: Buffer.from(coinbase.slice(2), "hex"),
210
- enableRip7212: config.enableRip7212,
211
- fork,
212
- hardfork: ethereumsjsHardforkToEdrSpecId(hardforkName),
213
- genesisAccounts: config.genesisAccounts.map((account) => {
214
- return {
215
- secretKey: account.privateKey,
216
- balance: BigInt(account.balance),
217
- };
218
- }),
219
- initialDate,
220
- initialBaseFeePerGas:
221
- config.initialBaseFeePerGas !== undefined
222
- ? BigInt(config.initialBaseFeePerGas!)
223
- : undefined,
224
- minGasPrice: config.minGasPrice,
225
- mining: {
226
- autoMine: config.automine,
227
- interval: ethereumjsIntervalMiningConfigToEdr(config.intervalMining),
228
- memPool: {
229
- order: ethereumjsMempoolOrderToEdrMineOrdering(config.mempoolOrder),
230
- },
217
+ const genesisState =
218
+ fork !== undefined
219
+ ? [] // TODO: Add support for overriding remote fork state when the local fork is different
220
+ : l1GenesisState(
221
+ l1HardforkFromString(ethereumsjsHardforkToEdrSpecId(hardforkName))
222
+ );
223
+
224
+ const ownedAccounts = config.genesisAccounts.map((account) => {
225
+ const privateKey = Uint8Array.from(
226
+ // Strip the `0x` prefix
227
+ Buffer.from(account.privateKey.slice(2), "hex")
228
+ );
229
+
230
+ genesisState.push({
231
+ address: privateToAddress(privateKey),
232
+ balance: BigInt(account.balance),
233
+ code: new Uint8Array(), // Empty account code, removing potential delegation code when forking
234
+ });
235
+
236
+ return account.privateKey;
237
+ });
238
+
239
+ const edrProviderConfig = {
240
+ allowBlocksWithSameTimestamp:
241
+ config.allowBlocksWithSameTimestamp ?? false,
242
+ allowUnlimitedContractSize: config.allowUnlimitedContractSize,
243
+ bailOnCallFailure: config.throwOnCallFailures,
244
+ bailOnTransactionFailure: config.throwOnTransactionFailures,
245
+ blockGasLimit: BigInt(config.blockGasLimit),
246
+ chainId: BigInt(config.chainId),
247
+ coinbase: Buffer.from(coinbase.slice(2), "hex"),
248
+ precompileOverrides: config.enableRip7212 ? [precompileP256Verify()] : [],
249
+ fork,
250
+ genesisState,
251
+ hardfork: ethereumsjsHardforkToEdrSpecId(hardforkName),
252
+ initialDate,
253
+ initialBaseFeePerGas:
254
+ config.initialBaseFeePerGas !== undefined
255
+ ? BigInt(config.initialBaseFeePerGas!)
256
+ : undefined,
257
+ minGasPrice: config.minGasPrice,
258
+ mining: {
259
+ autoMine: config.automine,
260
+ interval: ethereumjsIntervalMiningConfigToEdr(config.intervalMining),
261
+ memPool: {
262
+ order: ethereumjsMempoolOrderToEdrMineOrdering(config.mempoolOrder),
231
263
  },
232
- networkId: BigInt(config.networkId),
233
264
  },
234
- {
235
- enable: loggerConfig.enabled,
236
- decodeConsoleLogInputsCallback: ConsoleLogger.getDecodedLogs,
237
- printLineCallback: (message: string, replace: boolean) => {
238
- if (replace) {
239
- replaceLastLineFn(message);
240
- } else {
241
- printLineFn(message);
242
- }
243
- },
265
+ networkId: BigInt(config.networkId),
266
+ observability: {},
267
+ ownedAccounts,
268
+ };
269
+
270
+ const edrLoggerConfig = {
271
+ enable: loggerConfig.enabled,
272
+ decodeConsoleLogInputsCallback: (inputs: ArrayBuffer[]) => {
273
+ return ConsoleLogger.getDecodedLogs(
274
+ inputs.map((input) => {
275
+ return Buffer.from(input);
276
+ })
277
+ );
244
278
  },
245
- tracingConfig ?? {},
246
- (event: SubscriptionEvent) => {
279
+ printLineCallback: (message: string, replace: boolean) => {
280
+ if (replace) {
281
+ replaceLastLineFn(message);
282
+ } else {
283
+ printLineFn(message);
284
+ }
285
+ },
286
+ };
287
+
288
+ const edrSubscriptionConfig = {
289
+ subscriptionCallback: (event: SubscriptionEvent) => {
247
290
  eventAdapter.emit("ethEvent", event);
248
- }
291
+ },
292
+ };
293
+
294
+ const edrTracingConfig = tracingConfig ?? {};
295
+
296
+ const contractDecoder = ContractDecoder.withContracts(edrTracingConfig);
297
+
298
+ const context = await getGlobalEdrContext();
299
+ const provider = await context.createProvider(
300
+ GENERIC_CHAIN_TYPE,
301
+ edrProviderConfig,
302
+ edrLoggerConfig,
303
+ edrSubscriptionConfig,
304
+ contractDecoder
249
305
  );
250
306
 
251
307
  const minimalEthereumJsNode = {
252
308
  _vm: getMinimalEthereumJsVm(provider),
253
309
  };
254
310
 
255
- const wrapper = new EdrProviderWrapper(provider, minimalEthereumJsNode);
311
+ const wrapper = new EdrProviderWrapper(
312
+ provider,
313
+ edrProviderConfig,
314
+ edrLoggerConfig,
315
+ minimalEthereumJsNode,
316
+ edrSubscriptionConfig
317
+ );
256
318
 
257
319
  // Pass through all events from the provider
258
320
  eventAdapter.addListener(
@@ -272,9 +334,22 @@ export class EdrProviderWrapper
272
334
 
273
335
  const params = args.params ?? [];
274
336
 
275
- if (args.method === "hardhat_getStackTraceFailuresCount") {
276
- // stubbed for backwards compatibility
277
- return 0;
337
+ // stubbed for backwards compatibility
338
+ switch (args.method) {
339
+ case "hardhat_getStackTraceFailuresCount":
340
+ return 0;
341
+ case "eth_mining":
342
+ return false;
343
+ case "net_listening":
344
+ return true;
345
+ case "net_peerCount":
346
+ return numberToRpcQuantity(0);
347
+ case "hardhat_reset":
348
+ return this._reset(..._resetParams(params));
349
+ case "hardhat_addCompilationResult":
350
+ return this._addCompilationResult(
351
+ ..._addCompilationResultParams(params)
352
+ );
278
353
  }
279
354
 
280
355
  const stringifiedArgs = JSON.stringify({
@@ -301,7 +376,7 @@ export class EdrProviderWrapper
301
376
  const rawTraces = responseObject.traces;
302
377
  for (const rawTrace of rawTraces) {
303
378
  // For other consumers in JS we need to marshall the entire trace over FFI
304
- const trace = rawTrace.trace();
379
+ const trace = rawTrace.trace;
305
380
 
306
381
  // beforeTx event
307
382
  if (this._node._vm.events.listenerCount("beforeTx") > 0) {
@@ -377,9 +452,7 @@ export class EdrProviderWrapper
377
452
  throw error;
378
453
  }
379
454
 
380
- if (args.method === "hardhat_reset") {
381
- this.emit(HARDHAT_NETWORK_RESET_EVENT);
382
- } else if (args.method === "evm_revert") {
455
+ if (args.method === "evm_revert") {
383
456
  this.emit(HARDHAT_NETWORK_REVERT_SNAPSHOT_EVENT);
384
457
  }
385
458
 
@@ -397,19 +470,81 @@ export class EdrProviderWrapper
397
470
  }
398
471
  }
399
472
 
473
+ private async _addCompilationResult(
474
+ solcVersion: string,
475
+ input: CompilerInput,
476
+ output: CompilerOutput
477
+ ): Promise<boolean> {
478
+ try {
479
+ await this._provider.addCompilationResult(solcVersion, input, output);
480
+
481
+ return true;
482
+ } catch (error: any) {
483
+ // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error
484
+ throw new InternalError(error);
485
+ }
486
+ }
487
+
488
+ private async _reset(networkConfig?: RpcHardhatNetworkConfig) {
489
+ const { GENERIC_CHAIN_TYPE } = requireNapiRsModule(
490
+ "@nomicfoundation/edr"
491
+ ) as typeof import("@nomicfoundation/edr");
492
+ const forkConfig = networkConfig?.forking;
493
+
494
+ if (forkConfig !== undefined) {
495
+ this._providerConfig.fork = {
496
+ blockNumber:
497
+ forkConfig.blockNumber !== undefined
498
+ ? BigInt(forkConfig.blockNumber)
499
+ : undefined,
500
+ cacheDir: this._providerConfig.fork?.cacheDir,
501
+ chainOverrides: this._providerConfig.fork?.chainOverrides,
502
+ httpHeaders: httpHeadersToEdr(forkConfig.httpHeaders),
503
+ url: forkConfig.jsonRpcUrl,
504
+ };
505
+ } else {
506
+ this._providerConfig.fork = undefined;
507
+ }
508
+
509
+ const context = await getGlobalEdrContext();
510
+ const provider = await context.createProvider(
511
+ GENERIC_CHAIN_TYPE,
512
+ this._providerConfig,
513
+ this._loggerConfig,
514
+ this._subscriptionConfig,
515
+ this._provider.contractDecoder()
516
+ );
517
+
518
+ const minimalEthereumJsNode = {
519
+ _vm: getMinimalEthereumJsVm(provider),
520
+ };
521
+
522
+ this._provider = provider;
523
+ this._node = minimalEthereumJsNode;
524
+
525
+ this.emit(HARDHAT_NETWORK_RESET_EVENT);
526
+
527
+ return true;
528
+ }
529
+
400
530
  // temporarily added to make smock work with HH+EDR
401
- private _setCallOverrideCallback(callback: CallOverrideCallback) {
531
+ private async _setCallOverrideCallback(
532
+ callback: CallOverrideCallback
533
+ ): Promise<void> {
402
534
  this._callOverrideCallback = callback;
403
535
 
404
- this._provider.setCallOverrideCallback(
405
- async (address: Buffer, data: Buffer) => {
406
- return this._callOverrideCallback?.(address, data);
536
+ await this._provider.setCallOverrideCallback(
537
+ async (address: ArrayBuffer, data: ArrayBuffer) => {
538
+ return this._callOverrideCallback?.(
539
+ Buffer.from(address),
540
+ Buffer.from(data)
541
+ );
407
542
  }
408
543
  );
409
544
  }
410
545
 
411
- private _setVerboseTracing(enabled: boolean) {
412
- this._provider.setVerboseTracing(enabled);
546
+ private async _setVerboseTracing(enabled: boolean): Promise<void> {
547
+ await this._provider.setVerboseTracing(enabled);
413
548
  }
414
549
 
415
550
  private _ethEventListener(event: SubscriptionEvent) {
@@ -493,3 +628,13 @@ async function makeTracingConfig(
493
628
  }
494
629
  }
495
630
  }
631
+
632
+ function _addCompilationResultParams(
633
+ params: any[]
634
+ ): [string, CompilerInput, CompilerOutput] {
635
+ return validateParams(params, t.string, rpcCompilerInput, rpcCompilerOutput);
636
+ }
637
+
638
+ function _resetParams(params: any[]): [RpcHardhatNetworkConfig | undefined] {
639
+ return validateParams(params, optionalRpcHardhatNetworkConfig);
640
+ }
@@ -6,6 +6,27 @@ import type {
6
6
  TracingMessage,
7
7
  TracingMessageResult,
8
8
  TracingStep,
9
+ HttpHeader,
10
+ } from "@nomicfoundation/edr";
11
+ import {
12
+ FRONTIER,
13
+ HOMESTEAD,
14
+ DAO_FORK,
15
+ TANGERINE,
16
+ SPURIOUS_DRAGON,
17
+ BYZANTIUM,
18
+ CONSTANTINOPLE,
19
+ PETERSBURG,
20
+ ISTANBUL,
21
+ MUIR_GLACIER,
22
+ BERLIN,
23
+ LONDON,
24
+ ARROW_GLACIER,
25
+ GRAY_GLACIER,
26
+ MERGE,
27
+ SHANGHAI,
28
+ CANCUN,
29
+ PRAGUE,
9
30
  } from "@nomicfoundation/edr";
10
31
  import { Address } from "@ethereumjs/util";
11
32
 
@@ -21,48 +42,44 @@ import {
21
42
 
22
43
  /* eslint-disable @nomicfoundation/hardhat-internal-rules/only-hardhat-error */
23
44
 
24
- export function ethereumsjsHardforkToEdrSpecId(hardfork: HardforkName): SpecId {
25
- const { SpecId } = requireNapiRsModule(
26
- "@nomicfoundation/edr"
27
- ) as typeof import("@nomicfoundation/edr");
28
-
45
+ export function ethereumsjsHardforkToEdrSpecId(hardfork: HardforkName): string {
29
46
  switch (hardfork) {
30
47
  case HardforkName.FRONTIER:
31
- return SpecId.Frontier;
48
+ return FRONTIER;
32
49
  case HardforkName.HOMESTEAD:
33
- return SpecId.Homestead;
50
+ return HOMESTEAD;
34
51
  case HardforkName.DAO:
35
- return SpecId.DaoFork;
52
+ return DAO_FORK;
36
53
  case HardforkName.TANGERINE_WHISTLE:
37
- return SpecId.Tangerine;
54
+ return TANGERINE;
38
55
  case HardforkName.SPURIOUS_DRAGON:
39
- return SpecId.SpuriousDragon;
56
+ return SPURIOUS_DRAGON;
40
57
  case HardforkName.BYZANTIUM:
41
- return SpecId.Byzantium;
58
+ return BYZANTIUM;
42
59
  case HardforkName.CONSTANTINOPLE:
43
- return SpecId.Constantinople;
60
+ return CONSTANTINOPLE;
44
61
  case HardforkName.PETERSBURG:
45
- return SpecId.Petersburg;
62
+ return PETERSBURG;
46
63
  case HardforkName.ISTANBUL:
47
- return SpecId.Istanbul;
64
+ return ISTANBUL;
48
65
  case HardforkName.MUIR_GLACIER:
49
- return SpecId.MuirGlacier;
66
+ return MUIR_GLACIER;
50
67
  case HardforkName.BERLIN:
51
- return SpecId.Berlin;
68
+ return BERLIN;
52
69
  case HardforkName.LONDON:
53
- return SpecId.London;
70
+ return LONDON;
54
71
  case HardforkName.ARROW_GLACIER:
55
- return SpecId.ArrowGlacier;
72
+ return ARROW_GLACIER;
56
73
  case HardforkName.GRAY_GLACIER:
57
- return SpecId.GrayGlacier;
74
+ return GRAY_GLACIER;
58
75
  case HardforkName.MERGE:
59
- return SpecId.Merge;
76
+ return MERGE;
60
77
  case HardforkName.SHANGHAI:
61
- return SpecId.Shanghai;
78
+ return SHANGHAI;
62
79
  case HardforkName.CANCUN:
63
- return SpecId.Cancun;
80
+ return CANCUN;
64
81
  case HardforkName.PRAGUE:
65
- return SpecId.Prague;
82
+ return PRAGUE;
66
83
  default:
67
84
  const _exhaustiveCheck: never = hardfork;
68
85
  throw new Error(
@@ -195,7 +212,7 @@ export function edrRpcDebugTraceToHardhat(
195
212
  structLogs.shift();
196
213
  }
197
214
 
198
- let returnValue = rpcDebugTrace.output?.toString("hex") ?? "";
215
+ let returnValue = rpcDebugTrace.output?.toString() ?? "";
199
216
  if (returnValue === "0x") {
200
217
  returnValue = "";
201
218
  }
@@ -248,10 +265,10 @@ export function edrTracingMessageResultToMinimalEVMResult(
248
265
  }
249
266
  if ("output" in result) {
250
267
  const { output } = result;
251
- if (Buffer.isBuffer(output)) {
252
- minimalEVMResult.execResult.output = output;
268
+ if (output instanceof Uint8Array) {
269
+ minimalEVMResult.execResult.output = Buffer.from(output);
253
270
  } else {
254
- minimalEVMResult.execResult.output = output.returnValue;
271
+ minimalEVMResult.execResult.output = Buffer.from(output.returnValue);
255
272
  }
256
273
  }
257
274
 
@@ -278,3 +295,21 @@ export function edrTracingMessageToMinimalMessage(
278
295
  isStaticCall: message.isStaticCall,
279
296
  };
280
297
  }
298
+
299
+ export function httpHeadersToEdr(input?: {
300
+ [name: string]: string;
301
+ }): HttpHeader[] | undefined {
302
+ let httpHeaders: HttpHeader[] | undefined;
303
+ if (input !== undefined) {
304
+ httpHeaders = [];
305
+
306
+ for (const [name, value] of Object.entries(input)) {
307
+ httpHeaders.push({
308
+ name,
309
+ value,
310
+ });
311
+ }
312
+ }
313
+
314
+ return httpHeaders;
315
+ }