Skip to content

Commit

Permalink
feat: add methods.{include|exclude} on transport options (#3238)
Browse files Browse the repository at this point in the history
* feat: add `methods.{include|exclude}` on transport options

* docs: add

* chore: changeset

* u
  • Loading branch information
jxom authored Jan 19, 2025
1 parent 23c9598 commit 28ff809
Show file tree
Hide file tree
Showing 17 changed files with 297 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-taxis-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `methods` property to `http`, `ipc`, and `webSocket` transports to include or exclude RPC methods from being executed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
{
"name": "import { createClient, http } from 'viem'",
"path": "./src/_esm/index.js",
"limit": "6.5 kB",
"limit": "6.6 kB",
"import": "{ createClient, http }"
},
{
Expand Down
16 changes: 16 additions & 0 deletions site/pages/docs/clients/transports/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
})
```

### methods (optional)

- **Type:** `{ include?: string[], exclude?: string[] }`

Methods to include or exclude from sending RPC requests.

```ts twoslash
import { http } from 'viem'
// ---cut---
const transport = http('https://eth-mainnet.g.alchemy.com/v2/...', {
methods: {
include: ['eth_sendTransaction', 'eth_signTypedData_v4'],
},
})
```

### name (optional)

- **Type:** `string`
Expand Down
16 changes: 16 additions & 0 deletions site/pages/docs/clients/transports/ipc.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ const transport = ipc('/tmp/reth.ipc', {
})
```

### methods (optional)

- **Type:** `{ include?: string[], exclude?: string[] }`

Methods to include or exclude from sending RPC requests.

```ts twoslash
import { ipc } from 'viem/node'
// ---cut---
const transport = ipc('/tmp/reth.ipc', {
methods: {
include: ['eth_sendTransaction', 'eth_signTypedData_v4'],
},
})
```

### name (optional)

- **Type:** `string`
Expand Down
16 changes: 16 additions & 0 deletions site/pages/docs/clients/transports/websocket.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ const transport = webSocket('wss://eth-mainnet.g.alchemy.com/v2/...', {
})
```

### methods (optional)

- **Type:** `{ include?: string[], exclude?: string[] }`

Methods to include or exclude from sending RPC requests.

```ts twoslash
import { webSocket } from 'viem'
// ---cut---
const transport = webSocket('wss://eth-mainnet.g.alchemy.com/v2/...', {
methods: {
include: ['eth_sendTransaction', 'eth_signTypedData_v4'],
},
})
```

### name (optional)

- **Type:** `string`
Expand Down
15 changes: 14 additions & 1 deletion src/clients/transports/createTransport.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ErrorType } from '../../errors/utils.js'
import type { Chain } from '../../types/chain.js'
import type { EIP1193RequestFn } from '../../types/eip1193.js'
import type { OneOf } from '../../types/utils.js'
import { buildRequest } from '../../utils/buildRequest.js'
import { uid as uid_ } from '../../utils/uid.js'
import type { ClientConfig } from '../createClient.js'
Expand All @@ -13,6 +14,17 @@ export type TransportConfig<
name: string
/** The key of the transport. */
key: string
/** Methods to include or exclude from executing RPC requests. */
methods?:
| OneOf<
| {
include?: string[] | undefined
}
| {
exclude?: string[] | undefined
}
>
| undefined
/** The JSON-RPC request function that matches the EIP-1193 request spec. */
request: eip1193RequestFn
/** The base delay (in ms) between retries. */
Expand Down Expand Up @@ -53,6 +65,7 @@ export function createTransport<
>(
{
key,
methods,
name,
request,
retryCount = 3,
Expand All @@ -73,7 +86,7 @@ export function createTransport<
timeout,
type,
},
request: buildRequest(request, { retryCount, retryDelay, uid }),
request: buildRequest(request, { methods, retryCount, retryDelay, uid }),
value,
}
}
10 changes: 9 additions & 1 deletion src/clients/transports/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type EthereumProvider = { request(...args: any): Promise<any> }
export type CustomTransportConfig = {
/** The key of the transport. */
key?: TransportConfig['key'] | undefined
/** Methods to include or exclude from executing RPC requests. */
methods?: TransportConfig['methods'] | undefined
/** The name of the transport. */
name?: TransportConfig['name'] | undefined
/** The max number of times to retry. */
Expand All @@ -34,10 +36,16 @@ export function custom<provider extends EthereumProvider>(
provider: provider,
config: CustomTransportConfig = {},
): CustomTransport {
const { key = 'custom', name = 'Custom Provider', retryDelay } = config
const {
key = 'custom',
methods,
name = 'Custom Provider',
retryDelay,
} = config
return ({ retryCount: defaultRetryCount }) =>
createTransport({
key,
methods,
name,
request: provider.request.bind(provider),
retryCount: config.retryCount ?? defaultRetryCount,
Expand Down
51 changes: 51 additions & 0 deletions src/clients/transports/fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,57 @@ describe('request', () => {
`)
})

