voltaire-effect 0.3.0 → 1.0.1

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 (88) hide show
  1. package/dist/{X25519Test-D5Q-5fL9.d.ts → X25519Test-avt1DUgp.d.ts} +231 -6
  2. package/dist/crypto/index.d.ts +2 -2
  3. package/dist/crypto/index.js +72 -2
  4. package/dist/{index-3UKSP3cd.d.ts → index-DxwZo3xo.d.ts} +7781 -5513
  5. package/dist/index.d.ts +990 -3096
  6. package/dist/index.js +2374 -1652
  7. package/dist/native/index.d.ts +6 -6
  8. package/dist/native/index.js +2399 -1677
  9. package/dist/primitives/index.d.ts +7 -6
  10. package/dist/primitives/index.js +2966 -2361
  11. package/dist/services/index.d.ts +1631 -1255
  12. package/dist/services/index.js +4493 -3977
  13. package/package.json +7 -3
  14. package/src/crypto/Signers/SignersService.ts +1 -1
  15. package/src/crypto/Signers/errors.ts +29 -0
  16. package/src/crypto/Signers/index.ts +1 -0
  17. package/src/crypto/Signers/operations.ts +26 -8
  18. package/src/crypto/index.ts +10 -11
  19. package/src/index.ts +1 -2
  20. package/src/jsonrpc/Anvil.ts +13 -8
  21. package/src/jsonrpc/Eth.ts +13 -8
  22. package/src/jsonrpc/Hardhat.ts +13 -8
  23. package/src/jsonrpc/IdCounter.ts +21 -5
  24. package/src/jsonrpc/JsonRpc.test.ts +126 -61
  25. package/src/jsonrpc/Net.ts +13 -8
  26. package/src/jsonrpc/Request.ts +16 -8
  27. package/src/jsonrpc/Txpool.ts +13 -8
  28. package/src/jsonrpc/Wallet.ts +13 -8
  29. package/src/jsonrpc/Web3.ts +13 -8
  30. package/src/jsonrpc/index.ts +1 -1
  31. package/src/primitives/Abi/AbiSchema.ts +3 -4
  32. package/src/primitives/Abi/fromBytecode.test.ts +47 -0
  33. package/src/primitives/Abi/fromBytecode.ts +81 -0
  34. package/src/primitives/Abi/index.ts +3 -0
  35. package/src/primitives/AccessList/from.ts +12 -9
  36. package/src/primitives/Address/Checksummed.ts +21 -27
  37. package/src/primitives/Address/from.ts +12 -15
  38. package/src/primitives/Address/toHex.ts +2 -1
  39. package/src/primitives/Base64/from.ts +21 -4
  40. package/src/primitives/Blob/from.ts +12 -4
  41. package/src/primitives/BlockHash/index.ts +2 -2
  42. package/src/primitives/BlockNumber/index.ts +3 -3
  43. package/src/primitives/Bytecode/from.ts +11 -2
  44. package/src/primitives/ContractSignature/verifySignature.ts +3 -5
  45. package/src/primitives/Ens/from.ts +12 -11
  46. package/src/primitives/Hex/from.ts +12 -4
  47. package/src/primitives/Signature/from.ts +11 -2
  48. package/src/primitives/Transaction/EIP2930/index.ts +12 -12
  49. package/src/primitives/Transaction/EIP4844/index.ts +14 -14
  50. package/src/primitives/Transaction/EIP7702/index.ts +13 -13
  51. package/src/primitives/Transaction/Legacy/index.ts +13 -13
  52. package/src/primitives/TransactionHash/index.ts +3 -2
  53. package/src/primitives/TransactionIndex/index.ts +2 -2
  54. package/src/primitives/Trie/Trie.test.ts +70 -0
  55. package/src/primitives/Trie/TrieSchema.ts +26 -0
  56. package/src/primitives/Trie/clear.ts +16 -0
  57. package/src/primitives/Trie/del.ts +18 -0
  58. package/src/primitives/Trie/get.ts +18 -0
  59. package/src/primitives/Trie/index.ts +30 -0
  60. package/src/primitives/Trie/init.ts +13 -0
  61. package/src/primitives/Trie/prove.ts +19 -0
  62. package/src/primitives/Trie/put.ts +20 -0
  63. package/src/primitives/Trie/rootHash.ts +14 -0
  64. package/src/primitives/Trie/verify.ts +18 -0
  65. package/src/primitives/Uint/from.ts +11 -2
  66. package/src/primitives/Uint16/index.ts +5 -4
  67. package/src/primitives/Uint64/index.ts +5 -4
  68. package/src/primitives/Uint8/index.ts +5 -4
  69. package/src/primitives/index.ts +3 -2
  70. package/src/services/BlockExplorerApi/BlockExplorerApi.test.ts +789 -0
  71. package/src/services/BlockExplorerApi/BlockExplorerApi.ts +797 -0
  72. package/src/services/BlockExplorerApi/BlockExplorerApiErrors.ts +176 -0
  73. package/src/services/BlockExplorerApi/BlockExplorerApiService.ts +60 -0
  74. package/src/services/BlockExplorerApi/BlockExplorerApiTypes.ts +225 -0
  75. package/src/services/BlockExplorerApi/index.ts +42 -0
  76. package/src/services/Contract/Contract.test.ts +2 -6
  77. package/src/services/Contract/ContractTypes.ts +26 -8
  78. package/src/services/Contract/estimateGas.test.ts +4 -7
  79. package/src/services/Provider/actions/multicall.ts +28 -9
  80. package/src/services/Provider/actions/readContract.test.ts +8 -11
  81. package/src/services/Provider/actions/readContract.ts +28 -9
  82. package/src/services/Provider/functions/getBlock.ts +2 -1
  83. package/src/services/Provider/functions/getBlockReceipts.ts +2 -1
  84. package/src/services/Provider/functions/getBlockTransactionCount.ts +2 -1
  85. package/src/services/Provider/functions/getUncle.ts +2 -1
  86. package/src/services/Provider/functions/getUncleCount.ts +2 -1
  87. package/src/services/Signer/actions/deployContract.ts +1 -1
  88. package/src/services/index.ts +25 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voltaire-effect",
