Skip to content

Commit 28ff809

Browse files
authored
feat: add methods.{include|exclude} on transport options (#3238)
* feat: add `methods.{include|exclude}` on transport options * docs: add * chore: changeset * u
1 parent 23c9598 commit 28ff809

File tree

17 files changed

+297
-22
lines changed

17 files changed

+297
-22
lines changed

.changeset/shaggy-taxis-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"viem": patch
3+
---
4+
5+
Added `methods` property to `http`, `ipc`, and `webSocket` transports to include or exclude RPC methods from being executed.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
{
122122
"name": "import { createClient, http } from 'viem'",
123123
"path": "./src/_esm/index.js",
124-
"limit": "6.5 kB",
124+
"limit": "6.6 kB",
125125
"import": "{ createClient, http }"
126126
},
127127
{

site/pages/docs/clients/transports/http.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,22 @@ const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
156156
})
157157
```
158158

159+
### methods (optional)
160+
161+
- **Type:** `{ include?: string[], exclude?: string[] }`
162+
163+
Methods to include or exclude from sending RPC requests.
164+
165+
```ts twoslash
166+
import { http } from 'viem'
167+
// ---cut---
168+
const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
169+
methods: {
170+
include: ['eth_sendTransaction', 'eth_signTypedData_v4'],
171+
},
172+
})
173+
```
174+
159175
### name (optional)
160176

161177
- **Type:** `string`

site/pages/docs/clients/transports/ipc.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ const transport = ipc('/tmp/reth.ipc', {
5050
})
5151
```
5252

53+
### methods (optional)
54+
55+
- **Type:** `{ include?: string[], exclude?: string[] }`
56+
57+
Methods to include or exclude from sending RPC requests.
58+
59+
```ts twoslash
60+
import { ipc } from 'viem/node'
61+
// ---cut---
62+
const transport = ipc('/tmp/reth.ipc', {
63+
methods: {
64+
include: ['eth_sendTransaction', 'eth_signTypedData_v4'],
65+
},
66+
})
67+
```
68+
5369
### name (optional)
5470

5571
- **Type:** `string`

site/pages/docs/clients/transports/websocket.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ const transport = webSocket('wss://eth-mainnet.g.alchemy.com/v2/...', {
6868
})
6969
```
7070

71+
### methods (optional)
72+
73+
- **Type:** `{ include?: string[], exclude?: string[] }`
74+
75+
Methods to include or exclude from sending RPC requests.
76+
77+
```ts twoslash
78+
import { webSocket } from 'viem'
79+
// ---cut---
80+
const transport = webSocket('wss://eth-mainnet.g.alchemy.com/v2/...', {
81+
methods: {
82+
include: ['eth_sendTransaction', 'eth_signTypedData_v4'],
83+
},
84+
})
85+
```
86+
7187
### name (optional)
7288

7389
- **Type:** `string`

src/clients/transports/createTransport.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ErrorType } from '../../errors/utils.js'
22
import type { Chain } from '../../types/chain.js'
33
import type { EIP1193RequestFn } from '../../types/eip1193.js'
4+
import type { OneOf } from '../../types/utils.js'
45
import { buildRequest } from '../../utils/buildRequest.js'
56
import { uid as uid_ } from '../../utils/uid.js'
67
import type { ClientConfig } from '../createClient.js'
@@ -13,6 +14,17 @@ export type TransportConfig<
1314
name: string
1415
/** The key of the transport. */
1516
key: string
17+
/** Methods to include or exclude from executing RPC requests. */
18+
methods?:
19+
| OneOf<
20+
| {
21+
include?: string[] | undefined
22+
}
23+
| {
24+
exclude?: string[] | undefined
25+
}
26+
>
27+
| undefined
1628
/** The JSON-RPC request function that matches the EIP-1193 request spec. */
1729
request: eip1193RequestFn
1830
/** The base delay (in ms) between retries. */
@@ -53,6 +65,7 @@ export function createTransport<
5365
>(
5466
{
5567
key,
68+
methods,
5669
name,
5770
request,
5871
retryCount = 3,
@@ -73,7 +86,7 @@ export function createTransport<
7386
timeout,
7487
type,
7588
},
76-
request: buildRequest(request, { retryCount, retryDelay, uid }),
89+
request: buildRequest(request, { methods, retryCount, retryDelay, uid }),
7790
value,
7891
}
7992
}

src/clients/transports/custom.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ type EthereumProvider = { request(...args: any): Promise<any> }
1111
export type CustomTransportConfig = {
1212
/** The key of the transport. */
1313
key?: TransportConfig['key'] | undefined
14+
/** Methods to include or exclude from executing RPC requests. */
15+
methods?: TransportConfig['methods'] | undefined
1416
/** The name of the transport. */
1517
name?: TransportConfig['name'] | undefined
1618
/** The max number of times to retry. */
@@ -34,10 +36,16 @@ export function custom<provider extends EthereumProvider>(
3436
provider: provider,
3537
config: CustomTransportConfig = {},
3638
): CustomTransport {
37-
const { key = 'custom', name = 'Custom Provider', retryDelay } = config
39+
const {
40+
key = 'custom',
41+
methods,
42+
name = 'Custom Provider',
43+
retryDelay,
44+
} = config
3845
return ({ retryCount: defaultRetryCount }) =>
3946
createTransport({
4047
key,
48+
methods,
4149
name,
4250
request: provider.request.bind(provider),
4351
retryCount: config.retryCount ?? defaultRetryCount,

src/clients/transports/fallback.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,57 @@ describe('request', () => {
216216
`)
217217
})
218218

