Skip to content

Commit edd485b

Browse files
committed
feat: add signal to EIP1193RequestOptions
1 parent 25c50c0 commit edd485b

File tree

6 files changed

+111
-12
lines changed

6 files changed

+111
-12
lines changed

src/actions/public/call.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ export type CallParameters<
9292
factory?: Address | undefined
9393
/** Calldata to execute on the factory to deploy the contract. */
9494
factoryData?: Hex | undefined
95+
/** Request options. */
96+
requestOptions?:
97+
| import('../../types/eip1193.js').EIP1193RequestOptions
98+
| undefined
9599
/** State overrides for the call. */
96100
stateOverride?: StateOverride | undefined
97101
} & (
@@ -174,6 +178,7 @@ export async function call<chain extends Chain | undefined>(
174178
maxFeePerGas,
175179
maxPriorityFeePerGas,
176180
nonce,
181+
requestOptions,
177182
to,
178183
value,
179184
stateOverride,
@@ -276,10 +281,13 @@ export async function call<chain extends Chain | undefined>(
276281
return base
277282
})()
278283

279-
const response = await client.request({
280-
method: 'eth_call',
281-
params,
282-
})
284+
const response = await client.request(
285+
{
286+
method: 'eth_call',
287+
params,
288+
},
289+
requestOptions,
290+
)
283291
if (response === '0x') return { data: undefined }
284292
return { data: response }
285293
} catch (err) {

src/actions/public/getBlock.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export type GetBlockParameters<
2727
> = {
2828
/** Whether or not to include transaction data in the response. */
2929
includeTransactions?: includeTransactions | undefined
30+
/** Request options. */
31+
requestOptions?:
32+
| import('../../types/eip1193.js').EIP1193RequestOptions
33+
| undefined
3034
} & (
3135
| {
3236
/** Hash of the block. */
@@ -99,6 +103,7 @@ export async function getBlock<
99103
blockNumber,
100104
blockTag = client.experimental_blockTag ?? 'latest',
101105
includeTransactions: includeTransactions_,
106+
requestOptions,
102107
}: GetBlockParameters<includeTransactions, blockTag> = {},
103108
): Promise<GetBlockReturnType<chain, includeTransactions, blockTag>> {
104109
const includeTransactions = includeTransactions_ ?? false
@@ -113,15 +118,15 @@ export async function getBlock<
113118
method: 'eth_getBlockByHash',
114119
params: [blockHash, includeTransactions],
115120
},
116-
{ dedupe: true },
121+
{ dedupe: true, ...requestOptions },
117122
)
118123
} else {
119124
block = await client.request(
120125
{
121126
method: 'eth_getBlockByNumber',
122127
params: [blockNumberHex || blockTag, includeTransactions],
123128
},
124-
{ dedupe: Boolean(blockNumberHex) },
129+
{ dedupe: Boolean(blockNumberHex), ...requestOptions },
125130
)
126131
}
127132

