viem 2.48.11 → 2.49.2

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 (92) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/_cjs/actions/public/call.js +26 -6
  3. package/_cjs/actions/public/call.js.map +1 -1
  4. package/_cjs/clients/transports/http.js +19 -2
  5. package/_cjs/clients/transports/http.js.map +1 -1
  6. package/_cjs/errors/utils.js +17 -0
  7. package/_cjs/errors/utils.js.map +1 -1
  8. package/_cjs/errors/version.js +1 -1
  9. package/_cjs/errors/version.js.map +1 -1
  10. package/_cjs/index.js.map +1 -1
  11. package/_cjs/tempo/Formatters.js +11 -3
  12. package/_cjs/tempo/Formatters.js.map +1 -1
  13. package/_cjs/tempo/actions/wallet.js +3 -3
  14. package/_cjs/tempo/actions/wallet.js.map +1 -1
  15. package/_cjs/utils/buildRequest.js +12 -2
  16. package/_cjs/utils/buildRequest.js.map +1 -1
  17. package/_cjs/utils/ccip.js +17 -4
  18. package/_cjs/utils/ccip.js.map +1 -1
  19. package/_cjs/utils/promise/withRetry.js +23 -3
  20. package/_cjs/utils/promise/withRetry.js.map +1 -1
  21. package/_cjs/utils/promise/withTimeout.js +5 -2
  22. package/_cjs/utils/promise/withTimeout.js.map +1 -1
  23. package/_cjs/utils/rpc/http.js +5 -0
  24. package/_cjs/utils/rpc/http.js.map +1 -1
  25. package/_cjs/utils/wait.js +19 -2
  26. package/_cjs/utils/wait.js.map +1 -1
  27. package/_esm/actions/public/call.js +26 -6
  28. package/_esm/actions/public/call.js.map +1 -1
  29. package/_esm/clients/transports/http.js +19 -2
  30. package/_esm/clients/transports/http.js.map +1 -1
  31. package/_esm/errors/utils.js +15 -0
  32. package/_esm/errors/utils.js.map +1 -1
  33. package/_esm/errors/version.js +1 -1
  34. package/_esm/errors/version.js.map +1 -1
  35. package/_esm/index.js.map +1 -1
  36. package/_esm/tempo/Formatters.js +21 -4
  37. package/_esm/tempo/Formatters.js.map +1 -1
  38. package/_esm/tempo/actions/wallet.js +19 -6
  39. package/_esm/tempo/actions/wallet.js.map +1 -1
  40. package/_esm/utils/buildRequest.js +12 -2
  41. package/_esm/utils/buildRequest.js.map +1 -1
  42. package/_esm/utils/ccip.js +17 -4
  43. package/_esm/utils/ccip.js.map +1 -1
  44. package/_esm/utils/promise/withRetry.js +23 -3
  45. package/_esm/utils/promise/withRetry.js.map +1 -1
  46. package/_esm/utils/promise/withTimeout.js +5 -2
  47. package/_esm/utils/promise/withTimeout.js.map +1 -1
  48. package/_esm/utils/rpc/http.js +5 -0
  49. package/_esm/utils/rpc/http.js.map +1 -1
  50. package/_esm/utils/wait.js +19 -2
  51. package/_esm/utils/wait.js.map +1 -1
  52. package/_types/actions/public/call.d.ts +4 -1
  53. package/_types/actions/public/call.d.ts.map +1 -1
  54. package/_types/clients/transports/http.d.ts.map +1 -1
  55. package/_types/errors/utils.d.ts +3 -0
  56. package/_types/errors/utils.d.ts.map +1 -1
  57. package/_types/errors/version.d.ts +1 -1
  58. package/_types/errors/version.d.ts.map +1 -1
  59. package/_types/index.d.ts +1 -1
  60. package/_types/index.d.ts.map +1 -1
  61. package/_types/tempo/Formatters.d.ts.map +1 -1
  62. package/_types/tempo/actions/wallet.d.ts +77 -15
  63. package/_types/tempo/actions/wallet.d.ts.map +1 -1
  64. package/_types/types/eip1193.d.ts +2 -0
  65. package/_types/types/eip1193.d.ts.map +1 -1
  66. package/_types/utils/buildRequest.d.ts +5 -3
  67. package/_types/utils/buildRequest.d.ts.map +1 -1
  68. package/_types/utils/ccip.d.ts +5 -3
  69. package/_types/utils/ccip.d.ts.map +1 -1
  70. package/_types/utils/promise/withRetry.d.ts +3 -2
  71. package/_types/utils/promise/withRetry.d.ts.map +1 -1
  72. package/_types/utils/promise/withTimeout.d.ts +1 -1
  73. package/_types/utils/promise/withTimeout.d.ts.map +1 -1
  74. package/_types/utils/rpc/http.d.ts +1 -1
  75. package/_types/utils/rpc/http.d.ts.map +1 -1
  76. package/_types/utils/wait.d.ts +3 -1
  77. package/_types/utils/wait.d.ts.map +1 -1
  78. package/actions/public/call.ts +59 -23
  79. package/clients/transports/http.ts +18 -2
  80. package/errors/utils.ts +19 -0
  81. package/errors/version.ts +1 -1
  82. package/index.ts +1 -0
  83. package/package.json +1 -1
  84. package/tempo/Formatters.ts +20 -4
  85. package/tempo/actions/wallet.ts +85 -21
  86. package/types/eip1193.ts +2 -0
  87. package/utils/buildRequest.ts +22 -6
  88. package/utils/ccip.ts +22 -4
  89. package/utils/promise/withRetry.ts +29 -2
  90. package/utils/promise/withTimeout.ts +6 -3
  91. package/utils/rpc/http.ts +7 -1
  92. package/utils/wait.ts +24 -2