test('methods.exclude', async () => {
const server1 = await createHttpServer((_req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ result: '0x1' }))
})
const server2 = await createHttpServer((_req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ result: '0x2' }))
})

const transport = fallback([
http(server1.url, { methods: { exclude: ['eth_a'] } }),
http(server2.url),
])({
chain: localhost,
})

expect(await transport.request({ method: 'eth_a' })).toBe('0x2')
expect(await transport.request({ method: 'eth_b' })).toBe('0x1')
})

test('methods.include', async () => {
const server1 = await createHttpServer((_req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ result: '0x1' }))
})
const server2 = await createHttpServer((_req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ result: '0x2' }))
})

const transport = fallback([
http(server1.url, { methods: { include: ['eth_a', 'eth_b'] } }),
http(server2.url),
])({
chain: localhost,
})

expect(await transport.request({ method: 'eth_a' })).toBe('0x1')
expect(await transport.request({ method: 'eth_b' })).toBe('0x1')
expect(await transport.request({ method: 'eth_c' })).toBe('0x2')
})

test('error (rpc)', async () => {
let count = 0
const server1 = await createHttpServer((_req, res) => {
Expand Down
47 changes: 47 additions & 0 deletions src/clients/transports/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,53 @@ describe('request', () => {
await server.close()
})

test('behavior: methods.exclude', async () => {
const server = await createHttpServer((_, res) => {
res.end(JSON.stringify({ result: '0x1' }))
})

const transport = http(server.url, {
key: 'jsonRpc',
name: 'JSON RPC',
methods: { exclude: ['eth_a'] },
})({ chain: localhost })

await transport.request({ method: 'eth_b' })

await expect(() =>
transport.request({ method: 'eth_a' }),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[MethodNotSupportedRpcError: Method "eth_a" is not supported.
Details: method not supported
Version: [email protected]]
`)
})

test('behavior: methods.include', async () => {
const server = await createHttpServer((_, res) => {
res.end(JSON.stringify({ result: '0x1' }))
})

const transport = http(server.url, {
key: 'jsonRpc',
name: 'JSON RPC',
methods: { include: ['eth_a', 'eth_b'] },
})({ chain: localhost })

await transport.request({ method: 'eth_a' })
await transport.request({ method: 'eth_b' })

await expect(() =>
transport.request({ method: 'eth_c' }),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[MethodNotSupportedRpcError: Method "eth_c" is not supported.
Details: method not supported
Version: [email protected]]
`)
})

test('behavior: retryCount', async () => {
let retryCount = -1
const server = await createHttpServer((_req, res) => {
Expand Down
4 changes: 4 additions & 0 deletions src/clients/transports/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export type HttpTransportConfig = {
onFetchResponse?: HttpRpcClientOptions['onResponse'] | undefined
/** The key of the HTTP transport. */
key?: TransportConfig['key'] | undefined
/** Methods to include or exclude from executing RPC requests. */
methods?: TransportConfig['methods'] | undefined
/** The name of the HTTP transport. */
name?: TransportConfig['name'] | undefined
/** The max number of times to retry. */
Expand Down Expand Up @@ -78,6 +80,7 @@ export function http(
batch,
fetchOptions,
key = 'http',
methods,
name = 'HTTP JSON-RPC',
onFetchRequest,
onFetchResponse,
Expand All @@ -101,6 +104,7 @@ export function http(
return createTransport(
{
key,
methods,
name,
async request({ method, params }) {
const body = { method, params }
Expand Down
11 changes: 10 additions & 1 deletion src/clients/transports/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type IpcTransportSubscribe = {
export type IpcTransportConfig = {
/** The key of the Ipc transport. */
key?: TransportConfig['key'] | undefined
/** Methods to include or exclude from executing RPC requests. */
methods?: TransportConfig['methods'] | undefined
/** The name of the Ipc transport. */
name?: TransportConfig['name'] | undefined
/**
Expand Down Expand Up @@ -75,13 +77,20 @@ export function ipc(
path: string,
config: IpcTransportConfig = {},
): IpcTransport {
const { key = 'ipc', name = 'IPC JSON-RPC', reconnect, retryDelay } = config
const {
key = 'ipc',
methods,
name = 'IPC JSON-RPC',
reconnect,
retryDelay,
} = config
return ({ retryCount: retryCount_, timeout: timeout_ }) => {
const retryCount = config.retryCount ?? retryCount_
const timeout = timeout_ ?? config.timeout ?? 10_000
return createTransport(
{
key,
methods,
name,
async request({ method, params }) {
const body = { method, params }
Expand Down
4 changes: 4 additions & 0 deletions src/clients/transports/webSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type WebSocketTransportConfig = {
keepAlive?: GetWebSocketRpcClientOptions['keepAlive'] | undefined
/** The key of the WebSocket transport. */
key?: TransportConfig['key'] | undefined
/** Methods to include or exclude from executing RPC requests. */
methods?: TransportConfig['methods'] | undefined
/** The name of the WebSocket transport. */
name?: TransportConfig['name'] | undefined
/**
Expand Down Expand Up @@ -92,6 +94,7 @@ export function webSocket(
const {
keepAlive,
key = 'webSocket',
methods,
name = 'WebSocket JSON-RPC',
reconnect,
retryDelay,
Expand All @@ -104,6 +107,7 @@ export function webSocket(
return createTransport(
{
key,
methods,
name,
async request({ method, params }) {
const body = { method, params }
Expand Down
2 changes: 1 addition & 1 deletion src/errors/rpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ test('MethodNotSupportedRpcError', () => {
}),
),
).toMatchInlineSnapshot(`
[MethodNotSupportedRpcError: Method is not implemented.
[MethodNotSupportedRpcError: Method is not supported.
URL: http://localhost
Request body: {"foo":"bar"}
Expand Down
2 changes: 1 addition & 1 deletion src/errors/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ export class MethodNotSupportedRpcError extends RpcError {
super(cause, {
code: MethodNotSupportedRpcError.code,
name: 'MethodNotSupportedRpcError',
shortMessage: `Method${method ? ` "${method}"` : ''} is not implemented.`,
shortMessage: `Method${method ? ` "${method}"` : ''} is not supported.`,
})
}
}
Expand Down
17 changes: 14 additions & 3 deletions src/types/eip1193.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1922,11 +1922,22 @@ export type EIP1193Parameters<
}

export type EIP1193RequestOptions = {
// Deduplicate in-flight requests.
/** Deduplicate in-flight requests. */
dedupe?: boolean | undefined
// The base delay (in ms) between retries.
/** Methods to include or exclude from executing RPC requests. */
methods?:
| OneOf<
| {
include?: string[] | undefined
}
| {
exclude?: string[] | undefined
}
>
| undefined
/** The base delay (in ms) between retries. */
retryDelay?: number | undefined
// The max number of times to retry.
/** The max number of times to retry. */
retryCount?: number | undefined
/** Unique identifier for the request. */
uid?: string | undefined
Expand Down
Loading

0 comments on commit 28ff809

Please sign in to comment.