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.
- package/CHANGELOG.md +12 -0
- package/_cjs/chains/definitions/blastSepolia.js +26 -0
- package/_cjs/chains/definitions/blastSepolia.js.map +1 -0
- package/_cjs/chains/index.js +6 -4
- package/_cjs/chains/index.js.map +1 -1
- package/_cjs/clients/transports/http.js +8 -5
- package/_cjs/clients/transports/http.js.map +1 -1
- package/_cjs/clients/transports/ipc.js +77 -0
- package/_cjs/clients/transports/ipc.js.map +1 -0
- package/_cjs/clients/transports/webSocket.js +11 -7
- package/_cjs/clients/transports/webSocket.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_cjs/node/index.js +8 -0
- package/_cjs/node/index.js.map +1 -0
- package/_cjs/utils/index.js +13 -6
- package/_cjs/utils/index.js.map +1 -1
- package/_cjs/utils/rpc/compat.js +35 -0
- package/_cjs/utils/rpc/compat.js.map +1 -0
- package/_cjs/utils/rpc/http.js +74 -0
- package/_cjs/utils/rpc/http.js.map +1 -0
- package/_cjs/utils/rpc/id.js +17 -0
- package/_cjs/utils/rpc/id.js.map +1 -0
- package/_cjs/utils/rpc/ipc.js +75 -0
- package/_cjs/utils/rpc/ipc.js.map +1 -0
- package/_cjs/utils/rpc/socket.js +85 -0
- package/_cjs/utils/rpc/socket.js.map +1 -0
- package/_cjs/utils/rpc/webSocket.js +50 -0
- package/_cjs/utils/rpc/webSocket.js.map +1 -0
- package/_esm/chains/definitions/blastSepolia.js +23 -0
- package/_esm/chains/definitions/blastSepolia.js.map +1 -0
- package/_esm/chains/index.js +1 -0
- package/_esm/chains/index.js.map +1 -1
- package/_esm/clients/transports/http.js +8 -5
- package/_esm/clients/transports/http.js.map +1 -1
- package/_esm/clients/transports/ipc.js +77 -0
- package/_esm/clients/transports/ipc.js.map +1 -0
- package/_esm/clients/transports/webSocket.js +10 -6
- package/_esm/clients/transports/webSocket.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_esm/node/index.js +3 -0
- package/_esm/node/index.js.map +1 -0
- package/_esm/utils/index.js +4 -1
- package/_esm/utils/index.js.map +1 -1
- package/_esm/utils/rpc/compat.js +86 -0
- package/_esm/utils/rpc/compat.js.map +1 -0
- package/_esm/utils/rpc/http.js +70 -0
- package/_esm/utils/rpc/http.js.map +1 -0
- package/_esm/utils/rpc/id.js +13 -0
- package/_esm/utils/rpc/id.js.map +1 -0
- package/_esm/utils/rpc/ipc.js +71 -0
- package/_esm/utils/rpc/ipc.js.map +1 -0
- package/_esm/utils/rpc/socket.js +90 -0
- package/_esm/utils/rpc/socket.js.map +1 -0
- package/_esm/utils/rpc/webSocket.js +48 -0
- package/_esm/utils/rpc/webSocket.js.map +1 -0
- package/_types/chains/definitions/blastSepolia.d.ts +34 -0
- package/_types/chains/definitions/blastSepolia.d.ts.map +1 -0
- package/_types/chains/index.d.ts +1 -0
- package/_types/chains/index.d.ts.map +1 -1
- package/_types/clients/transports/http.d.ts +2 -2
- package/_types/clients/transports/http.d.ts.map +1 -1
- package/_types/clients/transports/ipc.d.ts +46 -0
- package/_types/clients/transports/ipc.d.ts.map +1 -0
- package/_types/clients/transports/webSocket.d.ts +6 -1
- package/_types/clients/transports/webSocket.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/_types/node/index.d.ts +3 -0
- package/_types/node/index.d.ts.map +1 -0
- package/_types/types/rpc.d.ts +35 -0
- package/_types/types/rpc.d.ts.map +1 -1
- package/_types/utils/buildRequest.d.ts +2 -2
- package/_types/utils/buildRequest.d.ts.map +1 -1
- package/_types/utils/index.d.ts +4 -1
- package/_types/utils/index.d.ts.map +1 -1
- package/_types/utils/rpc/compat.d.ts +78 -0
- package/_types/utils/rpc/compat.d.ts.map +1 -0
- package/_types/utils/rpc/http.d.ts +20 -0
- package/_types/utils/rpc/http.d.ts.map +1 -0
- package/_types/utils/rpc/id.d.ts +11 -0
- package/_types/utils/rpc/id.d.ts.map +1 -0
- package/_types/utils/rpc/ipc.d.ts +8 -0
- package/_types/utils/rpc/ipc.d.ts.map +1 -0
- package/_types/utils/rpc/socket.d.ts +45 -0
- package/_types/utils/rpc/socket.d.ts.map +1 -0
- package/_types/utils/rpc/webSocket.d.ts +3 -0
- package/_types/utils/rpc/webSocket.d.ts.map +1 -0
- package/chains/definitions/blastSepolia.ts +23 -0
- package/chains/index.ts +1 -0
- package/clients/transports/http.ts +15 -6
- package/clients/transports/ipc.ts +144 -0
- package/clients/transports/webSocket.ts +16 -6
- package/errors/version.ts +1 -1
- package/node/index.ts +11 -0
- package/node/package.json +6 -0
- package/package.json +9 -1
- package/types/rpc.ts +44 -0
- package/utils/buildRequest.ts +2 -2
- package/utils/index.ts +19 -8
- package/utils/rpc/compat.ts +117 -0
- package/utils/rpc/http.ts +132 -0
- package/utils/rpc/id.ts +13 -0
- package/utils/rpc/ipc.ts +89 -0
- package/utils/rpc/socket.ts +162 -0
- package/utils/rpc/webSocket.ts +63 -0
- package/_cjs/utils/rpc.js +0 -154
- package/_cjs/utils/rpc.js.map +0 -1
- package/_esm/utils/rpc.js +0 -160
- package/_esm/utils/rpc.js.map +0 -1
- package/_types/utils/rpc.d.ts +0 -80
- package/_types/utils/rpc.d.ts.map +0 -1
- package/utils/rpc.ts +0 -318
package/node/index.ts
ADDED
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
|
+
"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
|
+
)
|
package/utils/buildRequest.ts
CHANGED
@@ -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 {
|
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
|
-
|
|
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
|
+
}
|
package/utils/rpc/id.ts
ADDED
package/utils/rpc/ipc.ts
ADDED
@@ -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
|
+
}
|