@chainlink/ccip-sdk 0.90.2 → 0.91.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.
Files changed (202) hide show
  1. package/README.md +35 -26
  2. package/dist/aptos/exec.d.ts +4 -5
  3. package/dist/aptos/exec.d.ts.map +1 -1
  4. package/dist/aptos/exec.js +5 -14
  5. package/dist/aptos/exec.js.map +1 -1
  6. package/dist/aptos/hasher.d.ts +18 -0
  7. package/dist/aptos/hasher.d.ts.map +1 -1
  8. package/dist/aptos/hasher.js +18 -0
  9. package/dist/aptos/hasher.js.map +1 -1
  10. package/dist/aptos/index.d.ts +127 -28
  11. package/dist/aptos/index.d.ts.map +1 -1
  12. package/dist/aptos/index.js +199 -70
  13. package/dist/aptos/index.js.map +1 -1
  14. package/dist/aptos/logs.d.ts +18 -0
  15. package/dist/aptos/logs.d.ts.map +1 -1
  16. package/dist/aptos/logs.js +21 -3
  17. package/dist/aptos/logs.js.map +1 -1
  18. package/dist/aptos/send.d.ts +22 -5
  19. package/dist/aptos/send.d.ts.map +1 -1
  20. package/dist/aptos/send.js +23 -15
  21. package/dist/aptos/send.js.map +1 -1
  22. package/dist/aptos/token.d.ts +6 -0
  23. package/dist/aptos/token.d.ts.map +1 -1
  24. package/dist/aptos/token.js +6 -0
  25. package/dist/aptos/token.js.map +1 -1
  26. package/dist/aptos/types.d.ts +16 -1
  27. package/dist/aptos/types.d.ts.map +1 -1
  28. package/dist/aptos/types.js +13 -0
  29. package/dist/aptos/types.js.map +1 -1
  30. package/dist/aptos/utils.d.ts +1 -1
  31. package/dist/aptos/utils.js +1 -1
  32. package/dist/chain.d.ts +185 -99
  33. package/dist/chain.d.ts.map +1 -1
  34. package/dist/chain.js +38 -15
  35. package/dist/chain.js.map +1 -1
  36. package/dist/commits.d.ts +4 -10
  37. package/dist/commits.d.ts.map +1 -1
  38. package/dist/commits.js +2 -1
  39. package/dist/commits.js.map +1 -1
  40. package/dist/evm/const.d.ts +5 -0
  41. package/dist/evm/const.d.ts.map +1 -1
  42. package/dist/evm/const.js +5 -0
  43. package/dist/evm/const.js.map +1 -1
  44. package/dist/evm/errors.d.ts +5 -0
  45. package/dist/evm/errors.d.ts.map +1 -1
  46. package/dist/evm/errors.js +6 -1
  47. package/dist/evm/errors.js.map +1 -1
  48. package/dist/evm/hasher.d.ts +16 -2
  49. package/dist/evm/hasher.d.ts.map +1 -1
  50. package/dist/evm/hasher.js +17 -3
  51. package/dist/evm/hasher.js.map +1 -1
  52. package/dist/evm/index.d.ts +176 -31
  53. package/dist/evm/index.d.ts.map +1 -1
  54. package/dist/evm/index.js +312 -154
  55. package/dist/evm/index.js.map +1 -1
  56. package/dist/evm/logs.d.ts +20 -0
  57. package/dist/evm/logs.d.ts.map +1 -0
  58. package/dist/evm/logs.js +194 -0
  59. package/dist/evm/logs.js.map +1 -0
  60. package/dist/evm/messages.d.ts +11 -2
  61. package/dist/evm/messages.d.ts.map +1 -1
  62. package/dist/evm/messages.js +4 -2
  63. package/dist/evm/messages.js.map +1 -1
  64. package/dist/evm/offchain.d.ts +7 -2
  65. package/dist/evm/offchain.d.ts.map +1 -1
  66. package/dist/evm/offchain.js +12 -7
  67. package/dist/evm/offchain.js.map +1 -1
  68. package/dist/execution.d.ts +19 -62
  69. package/dist/execution.d.ts.map +1 -1
  70. package/dist/execution.js +28 -31
  71. package/dist/execution.js.map +1 -1
  72. package/dist/extra-args.d.ts +35 -5
  73. package/dist/extra-args.d.ts.map +1 -1
  74. package/dist/extra-args.js +10 -5
  75. package/dist/extra-args.js.map +1 -1
  76. package/dist/gas.d.ts +6 -8
  77. package/dist/gas.d.ts.map +1 -1
  78. package/dist/gas.js +7 -9
  79. package/dist/gas.js.map +1 -1
  80. package/dist/hasher/common.d.ts +3 -2
  81. package/dist/hasher/common.d.ts.map +1 -1
  82. package/dist/hasher/common.js +2 -2
  83. package/dist/hasher/common.js.map +1 -1
  84. package/dist/hasher/hasher.d.ts +8 -2
  85. package/dist/hasher/hasher.d.ts.map +1 -1
  86. package/dist/hasher/hasher.js +8 -3
  87. package/dist/hasher/hasher.js.map +1 -1
  88. package/dist/hasher/merklemulti.d.ts +11 -9
  89. package/dist/hasher/merklemulti.d.ts.map +1 -1
  90. package/dist/hasher/merklemulti.js +17 -16
  91. package/dist/hasher/merklemulti.js.map +1 -1
  92. package/dist/index.d.ts +16 -8
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +17 -7
  95. package/dist/index.js.map +1 -1
  96. package/dist/requests.d.ts +39 -25
  97. package/dist/requests.d.ts.map +1 -1
  98. package/dist/requests.js +42 -35
  99. package/dist/requests.js.map +1 -1
  100. package/dist/selectors.d.ts +1 -1
  101. package/dist/solana/cleanup.d.ts +14 -10
  102. package/dist/solana/cleanup.d.ts.map +1 -1
  103. package/dist/solana/cleanup.js +35 -33
  104. package/dist/solana/cleanup.js.map +1 -1
  105. package/dist/solana/exec.d.ts +19 -11
  106. package/dist/solana/exec.d.ts.map +1 -1
  107. package/dist/solana/exec.js +86 -163
  108. package/dist/solana/exec.js.map +1 -1
  109. package/dist/solana/hasher.d.ts +7 -2
  110. package/dist/solana/hasher.d.ts.map +1 -1
  111. package/dist/solana/hasher.js +7 -2
  112. package/dist/solana/hasher.js.map +1 -1
  113. package/dist/solana/index.d.ts +202 -84
  114. package/dist/solana/index.d.ts.map +1 -1
  115. package/dist/solana/index.js +367 -252
  116. package/dist/solana/index.js.map +1 -1
  117. package/dist/solana/offchain.d.ts +8 -18
  118. package/dist/solana/offchain.d.ts.map +1 -1
  119. package/dist/solana/offchain.js +29 -83
  120. package/dist/solana/offchain.js.map +1 -1
  121. package/dist/solana/patchBorsh.d.ts +5 -1
  122. package/dist/solana/patchBorsh.d.ts.map +1 -1
  123. package/dist/solana/patchBorsh.js +57 -46
  124. package/dist/solana/patchBorsh.js.map +1 -1
  125. package/dist/solana/send.d.ts +28 -10
  126. package/dist/solana/send.d.ts.map +1 -1
  127. package/dist/solana/send.js +44 -77
  128. package/dist/solana/send.js.map +1 -1
  129. package/dist/solana/types.d.ts +22 -1
  130. package/dist/solana/types.d.ts.map +1 -1
  131. package/dist/solana/types.js +12 -1
  132. package/dist/solana/types.js.map +1 -1
  133. package/dist/solana/utils.d.ts +58 -4
  134. package/dist/solana/utils.d.ts.map +1 -1
  135. package/dist/solana/utils.js +110 -7
  136. package/dist/solana/utils.js.map +1 -1
  137. package/dist/sui/hasher.d.ts +18 -0
  138. package/dist/sui/hasher.d.ts.map +1 -1
  139. package/dist/sui/hasher.js +18 -0
  140. package/dist/sui/hasher.js.map +1 -1
  141. package/dist/sui/index.d.ts +99 -12
  142. package/dist/sui/index.d.ts.map +1 -1
  143. package/dist/sui/index.js +108 -19
  144. package/dist/sui/index.js.map +1 -1
  145. package/dist/sui/types.d.ts +6 -0
  146. package/dist/sui/types.d.ts.map +1 -1
  147. package/dist/sui/types.js +5 -0
  148. package/dist/sui/types.js.map +1 -1
  149. package/dist/supported-chains.d.ts +2 -1
  150. package/dist/supported-chains.d.ts.map +1 -1
  151. package/dist/supported-chains.js.map +1 -1
  152. package/dist/types.d.ts +127 -16
  153. package/dist/types.d.ts.map +1 -1
  154. package/dist/types.js +18 -0
  155. package/dist/types.js.map +1 -1
  156. package/dist/utils.d.ts +67 -46
  157. package/dist/utils.d.ts.map +1 -1
  158. package/dist/utils.js +143 -21
  159. package/dist/utils.js.map +1 -1
  160. package/package.json +13 -9
  161. package/src/aptos/exec.ts +7 -18
  162. package/src/aptos/hasher.ts +18 -0
  163. package/src/aptos/index.ts +288 -110
  164. package/src/aptos/logs.ts +21 -3
  165. package/src/aptos/send.ts +25 -22
  166. package/src/aptos/token.ts +6 -0
  167. package/src/aptos/types.ts +26 -2
  168. package/src/aptos/utils.ts +1 -1
  169. package/src/chain.ts +243 -108
  170. package/src/commits.ts +6 -7
  171. package/src/evm/const.ts +5 -0
  172. package/src/evm/errors.ts +6 -1
  173. package/src/evm/hasher.ts +20 -4
  174. package/src/evm/index.ts +416 -214
  175. package/src/evm/logs.ts +255 -0
  176. package/src/evm/messages.ts +11 -5
  177. package/src/evm/offchain.ts +13 -4
  178. package/src/execution.ts +40 -32
  179. package/src/extra-args.ts +38 -6
  180. package/src/gas.ts +7 -9
  181. package/src/hasher/common.ts +3 -2
  182. package/src/hasher/hasher.ts +12 -4
  183. package/src/hasher/merklemulti.ts +17 -16
  184. package/src/index.ts +29 -23
  185. package/src/requests.ts +64 -46
  186. package/src/selectors.ts +1 -1
  187. package/src/solana/cleanup.ts +49 -34
  188. package/src/solana/exec.ts +128 -272
  189. package/src/solana/hasher.ts +13 -4
  190. package/src/solana/index.ts +483 -356
  191. package/src/solana/offchain.ts +32 -102
  192. package/src/solana/patchBorsh.ts +65 -50
  193. package/src/solana/send.ts +52 -111
  194. package/src/solana/types.ts +44 -3
  195. package/src/solana/utils.ts +143 -19
  196. package/src/sui/hasher.ts +18 -0
  197. package/src/sui/index.ts +143 -31
  198. package/src/sui/types.ts +6 -0
  199. package/src/supported-chains.ts +2 -1
  200. package/src/types.ts +130 -18
  201. package/src/utils.ts +168 -26
  202. package/tsconfig.json +2 -1