3
- "version": "0.3.0",
3
+ "version": "1.0.1",
4
4
  "description": "Effect-TS integration for Voltaire Ethereum primitives library",
5
5
  "author": "TEVM",
6
6
  "license": "MIT",
@@ -80,7 +80,7 @@
80
80
  "peerDependencies": {
81
81
  "@effect/platform": "^0.94.0",
82
82
  "effect": "^3.19.0",
83
- "@tevm/voltaire": "0.2.28"
83
+ "@tevm/voltaire": "0.4.0"
84
84
  },
85
85
  "optionalDependencies": {
86
86
  "@effect/platform-browser": "^0.74.0",
@@ -91,12 +91,16 @@
91
91
  "@effect/platform": "^0.94.0",
92
92
  "@effect/platform-node": "^0.104.0",
93
93
  "@effect/vitest": "^0.27.0",
94
- "effect": "^3.19.0",
94
+ "@types/node": "^22.0.0",
95
95
  "astro": "^5.0.0",
96
+ "effect": "^3.19.0",
96
97
  "tsup": "^8.4.0",
97
98
  "typescript": "^5.9.3",
98
99
  "vitest": "^2.1.8"
99
100
  },
101
+ "dependencies": {
102
+ "@shazow/whatsabi": "^0.25.0"
103
+ },
100
104
  "scripts": {
101
105
  "build": "tsup",
102
106
  "test": "vitest",
@@ -4,7 +4,7 @@
4
4
  * @since 0.0.1
5
5
  */
6
6
 
7
- import type { CryptoError, InvalidPrivateKeyError } from "@tevm/voltaire";
7
+ import type { CryptoError, InvalidPrivateKeyError } from "./errors.js";
8
8
  import * as Context from "effect/Context";
9
9
  import type * as Effect from "effect/Effect";
10
10
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @fileoverview Error types for Signers module using Effect TaggedError.
3
+ * @module Signers/errors
4
+ * @since 0.0.1
5
+ */
6
+
7
+ import * as Data from "effect/Data";
8
+
9
+ /**
10
+ * General cryptographic error during signing operations.
11
+ *
12
+ * @since 0.0.1
13
+ */
14
+ export class CryptoError extends Data.TaggedError("CryptoError")<{
15
+ readonly message: string;
16
+ readonly cause?: unknown;
17
+ }> {}
18
+
19
+ /**
20
+ * Invalid private key format or value.
21
+ *
22
+ * @since 0.0.1
23
+ */
24
+ export class InvalidPrivateKeyError extends Data.TaggedError(
25
+ "InvalidPrivateKeyError",
26
+ )<{
27
+ readonly message: string;
28
+ readonly cause?: unknown;
29
+ }> {}
@@ -29,6 +29,7 @@
29
29
  * @see {@link https://eips.ethereum.org/EIPS/eip-191 | EIP-191}
30
30
  * @see {@link https://eips.ethereum.org/EIPS/eip-712 | EIP-712}
31
31
  */
32
+ export { CryptoError, InvalidPrivateKeyError } from "./errors.js";
32
33
  export {
33
34
  fromPrivateKey,
34
35
  getAddress,
@@ -6,11 +6,9 @@
6
6
 
7
7
  import { Signers } from "@tevm/voltaire";
8
8
  import * as Effect from "effect/Effect";
9
+ import { CryptoError, InvalidPrivateKeyError } from "./errors.js";
9
10
  import type { Signer } from "./SignersService.js";
10
11
 
11
- type CryptoError = Error;
12
- type InvalidPrivateKeyError = Error & { code: "INVALID_PRIVATE_KEY" };
13
-
14
12
  /**
15
13
  * Creates a signer from a private key.
16
14
  *
@@ -49,21 +47,37 @@ export const fromPrivateKey = (
49
47
  signMessage: (message: string | Uint8Array) =>
50
48
  Effect.tryPromise({
51
49
  try: () => impl.signMessage(message),
52
- catch: (e) => e as CryptoError,
50
+ catch: (e) =>
51
+ new CryptoError({
52
+ message: e instanceof Error ? e.message : String(e),
53
+ cause: e,
54
+ }),
53
55
  }),
54
56
  signTransaction: (transaction: unknown) =>
55
57
  Effect.tryPromise({
56
58
  try: () => impl.signTransaction(transaction),
57
- catch: (e) => e as CryptoError,
59
+ catch: (e) =>
60
+ new CryptoError({
61
+ message: e instanceof Error ? e.message : String(e),
62
+ cause: e,
63
+ }),
58
64
  }),
59
65
  signTypedData: (typedData: unknown) =>
60
66
  Effect.tryPromise({
61
67
  try: () => impl.signTypedData(typedData),
62
- catch: (e) => e as CryptoError,
68
+ catch: (e) =>
69
+ new CryptoError({
70
+ message: e instanceof Error ? e.message : String(e),
71
+ cause: e,
72
+ }),
63
73
  }),
64
74
  } as Signer;
65
75
  },
66
- catch: (e) => e as InvalidPrivateKeyError,
76
+ catch: (e) =>
77
+ new InvalidPrivateKeyError({
78
+ message: e instanceof Error ? e.message : String(e),
79
+ cause: e,
80
+ }),
67
81
  });
68
82
 
69
83
  /**
@@ -119,5 +133,9 @@ export const recoverTransactionAddress = (
119
133
  ): Effect.Effect<string, CryptoError> =>
120
134
  Effect.tryPromise({
121
135
  try: () => Signers.recoverTransactionAddress(transaction),
122
- catch: (e) => e as CryptoError,
136
+ catch: (e) =>
137
+ new CryptoError({
138
+ message: e instanceof Error ? e.message : String(e),
139
+ cause: e,
140
+ }),
123
141
  });
@@ -217,17 +217,16 @@ export {
217
217
  verifyMessage,
218
218
  verifyTypedData as verifyTypedDataSignature,
219
219
  } from "./Signature/index.js";
220
- // Signers module temporarily disabled - requires @tevm/voltaire exports update
221
- // export {
222
- // fromPrivateKey,
223
- // getAddress as signersGetAddress,
224
- // recoverTransactionAddress,
225
- // type Signer,
226
- // SignersLive,
227
- // SignersService,
228
- // type SignersServiceShape,
229
- // SignersTest,
230
- // } from "./Signers/index.js";
220
+ export {
221
+ fromPrivateKey,
222
+ getAddress as signersGetAddress,
223
+ recoverTransactionAddress,
224
+ type Signer,
225
+ SignersLive,
226
+ SignersService,
227
+ type SignersServiceShape,
228
+ SignersTest,
229
+ } from "./Signers/index.js";
231
230
  export {
232
231
  computeSecret as x25519ComputeSecret,
233
232
  generateKeyPair as x25519GenerateKeyPair,
package/src/index.ts CHANGED
@@ -70,8 +70,7 @@ export * as P256 from "./crypto/P256/index.js";
70
70
  export * as Ripemd160 from "./crypto/Ripemd160/index.js";
71
71
  export * as Secp256k1 from "./crypto/Secp256k1/index.js";
72
72
  export * as SHA256 from "./crypto/SHA256/index.js";
73
- // Signers temporarily disabled - requires @tevm/voltaire exports update
74
- // export * as Signers from "./crypto/Signers/index.js";
73
+ export * as Signers from "./crypto/Signers/index.js";
75
74
  export * as X25519 from "./crypto/X25519/index.js";
76
75
  // JSON-RPC module
77
76
  export * as JsonRpc from "./jsonrpc/index.js";
@@ -1,16 +1,21 @@
1
- import { nextId } from "./IdCounter.js";
1
+ import * as Effect from "effect/Effect";
2
+ import { IdCounterService } from "./IdCounter.js";
2
3
  import type { JsonRpcRequestType } from "./Request.js";
3
4
 
4
5
  function makeRequest(
5
6
  method: string,
6
7
  params: unknown[] = [],
7
- ): JsonRpcRequestType {
8
- return {
9
- jsonrpc: "2.0",
10
- method,
11
- params,
12
- id: nextId(),
13
- };
8
+ ): Effect.Effect<JsonRpcRequestType, never, IdCounterService> {
9
+ return Effect.gen(function* () {
10
+ const counter = yield* IdCounterService;
11
+ const id = yield* counter.next();
12
+ return {
13
+ jsonrpc: "2.0",
14
+ method,
15
+ params,
16
+ id,
17
+ };
18
+ });
14
19
  }
15
20
 
16
21
  export const MineRequest = (blocks?: number, interval?: number) =>
@@ -1,16 +1,21 @@
1
- import { nextId } from "./IdCounter.js";
1
+ import * as Effect from "effect/Effect";
2
+ import { IdCounterService } from "./IdCounter.js";
2
3
  import type { JsonRpcRequestType } from "./Request.js";
3
4
 
4
5
  function makeRequest(
5
6
  method: string,
6
7
  params: unknown[] = [],
7
- ): JsonRpcRequestType {
8
- return {
9
- jsonrpc: "2.0",
10
- method,
11
- params,
12
- id: nextId(),
13
- };
8
+ ): Effect.Effect<JsonRpcRequestType, never, IdCounterService> {
9
+ return Effect.gen(function* () {
10
+ const counter = yield* IdCounterService;
11
+ const id = yield* counter.next();
12
+ return {
13
+ jsonrpc: "2.0",
14
+ method,
15
+ params,
16
+ id,
17
+ };
18
+ });
14
19
  }
15
20
 
16
21
  export const AccountsRequest = () => makeRequest("eth_accounts");
@@ -1,16 +1,21 @@
1
- import { nextId } from "./IdCounter.js";
1
+ import * as Effect from "effect/Effect";
2
+ import { IdCounterService } from "./IdCounter.js";
2
3
  import type { JsonRpcRequestType } from "./Request.js";
3
4
 
4
5
  function makeRequest(
5
6
  method: string,
6
7
  params: unknown[] = [],
7
- ): JsonRpcRequestType {
8
- return {
9
- jsonrpc: "2.0",
10
- method,
11
- params,
12
- id: nextId(),
13
- };
8
+ ): Effect.Effect<JsonRpcRequestType, never, IdCounterService> {
9
+ return Effect.gen(function* () {
10
+ const counter = yield* IdCounterService;
11
+ const id = yield* counter.next();
12
+ return {
13
+ jsonrpc: "2.0",
14
+ method,
15
+ params,
16
+ id,
17
+ };
18
+ });
14
19
  }
15
20
 
16
21
  export const ResetRequest = (options?: {
@@ -1,7 +1,23 @@
1
- let idCounter = 0;
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Ref from "effect/Ref";
2
5
 
3
- export const nextId = (): number => ++idCounter;
4
-
5
- export const resetId = (): void => {
6
- idCounter = 0;
6
+ export type IdCounterShape = {
7
+ readonly next: () => Effect.Effect<number>;
7
8
  };
9
+
10
+ export class IdCounterService extends Context.Tag("IdCounterService")<
11
+ IdCounterService,
12
+ IdCounterShape
13
+ >() {}
14
+
15
+ export const IdCounterLive: Layer.Layer<IdCounterService> = Layer.effect(
16
+ IdCounterService,
17
+ Effect.gen(function* () {
18
+ const ref = yield* Ref.make(0);
19
+ return {
20
+ next: () => Ref.updateAndGet(ref, (n) => n + 1),
21
+ };
22
+ }),
23
+ );
@@ -1,5 +1,5 @@
1
1
  import * as Effect from "effect/Effect";
2
- import { beforeEach, describe, expect, it } from "vitest";
2
+ import { describe, expect, it } from "vitest";
3
3
  import {
4
4
  Anvil,
5
5
  BatchRequest,
@@ -10,6 +10,7 @@ import {
10
10
  EXECUTION_REVERTED,
11
11
  ExecutionRevertedError,
12
12
  Hardhat,
13
+ IdCounterLive,
13
14
  INSUFFICIENT_FUNDS,
14
15
  InsufficientFundsError,
15
16
  isDisconnected,
@@ -28,7 +29,6 @@ import {
28
29
  parseErrorCode,
29
30
  Request,
30
31
  Response,
31
- resetId,
32
32
  Txpool,
33
33
  USER_REJECTED_REQUEST,
34
34
  Wallet,
@@ -66,10 +66,13 @@ describe("JsonRpc", () => {
66
66
  expect(Request.isNotification(req)).toBe(false);
67
67
  });
68
68
 
69
- it("withParams adds parameters to request", () => {
69
+ it("withParams adds parameters to request", async () => {
70
70
  const base = Request.from({ method: "eth_getBalance", id: 1 });
71
71
  const addParams = Request.withParams<[string, string]>(base);
72
- const req = addParams(["0x1234", "latest"]);
72
+ const program = addParams(["0x1234", "latest"]).pipe(
73
+ Effect.provide(IdCounterLive),
74
+ );
75
+ const req = await Effect.runPromise(program);
73
76
  expect(req.params).toEqual(["0x1234", "latest"]);
74
77
  });
75
78
 
@@ -98,10 +101,13 @@ describe("JsonRpc", () => {
98
101
  expect(Request.isNotification(req)).toBe(false);
99
102
  });
100
103
 
101
- it("withParams auto-assigns id when undefined", () => {
104
+ it("withParams auto-assigns id when undefined", async () => {
102
105
  const base = Request.from({ method: "eth_getBalance" });
103
106
  const addParams = Request.withParams<[string, string]>(base);
104
- const req = addParams(["0x1234", "latest"]);
107
+ const program = addParams(["0x1234", "latest"]).pipe(
108
+ Effect.provide(IdCounterLive),
109
+ );
110
+ const req = await Effect.runPromise(program);
105
111
  expect(req.id).toBeDefined();
106
112
  expect(typeof req.id).toBe("number");
107
113
  });
@@ -478,84 +484,123 @@ describe("JsonRpc", () => {
478
484
  });
479
485
 
480
486
  describe("Eth namespace", () => {
481
- it("creates GetBalanceRequest", () => {
482
- const req = Eth.GetBalanceRequest("0x1234", "latest");
487
+ it("creates GetBalanceRequest", async () => {
488
+ const program = Eth.GetBalanceRequest("0x1234", "latest").pipe(
489
+ Effect.provide(IdCounterLive),
490
+ );
491
+ const req = await Effect.runPromise(program);
483
492
  expect(req.method).toBe("eth_getBalance");
484
493
  expect(req.params).toEqual(["0x1234", "latest"]);
485
494
  });
486
495
 
487
- it("creates BlockNumberRequest", () => {
488
- const req = Eth.BlockNumberRequest();
496
+ it("creates BlockNumberRequest", async () => {
497
+ const program = Eth.BlockNumberRequest().pipe(
498
+ Effect.provide(IdCounterLive),
499
+ );
500
+ const req = await Effect.runPromise(program);
489
501
  expect(req.method).toBe("eth_blockNumber");
490
502
  });
491
503
 
492
- it("creates ChainIdRequest", () => {
493
- const req = Eth.ChainIdRequest();
504
+ it("creates ChainIdRequest", async () => {
505
+ const program = Eth.ChainIdRequest().pipe(Effect.provide(IdCounterLive));
506
+ const req = await Effect.runPromise(program);
494
507
  expect(req.method).toBe("eth_chainId");
495
508
  });
496
509
 
497
- it("creates CallRequest", () => {
498
- const req = Eth.CallRequest({ to: "0xabc", data: "0x123" }, "latest");
510
+ it("creates CallRequest", async () => {
511
+ const program = Eth.CallRequest(
512
+ { to: "0xabc", data: "0x123" },
513
+ "latest",
514
+ ).pipe(Effect.provide(IdCounterLive));
515
+ const req = await Effect.runPromise(program);
499
516
  expect(req.method).toBe("eth_call");
500
517
  });
501
518
 
502
- it("creates GetBlockByNumberRequest", () => {
503
- const req = Eth.GetBlockByNumberRequest("0x1", false);
519
+ it("creates GetBlockByNumberRequest", async () => {
520
+ const program = Eth.GetBlockByNumberRequest("0x1", false).pipe(
521
+ Effect.provide(IdCounterLive),
522
+ );
523
+ const req = await Effect.runPromise(program);
504
524
  expect(req.method).toBe("eth_getBlockByNumber");
505
525
  expect(req.params).toEqual(["0x1", false]);
506
526
  });
507
527
  });
508
528
 
509
529
  describe("Wallet namespace", () => {
510
- it("creates SwitchEthereumChainRequest", () => {
511
- const req = Wallet.SwitchEthereumChainRequest("0x1");
530
+ it("creates SwitchEthereumChainRequest", async () => {
531
+ const program = Wallet.SwitchEthereumChainRequest("0x1").pipe(
532
+ Effect.provide(IdCounterLive),
533
+ );
534
+ const req = await Effect.runPromise(program);
512
535
  expect(req.method).toBe("wallet_switchEthereumChain");
513
536
  });
514
537
  });
515
538
 
516
539
  describe("Net namespace", () => {
517
- it("creates VersionRequest", () => {
518
- const req = Net.VersionRequest();
540
+ it("creates VersionRequest", async () => {
541
+ const program = Net.VersionRequest().pipe(Effect.provide(IdCounterLive));
542
+ const req = await Effect.runPromise(program);
519
543
  expect(req.method).toBe("net_version");
520
544
  });
521
545
 
522
- it("creates ListeningRequest", () => {
523
- const req = Net.ListeningRequest();
546
+ it("creates ListeningRequest", async () => {
547
+ const program = Net.ListeningRequest().pipe(
548
+ Effect.provide(IdCounterLive),
549
+ );
550
+ const req = await Effect.runPromise(program);
524
551
  expect(req.method).toBe("net_listening");
525
552
  });
526
553
 
527
- it("creates PeerCountRequest", () => {
528
- const req = Net.PeerCountRequest();
554
+ it("creates PeerCountRequest", async () => {
555
+ const program = Net.PeerCountRequest().pipe(
556
+ Effect.provide(IdCounterLive),
557
+ );
558
+ const req = await Effect.runPromise(program);
529
559
  expect(req.method).toBe("net_peerCount");
530
560
  });
531
561
  });
532
562
 
533
563
  describe("Web3 namespace", () => {
534
- it("creates ClientVersionRequest", () => {
535
- const req = Web3.ClientVersionRequest();
564
+ it("creates ClientVersionRequest", async () => {
565
+ const program = Web3.ClientVersionRequest().pipe(
566
+ Effect.provide(IdCounterLive),
567
+ );
568
+ const req = await Effect.runPromise(program);
536
569
  expect(req.method).toBe("web3_clientVersion");
537
570
  });
538
571
 
539
- it("creates Sha3Request", () => {
540
- const req = Web3.Sha3Request("0x68656c6c6f");
572
+ it("creates Sha3Request", async () => {
573
+ const program = Web3.Sha3Request("0x68656c6c6f").pipe(
574
+ Effect.provide(IdCounterLive),
575
+ );
576
+ const req = await Effect.runPromise(program);
541
577
  expect(req.method).toBe("web3_sha3");
542
578
  expect(req.params).toEqual(["0x68656c6c6f"]);
543
579
  });
544
580
  });
545
581
 
546
582
  describe("Txpool namespace", () => {
547
- it("creates StatusRequest", () => {
548
- const req = Txpool.StatusRequest();
583
+ it("creates StatusRequest", async () => {
584
+ const program = Txpool.StatusRequest().pipe(
585
+ Effect.provide(IdCounterLive),
586
+ );
587
+ const req = await Effect.runPromise(program);
549
588
  expect(req.method).toBe("txpool_status");
550
589
  });
551
590
 
552
- it("creates ContentRequest", () => {
553
- const req = Txpool.ContentRequest();
591
+ it("creates ContentRequest", async () => {
592
+ const program = Txpool.ContentRequest().pipe(
593
+ Effect.provide(IdCounterLive),
594
+ );
595
+ const req = await Effect.runPromise(program);
554
596
  expect(req.method).toBe("txpool_content");
555
597
  });
556
598
 
557
- it("creates InspectRequest", () => {
558
- const req = Txpool.InspectRequest();
599
+ it("creates InspectRequest", async () => {
600
+ const program = Txpool.InspectRequest().pipe(
601
+ Effect.provide(IdCounterLive),
602
+ );
603
+ const req = await Effect.runPromise(program);
559
604
  expect(req.method).toBe("txpool_inspect");
560
605
  });
561
606
  });
@@ -569,41 +614,61 @@ describe("JsonRpc", () => {
569
614
  });
570
615
 
571
616
  describe("ID Counter", () => {
572
- beforeEach(() => {
573
- resetId();
574
- });
575
-
576
- it("generates unique IDs across namespaces", () => {
577
- const ids = new Set<number>();
578
-
579
- // Generate requests from multiple namespaces
580
- ids.add(Eth.BlockNumberRequest().id as number);
581
- ids.add(Eth.ChainIdRequest().id as number);
582
- ids.add(Net.VersionRequest().id as number);
583
- ids.add(Web3.ClientVersionRequest().id as number);
584
- ids.add(Txpool.StatusRequest().id as number);
585
- ids.add(Wallet.GetPermissionsRequest().id as number);
586
- ids.add(Anvil.GetAutomineRequest().id as number);
587
- ids.add(Hardhat.MineRequest().id as number);
588
-
617
+ it("generates unique IDs across namespaces", async () => {
618
+ const program = Effect.gen(function* () {
619
+ const ids = new Set<number>();
620
+
621
+ // Generate requests from multiple namespaces
622
+ const req1 = yield* Eth.BlockNumberRequest();
623
+ ids.add(req1.id as number);
624
+ const req2 = yield* Eth.ChainIdRequest();
625
+ ids.add(req2.id as number);
626
+ const req3 = yield* Net.VersionRequest();
627
+ ids.add(req3.id as number);
628
+ const req4 = yield* Web3.ClientVersionRequest();
629
+ ids.add(req4.id as number);
630
+ const req5 = yield* Txpool.StatusRequest();
631
+ ids.add(req5.id as number);
632
+ const req6 = yield* Wallet.GetPermissionsRequest();
633
+ ids.add(req6.id as number);
634
+ const req7 = yield* Anvil.GetAutomineRequest();
635
+ ids.add(req7.id as number);
636
+ const req8 = yield* Hardhat.MineRequest();
637
+ ids.add(req8.id as number);
638
+
639
+ return ids;
640
+ }).pipe(Effect.provide(IdCounterLive));
641
+
642
+ const ids = await Effect.runPromise(program);
589
643
  // All 8 IDs should be unique
590
644
  expect(ids.size).toBe(8);
591
645
  });
592
646
 
593
- it("increments IDs sequentially", () => {
594
- const id1 = Eth.BlockNumberRequest().id as number;
595
- const id2 = Net.VersionRequest().id as number;
596
- const id3 = Web3.ClientVersionRequest().id as number;
597
-
647
+ it("increments IDs sequentially", async () => {
648
+ const program = Effect.gen(function* () {
649
+ const req1 = yield* Eth.BlockNumberRequest();
650
+ const req2 = yield* Net.VersionRequest();
651
+ const req3 = yield* Web3.ClientVersionRequest();
652
+
653
+ return {
654
+ id1: req1.id as number,
655
+ id2: req2.id as number,
656
+ id3: req3.id as number,
657
+ };
658
+ }).pipe(Effect.provide(IdCounterLive));
659
+
660
+ const { id1, id2, id3 } = await Effect.runPromise(program);
598
661
  expect(id2).toBe(id1 + 1);
599
662
  expect(id3).toBe(id2 + 1);
600
663
  });
601
664
 
602
- it("resetId resets the counter", () => {
603
- Eth.BlockNumberRequest();
604
- Eth.ChainIdRequest();
605
- resetId();
606
- const id = Eth.BlockNumberRequest().id as number;
665
+ it("isolated counter starts fresh", async () => {
666
+ const program = Effect.gen(function* () {
667
+ const req = yield* Eth.BlockNumberRequest();
668
+ return req.id as number;
669
+ }).pipe(Effect.provide(IdCounterLive));
670
+
671
+ const id = await Effect.runPromise(program);
607
672
  expect(id).toBe(1);
608
673
  });
609
674
  });
@@ -1,16 +1,21 @@
1
- import { nextId } from "./IdCounter.js";
1
+ import * as Effect from "effect/Effect";
2
+ import { IdCounterService } from "./IdCounter.js";
2
3
  import type { JsonRpcRequestType } from "./Request.js";
3
4
 
4
5
  function makeRequest(
5
6
  method: string,
6
7
  params: unknown[] = [],
7
- ): JsonRpcRequestType {
8
- return {
9
- jsonrpc: "2.0",
10
- method,
11
- params,
12
- id: nextId(),
13
- };
8
+ ): Effect.Effect<JsonRpcRequestType, never, IdCounterService> {
9
+ return Effect.gen(function* () {
10
+ const counter = yield* IdCounterService;
11
+ const id = yield* counter.next();
12
+ return {
13
+ jsonrpc: "2.0",
14
+ method,
15
+ params,
16
+ id,
17
+ };
18
+ });
14
19
  }
15
20
 
16
21
  export const VersionRequest = () => makeRequest("net_version");
@@ -1,4 +1,5 @@
1
- import { nextId } from "./IdCounter.js";
1
+ import * as Effect from "effect/Effect";
2
+ import { IdCounterService } from "./IdCounter.js";
2
3
 
3
4
  export type JsonRpcIdType = number | string | null;
4
5
 
@@ -28,13 +29,20 @@ export function isNotification(request: JsonRpcRequestType): boolean {
28
29
 
29
30
  export function withParams<TParams>(
30
31
  request: JsonRpcRequestType,
31
- ): (params: TParams) => JsonRpcRequestType<TParams> {
32
- return (params: TParams) => ({
33
- jsonrpc: "2.0",
34
- method: request.method,
35
- params,
36
- id: request.id ?? nextId(),
37
- });
32
+ ): (
33
+ params: TParams,
34
+ ) => Effect.Effect<JsonRpcRequestType<TParams>, never, IdCounterService> {
35
+ return (params: TParams) =>
36
+ Effect.gen(function* () {
37
+ const counter = yield* IdCounterService;
38
+ const id = request.id ?? (yield* counter.next());
39
+ return {
40
+ jsonrpc: "2.0",
41
+ method: request.method,
42
+ params,
43
+ id,
44
+ };
45
+ });
38
46
  }
39
47
 
40
48
  export const Request = {