@@ -81,6 +81,17 @@ export type HttpTransportErrorType =
81
81
  | UrlRequiredErrorType
82
82
  | ErrorType
83
83
 
84
+ let signalId = 0
85
+ const signalIds = new WeakMap<AbortSignal, number>()
86
+ function getSignalId(signal: AbortSignal | undefined) {
87
+ if (!signal) return 'default'
88
+ const id = signalIds.get(signal)
89
+ if (id !== undefined) return id
90
+ const nextId = signalId++
91
+ signalIds.set(signal, nextId)
92
+ return nextId
93
+ }
94
+
84
95
  /**
85
96
  * @description Creates a HTTP transport that connects to a JSON-RPC API.
86
97
  */
@@ -125,11 +136,14 @@ export function http<
125
136
  key,
126
137
  methods,
127
138
  name,
128
- async request({ method, params }) {
139
+ async request({ method, params }, options) {
129
140
  const body = { method, params }
141
+ const fetchOptions = options?.signal
142
+ ? { signal: options.signal }
143
+ : undefined
130
144
 
131
145
  const { schedule } = createBatchScheduler({
132
- id: url_,
146
+ id: `${url_}.${getSignalId(options?.signal)}`,
133
147
  wait,
134
148
  shouldSplitBatch(requests) {
135
149
  return requests.length > batchSize
@@ -137,6 +151,7 @@ export function http<
137
151
  fn: (body: RpcRequest[]) =>
138
152
  rpcClient.request({
139
153
  body,
154
+ fetchOptions,
140
155
  }),
141
156
  sort: (a, b) => a.id - b.id,
142
157
  })
@@ -147,6 +162,7 @@ export function http<
147
162
  : [
148
163
  await rpcClient.request({
149
164
  body,
165
+ fetchOptions,
150
166
  }),
151
167
  ]
152
168
 
package/errors/utils.ts CHANGED
@@ -1,9 +1,28 @@
1
1
  import type { Address } from 'abitype'
2
2
 
3
3
  export type ErrorType<name extends string = 'Error'> = Error & { name: name }
4
+ export type AbortErrorType = ErrorType<'AbortError'>
4
5
 
5
6
  export const getContractAddress = (address: Address) => address
6
7
 
8
+ export function getAbortError(signal?: AbortSignal | undefined) {
9
+ if (signal?.reason) return signal.reason
10
+ if (typeof DOMException === 'function')
11
+ return new DOMException('This operation was aborted', 'AbortError')
12
+ const error = new Error('This operation was aborted') as AbortErrorType
13
+ error.name = 'AbortError'
14
+ return error
15
+ }
16
+
17
+ export function isAbortError(error: unknown): error is AbortErrorType {
18
+ return (
19
+ typeof error === 'object' &&
20
+ error !== null &&
21
+ 'name' in error &&
22
+ error.name === 'AbortError'
23
+ )
24
+ }
25
+
7
26
  /**
8
27
  * Returns the URL with any embedded basic-auth credentials stripped, so
9
28
  * error messages and logs don't leak secrets when an RPC URL like
package/errors/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '2.48.11'
1
+ export const version = '2.49.2'
package/index.ts CHANGED
@@ -1135,6 +1135,7 @@ export type {
1135
1135
  EIP1193Parameters,
1136
1136
  EIP1193Provider,
1137
1137
  EIP1193RequestFn,
1138
+ EIP1193RequestOptions,
1138
1139
  EIP1474Methods,
1139
1140
  NetworkSync,
1140
1141
  PaymasterRpcSchema,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "viem",
3
3
  "description": "TypeScript Interface for Ethereum",
4
- "version": "2.48.11",
4
+ "version": "2.49.2",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
@@ -122,12 +122,15 @@ export function formatTransactionRequest(
122
122
  const [keyType, keyData] = (() => {
123
123
  const type =
124
124
  account && 'keyType' in account ? account.keyType : account?.source
125
- if (!type) return [request.keyType, request.keyData]
125
+ if (!type) return [request.keyType, shimKeyData(request.keyData)]
126
126
  if (type === 'webAuthn')
127
- // TODO: derive correct bytes size of key data based on webauthn create metadata.
128
- return ['webAuthn', `0x${'ff'.repeat(1400)}`]
127
+ // Send a 2-byte big-endian length hint (1400 = 0x0578) instead of a
128
+ // 1400-byte dummy blob. The node's gas estimator expects key_data to
129
+ // be 1, 2, or 4 bytes encoding the desired WebAuthn signature size;
130
+ // anything else falls back to the 800-byte default.
131
+ return ['webAuthn', '0x0578']
129
132
  if (['p256', 'secp256k1'].includes(type)) return [type, undefined]
130
- return [request.keyType, request.keyData]
133
+ return [request.keyType, shimKeyData(request.keyData)]
131
134
  })()
132
135
 
133
136
  const keyId =
@@ -157,3 +160,16 @@ export function formatTransactionRequest(
157
160
  : {}),
158
161
  } as never
159
162
  }
163
+
164
+ /**
165
+ * Auto-shim user-provided keyData that is longer than 4 bytes into a
166
+ * 2-byte big-endian length hint. The node gas estimator only accepts
167
+ * 1, 2, or 4-byte key_data as a size hint; anything else silently falls
168
+ * back to the 800-byte default.
169
+ */
170
+ function shimKeyData(data: Hex.Hex | undefined): Hex.Hex | undefined {
171
+ if (!data) return data
172
+ const byteLength = (data.length - 2) / 2 // subtract "0x" prefix
173
+ if (byteLength <= 4) return data
174
+ return Hex.fromNumber(byteLength, { size: 2 })
175
+ }
@@ -3,11 +3,18 @@ import type { Client } from '../../clients/createClient.js'
3
3
  import type { Transport } from '../../clients/transports/createTransport.js'
4
4
  import type { ErrorType as CoreErrorType } from '../../errors/utils.js'
5
5
  import type { Chain } from '../../types/chain.js'
6
+ import type { OneOf } from '../../types/utils.js'
6
7
  import type { RequestErrorType } from '../../utils/buildRequest.js'
7
8
  import type { TransactionReceipt } from '../Transaction.js'
8
9
 
9
10
  /**
10
- * Opens the wallet send flow with optional pre-filled send fields.
11
+ * Transfers a TIP-20 token. Discriminated on `editable`:
12
+ *
13
+ * - omitted or `false` (default): read-only. Uses an access key when
14
+ * one matches (signs without showing the wallet UI), otherwise falls
15
+ * back to a confirm dialog the user has to approve.
16
+ * - `true`: editable. Opens the wallet send UI with the supplied fields
17
+ * pre-filled for the user to confirm or edit before signing.
11
18
  *
12
19
  * @example
13
20
  * ```ts
@@ -18,53 +25,110 @@ import type { TransactionReceipt } from '../Transaction.js'
18
25
  * transport: custom(window.ethereum),
19
26
  * })
20
27
  *
21
- * const { receipt } = await Actions.wallet.send(client, {
28
+ * // Read-only (no UI when an access key matches)
29
+ * const { receipt } = await Actions.wallet.transfer(client, {
30
+ * amount: '1.5',
22
31
  * to: '0x...',
23
32
  * token: '0x...',
24
- * value: '1.5',
33
+ * })
34
+ *
35
+ * // Editable (opens wallet UI)
36
+ * await Actions.wallet.transfer(client, {
37
+ * editable: true,
38
+ * token: 'pathUSD',
25
39
  * })
26
40
  * ```
27
41
  *
28
42
  * @param client - Client.
29
43
  * @param parameters - Parameters.
30
- * @returns The submitted send receipt and chain ID.
44
+ * @returns The submitted transfer receipt and chain ID.
31
45
  */
32
- export async function send<chain extends Chain | undefined>(
46
+ export async function transfer<chain extends Chain | undefined>(
33
47
  client: Client<Transport, chain>,
34
- parameters: send.Parameters = {},
35
- ): Promise<send.ReturnValue> {
48
+ parameters: transfer.Parameters,
49
+ ): Promise<transfer.ReturnValue> {
36
50
  return client.request<{
37
- Method: 'wallet_send'
38
- Parameters: [send.Parameters]
39
- ReturnType: send.ReturnValue
51
+ Method: 'wallet_transfer'
52
+ Parameters: [transfer.Parameters]
53
+ ReturnType: transfer.ReturnValue
40
54
  }>(
41
55
  {
42
- method: 'wallet_send',
56
+ method: 'wallet_transfer',
43
57
  params: [parameters],
44
58
  },
45
59
  { retryCount: 0 },
46
60
  )
47
61
  }
48
62
 
49
- export declare namespace send {
63
+ export declare namespace transfer {
64
+ /**
65
+ * Read-only variant — uses an access key when one matches, otherwise
66
+ * shows a confirm dialog.
67
+ */
68
+ type ReadOnly = {
69
+ /** Human-readable amount to transfer (for example, `"1.5"`). */
70
+ amount: string
71
+ /**
72
+ * Skip the editable wallet UI. The wallet still shows a confirm
73
+ * dialog when no matching access key is available.
74
+ * @default false
75
+ */
76
+ editable?: false | undefined
77
+ /**
78
+ * Address to transfer tokens from. Defaults to the active account. When
79
+ * set to a different address, the call uses `transferFrom` and requires
80
+ * the active account to have an allowance from `from`.
81
+ */
82
+ from?: Address | undefined
83
+ /**
84
+ * UTF-8 memo (max 32 bytes) to attach to the transfer.
85
+ */
86
+ memo?: string | undefined
87
+ /** Recipient address. */
88
+ to: Address
89
+ /**
90
+ * Token to transfer, accepted as either a contract address or a curated
91
+ * tokenlist symbol (case-insensitive, for example `"pathUsd"`). Symbols
92
+ * are resolved against the curated tokenlist on the active chain.
93
+ */
94
+ token: Address | string
95
+ }
96
+
97
+ /** Editable variant — opens the wallet send UI with optional pre-filled fields. */
98
+ type Editable = {
99
+ /** Human-readable amount to pre-fill (for example, `"1.5"`). */
100
+ amount?: string | undefined
101
+ /** Show the wallet UI for the user to confirm or edit. */
102
+ editable: true
103
+ /**
104
+ * UTF-8 memo (max 32 bytes) to attach to the transfer.
105
+ */
106
+ memo?: string | undefined
107
+ /** Recipient address to pre-fill. */
108
+ to?: Address | undefined
109
+ /**
110
+ * Token to pre-fill, accepted as either a contract address or a curated
111
+ * tokenlist symbol (case-insensitive, for example `"pathUsd"`). Symbols
112
+ * are resolved against the curated tokenlist on the active chain. Omit
113
+ * to let the user choose.
114
+ */
115
+ token?: Address | string | undefined
116
+ }
117
+
50
118
  export type Parameters = {
119
+ /** Chain id. Defaults to the active chain. */
120
+ chainId?: number | undefined
51
121
  /**
52
122
  * Fee payer override. `false` to disable the wallet's default fee payer,
53
123
  * a URL string to use a custom fee payer service.
54
124
  */
55
125
  feePayer?: boolean | string | undefined
56
- /** Recipient address to pre-fill. */
57
- to?: Address | undefined
58
- /** Token contract address to pre-fill. Omit to let the user choose. */
59
- token?: Address | undefined
60
- /** Human-readable amount to pre-fill (for example, "1.5"). */
61
- value?: string | undefined
62
- }
126
+ } & OneOf<ReadOnly | Editable>
63
127
 
64
128
  export type ReturnValue = {
65
- /** Chain ID the send was submitted to. */
129
+ /** Chain ID the transfer was submitted to. */
66
130
  chainId: number
67
- /** Receipt of the submitted send. */
131
+ /** Receipt of the submitted transfer. */
68
132
  receipt: TransactionReceipt
69
133
  }
70
134
 
package/types/eip1193.ts CHANGED
@@ -2140,6 +2140,8 @@ export type EIP1193RequestOptions = {
2140
2140
  retryDelay?: number | undefined
2141
2141
  /** The max number of times to retry. */
2142
2142
  retryCount?: number | undefined
2143
+ /** Abort signal to cancel the request. */
2144
+ signal?: AbortSignal | undefined
2143
2145
  /** Unique identifier for the request. */
2144
2146
  uid?: string | undefined
2145
2147
  }
@@ -66,7 +66,12 @@ import {
66
66
  WalletConnectSessionSettlementError,
67
67
  type WalletConnectSessionSettlementErrorType,
68
68
  } from '../errors/rpc.js'
69
- import type { ErrorType } from '../errors/utils.js'
69
+ import {
70
+ type AbortErrorType,
71
+ type ErrorType,
72
+ getAbortError,
73
+ isAbortError,
74
+ } from '../errors/utils.js'
70
75
  import type {
71
76
  EIP1193RequestFn,
72
77
  EIP1193RequestOptions,
@@ -113,18 +118,22 @@ export type RequestErrorType =
113
118
  | WalletConnectSessionSettlementErrorType
114
119
  | WebSocketRequestErrorType
115
120
  | WithRetryErrorType
121
+ | AbortErrorType
116
122
  | ErrorType
117
123
 
118
- export function buildRequest<request extends (args: any) => Promise<any>>(
119
- request: request,
120
- options: EIP1193RequestOptions = {},
121
- ): EIP1193RequestFn {
124
+ export function buildRequest<
125
+ request extends (
126
+ args: any,
127
+ options?: { signal?: AbortSignal | undefined } | undefined,
128
+ ) => Promise<any>,
129
+ >(request: request, options: EIP1193RequestOptions = {}): EIP1193RequestFn {
122
130
  return async (args, overrideOptions = {}) => {
123
131
  const {
124
132
  dedupe = false,
125
133
  methods,
126
134
  retryDelay = 150,
127
135
  retryCount = 3,
136
+ signal,
128
137
  uid,
129
138
  } = {
130
139
  ...options,
@@ -141,6 +150,8 @@ export function buildRequest<request extends (args: any) => Promise<any>>(
141
150
  method,
142
151
  })
143
152
 
153
+ if (signal?.aborted) throw getAbortError(signal)
154
+
144
155
  const requestId = dedupe
145
156
  ? hashString(`${uid}.${stringify(args)}`)
146
157
  : undefined
@@ -149,8 +160,11 @@ export function buildRequest<request extends (args: any) => Promise<any>>(
149
160
  withRetry(
150
161
  async () => {
151
162
  try {
152
- return await request(args)
163
+ return await request(args, signal ? { signal } : undefined)
153
164
  } catch (err_) {
165
+ if (signal?.aborted) throw getAbortError(signal)
166
+ if (isAbortError(err_)) throw err_
167
+
154
168
  const err = err_ as unknown as RpcError<
155
169
  RpcErrorCode | ProviderRpcErrorCode
156
170
  >
@@ -264,6 +278,7 @@ export function buildRequest<request extends (args: any) => Promise<any>>(
264
278
  return ~~(1 << count) * retryDelay
265
279
  },
266
280
  retryCount,
281
+ signal,
267
282
  shouldRetry: ({ error }) => shouldRetry(error),
268
283
  },
269
284
  ),
@@ -274,6 +289,7 @@ export function buildRequest<request extends (args: any) => Promise<any>>(
274
289
 
275
290
  /** @internal */
276
291
  export function shouldRetry(error: Error) {
292
+ if (isAbortError(error)) return false
277
293
  if ('code' in error && typeof error.code === 'number') {
278
294
  if (error.code === -1) return true // Unknown error
279
295
  if (error.code === LimitExceededRpcError.code) return true
package/utils/ccip.ts CHANGED
@@ -15,8 +15,9 @@ import {
15
15
  HttpRequestError,
16
16
  type HttpRequestErrorType,
17
17
  } from '../errors/request.js'
18
- import type { ErrorType } from '../errors/utils.js'
18
+ import { type ErrorType, getAbortError, isAbortError } from '../errors/utils.js'
19
19
  import type { Chain } from '../types/chain.js'
20
+ import type { EIP1193RequestOptions } from '../types/eip1193.js'
20
21
  import type { Hex } from '../types/misc.js'
21
22
  import { decodeErrorResult } from './abi/decodeErrorResult.js'
22
23
  import { encodeAbiParameters } from './abi/encodeAbiParameters.js'
@@ -65,8 +66,9 @@ export async function offchainLookup<chain extends Chain | undefined>(
65
66
  blockNumber,
66
67
  blockTag,
67
68
  data,
69
+ requestOptions,
68
70
  to,
69
- }: Pick<CallParameters, 'blockNumber' | 'blockTag'> & {
71
+ }: Pick<CallParameters, 'blockNumber' | 'blockTag' | 'requestOptions'> & {
70
72
  data: Hex
71
73
  to: Address
72
74
  },
@@ -90,9 +92,10 @@ export async function offchainLookup<chain extends Chain | undefined>(
90
92
  const result = urls.includes(localBatchGatewayUrl)
91
93
  ? await localBatchGatewayRequest({
92
94
  data: callData,
93
- ccipRequest: ccipRequest_,
95
+ ccipRequest: (parameters) =>
96
+ ccipRequest_({ ...parameters, requestOptions }),
94
97
  })
95
- : await ccipRequest_({ data: callData, sender, urls })
98
+ : await ccipRequest_({ data: callData, requestOptions, sender, urls })
96
99
 
97
100
  const { data: data_ } = await call(client, {
98
101
  blockNumber,
@@ -104,11 +107,16 @@ export async function offchainLookup<chain extends Chain | undefined>(
104
107
  [result, extraData],
105
108
  ),
106
109
  ]),
110
+ requestOptions,
107
111
  to,
108
112
  } as CallParameters)
109
113
 
110
114
  return data_!
111
115
  } catch (err) {
116
+ if (requestOptions?.signal?.aborted)
117
+ throw getAbortError(requestOptions.signal)
118
+ if (isAbortError(err)) throw err
119
+
112
120
  throw new OffchainLookupError({
113
121
  callbackSelector,
114
122
  cause: err as BaseError,
@@ -122,6 +130,7 @@ export async function offchainLookup<chain extends Chain | undefined>(
122
130
 
123
131
  export type CcipRequestParameters = {
124
132
  data: Hex
133
+ requestOptions?: EIP1193RequestOptions | undefined
125
134
  sender: Address
126
135
  urls: readonly string[]
127
136
  }
@@ -135,12 +144,16 @@ export type CcipRequestErrorType =
135
144
 
136
145
  export async function ccipRequest({
137
146
  data,
147
+ requestOptions,
138
148
  sender,
139
149
  urls,
140
150
  }: CcipRequestParameters): Promise<CcipRequestReturnType> {
141
151
  let error = new Error('An unknown error occurred.')
142
152
 
143
153
  for (let i = 0; i < urls.length; i++) {
154
+ if (requestOptions?.signal?.aborted)
155
+ throw getAbortError(requestOptions.signal)
156
+
144
157
  const url = urls[i]
145
158
  const method = url.includes('{data}') ? 'GET' : 'POST'
146
159
  const body = method === 'POST' ? { data, sender } : undefined
@@ -154,6 +167,7 @@ export async function ccipRequest({
154
167
  body: JSON.stringify(body),
155
168
  headers,
156
169
  method,
170
+ ...(requestOptions?.signal ? { signal: requestOptions.signal } : {}),
157
171
  },
158
172
  )
159
173
 
@@ -189,6 +203,10 @@ export async function ccipRequest({
189
203
 
190
204
  return result
191
205
  } catch (err) {
206
+ if (requestOptions?.signal?.aborted)
207
+ throw getAbortError(requestOptions.signal)
208
+ if (isAbortError(err)) throw err
209
+
192
210
  error = new HttpRequestError({
193
211
  body,
194
212
  details: (err as Error).message,
@@ -1,4 +1,8 @@
1
- import type { ErrorType } from '../../errors/utils.js'
1
+ import {
2
+ type ErrorType,
3
+ getAbortError,
4
+ isAbortError,
5
+ } from '../../errors/utils.js'
2
6
  import { wait } from '../wait.js'
3
7
 
4
8
  export type WithRetryParameters = {
@@ -19,6 +23,8 @@ export type WithRetryParameters = {
19
23
  error: Error
20
24
  }) => Promise<boolean> | boolean)
21
25
  | undefined
26
+ // AbortSignal to cancel retries.
27
+ signal?: AbortSignal | undefined
22
28
  }
23
29
 
24
30
  export type WithRetryErrorType = ErrorType
@@ -29,14 +35,27 @@ export function withRetry<data>(
29
35
  delay: delay_ = 100,
30
36
  retryCount = 2,
31
37
  shouldRetry = () => true,
38
+ signal,
32
39
  }: WithRetryParameters = {},
33
40
  ) {
34
41
  return new Promise<data>((resolve, reject) => {
35
42
  const attemptRetry = async ({ count = 0 } = {}) => {
43
+ if (signal?.aborted) {
44
+ reject(getAbortError(signal))
45
+ return
46
+ }
47
+
36
48
  const retry = async ({ error }: { error: Error }) => {
37
49
  const delay =
38
50
  typeof delay_ === 'function' ? delay_({ count, error }) : delay_
39
- if (delay) await wait(delay)
51
+ if (delay) {
52
+ try {
53
+ await wait(delay, { signal })
54
+ } catch (err) {
55
+ reject(err)
56
+ return
57
+ }
58
+ }
40
59
  attemptRetry({ count: count + 1 })
41
60
  }
42
61
 
@@ -44,6 +63,14 @@ export function withRetry<data>(
44
63
  const data = await fn()
45
64
  resolve(data)
46
65
  } catch (err) {
66
+ if (signal?.aborted) {
67
+ reject(getAbortError(signal))
68
+ return
69
+ }
70
+ if (isAbortError(err)) {
71
+ reject(err)
72
+ return
73
+ }
47
74
  if (
48
75
  count < retryCount &&
49
76
  (await shouldRetry({ count, error: err as Error }))
@@ -1,4 +1,4 @@
1
- import type { ErrorType } from '../../errors/utils.js'
1
+ import { type ErrorType, isAbortError } from '../../errors/utils.js'
2
2
 
3
3
  export type WithTimeoutErrorType = ErrorType
4
4
 
@@ -24,8 +24,8 @@ export function withTimeout<data>(
24
24
  return new Promise((resolve, reject) => {
25
25
  ;(async () => {
26
26
  let timeoutId!: NodeJS.Timeout
27
+ const controller = new AbortController()
27
28
  try {
28
- const controller = new AbortController()
29
29
  if (timeout > 0) {
30
30
  timeoutId = setTimeout(() => {
31
31
  if (signal) {
@@ -37,7 +37,10 @@ export function withTimeout<data>(
37
37
  }
38
38
  resolve(await fn({ signal: controller?.signal || null }))
39
39
  } catch (err) {
40
- if ((err as Error)?.name === 'AbortError') reject(errorInstance)
40
+ if (controller?.signal.aborted && isAbortError(err)) {
41
+ reject(errorInstance)
42
+ return
43
+ }
41
44
  reject(err)
42
45
  } finally {
43
46
  clearTimeout(timeoutId)
package/utils/rpc/http.ts CHANGED
@@ -4,7 +4,11 @@ import {
4
4
  TimeoutError,
5
5
  type TimeoutErrorType,
6
6
  } from '../../errors/request.js'
7
- import type { ErrorType } from '../../errors/utils.js'
7
+ import {
8
+ type ErrorType,
9
+ getAbortError,
10
+ isAbortError,
11
+ } from '../../errors/utils.js'
8
12
  import type { RpcRequest, RpcResponse } from '../../types/rpc.js'
9
13
  import type { MaybePromise } from '../../types/utils.js'
10
14
  import {
@@ -174,6 +178,8 @@ export function getHttpRpcClient(
174
178
 
175
179
  return data
176
180
  } catch (err) {
181
+ if (signal_?.aborted) throw getAbortError(signal_)
182
+ if (isAbortError(err)) throw err
177
183
  if (err instanceof HttpRequestError) throw err
178
184
  if (err instanceof TimeoutError) throw err
179
185
  throw new HttpRequestError({
package/utils/wait.ts CHANGED
@@ -1,3 +1,25 @@
1
- export async function wait(time: number) {
2
- return new Promise((res) => setTimeout(res, time))
1
+ import { getAbortError } from '../errors/utils.js'
2
+
3
+ export async function wait(
4
+ time: number,
5
+ { signal }: { signal?: AbortSignal | undefined } = {},
6
+ ) {
7
+ return new Promise<void>((resolve, reject) => {
8
+ if (signal?.aborted) {
9
+ reject(getAbortError(signal))
10
+ return
11
+ }
12
+
13
+ const cleanup = () => signal?.removeEventListener('abort', onAbort)
14
+ const timeout = setTimeout(() => {
15
+ cleanup()
16
+ resolve()
17
+ }, time)
18
+ const onAbort = () => {
19
+ clearTimeout(timeout)
20
+ cleanup()
21
+ reject(getAbortError(signal))
22
+ }
23
+ signal?.addEventListener('abort', onAbort, { once: true })
24
+ })
3
25
  }