package/src/types.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { AbiParametersToPrimitiveTypes, ExtractAbiEvent } from 'abitype'
2
2
  import type { BytesLike, Log } from 'ethers'
3
3
 
4
- import type { ChainFamily, ChainTransaction } from './chain.ts'
5
4
  import type OffRamp_1_6_ABI from './evm/abi/OffRamp_1_6.ts'
6
5
  import type { CCIPMessage_EVM, CCIPMessage_V1_6_EVM } from './evm/messages.ts'
7
6
  import type { ExtraArgs } from './extra-args.ts'
@@ -11,24 +10,28 @@ import type { CCIPMessage_V1_6_Sui } from './sui/types.ts'
11
10
  export type { CCIPMessage_V1_6 } from './evm/messages.ts'
12
11
 
13
12
  /**
14
- * DeepReadonly is a type that recursively makes all properties of an object readonly.
13
+ * Logger interface for logging messages (compatible with console)
15
14
  */
16
- export type DeepReadonly<T> = Readonly<{
17
- [K in keyof T]: T[K] extends number | string | symbol // Is it a primitive? Then make it readonly
18
- ? Readonly<T[K]>
19
- : // Is it an array of items? Then make the array readonly and the item as well
20
- T[K] extends Array<infer A>
21
- ? Readonly<Array<DeepReadonly<A>>>
22
- : // It is some other object, make it readonly as well
23
- DeepReadonly<T[K]>
24
- }>
15
+ export type Logger = {
16
+ debug: (...args: unknown[]) => void
17
+ info: (...args: unknown[]) => void
18
+ warn: (...args: unknown[]) => void
19
+ error: (...args: unknown[]) => void
20
+ }
21
+
22
+ /**
23
+ * An options object which may have a logger
24
+ */
25
+ export type WithLogger = {
26
+ logger?: Logger
27
+ }
25
28
 