219+
test('methods.exclude', async () => {
220+
const server1 = await createHttpServer((_req, res) => {
221+
res.writeHead(200, {
222+
'Content-Type': 'application/json',
223+
})
224+
res.end(JSON.stringify({ result: '0x1' }))
225+
})
226+
const server2 = await createHttpServer((_req, res) => {
227+
res.writeHead(200, {
228+
'Content-Type': 'application/json',
229+
})
230+
res.end(JSON.stringify({ result: '0x2' }))
231+
})
232+
233+
const transport = fallback([
234+
http(server1.url, { methods: { exclude: ['eth_a'] } }),
235+
http(server2.url),
236+
])({
237+
chain: localhost,
238+
})
239+
240+
expect(await transport.request({ method: 'eth_a' })).toBe('0x2')
241+
expect(await transport.request({ method: 'eth_b' })).toBe('0x1')
242+
})
243+
244+
test('methods.include', async () => {
245+
const server1 = await createHttpServer((_req, res) => {
246+
res.writeHead(200, {
247+
'Content-Type': 'application/json',
248+
})
249+
res.end(JSON.stringify({ result: '0x1' }))
250+
})
251+
const server2 = await createHttpServer((_req, res) => {
252+
res.writeHead(200, {
253+
'Content-Type': 'application/json',
254+
})
255+
res.end(JSON.stringify({ result: '0x2' }))
256+
})
257+
258+
const transport = fallback([
259+
http(server1.url, { methods: { include: ['eth_a', 'eth_b'] } }),
260+
http(server2.url),
261+
])({
262+
chain: localhost,
263+
})
264+
265+
expect(await transport.request({ method: 'eth_a' })).toBe('0x1')
266+
expect(await transport.request({ method: 'eth_b' })).toBe('0x1')
267+
expect(await transport.request({ method: 'eth_c' })).toBe('0x2')
268+
})
269+
219270
test('error (rpc)', async () => {
220271
let count = 0
221272
const server1 = await createHttpServer((_req, res) => {

src/clients/transports/http.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,53 @@ describe('request', () => {
389389
await server.close()
390390
})
391391

392+
test('behavior: methods.exclude', async () => {
393+
const server = await createHttpServer((_, res) => {
394+
res.end(JSON.stringify({ result: '0x1' }))
395+
})
396+
397+
const transport = http(server.url, {
398+
key: 'jsonRpc',
399+
name: 'JSON RPC',
400+
methods: { exclude: ['eth_a'] },
401+
})({ chain: localhost })
402+
403+
await transport.request({ method: 'eth_b' })
404+
405+
await expect(() =>
406+
transport.request({ method: 'eth_a' }),
407+
).rejects.toThrowErrorMatchingInlineSnapshot(`
408+
[MethodNotSupportedRpcError: Method "eth_a" is not supported.
409+
410+
Details: method not supported
411+
412+
`)
413+
})
414+
415+
test('behavior: methods.include', async () => {
416+
const server = await createHttpServer((_, res) => {
417+
res.end(JSON.stringify({ result: '0x1' }))
418+
})
419+
420+
const transport = http(server.url, {
421+
key: 'jsonRpc',
422+
name: 'JSON RPC',
423+
methods: { include: ['eth_a', 'eth_b'] },
424+
})({ chain: localhost })
425+
426+
await transport.request({ method: 'eth_a' })
427+
await transport.request({ method: 'eth_b' })
428+
429+
await expect(() =>
430+
transport.request({ method: 'eth_c' }),
431+
).rejects.toThrowErrorMatchingInlineSnapshot(`
432+
[MethodNotSupportedRpcError: Method "eth_c" is not supported.
433+
434+
Details: method not supported
435+
436+
`)
437+
})
438+
392439
test('behavior: retryCount', async () => {
393440
let retryCount = -1
394441
const server = await createHttpServer((_req, res) => {

src/clients/transports/http.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export type HttpTransportConfig = {
4343
onFetchResponse?: HttpRpcClientOptions['onResponse'] | undefined
4444
/** The key of the HTTP transport. */
4545
key?: TransportConfig['key'] | undefined
46+
/** Methods to include or exclude from executing RPC requests. */
47+
methods?: TransportConfig['methods'] | undefined
4648
/** The name of the HTTP transport. */
4749
name?: TransportConfig['name'] | undefined
4850
/** The max number of times to retry. */
@@ -78,6 +80,7 @@ export function http(
7880
batch,
7981
fetchOptions,
8082
key = 'http',
83+
methods,
8184
name = 'HTTP JSON-RPC',
8285
onFetchRequest,
8386
onFetchResponse,
@@ -101,6 +104,7 @@ export function http(
101104
return createTransport(
102105
{
103106
key,
107+
methods,
104108
name,
105109
async request({ method, params }) {
106110
const body = { method, params }

0 commit comments

Comments
 (0)