viem 2.4.0 → 2.5.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 (111) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/chains/definitions/blastSepolia.js +26 -0
  3. package/_cjs/chains/definitions/blastSepolia.js.map +1 -0
  4. package/_cjs/chains/index.js +6 -4
  5. package/_cjs/chains/index.js.map +1 -1
  6. package/_cjs/clients/transports/http.js +8 -5
  7. package/_cjs/clients/transports/http.js.map +1 -1
  8. package/_cjs/clients/transports/ipc.js +77 -0
  9. package/_cjs/clients/transports/ipc.js.map +1 -0
  10. package/_cjs/clients/transports/webSocket.js +11 -7
  11. package/_cjs/clients/transports/webSocket.js.map +1 -1
  12. package/_cjs/errors/version.js +1 -1
  13. package/_cjs/node/index.js +8 -0
  14. package/_cjs/node/index.js.map +1 -0
  15. package/_cjs/utils/index.js +13 -6
  16. package/_cjs/utils/index.js.map +1 -1
  17. package/_cjs/utils/rpc/compat.js +35 -0
  18. package/_cjs/utils/rpc/compat.js.map +1 -0
  19. package/_cjs/utils/rpc/http.js +74 -0
  20. package/_cjs/utils/rpc/http.js.map +1 -0
  21. package/_cjs/utils/rpc/id.js +17 -0
  22. package/_cjs/utils/rpc/id.js.map +1 -0
  23. package/_cjs/utils/rpc/ipc.js +75 -0
  24. package/_cjs/utils/rpc/ipc.js.map +1 -0
  25. package/_cjs/utils/rpc/socket.js +85 -0
  26. package/_cjs/utils/rpc/socket.js.map +1 -0
  27. package/_cjs/utils/rpc/webSocket.js +50 -0
  28. package/_cjs/utils/rpc/webSocket.js.map +1 -0
  29. package/_esm/chains/definitions/blastSepolia.js +23 -0
  30. package/_esm/chains/definitions/blastSepolia.js.map +1 -0
  31. package/_esm/chains/index.js +1 -0
  32. package/_esm/chains/index.js.map +1 -1
  33. package/_esm/clients/transports/http.js +8 -5
  34. package/_esm/clients/transports/http.js.map +1 -1
  35. package/_esm/clients/transports/ipc.js +77 -0
  36. package/_esm/clients/transports/ipc.js.map +1 -0
  37. package/_esm/clients/transports/webSocket.js +10 -6
  38. package/_esm/clients/transports/webSocket.js.map +1 -1
  39. package/_esm/errors/version.js +1 -1
  40. package/_esm/node/index.js +3 -0
  41. package/_esm/node/index.js.map +1 -0
  42. package/_esm/utils/index.js +4 -1
  43. package/_esm/utils/index.js.map +1 -1
  44. package/_esm/utils/rpc/compat.js +86 -0
  45. package/_esm/utils/rpc/compat.js.map +1 -0
  46. package/_esm/utils/rpc/http.js +70 -0
  47. package/_esm/utils/rpc/http.js.map +1 -0
  48. package/_esm/utils/rpc/id.js +13 -0
  49. package/_esm/utils/rpc/id.js.map +1 -0
  50. package/_esm/utils/rpc/ipc.js +71 -0
  51. package/_esm/utils/rpc/ipc.js.map +1 -0
  52. package/_esm/utils/rpc/socket.js +90 -0
  53. package/_esm/utils/rpc/socket.js.map +1 -0
  54. package/_esm/utils/rpc/webSocket.js +48 -0
  55. package/_esm/utils/rpc/webSocket.js.map +1 -0
  56. package/_types/chains/definitions/blastSepolia.d.ts +34 -0
  57. package/_types/chains/definitions/blastSepolia.d.ts.map +1 -0
  58. package/_types/chains/index.d.ts +1 -0
  59. package/_types/chains/index.d.ts.map +1 -1
  60. package/_types/clients/transports/http.d.ts +2 -2
  61. package/_types/clients/transports/http.d.ts.map +1 -1
  62. package/_types/clients/transports/ipc.d.ts +46 -0
  63. package/_types/clients/transports/ipc.d.ts.map +1 -0
  64. package/_types/clients/transports/webSocket.d.ts +6 -1
  65. package/_types/clients/transports/webSocket.d.ts.map +1 -1
  66. package/_types/errors/version.d.ts +1 -1
  67. package/_types/node/index.d.ts +3 -0
  68. package/_types/node/index.d.ts.map +1 -0
  69. package/_types/types/rpc.d.ts +35 -0
  70. package/_types/types/rpc.d.ts.map +1 -1
  71. package/_types/utils/buildRequest.d.ts +2 -2
  72. package/_types/utils/buildRequest.d.ts.map +1 -1
  73. package/_types/utils/index.d.ts +4 -1
  74. package/_types/utils/index.d.ts.map +1 -1
  75. package/_types/utils/rpc/compat.d.ts +78 -0
  76. package/_types/utils/rpc/compat.d.ts.map +1 -0
  77. package/_types/utils/rpc/http.d.ts +20 -0
  78. package/_types/utils/rpc/http.d.ts.map +1 -0
  79. package/_types/utils/rpc/id.d.ts +11 -0
  80. package/_types/utils/rpc/id.d.ts.map +1 -0
  81. package/_types/utils/rpc/ipc.d.ts +8 -0
  82. package/_types/utils/rpc/ipc.d.ts.map +1 -0
  83. package/_types/utils/rpc/socket.d.ts +45 -0
  84. package/_types/utils/rpc/socket.d.ts.map +1 -0
  85. package/_types/utils/rpc/webSocket.d.ts +3 -0
  86. package/_types/utils/rpc/webSocket.d.ts.map +1 -0
  87. package/chains/definitions/blastSepolia.ts +23 -0
  88. package/chains/index.ts +1 -0
  89. package/clients/transports/http.ts +15 -6
  90. package/clients/transports/ipc.ts +144 -0
  91. package/clients/transports/webSocket.ts +16 -6
  92. package/errors/version.ts +1 -1
  93. package/node/index.ts +11 -0
  94. package/node/package.json +6 -0
  95. package/package.json +9 -1
  96. package/types/rpc.ts +44 -0
  97. package/utils/buildRequest.ts +2 -2
  98. package/utils/index.ts +19 -8
  99. package/utils/rpc/compat.ts +117 -0
  100. package/utils/rpc/http.ts +132 -0
  101. package/utils/rpc/id.ts +13 -0
  102. package/utils/rpc/ipc.ts +89 -0
  103. package/utils/rpc/socket.ts +162 -0
  104. package/utils/rpc/webSocket.ts +63 -0
  105. package/_cjs/utils/rpc.js +0 -154
  106. package/_cjs/utils/rpc.js.map +0 -1
  107. package/_esm/utils/rpc.js +0 -160
  108. package/_esm/utils/rpc.js.map +0 -1
  109. package/_types/utils/rpc.d.ts +0 -80
  110. package/_types/utils/rpc.d.ts.map +0 -1
  111. package/utils/rpc.ts +0 -318