26
29
  /**
27
- * "Fix" for deeply intersecting types containing arrays: A[] & B[] => (A & B)[]
28
- * Usually, if you intersect { arr: A[] } & { arr: B[] }, arr will have type A[] & B[],
30
+ * "Fix" for deeply intersecting types containing arrays: converts `A[] & B[]` to `(A & B)[]`.
31
+ * Usually, if you intersect `\{ arr: A[] \} & \{ arr: B[] \}`, arr will have type `A[] & B[]`,
29
32
  * i.e. all/each *index* of A[] and B[] should be present in the intersection, with quite undefined
30
- * types of the elements themselves, oftentimes assigning only one of A or B to the element type;
31
- * This converts deeply to (A & B)[], i.e. each *element* should have all properties of A & B
33
+ * types of the elements themselves, oftentimes assigning only one of A or B to the element type.
34
+ * This converts deeply to `(A & B)[]`, i.e. each *element* should have all properties of A & B.
32
35
  */
33
36
  export type MergeArrayElements<T, U> = {
34
37
  [K in keyof (T & U)]: K extends keyof T & keyof U
@@ -52,13 +55,30 @@ export type MergeArrayElements<T, U> = {
52
55
  : never
53
56
  }
54
57
 
58
+ /**
59
+ * Enumeration of supported blockchain families.
60
+ */
61
+ export const ChainFamily = {
62
+ EVM: 'evm',
63
+ Solana: 'solana',
64
+ Aptos: 'aptos',
65
+ Sui: 'sui',
66
+ } as const
67
+ /** Type representing one of the supported chain families. */
68
+ export type ChainFamily = (typeof ChainFamily)[keyof typeof ChainFamily]
69
+
70
+ /**
71
+ * Enumeration of supported CCIP protocol versions.
72
+ */
55
73
  export const CCIPVersion = {
56
74
  V1_2: '1.2.0',
57
75
  V1_5: '1.5.0',
58
76
  V1_6: '1.6.0',
59
77
  } as const
78
+ /** Type representing one of the supported CCIP versions. */
60
79
  export type CCIPVersion = (typeof CCIPVersion)[keyof typeof CCIPVersion]
61
80
 
81
+ /** Helper type that maps chain family to its chain ID format. */
62
82
  type ChainFamilyWithId<F extends ChainFamily> = F extends typeof ChainFamily.EVM
63
83
  ? { readonly family: F; readonly chainId: number }
64
84
  : F extends typeof ChainFamily.Solana
@@ -67,87 +87,179 @@ type ChainFamilyWithId<F extends ChainFamily> = F extends typeof ChainFamily.EVM
67
87
  ? { readonly family: F; readonly chainId: `${F}:${number}` }
68
88
  : never
69
89
 
90
+ /**
91
+ * Network information including chain selector and metadata.
92
+ */
70
93
  export type NetworkInfo<F extends ChainFamily = ChainFamily> = {
94
+ /** Unique chain selector used by CCIP. */
71
95
  readonly chainSelector: bigint
96
+ /** Human-readable network name. */
72
97
  readonly name: string
98
+ /** Whether this is a testnet. */
73
99
  readonly isTestnet: boolean
74
100
  } & ChainFamilyWithId<F>
75
101
 
102
+ /**
103
+ * CCIP lane configuration connecting source and destination chains.
104
+ */
76
105
  export interface Lane<V extends CCIPVersion = CCIPVersion> {
106
+ /** Source chain selector. */
77
107
  sourceChainSelector: bigint
108
+ /** Destination chain selector. */
78
109
  destChainSelector: bigint
110
+ /** OnRamp contract address on source chain. */
79
111
  onRamp: string
112
+ /** CCIP protocol version for this lane. */
80
113
  version: V
81
114
  }
82
115
 
116
+ /**
117
+ * Union type representing a CCIP message across different versions and chain families.
118
+ */
83
119
  export type CCIPMessage<V extends CCIPVersion = CCIPVersion> = V extends
84
120
  | typeof CCIPVersion.V1_2
85
121
  | typeof CCIPVersion.V1_5
86
122
  ? CCIPMessage_EVM<V>
87
123
  : CCIPMessage_V1_6_EVM | CCIPMessage_V1_6_Solana | CCIPMessage_V1_6_Sui
88
124
 
125
+ /**
126
+ * Generic log structure compatible across chain families.
127
+ */
89
128
  export type Log_ = Pick<Log, 'topics' | 'index' | 'address' | 'blockNumber' | 'transactionHash'> & {
129
+ /** Log data as bytes or parsed object. */
90
130
  data: BytesLike | Record<string, unknown>
131
+ /** Optional reference to the containing transaction. */
91
132
  tx?: ChainTransaction
92
133
  }
93
134
 
135
+ /**
136
+ * Generic transaction structure compatible across chain families.
137
+ */
138
+ export type ChainTransaction = {
139
+ /** Transaction hash. */
140
+ hash: string
141
+ /** Logs emitted by this transaction. */
142
+ logs: readonly Log_[]
143
+ /** Block number containing this transaction. */
144
+ blockNumber: number
145
+ /** Unix timestamp of the block. */
146
+ timestamp: number
147
+ /** Sender address. */
148
+ from: string
149
+ /** Optional error if transaction failed. */
150
+ error?: unknown
151
+ }
152
+
153
+ /**
154
+ * Complete CCIP request containing lane, message, log, and transaction info.
155
+ */
94
156
  export interface CCIPRequest<V extends CCIPVersion = CCIPVersion> {
157
+ /** Lane configuration for this request. */
95
158
  lane: Lane<V>
159
+ /** The CCIP message being sent. */
96
160
  message: CCIPMessage<V>
161
+ /** Log event from the OnRamp. */
97
162
  log: Log_
98
- tx: { logs: readonly Log_[]; from?: string; error?: unknown }
99
- timestamp: number
163
+ /** Transaction that emitted the request. */
164
+ tx: Pick<ChainTransaction, 'hash' | 'logs' | 'blockNumber' | 'timestamp' | 'from' | 'error'>
100
165
  }
101
166
 
167
+ /**
168
+ * Commit report structure from the OffRamp CommitReportAccepted event.
169
+ */
102
170
  export type CommitReport = AbiParametersToPrimitiveTypes<
103
171
  ExtractAbiEvent<typeof OffRamp_1_6_ABI, 'CommitReportAccepted'>['inputs']
104
172
  >[0][number]
105
173
 
174
+ /**
175
+ * CCIP commit information containing the report and its log.
176
+ */
106
177
  export interface CCIPCommit {
178
+ /** The commit report data. */
107
179
  report: CommitReport
180
+ /** Log event from the commit. */
108
181
  log: Log_
109
182
  }
110
183
 
184
+ /**
185
+ * Enumeration of possible execution states for a CCIP message.
186
+ */
111
187
  export const ExecutionState = {
188
+ /** Execution is in progress. */
112
189
  InProgress: 1,
190
+ /** Execution completed successfully. */
113
191
  Success: 2,
192
+ /** Execution failed. */
114
193
  Failed: 3,
115
194
  } as const
195
+ /** Type representing an execution state value. */
116
196
  export type ExecutionState = (typeof ExecutionState)[keyof typeof ExecutionState]
117
197
 
198
+ /**
199
+ * Receipt of a CCIP message execution on the destination chain.
200
+ */
118
201
  export type ExecutionReceipt = {
202
+ /** Unique message identifier. */
119
203
  messageId: string
204
+ /** Sequence number of the message. */
120
205
  sequenceNumber: bigint
206
+ /** Current execution state. */
121
207
  state: ExecutionState
208
+ /** Source chain selector (if available). */
122
209
  sourceChainSelector?: bigint
210
+ /** Hash of the message (if available). */
123
211
  messageHash?: string
212
+ /** Return data from the receiver contract (if any). */
124
213
  returnData?: BytesLike | Record<string, string>
214
+ /** Gas consumed by execution (if available). */
125
215
  gasUsed?: bigint
126
216
  }
127
217
 
218
+ /**
219
+ * Complete CCIP execution event with receipt, log, and timestamp.
220
+ */
128
221
  export interface CCIPExecution {
222
+ /** Execution receipt data. */
129
223
  receipt: ExecutionReceipt
224
+ /** Log event from the execution. */
130
225
  log: Log_
226
+ /** Unix timestamp of the execution. */
131
227
  timestamp: number
132
228
  }
133
229
 
230
+ /**
231
+ * Offchain token data for CCTP or other bridge attestations.
232
+ */
134
233
  export type OffchainTokenData = { _tag: string; [k: string]: BytesLike } | undefined
135
234
 
235
+ /**
236
+ * Execution report containing message, proofs, and offchain token data.
237
+ */
136
238
  export type ExecutionReport<M extends CCIPMessage = CCIPMessage> = {
239
+ /** The CCIP message to execute. */
137
240
  message: M
241
+ /** Merkle proofs for the message. */
138
242
  proofs: readonly BytesLike[]
243
+ /** Bit flags for proof verification. */
139
244
  proofFlagBits: bigint
245
+ /** Merkle root for verification. */
140
246
  merkleRoot: string
247
+ /** Offchain token data for each token transfer. */
141
248
  offchainTokenData: readonly OffchainTokenData[]
142
249
  }
143
250
 
144
251
  /**
145
- * A message to be sent to another network
252
+ * A message to be sent to another network.
146
253
  */
147
254
  export type AnyMessage = {
255
+ /** Receiver address on the destination chain. */
148
256
  receiver: BytesLike
257
+ /** Arbitrary data payload. */
149
258
  data: BytesLike
259
+ /** Extra arguments for gas limits and other settings. */
150
260
  extraArgs: ExtraArgs
261
+ /** Optional token transfers. */
151
262
  tokenAmounts?: readonly { token: string; amount: bigint }[]
263
+ /** Optional fee token address (native if omitted). */
152
264
  feeToken?: string
153
265
  }
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import util from 'util'
1
+ import { Buffer } from 'buffer'
2
2
 
3
3
  import bs58 from 'bs58'
4
4
  import {
@@ -8,15 +8,15 @@ import {
8
8
  decodeBase64,
9
9
  getBytes,
10
10
  isBytesLike,
11
- toBeHex,
11
+ toBeArray,
12
12
  toBigInt,
13
13
  } from 'ethers'
14
- import moize from 'moize'
14
+ import { memoize } from 'micro-memoize'
15
15
 
16
- import { type Chain, ChainFamily } from './chain.ts'
16
+ import type { Chain } from './chain.ts'
17
17
  import SELECTORS from './selectors.ts'
18
18
  import { supportedChains } from './supported-chains.ts'
19
- import type { NetworkInfo } from './types.ts'
19
+ import { type NetworkInfo, type WithLogger, ChainFamily } from './types.ts'
20
20
 
21
21
  /**
22
22
  * Returns *some* block number with timestamp prior to `timestamp`
@@ -31,7 +31,7 @@ export async function getSomeBlockNumberBefore(
31
31
  getBlockTimestamp: (blockNumber: number) => Promise<number>,
32
32
  recentBlockNumber: number,
33
33
  timestamp: number,
34
- precision = 10,
34
+ { precision = 10, logger = console }: { precision?: number } & WithLogger = {},
35
35
  ): Promise<number> {
36
36
  let beforeBlockNumber = Math.max(1, recentBlockNumber - precision * 1000)
37
37
  let beforeTimestamp = await getBlockTimestamp(beforeBlockNumber)
@@ -78,7 +78,7 @@ export async function getSomeBlockNumberBefore(
78
78
  beforeBlockNumber = pivot
79
79
  beforeTimestamp = pivotTimestamp
80
80
  }
81
- console.debug('getSomeBlockNumberBefore: searching block before', {
81
+ logger.debug('getSomeBlockNumberBefore: searching block before', {
82
82
  beforeBlockNumber,
83
83
  beforeTimestamp,
84
84
  pivot,
@@ -94,7 +94,7 @@ export async function getSomeBlockNumberBefore(
94
94
  }
95
95
 
96
96
  // memoized so we always output the same object for a given chainId
97
- const networkInfoFromChainId = moize.default((chainId: NetworkInfo['chainId']): NetworkInfo => {
97
+ const networkInfoFromChainId = memoize((chainId: NetworkInfo['chainId']): NetworkInfo => {
98
98
  const sel = SELECTORS[chainId]
99
99
  if (!sel?.name) throw new Error(`Chain not found: ${chainId}`)
100
100
  return {
@@ -115,7 +115,7 @@ const networkInfoFromChainId = moize.default((chainId: NetworkInfo['chainId']):
115
115
  * - Chain name as string ("ethereum-mainnet")
116
116
  * @returns Complete NetworkInfo object
117
117
  */
118
- export const networkInfo = moize.default(function networkInfo_(
118
+ export const networkInfo = memoize(function networkInfo_(
119
119
  selectorOrIdOrName: bigint | number | string,
120
120
  ): NetworkInfo {
121
121
  let chainId
@@ -181,6 +181,12 @@ export function* blockRangeGenerator(
181
181
  }
182
182
  }
183
183
 
184
+ /**
185
+ * JSON replacer function that converts BigInt values to strings.
186
+ * @param _key - Property key (unused).
187
+ * @param value - Value to transform.
188
+ * @returns String representation if BigInt, otherwise unchanged value.
189
+ */
184
190
  export function bigIntReplacer(_key: string, value: unknown): unknown {
185
191
  if (typeof value === 'bigint') {
186
192
  return value.toString()
@@ -188,6 +194,12 @@ export function bigIntReplacer(_key: string, value: unknown): unknown {
188
194
  return value
189
195
  }
190
196
 
197
+ /**
198
+ * JSON reviver function that converts numeric strings back to BigInt.
199
+ * @param _key - Property key (unused).
200
+ * @param value - Value to transform.
201
+ * @returns BigInt if numeric string, otherwise unchanged value.
202
+ */
191
203
  export function bigIntReviver(_key: string, value: unknown): unknown {
192
204
  if (typeof value === 'string' && /^\d+$/.test(value)) {
193
205
  return BigInt(value)
@@ -216,15 +228,31 @@ export function decodeOnRampAddress(
216
228
  return decoded
217
229
  }
218
230
 
231
+ /**
232
+ * Converts little-endian bytes to BigInt.
233
+ * @param data - Little-endian byte data.
234
+ * @returns BigInt value.
235
+ */
219
236
  export function leToBigInt(data: BytesLike | readonly number[]): bigint {
220
237
  if (Array.isArray(data)) data = new Uint8Array(data)
221
238
  return toBigInt(getBytes(data as BytesLike).reverse())
222
239
  }
223
240
 
241
+ /**
242
+ * Converts a BigNumber to little-endian byte array.
243
+ * @param value - Numeric value to convert.
244
+ * @param width - Optional byte width for padding.
245
+ * @returns Little-endian Uint8Array.
246
+ */
224
247
  export function toLeArray(value: BigNumberish, width?: Numeric): Uint8Array {
225
- return getBytes(toBeHex(value, width)).reverse()
248
+ return toBeArray(value, width).reverse()
226
249
  }
227
250
 
251
+ /**
252
+ * Checks if the given data is a valid Base64 encoded string.
253
+ * @param data - Data to check.
254
+ * @returns True if valid Base64 string.
255
+ */
228
256
  export function isBase64(data: unknown): data is string {
229
257
  return (
230
258
  typeof data === 'string' &&
@@ -232,6 +260,11 @@ export function isBase64(data: unknown): data is string {
232
260
  )
233
261
  }
234
262
 
263
+ /**
264
+ * Converts various data formats to Uint8Array.
265
+ * @param data - Bytes, number array, or Base64 string.
266
+ * @returns Uint8Array representation.
267
+ */
235
268
  export function getDataBytes(data: BytesLike | readonly number[]): Uint8Array {
236
269
  if (Array.isArray(data)) {
237
270
  return new Uint8Array(data)
@@ -245,6 +278,11 @@ export function getDataBytes(data: BytesLike | readonly number[]): Uint8Array {
245
278
  }
246
279
  }
247
280
 
281
+ /**
282
+ * Extracts address bytes, handling both hex and Base58 formats.
283
+ * @param address - Address in hex or Base58 format.
284
+ * @returns Address bytes as Uint8Array.
285
+ */
248
286
  export function getAddressBytes(address: BytesLike): Uint8Array {
249
287
  let bytes: Uint8Array
250
288
  if (isBytesLike(address)) {
@@ -295,8 +333,18 @@ export function convertKeysToCamelCase(
295
333
  return converted
296
334
  }
297
335
 
298
- export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
336
+ /**
337
+ * Promise-based sleep utility.
338
+ * @param ms - Duration in milliseconds.
339
+ * @returns Promise that resolves after the specified duration.
340
+ */
341
+ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms).unref())
299
342
 
343
+ /**
344
+ * Parses a typeAndVersion string into its components.
345
+ * @param typeAndVersion - String in format "TypeName vX.Y.Z".
346
+ * @returns Tuple of [type, version, original, suffix?].
347
+ */
300
348
  export function parseTypeAndVersion(
301
349
  typeAndVersion: string,
302
350
  ): Awaited<ReturnType<Chain['typeAndVersion']>> {
@@ -305,7 +353,7 @@ export function parseTypeAndVersion(
305
353
  throw new Error(
306
354
  `Invalid typeAndVersion: "${typeAndVersion}", len=${typeAndVersion.length}, hex=0x${Buffer.from(typeAndVersion).toString('hex')}`,
307
355
  )
308
- const [_, typeRaw, version] = match
356
+ const [, typeRaw, version] = match
309
357
  // some string normalization
310
358
  const type = typeRaw
311
359
  .replaceAll(/-(\w)/g, (_, w: string) => w.toUpperCase()) // kebabToPascal
@@ -319,14 +367,26 @@ export function parseTypeAndVersion(
319
367
  else return [type, version, typeAndVersion, match[3]]
320
368
  }
321
369
 
322
- export function createRateLimitedFetch({
323
- maxRequests = Number(process.env['RL_MAX_REQUESTS'] || 2),
324
- windowMs = Number(process.env['RL_WINDOW_MS'] || 10000),
325
- maxRetries = Number(process.env['RL_MAX_RETRIES'] || 5),
326
- }: { maxRequests?: number; windowMs?: number; maxRetries?: number } = {}): typeof fetch {
370
+ /**
371
+ * Creates a rate-limited fetch function with retry logic.
372
+ * Configurable via maxRequests, windowMs, and maxRetries options.
373
+ * @returns Rate-limited fetch function.
374
+ */
375
+ export function createRateLimitedFetch(
376
+ {
377
+ maxRequests = 40,
378
+ windowMs = 11e3,
379
+ maxRetries = 5,
380
+ }: { maxRequests?: number; windowMs?: number; maxRetries?: number } = {},
381
+ { logger = console }: WithLogger = {},
382
+ ): typeof fetch {
327
383
  // Custom fetch implementation with retry logic and rate limiting
328
384
  // Per-instance state
329
385
  const requestQueue: Array<{ timestamp: number }> = []
386
+ const methodRateLimits: Record<
387
+ string,
388
+ { limit: number; remaining: number; queue: Array<{ timestamp: number }> }
389
+ > = {}
330
390
 
331
391
  const isRateLimited = (): boolean => {
332
392
  const now = Date.now()
@@ -337,7 +397,31 @@ export function createRateLimitedFetch({
337
397
  return requestQueue.length >= maxRequests
338
398
  }
339
399
 
340
- const waitForRateLimit = async (): Promise<void> => {
400
+ const isMethodRateLimited = (method: string): boolean => {
401
+ const methodLimit = methodRateLimits[method]
402
+ if (!methodLimit) return false
403
+
404
+ const now = Date.now()
405
+ // Remove old requests outside the window
406
+ while (methodLimit.queue.length > 0 && now - methodLimit.queue[0].timestamp > windowMs) {
407
+ methodLimit.queue.shift()
408
+ }
409
+ return methodLimit.queue.length >= methodLimit.limit
410
+ }
411
+
412
+ const waitForRateLimit = async (method?: string): Promise<void> => {
413
+ // Wait for method-specific rate limit if applicable
414
+ if (method && methodRateLimits[method]) {
415
+ while (isMethodRateLimited(method)) {
416
+ const oldestRequest = methodRateLimits[method].queue[0]
417
+ const waitTime = windowMs - (Date.now() - oldestRequest.timestamp)
418
+ if (waitTime > 0) {
419
+ await sleep(waitTime + 100) // Add small buffer
420
+ }
421
+ }
422
+ }
423
+
424
+ // Wait for global rate limit
341
425
  while (isRateLimited()) {
342
426
  const oldestRequest = requestQueue[0]
343
427
  const waitTime = windowMs - (Date.now() - oldestRequest.timestamp)
@@ -347,8 +431,39 @@ export function createRateLimitedFetch({
347
431
  }
348
432
  }
349
433
 
350
- const recordRequest = (): void => {
351
- requestQueue.push({ timestamp: Date.now() })
434
+ const recordRequest = (method?: string): void => {
435
+ const timestamp = Date.now()
436
+ requestQueue.push({ timestamp })
437
+ if (method && methodRateLimits[method]) {
438
+ methodRateLimits[method].queue.push({ timestamp })
439
+ }
440
+ }
441
+
442
+ const updateMethodRateLimits = (response: Response, method?: string): void => {
443
+ if (!method) return
444
+
445
+ const limit = Number(response.headers.get('x-ratelimit-method-limit'))
446
+ const remaining = Number(response.headers.get('x-ratelimit-method-remaining'))
447
+
448
+ if (isNaN(limit) || isNaN(remaining)) return
449
+ if (!methodRateLimits[method]) {
450
+ methodRateLimits[method] = { limit, remaining, queue: [] }
451
+ } else {
452
+ methodRateLimits[method].limit = limit
453
+ methodRateLimits[method].remaining = remaining
454
+ }
455
+ }
456
+
457
+ const extractMethod = (init?: RequestInit): string | undefined => {
458
+ if (!init?.body || (typeof init.body !== 'string' && typeof init.body !== 'object')) return
459
+ try {
460
+ const parsed = (typeof init.body === 'string' ? JSON.parse(init.body) : init.body) as {
461
+ method?: string
462
+ }
463
+ if (parsed && typeof parsed.method === 'string') return parsed.method
464
+ } catch {
465
+ // Not JSON or no method field
466
+ }
352
467
  }
353
468
 
354
469
  const isRateLimitError = (error: unknown): boolean => {
@@ -360,19 +475,23 @@ export function createRateLimitedFetch({
360
475
 
361
476
  return async (input, init?) => {
362
477
  let lastError: Error | null = null
478
+ const method = extractMethod(init)
363
479
 
364
480
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
365
481
  try {
366
482
  // Wait for rate limit before making request
367
- await waitForRateLimit()
368
- recordRequest()
369
- // console.debug('__fetching', input, init?.body)
483
+ await waitForRateLimit(method)
484
+ recordRequest(method)
485
+ // logger.debug('__fetching', input, init?.body)
370
486
 
371
487
  const response = await fetch(input, init)
372
488
 
489
+ // Update method rate limits from response headers
490
+ updateMethodRateLimits(response, method)
491
+
373
492
  // If response is successful, return it
374
493
  if (response.ok) {
375
- console.debug('fetched', input, response.status, init?.body)
494
+ logger.debug('fetched', input, response.status, init?.body)
376
495
  return response
377
496
  }
378
497
 
@@ -382,10 +501,10 @@ export function createRateLimitedFetch({
382
501
  }
383
502
 
384
503
  // For other non-2xx responses, don't retry
385
- console.debug('fetch non-retryable error', input, response.status, init?.body)
504
+ logger.debug('fetch non-retryable error', input, response.status, init?.body)
386
505
  throw new Error(`HTTP ${response.status}: ${response.statusText}`)
387
506
  } catch (error) {
388
- console.debug('fetch errored', attempt, error, input, init?.body)
507
+ logger.debug('fetch errored', attempt, error, input, init?.body)
389
508
  lastError = error instanceof Error ? error : new Error(String(error))
390
509
 
391
510
  // Only retry on rate limit errors
@@ -403,3 +522,26 @@ export function createRateLimitedFetch({
403
522
  throw lastError || new Error('Request failed after all retries')
404
523
  }
405
524
  }
525
+
526
+ // barebones `node:util` backfill, if needed
527
+ const util =
528
+ 'util' in globalThis
529
+ ? (
530
+ globalThis as unknown as {
531
+ util: {
532
+ inspect: ((v: unknown) => string) & {
533
+ custom: symbol
534
+ defaultOptions: Record<string, unknown>
535
+ }
536
+ }
537
+ }
538
+ ).util
539
+ : {
540
+ inspect: Object.assign((v: unknown) => JSON.stringify(v), {
541
+ custom: Symbol('custom'),
542
+ defaultOptions: {
543
+ depth: 2,
544
+ } as Record<string, unknown>,
545
+ }),
546
+ }
547
+ export { util }
package/tsconfig.json CHANGED
@@ -13,6 +13,7 @@
13
13
  "rewriteRelativeImportExtensions": true,
14
14
  "verbatimModuleSyntax": true,
15
15
  "erasableSyntaxOnly": true,
16
- "resolveJsonModule": true
16
+ "resolveJsonModule": true,
17
+ "noImplicitOverride": true
17
18
  }
18
19
  }