src/clients/transports/http.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,3 +612,81 @@ test('no url', () => {
612612
`,
613613
)
614614
})
615+
616+
describe('request cancellation', () => {
617+
test('cancels request with AbortSignal', async () => {
618+
const server = await createHttpServer((_, res) => {
619+
// Delay response to allow time for cancellation
620+
setTimeout(() => {
621+
res.writeHead(200, { 'Content-Type': 'application/json' })
622+
res.end(JSON.stringify({ jsonrpc: '2.0', result: '0x1', id: 0 }))
623+
}, 100)
624+
})
625+
626+
const controller = new AbortController()
627+
const transport = http(server.url)({})
628+
629+
// Cancel after 50ms (before server responds at 100ms)
630+
setTimeout(() => controller.abort(), 50)
631+
632+
await expect(
633+
transport.request(
634+
{ method: 'eth_blockNumber' },
635+
{ signal: controller.signal },
636+
),
637+
).rejects.toThrow()
638+
639+
await server.close()
640+
})
641+
642+
test('successful request with signal', async () => {
643+
const server = await createHttpServer((_, res) => {
644+
res.writeHead(200, { 'Content-Type': 'application/json' })
645+
res.end(JSON.stringify({ jsonrpc: '2.0', result: '0x1', id: 0 }))
646+
})
647+
648+
const controller = new AbortController()
649+
const transport = http(server.url)({})
650+
651+
const result = await transport.request(
652+
{ method: 'eth_blockNumber' },
653+
{ signal: controller.signal },
654+
)
655+
656+
expect(result).toBe('0x1')
657+
await server.close()
658+
})
659+
660+
test('multiple requests with same controller', async () => {
661+
const server = await createHttpServer((_, res) => {
662+
setTimeout(() => {
663+
res.writeHead(200, { 'Content-Type': 'application/json' })
664+
res.end(JSON.stringify({ jsonrpc: '2.0', result: '0x1', id: 0 }))
665+
}, 100)
666+
})
667+
668+
const controller = new AbortController()
669+
const transport = http(server.url)({})
670+
671+
// Start multiple requests
672+
const promise1 = transport.request(
673+
{ method: 'eth_blockNumber' },
674+
{ signal: controller.signal },
675+
)
676+
const promise2 = transport.request(
677+
{
678+
method: 'eth_getBalance',
679+
params: ['0x0000000000000000000000000000000000000000'],
680+
},
681+
{ signal: controller.signal },
682+
)
683+
684+
// Cancel after 50ms
685+
setTimeout(() => controller.abort(), 50)
686+
687+
await expect(promise1).rejects.toThrow()
688+
await expect(promise2).rejects.toThrow()
689+
690+
await server.close()
691+
})
692+
})

src/clients/transports/http.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function http<
122122
key,
123123
methods,
124124
name,
125-
async request({ method, params }) {
125+
async request({ method, params }, options) {
126126
const body = { method, params }
127127

128128
const { schedule } = createBatchScheduler({
@@ -134,6 +134,9 @@ export function http<
134134
fn: (body: RpcRequest[]) =>
135135
rpcClient.request({
136136
body,
137+
fetchOptions: options?.signal
138+
? { signal: options.signal }
139+
: undefined,
137140
}),
138141
sort: (a, b) => a.id - b.id,
139142
})
@@ -144,6 +147,9 @@ export function http<
144147
: [
145148
await rpcClient.request({
146149
body,
150+
fetchOptions: options?.signal
151+
? { signal: options.signal }
152+
: undefined,
147153
}),
148154
]
149155

src/types/eip1193.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,8 @@ export type EIP1193RequestOptions = {
20312031
retryDelay?: number | undefined
20322032
/** The max number of times to retry. */
20332033
retryCount?: number | undefined
2034+
/** AbortSignal to cancel the request. */
2035+
signal?: AbortSignal | undefined
20342036
/** Unique identifier for the request. */
20352037
uid?: string | undefined
20362038
}

src/utils/buildRequest.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,16 @@ export type RequestErrorType =
113113
| WithRetryErrorType
114114
| ErrorType
115115

116-
export function buildRequest<request extends (args: any) => Promise<any>>(
117-
request: request,
118-
options: EIP1193RequestOptions = {},
119-
): EIP1193RequestFn {
116+
export function buildRequest<
117+
request extends (args: any, options?: any) => Promise<any>,
118+
>(request: request, options: EIP1193RequestOptions = {}): EIP1193RequestFn {
120119
return async (args, overrideOptions = {}) => {
121120
const {
122121
dedupe = false,
123122
methods,
124123
retryDelay = 150,
125124
retryCount = 3,
125+
signal,
126126
uid,
127127
} = {
128128
...options,
@@ -147,7 +147,7 @@ export function buildRequest<request extends (args: any) => Promise<any>>(
147147
withRetry(
148148
async () => {
149149
try {
150-
return await request(args)
150+
return await request(args, signal ? { signal } : undefined)
151151
} catch (err_) {
152152
const err = err_ as unknown as RpcError<
153153
RpcErrorCode | ProviderRpcErrorCode

0 commit comments

Comments
 (0)