package/node/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export {
2
+ type IpcTransport,
3
+ type IpcTransportConfig,
4
+ type IpcTransportErrorType,
5
+ ipc,
6
+ } from '../clients/transports/ipc.js'
7
+
8
+ export {
9
+ type IpcRpcClient,
10
+ getIpcRpcClient,
11
+ } from '../utils/rpc/ipc.js'
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../_types/node/index.d.ts",
4
+ "module": "../_esm/node/index.js",
5
+ "main": "../_cjs/node/index.js"
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "viem",
3
3
  "description": "TypeScript Interface for Ethereum",
4
- "version": "2.4.0",
4
+ "version": "2.5.0",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
@@ -52,6 +52,11 @@
52
52
  "import": "./_esm/ens/index.js",
53
53
  "default": "./_cjs/ens/index.js"
54
54
  },
55
+ "./node": {
56
+ "types": "./_types/node/index.d.ts",
57
+ "import": "./_esm/node/index.js",
58
+ "default": "./_cjs/node/index.js"
59
+ },
55
60
  "./op-stack": {
56
61
  "types": "./_types/chains/opStack/index.d.ts",
57
62
  "import": "./_esm/chains/opStack/index.js",
@@ -94,6 +99,9 @@
94
99
  "ens": [
95
100
  "./_types/ens/index.d.ts"
96
101
  ],
102
+ "node": [
103
+ "./_types/node/index.d.ts"
104
+ ],
97
105
  "op-stack": [
98
106
  "./_types/chains/opStack/index.d.ts"
99
107
  ],
package/types/rpc.ts CHANGED
@@ -57,3 +57,47 @@ export type RpcTransaction<TPending extends boolean = boolean> = UnionOmit<
57
57
  >,
58
58
  'typeHex'
59
59
  >
60
+
61
+ type SuccessResult<T> = {
62
+ method?: never
63
+ result: T
64
+ error?: never
65
+ }
66
+ type ErrorResult<T> = {
67
+ method?: never
68
+ result?: never
69
+ error: T
70
+ }
71
+ type Subscription<TResult, TError> = {
72
+ method: 'eth_subscription'
73
+ error?: never
74
+ result?: never
75
+ params: {
76
+ subscription: string
77
+ } & (
78
+ | {
79
+ result: TResult
80
+ error?: never
81
+ }
82
+ | {
83
+ result?: never
84
+ error: TError
85
+ }
86
+ )
87
+ }
88
+
89
+ export type RpcRequest = {
90
+ jsonrpc?: '2.0'
91
+ method: string
92
+ params?: any
93
+ id?: number
94
+ }
95
+
96
+ export type RpcResponse<TResult = any, TError = any> = {
97
+ jsonrpc: `${number}`
98
+ id: number
99
+ } & (
100
+ | SuccessResult<TResult>
101
+ | ErrorResult<TError>
102
+ | Subscription<TResult, TError>
103
+ )
@@ -57,7 +57,7 @@ import type {
57
57
  } from '../types/eip1193.js'
58
58
  import type { CreateBatchSchedulerErrorType } from './promise/createBatchScheduler.js'
59
59
  import { type WithRetryErrorType, withRetry } from './promise/withRetry.js'
60
- import type { GetSocketErrorType } from './rpc.js'
60
+ import type { GetSocketRpcClientErrorType } from './rpc/socket.js'
61
61
 
62
62
  export type RequestErrorType =
63
63
  | ChainDisconnectedErrorType
@@ -67,7 +67,7 @@ export type RequestErrorType =
67
67
  | InvalidInputRpcErrorType
68
68
  | InvalidParamsRpcErrorType
69
69
  | InvalidRequestRpcErrorType
70
- | GetSocketErrorType
70
+ | GetSocketRpcClientErrorType
71
71
  | JsonRpcVersionUnsupportedErrorType
72
72
  | LimitExceededRpcErrorType
73
73
  | MethodNotFoundRpcErrorType
package/utils/index.ts CHANGED
@@ -32,13 +32,6 @@ export {
32
32
  export { arrayRegex, bytesRegex, integerRegex } from './regex.js'
33
33
 
34
34
  export {
35
- type GetSocketErrorType,
36
- type HttpErrorType,
37
- type HttpOptions,
38
- type HttpReturnType,
39
- type RpcRequest,
40
- type RpcResponse,
41
- type Socket,
42
35
  type WebSocketAsyncErrorType,
43
36
  type WebSocketAsyncOptions,
44
37
  type WebSocketAsyncReturnType,
@@ -47,7 +40,25 @@ export {
47
40
  type WebSocketReturnType,
48
41
  getSocket,
49
42
  rpc,
50
- } from './rpc.js'
43
+ } from './rpc/compat.js'
44
+ export {
45
+ type HttpRpcClient,
46
+ type HttpRpcClientOptions,
47
+ type HttpRequestErrorType,
48
+ type HttpRequestParameters,
49
+ type HttpRequestReturnType,
50
+ getHttpRpcClient,
51
+ } from './rpc/http.js'
52
+ export {
53
+ type GetSocketRpcClientErrorType,
54
+ type GetSocketRpcClientParameters,
55
+ type GetSocketParameters,
56
+ type Socket,
57
+ type SocketRpcClient,
58
+ getSocketRpcClient,
59
+ socketClientCache,
60
+ } from './rpc/socket.js'
61
+ export { getWebSocketRpcClient } from './rpc/webSocket.js'
51
62
  export { type StringifyErrorType, stringify } from './stringify.js'
52
63
  export {
53
64
  type DomainSeparatorErrorType,
@@ -0,0 +1,117 @@
1
+ // TODO(v3): This file is here for backwards compatibility, and to prevent breaking changes.
2
+ // These APIs will be removed in v3.
3
+
4
+ /* c8 ignore start */
5
+ import {
6
+ type TimeoutErrorType,
7
+ WebSocketRequestError,
8
+ } from '../../errors/request.js'
9
+ import type { ErrorType } from '../../errors/utils.js'
10
+ import type { RpcResponse } from '../../types/rpc.js'
11
+ import { type WithTimeoutErrorType } from '../promise/withTimeout.js'
12
+ import { type HttpRequestParameters, getHttpRpcClient } from './http.js'
13
+ import { type SocketRpcClient } from './socket.js'
14
+ import { getWebSocketRpcClient } from './webSocket.js'
15
+
16
+ export type WebSocketOptions = Parameters<
17
+ SocketRpcClient<WebSocket>['request']
18
+ >[0]
19
+ export type WebSocketReturnType = SocketRpcClient<WebSocket>
20
+ export type WebSocketErrorType = WebSocketRequestError | ErrorType
21
+
22
+ function webSocket(
23
+ socketClient: SocketRpcClient<WebSocket>,
24
+ { body, onError, onResponse }: WebSocketOptions,
25
+ ): WebSocketReturnType {
26
+ socketClient.request({
27
+ body,
28
+ onError,
29
+ onResponse,
30
+ })
31
+ return socketClient
32
+ }
33
+
34
+ export type WebSocketAsyncOptions = Parameters<
35
+ SocketRpcClient<WebSocket>['requestAsync']
36
+ >[0]
37
+ export type WebSocketAsyncReturnType = RpcResponse
38
+ export type WebSocketAsyncErrorType =
39
+ | WebSocketErrorType
40
+ | TimeoutErrorType
41
+ | WithTimeoutErrorType
42
+ | ErrorType
43
+
44
+ async function webSocketAsync(
45
+ socketClient: SocketRpcClient<WebSocket>,
46
+ { body, timeout = 10_000 }: WebSocketAsyncOptions,
47
+ ): Promise<WebSocketAsyncReturnType> {
48
+ return socketClient.requestAsync({
49
+ body,
50
+ timeout,
51
+ })
52
+ }
53
+
54
+ /**
55
+ * @deprecated use `getSocketClient` instead.
56
+ *
57
+ * ```diff
58
+ * -import { getSocket } from 'viem/utils'
59
+ * +import { getSocketClient } from 'viem/utils'
60
+ *
61
+ * -const socket = await getSocket(url)
62
+ * +const socketClient = await getSocketClient(url)
63
+ * +const socket = socketClient.socket
64
+ * ```
65
+ */
66
+ export async function getSocket(url: string) {
67
+ const client = await getWebSocketRpcClient(url)
68
+ return Object.assign(client.socket, {
69
+ requests: client.requests,
70
+ subscriptions: client.subscriptions,
71
+ })
72
+ }
73
+
74
+ export const rpc = {
75
+ /**
76
+ * @deprecated use `getHttpRpcClient` instead.
77
+ *
78
+ * ```diff
79
+ * -import { rpc } from 'viem/utils'
80
+ * +import { getHttpRpcClient } from 'viem/utils'
81
+ *
82
+ * -rpc.http(url, params)
83
+ * +const httpClient = getHttpRpcClient(url)
84
+ * +httpClient.request(params)
85
+ * ```
86
+ */
87
+ http(url: string, params: HttpRequestParameters) {
88
+ return getHttpRpcClient(url).request(params)
89
+ },
90
+ /**
91
+ * @deprecated use `getWebSocketRpcClient` instead.
92
+ *
93
+ * ```diff
94
+ * -import { rpc } from 'viem/utils'
95
+ * +import { getWebSocketRpcClient } from 'viem/utils'
96
+ *
97
+ * -rpc.webSocket(url, params)
98
+ * +const webSocketClient = getWebSocketRpcClient(url)
99
+ * +webSocketClient.request(params)
100
+ * ```
101
+ */
102
+ webSocket,
103
+ /**
104
+ * @deprecated use `getWebSocketRpcClient` instead.
105
+ *
106
+ * ```diff
107
+ * -import { rpc } from 'viem/utils'
108
+ * +import { getWebSocketRpcClient } from 'viem/utils'
109
+ *
110
+ * -const response = await rpc.webSocketAsync(url, params)
111
+ * +const webSocketClient = getWebSocketRpcClient(url)
112
+ * +const response = await webSocketClient.requestAsync(params)
113
+ * ```
114
+ */
115
+ webSocketAsync,
116
+ }
117
+ /* c8 ignore end */
@@ -0,0 +1,132 @@
1
+ import {
2
+ HttpRequestError,
3
+ type HttpRequestErrorType as HttpRequestErrorType_,
4
+ TimeoutError,
5
+ type TimeoutErrorType,
6
+ } from '../../errors/request.js'
7
+ import type { ErrorType } from '../../errors/utils.js'
8
+ import type { RpcRequest, RpcResponse } from '../../types/rpc.js'
9
+ import {
10
+ type WithTimeoutErrorType,
11
+ withTimeout,
12
+ } from '../promise/withTimeout.js'
13
+ import { stringify } from '../stringify.js'
14
+ import { idCache } from './id.js'
15
+
16
+ export type HttpRpcClientOptions = {
17
+ // Request configuration to pass to `fetch`.
18
+ fetchOptions?: Omit<RequestInit, 'body'>
19
+ // The timeout (in ms) for the request.
20
+ timeout?: number
21
+ }
22
+
23
+ export type HttpRequestParameters<
24
+ TBody extends RpcRequest | RpcRequest[] = RpcRequest,
25
+ > = {
26
+ // The RPC request body.
27
+ body: TBody
28
+ // Request configuration to pass to `fetch`.
29
+ fetchOptions?: HttpRpcClientOptions['fetchOptions']
30
+ // The timeout (in ms) for the request.
31
+ timeout?: HttpRpcClientOptions['timeout']
32
+ }
33
+
34
+ export type HttpRequestReturnType<
35
+ TBody extends RpcRequest | RpcRequest[] = RpcRequest,
36
+ > = TBody extends RpcRequest[] ? RpcResponse[] : RpcResponse
37
+
38
+ export type HttpRequestErrorType =
39
+ | HttpRequestErrorType_
40
+ | TimeoutErrorType
41
+ | WithTimeoutErrorType
42
+ | ErrorType
43
+
44
+ export type HttpRpcClient = {
45
+ request<TBody extends RpcRequest | RpcRequest[]>(
46
+ params: HttpRequestParameters<TBody>,
47
+ ): Promise<HttpRequestReturnType<TBody>>
48
+ }
49
+
50
+ export function getHttpRpcClient(
51
+ url: string,
52
+ options: HttpRpcClientOptions = {},
53
+ ): HttpRpcClient {
54
+ return {
55
+ async request(params) {
56
+ const {
57
+ body,
58
+ fetchOptions = {},
59
+ timeout = options.timeout ?? 10_000,
60
+ } = params
61
+ const {
62
+ headers,
63
+ method,
64
+ signal: signal_,
65
+ } = { ...options.fetchOptions, ...fetchOptions }
66
+
67
+ try {
68
+ const response = await withTimeout(
69
+ async ({ signal }) => {
70
+ const response = await fetch(url, {
71
+ ...fetchOptions,
72
+ body: Array.isArray(body)
73
+ ? stringify(
74
+ body.map((body) => ({
75
+ jsonrpc: '2.0',
76
+ id: body.id ?? idCache.take(),
77
+ ...body,
78
+ })),
79
+ )
80
+ : stringify({
81
+ jsonrpc: '2.0',
82
+ id: body.id ?? idCache.take(),
83
+ ...body,
84
+ }),
85
+ headers: {
86
+ ...headers,
87
+ 'Content-Type': 'application/json',
88
+ },
89
+ method: method || 'POST',
90
+ signal: signal_ || (timeout > 0 ? signal : undefined),
91
+ })
92
+ return response
93
+ },
94
+ {
95
+ errorInstance: new TimeoutError({ body, url }),
96
+ timeout,
97
+ signal: true,
98
+ },
99
+ )
100
+
101
+ let data: any
102
+ if (
103
+ response.headers.get('Content-Type')?.startsWith('application/json')
104
+ ) {
105
+ data = await response.json()
106
+ } else {
107
+ data = await response.text()
108
+ }
109
+
110
+ if (!response.ok) {
111
+ throw new HttpRequestError({
112
+ body,
113
+ details: stringify(data.error) || response.statusText,
114
+ headers: response.headers,
115
+ status: response.status,
116
+ url,
117
+ })
118
+ }
119
+
120
+ return data
121
+ } catch (err) {
122
+ if (err instanceof HttpRequestError) throw err
123
+ if (err instanceof TimeoutError) throw err
124
+ throw new HttpRequestError({
125
+ body,
126
+ details: (err as Error).message,
127
+ url,
128
+ })
129
+ }
130
+ },
131
+ }
132
+ }
@@ -0,0 +1,13 @@
1
+ export function createIdStore() {
2
+ return {
3
+ current: 0,
4
+ take() {
5
+ return this.current++
6
+ },
7
+ reset() {
8
+ this.current = 0
9
+ },
10
+ }
11
+ }
12
+
13
+ export const idCache = /*#__PURE__*/ createIdStore()
@@ -0,0 +1,89 @@
1
+ import { type Socket as NetSocket, connect } from 'node:net'
2
+ import { WebSocketRequestError } from '../../index.js'
3
+ import {
4
+ type Socket,
5
+ type SocketRpcClient,
6
+ getSocketRpcClient,
7
+ } from './socket.js'
8
+
9
+ const openingBrace = '{'.charCodeAt(0)
10
+ const closingBrace = '}'.charCodeAt(0)
11
+
12
+ export function extractMessages(buffer: Buffer): [Buffer[], Buffer] {
13
+ const messages: Buffer[] = []
14
+
15
+ let cursor = 0
16
+ let level = 0
17
+ for (let i = 0; i < buffer.length; i++) {
18
+ if (buffer[i] === openingBrace) level++
19
+ if (buffer[i] === closingBrace) level--
20
+ if (level === 0) {
21
+ const message = buffer.subarray(cursor, i + 1)
22
+ if (
23
+ message[0] === openingBrace &&
24
+ message[message.length - 1] === closingBrace
25
+ )
26
+ messages.push(message)
27
+ cursor = i + 1
28
+ }
29
+ }
30
+
31
+ return [messages, buffer.subarray(cursor)]
32
+ }
33
+
34
+ export type IpcRpcClient = SocketRpcClient<NetSocket>
35
+
36
+ export async function getIpcRpcClient(path: string): Promise<IpcRpcClient> {
37
+ return getSocketRpcClient({
38
+ async getSocket({ onResponse }) {
39
+ const socket = connect(path)
40
+
41
+ function onClose() {
42
+ socket.off('close', onClose)
43
+ socket.off('message', onData)
44
+ }
45
+
46
+ let lastRemaining = Buffer.alloc(0)
47
+ function onData(buffer: Buffer) {
48
+ const [messages, remaining] = extractMessages(
49
+ Buffer.concat([lastRemaining, buffer]),
50
+ )
51
+ for (const message of messages) {
52
+ const response = JSON.parse(Buffer.from(message).toString())
53
+ onResponse(response)
54
+ }
55
+ lastRemaining = remaining
56
+ }
57
+
58
+ socket.on('close', onClose)
59
+ socket.on('data', onData)
60
+
61
+ // Wait for the socket to open.
62
+ await new Promise<void>((resolve, reject) => {
63
+ socket.on('ready', () => {
64
+ resolve()
65
+ socket.off('error', reject)
66
+ })
67
+ socket.on('error', reject)
68
+ })
69
+
70
+ return Object.assign(socket, {
71
+ close() {
72
+ socket.destroy()
73
+ socket.end()
74
+ },
75
+ request({ body }) {
76
+ if (socket.readyState !== 'open')
77
+ throw new WebSocketRequestError({
78
+ body,
79
+ url: path,
80
+ details: 'Socket is closed.',
81
+ })
82
+
83
+ return socket.write(JSON.stringify(body))
84
+ },
85
+ } as Socket<{}>)
86
+ },
87
+ url: path,
88
+ })
89
+ }
@@ -0,0 +1,162 @@
1
+ import { TimeoutError } from '../../errors/request.js'
2
+ import type { ErrorType } from '../../errors/utils.js'
3
+ import type { RpcRequest, RpcResponse } from '../../types/rpc.js'
4
+ import {
5
+ type CreateBatchSchedulerErrorType,
6
+ createBatchScheduler,
7
+ } from '../promise/createBatchScheduler.js'
8
+ import { withTimeout } from '../promise/withTimeout.js'
9
+ import { idCache } from './id.js'
10
+
11
+ type Id = string | number
12
+ type CallbackFn = (message: any) => void
13
+ type CallbackMap = Map<Id, CallbackFn>
14
+
15
+ export type GetSocketParameters = {
16
+ onResponse: (data: RpcResponse) => void
17
+ }
18
+
19
+ export type Socket<socket extends {}> = socket & {
20
+ close(): void
21
+ request(params: {
22
+ body: RpcRequest
23
+ }): void
24
+ }
25
+
26
+ export type SocketRpcClient<socket extends {}> = {
27
+ close(): void
28
+ socket: Socket<socket>
29
+ request(params: {
30
+ body: RpcRequest
31
+ onError?: (error: Error) => void
32
+ onResponse: (message: RpcResponse) => void
33
+ }): void
34
+ requestAsync(params: {
35
+ body: RpcRequest
36
+ timeout?: number
37
+ }): Promise<RpcResponse>
38
+ requests: CallbackMap
39
+ subscriptions: CallbackMap
40
+ url: string
41
+ }
42
+
43
+ export type GetSocketRpcClientParameters<socket extends {}> = {
44
+ url: string
45
+ getSocket(params: GetSocketParameters): Promise<Socket<socket>>
46
+ }
47
+
48
+ export type GetSocketRpcClientErrorType =
49
+ | CreateBatchSchedulerErrorType
50
+ | ErrorType
51
+
52
+ export const socketClientCache = /*#__PURE__*/ new Map<
53
+ string,
54
+ SocketRpcClient<Socket<{}>>
55
+ >()
56
+
57
+ export async function getSocketRpcClient<socket extends {}>(
58
+ params: GetSocketRpcClientParameters<socket>,
59
+ ): Promise<SocketRpcClient<socket>> {
60
+ const { getSocket, url } = params
61
+
62
+ let socketClient = socketClientCache.get(url)
63
+
64
+ // If the socket already exists, return it.
65
+ if (socketClient) return socketClient as {} as SocketRpcClient<socket>
66
+
67
+ const { schedule } = createBatchScheduler<
68
+ undefined,
69
+ [SocketRpcClient<socket>]
70
+ >({
71
+ id: url,
72
+ fn: async () => {
73
+ // Set up a cache for incoming "synchronous" requests.
74
+ const requests = new Map<Id, CallbackFn>()
75
+
76
+ // Set up a cache for subscriptions (eth_subscribe).
77
+ const subscriptions = new Map<Id, CallbackFn>()
78
+
79
+ // Set up socket implementation.
80
+ const socket = await getSocket({
81
+ onResponse(data) {
82
+ const isSubscription = data.method === 'eth_subscription'
83
+ const id = isSubscription ? data.params.subscription : data.id
84
+ const cache = isSubscription ? subscriptions : requests
85
+ const callback = cache.get(id)
86
+ if (callback) callback(data)
87
+ if (!isSubscription) cache.delete(id)
88
+ },
89
+ })
90
+
91
+ // Create a new socket instance.
92
+ socketClient = {
93
+ close() {
94
+ socket.close()
95
+ socketClientCache.delete(url)
96
+ },
97
+ socket,
98
+ request({ body, onError, onResponse }) {
99
+ const id = body.id ?? idCache.take()
100
+
101
+ const callback = (response: RpcResponse) => {
102
+ if (typeof response.id === 'number' && id !== response.id) return
103
+
104
+ // If we are subscribing to a topic, we want to set up a listener for incoming
105
+ // messages.
106
+ if (
107
+ body.method === 'eth_subscribe' &&
108
+ typeof response.result === 'string'
109
+ )
110
+ subscriptions.set(response.result, callback)
111
+
112
+ // If we are unsubscribing from a topic, we want to remove the listener.
113
+ if (body.method === 'eth_unsubscribe')
114
+ subscriptions.delete(body.params?.[0])
115
+
116
+ onResponse(response)
117
+
118
+ // TODO: delete request?
119
+ }
120
+
121
+ requests.set(id, callback)
122
+ try {
123
+ socket.request({
124
+ body: {
125
+ jsonrpc: '2.0',
126
+ id,
127
+ ...body,
128
+ },
129
+ })
130
+ } catch (error) {
131
+ onError?.(error as Error)
132
+ }
133
+ },
134
+ requestAsync({ body, timeout = 10_000 }) {
135
+ return withTimeout(
136
+ () =>
137
+ new Promise<RpcResponse>((onResponse, onError) =>
138
+ this.request({
139
+ body,
140
+ onError,
141
+ onResponse,
142
+ }),
143
+ ),
144
+ {
145
+ errorInstance: new TimeoutError({ body, url }),
146
+ timeout,
147
+ },
148
+ )
149
+ },
150
+ requests,
151
+ subscriptions,
152
+ url,
153
+ }
154
+ socketClientCache.set(url, socketClient)
155
+
156
+ return [socketClient as {} as SocketRpcClient<socket>]
157
+ },
158
+ })
159
+
160
+ const [_, [socketClient_]] = await schedule()
161
+ return socketClient_
162
+ }