@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
@@ -0,0 +1,255 @@
1
+ import {
2
+ type JsonRpcApiProvider,
3
+ type Log,
4
+ FetchRequest,
5
+ JsonRpcProvider,
6
+ isHexString,
7
+ } from 'ethers'
8
+ import { memoize } from 'micro-memoize'
9
+
10
+ import type { LogFilter } from '../chain.ts'
11
+ import { blockRangeGenerator, getSomeBlockNumberBefore, util } from '../utils.ts'
12
+ import { getAllFragmentsMatchingEvents } from './const.ts'
13
+ import type { WithLogger } from '../types.ts'
14
+
15
+ const MAX_PARALLEL_JOBS = 24
16
+ const PER_REQUEST_TIMEOUT = 5000
17
+
18
+ const getFallbackRpcsList = memoize(
19
+ async () => {
20
+ const response = await fetch('https://chainlist.org/rpcs.json')
21
+ const data = await response.json()
22
+ return data as {
23
+ chainId: number
24
+ rpc: { url: string }[]
25
+ explorers: { url: string }[]
26
+ }[]
27
+ },
28
+ { async: true },
29
+ )
30
+
31
+ // like Promise.any, but receives Promise factories and spawn a maximum number of them in parallel
32
+ function anyPromiseMax<T>(
33
+ promises: readonly (() => Promise<T>)[],
34
+ maxParallelJobs: number,
35
+ cancel?: Promise<unknown>,
36
+ ): Promise<T> {
37
+ return new Promise((resolve, reject) => {
38
+ const errors: unknown[] = new Array(promises.length)
39
+ let index = 0
40
+ let inFlight = 0
41
+ let completed = 0
42
+
43
+ if (promises.length === 0) {
44
+ reject(new AggregateError([], 'All promises were rejected'))
45
+ return
46
+ }
47
+ let cancelled = false
48
+ void cancel?.finally(() => {
49
+ cancelled = true
50
+ })
51
+
52
+ const startNext = () => {
53
+ while (!cancelled && inFlight < maxParallelJobs && index < promises.length) {
54
+ const currentIndex = index++
55
+ inFlight++
56
+
57
+ void promises[currentIndex]()
58
+ .then(resolve)
59
+ .catch((error) => {
60
+ errors[currentIndex] = error
61
+ completed++
62
+ inFlight--
63
+
64
+ if (completed === promises.length) {
65
+ reject(new AggregateError(errors, 'All promises were rejected'))
66
+ } else {
67
+ startNext()
68
+ }
69
+ })
70
+ }
71
+ }
72
+
73
+ startNext()
74
+ })
75
+ }
76
+
77
+ // cache
78
+ const archiveRpcs: Record<number, Promise<JsonRpcApiProvider>> = {}
79
+
80
+ /**
81
+ * Like provider.getLogs, but from a public list of archive nodes and wide range, races the first to reply
82
+ * @param chainId - The chain ID of the network to query
83
+ * @param filter - Log filter options
84
+ * @param destroy$ - An optional promise that, when resolved, cancels the requests
85
+ * @returns Array of Logs
86
+ */
87
+ async function getFallbackArchiveLogs(
88
+ chainId: number,
89
+ filter: {
90
+ address: string
91
+ topics: (string | string[] | null)[]
92
+ startBlock?: number
93
+ endBlock?: number | 'latest'
94
+ },
95
+ { logger = console, destroy$ }: { destroy$?: Promise<unknown> } & WithLogger = {},
96
+ ) {
97
+ const provider = archiveRpcs[chainId]
98
+ if (provider != null) {
99
+ return (await provider).getLogs({
100
+ ...filter,
101
+ fromBlock: filter.startBlock ?? 1,
102
+ toBlock: filter.endBlock ?? 'latest',
103
+ })
104
+ }
105
+ let cancel!: (_?: unknown) => void
106
+ let cancel$ = new Promise<unknown>((resolve) => (cancel = resolve))
107
+ if (destroy$) cancel$ = Promise.race([destroy$, cancel$])
108
+
109
+ let winner: string
110
+ const providerLogs$ = getFallbackRpcsList()
111
+ .then((rpcs) => {
112
+ const rpc = rpcs.find(({ chainId: id }) => id === chainId)
113
+ if (!rpc) throw new Error(`No RPC found for chainId=${chainId}`)
114
+ return Array.from(
115
+ new Set(rpc.rpc.map(({ url }) => url).filter((url) => url.match(/^https?:\/\//))),
116
+ )
117
+ })
118
+ .then((urls) =>
119
+ anyPromiseMax(
120
+ urls.map((url) => async () => {
121
+ const fetchReq = new FetchRequest(url)
122
+ fetchReq.timeout = PER_REQUEST_TIMEOUT
123
+ const provider = new JsonRpcProvider(fetchReq, chainId)
124
+ void cancel$.finally(() => {
125
+ if (url === winner) return
126
+ provider.destroy()
127
+ try {
128
+ fetchReq.cancel()
129
+ } catch (_) {
130
+ // ignore
131
+ }
132
+ })
133
+ return [
134
+ provider,
135
+ await provider
136
+ .getLogs({
137
+ ...filter,
138
+ fromBlock: filter.startBlock ?? 1,
139
+ toBlock: filter.endBlock ?? 'latest',
140
+ })
141
+ .then((logs) => {
142
+ if (!logs.length) throw new Error('No logs found')
143
+ logger.debug(
144
+ 'getFallbackArchiveLogs raced',
145
+ url,
146
+ 'from',
147
+ urls.length,
148
+ 'urls, got',
149
+ logs.length,
150
+ 'logs for',
151
+ filter,
152
+ )
153
+ winner ??= url
154
+ cancel()
155
+ return logs
156
+ }),
157
+ ] as const // return both winner provider and logs
158
+ }),
159
+ MAX_PARALLEL_JOBS,
160
+ cancel$,
161
+ ),
162
+ )
163
+ .finally(cancel)
164
+ archiveRpcs[chainId] = providerLogs$.then(([provider]) => provider) // cache provider
165
+ archiveRpcs[chainId].catch(() => {
166
+ delete archiveRpcs[chainId]
167
+ })
168
+ return providerLogs$.then(([, logs]) => logs) // return logs
169
+ }
170
+
171
+ /**
172
+ * Implements Chain.getLogs for EVM.
173
+ * If !(filter.startBlock|startTime), walks backwards from endBlock, otherwise forward from then.
174
+ * @param filter - Chain LogFilter. The `onlyFallback` option controls pagination behavior:
175
+ * - If undefined (default): paginate main provider only by filter.page
176
+ * - If false: first try whole range with main provider, then fallback to archive provider
177
+ * - If true: don't paginate (throw if can't fetch wide range from either provider)
178
+ * @param ctx - Context object containing provider, logger and destry$ notify promise
179
+ * @returns Async iterator of logs.
180
+ */
181
+ export async function* getEvmLogs(
182
+ filter: LogFilter & { onlyFallback?: boolean },
183
+ ctx: { provider: JsonRpcApiProvider; destroy$?: Promise<unknown> } & WithLogger,
184
+ ): AsyncIterableIterator<Log> {
185
+ const { provider, logger = console } = ctx
186
+ const endBlock = filter.endBlock ?? (await provider.getBlockNumber())
187
+ if (
188
+ filter.topics?.length &&
189
+ filter.topics.every((t: string | string[] | null): t is string => typeof t === 'string')
190
+ ) {
191
+ const topics = new Set(
192
+ filter.topics
193
+ .filter(isHexString)
194
+ .concat(Object.keys(getAllFragmentsMatchingEvents(filter.topics)) as `0x${string}`[])
195
+ .flat(),
196
+ )
197
+ if (!topics.size) {
198
+ throw new Error(`Could not find matching topics: ${util.inspect(filter.topics)}`)
199
+ }
200
+ filter.topics = [Array.from(topics)]
201
+ }
202
+ if (filter.startBlock == null && filter.startTime) {
203
+ filter.startBlock = await getSomeBlockNumberBefore(
204
+ async (block: number | 'finalized') => (await provider.getBlock(block))!.timestamp, // cached
205
+ endBlock,
206
+ filter.startTime,
207
+ ctx,
208
+ )
209
+ }
210
+ if (filter.onlyFallback != null && filter.address && filter.topics?.length) {
211
+ let logs
212
+ try {
213
+ logs = await provider.getLogs({
214
+ ...filter,
215
+ fromBlock: filter.startBlock ?? 1,
216
+ toBlock: filter.endBlock ?? 'latest',
217
+ })
218
+ } catch (_) {
219
+ try {
220
+ logs = await getFallbackArchiveLogs(
221
+ Number((await provider.getNetwork()).chainId),
222
+ {
223
+ address: filter.address,
224
+ topics: filter.topics,
225
+ startBlock: filter.startBlock ?? 1,
226
+ endBlock: filter.endBlock ?? 'latest',
227
+ },
228
+ ctx,
229
+ )
230
+ } catch (err) {
231
+ if (filter.onlyFallback === true) throw err
232
+ }
233
+ }
234
+ if (logs) {
235
+ if (!filter.startBlock) logs.reverse()
236
+ yield* logs
237
+ return
238
+ }
239
+ }
240
+ // paginate only if filter.onlyFallback is nullish
241
+ for (const blockRange of blockRangeGenerator({ ...filter, endBlock })) {
242
+ logger.debug('evm getLogs:', {
243
+ ...blockRange,
244
+ ...(filter.address ? { address: filter.address } : {}),
245
+ ...(filter.topics?.length ? { topics: filter.topics } : {}),
246
+ })
247
+ const logs = await provider.getLogs({
248
+ ...blockRange,
249
+ ...(filter.address ? { address: filter.address } : {}),
250
+ ...(filter.topics?.length ? { topics: filter.topics } : {}),
251
+ })
252
+ if (!filter.startBlock) logs.reverse()
253
+ yield* logs
254
+ }
255
+ }
@@ -7,7 +7,7 @@ import type EVM2EVMOnRamp_1_5_ABI from './abi/OnRamp_1_5.ts'
7
7
  import type OnRamp_1_6_ABI from './abi/OnRamp_1_6.ts'
8
8
  import { defaultAbiCoder } from './const.ts'
9
9
 
10
- // addresses often come as `string | Addressable`, this type cleans them up to just `string`
10
+ /** Utility type that cleans up address types to just `string`. */
11
11
  export type CleanAddressable<T> = T extends string | Addressable
12
12
  ? string
13
13
  : T extends Record<string, unknown>
@@ -23,7 +23,7 @@ type EVM2AnyMessageRequested = CleanAddressable<
23
23
  >[0]
24
24
  >
25
25
 
26
- // v1.6+ Message Base (all other dests share this intersection)
26
+ /** v1.6+ Message Base type (all other destinations share this intersection). */
27
27
  export type CCIPMessage_V1_6 = MergeArrayElements<
28
28
  CleanAddressable<
29
29
  AbiParametersToPrimitiveTypes<
@@ -33,6 +33,7 @@ export type CCIPMessage_V1_6 = MergeArrayElements<
33
33
  { tokenAmounts: readonly SourceTokenData[] }
34
34
  >
35
35
 
36
+ /** CCIP v1.5 EVM message type. */
36
37
  export type CCIPMessage_V1_5_EVM = MergeArrayElements<
37
38
  EVM2AnyMessageRequested,
38
39
  {
@@ -41,13 +42,15 @@ export type CCIPMessage_V1_5_EVM = MergeArrayElements<
41
42
  }
42
43
  >
43
44
 
45
+ /** CCIP v1.2 EVM message type. */
44
46
  export type CCIPMessage_V1_2_EVM = EVM2AnyMessageRequested & {
45
47
  header: Omit<CCIPMessage_V1_6['header'], 'destChainSelector'>
46
48
  }
47
49
 
48
- // v1.6 EVM specialization, extends CCIPMessage_V1_6, plus EVMExtraArgsV2 and tokenAmounts.*.destGasAmount
50
+ /** v1.6 EVM specialization with EVMExtraArgsV2 and tokenAmounts.*.destGasAmount. */
49
51
  export type CCIPMessage_V1_6_EVM = CCIPMessage_V1_6 & EVMExtraArgsV2
50
52
 
53
+ /** Union type for CCIP EVM messages across versions. */
51
54
  export type CCIPMessage_EVM<V extends CCIPVersion = CCIPVersion> = V extends typeof CCIPVersion.V1_2
52
55
  ? CCIPMessage_V1_2_EVM
53
56
  : V extends typeof CCIPVersion.V1_5
@@ -56,6 +59,7 @@ export type CCIPMessage_EVM<V extends CCIPVersion = CCIPVersion> = V extends typ
56
59
 
57
60
  const SourceTokenData =
58
61
  'tuple(bytes sourcePoolAddress, bytes destTokenAddress, bytes extraData, uint64 destGasAmount)'
62
+ /** Token transfer data in a CCIP message. */
59
63
  export type SourceTokenData = {
60
64
  sourcePoolAddress: string
61
65
  destTokenAddress: string
@@ -64,8 +68,10 @@ export type SourceTokenData = {
64
68
  }
65
69
 
66
70
  /**
67
- * parse <=v1.5 `message.sourceTokenData`;
68
- * v1.6+ already contains this in `message.tokenAmounts`
71
+ * Parses v1.5 and earlier `message.sourceTokenData`.
72
+ * Version 1.6+ already contains this in `message.tokenAmounts`.
73
+ * @param data - The source token data string to parse.
74
+ * @returns The parsed SourceTokenData object.
69
75
  */
70
76
  export function parseSourceTokenData(data: string): SourceTokenData {
71
77
  const decoded = defaultAbiCoder.decode([SourceTokenData], data)
@@ -1,7 +1,7 @@
1
1
  import { type Addressable, type Log, EventFragment } from 'ethers'
2
2
 
3
3
  import { getLbtcAttestation, getUsdcAttestation } from '../offchain.ts'
4
- import type { CCIPMessage, CCIPRequest, OffchainTokenData } from '../types.ts'
4
+ import type { CCIPMessage, CCIPRequest, OffchainTokenData, WithLogger } from '../types.ts'
5
5
  import { networkInfo } from '../utils.ts'
6
6
  import { defaultAbiCoder, interfaces, requestsFragments } from './const.ts'
7
7
  import { type SourceTokenData, parseSourceTokenData } from './messages.ts'
@@ -32,6 +32,7 @@ export async function fetchEVMOffchainTokenData(
32
32
  message: CCIPMessage
33
33
  log: Pick<CCIPRequest['log'], 'index'>
34
34
  },
35
+ ctx: WithLogger,
35
36
  ): Promise<OffchainTokenData[]> {
36
37
  const { isTestnet } = networkInfo(request.message.header.sourceChainSelector)
37
38
  // there's a chance there are other CCIPSendRequested in same tx,
@@ -52,6 +53,7 @@ export async function fetchEVMOffchainTokenData(
52
53
  request.message.tokenAmounts,
53
54
  usdcRequestLogs,
54
55
  isTestnet,
56
+ ctx,
55
57
  )
56
58
  let lbtcTokenData: OffchainTokenData[] = []
57
59
  try {
@@ -62,7 +64,7 @@ export async function fetchEVMOffchainTokenData(
62
64
  tokenAmounts = request.message.tokenAmounts
63
65
  }
64
66
  //for lbtc we distinguish logs by hash in event, so we can pass all of them
65
- lbtcTokenData = await getLbtcTokenData(tokenAmounts, request.tx.logs as Log[], isTestnet)
67
+ lbtcTokenData = await getLbtcTokenData(tokenAmounts, request.tx.logs as Log[], isTestnet, ctx)
66
68
  } catch (_) {
67
69
  // pass
68
70
  }
@@ -78,6 +80,11 @@ export async function fetchEVMOffchainTokenData(
78
80
  return offchainTokenData
79
81
  }
80
82
 
83
+ /**
84
+ * Encodes offchain token data for EVM execution.
85
+ * @param data - Offchain token data to encode.
86
+ * @returns ABI-encoded data or empty hex string.
87
+ */
81
88
  export function encodeEVMOffchainTokenData(data: OffchainTokenData): string {
82
89
  if (data?._tag === 'usdc') {
83
90
  return defaultAbiCoder.encode(['tuple(bytes message, bytes attestation)'], [data])
@@ -99,6 +106,7 @@ async function getUsdcTokenData(
99
106
  tokenAmounts: CCIPMessage['tokenAmounts'],
100
107
  allLogsInRequest: Pick<Log, 'topics' | 'address' | 'data'>[],
101
108
  isTestnet: boolean,
109
+ { logger = console }: WithLogger = {},
102
110
  ): Promise<OffchainTokenData[]> {
103
111
  const attestations: OffchainTokenData[] = []
104
112
 
@@ -151,7 +159,7 @@ async function getUsdcTokenData(
151
159
  // encoding of OffchainTokenData to be done as part of Chain.executeReceipt
152
160
  } catch (err) {
153
161
  // maybe not a USDC transfer, or not ready
154
- console.warn(`❌ EVM CCTP: Failed to fetch attestation for message:`, message, err)
162
+ logger.warn(`❌ EVM CCTP: Failed to fetch attestation for message:`, message, err)
155
163
  }
156
164
  }
157
165
  attestations.push(tokenData)
@@ -167,6 +175,7 @@ async function getLbtcTokenData(
167
175
  tokenAmounts: readonly SourceTokenData[],
168
176
  allLogsInRequest: readonly Pick<Log, 'topics' | 'address' | 'data'>[],
169
177
  isTestnet: boolean,
178
+ { logger = console }: WithLogger = {},
170
179
  ): Promise<OffchainTokenData[]> {
171
180
  const lbtcDepositHashes = new Set(
172
181
  allLogsInRequest
@@ -181,7 +190,7 @@ async function getLbtcTokenData(
181
190
  try {
182
191
  return { _tag: 'lbtc', extraData, ...(await getLbtcAttestation(extraData, isTestnet)) }
183
192
  } catch (err) {
184
- console.warn(`❌ EVM LBTC: Failed to fetch attestation for message:`, extraData, err)
193
+ logger.warn(`❌ EVM LBTC: Failed to fetch attestation for message:`, extraData, err)
185
194
  }
186
195
  }
187
196
  }),
package/src/execution.ts CHANGED
@@ -1,14 +1,16 @@
1
- import moize from 'moize'
1
+ import { memoize } from 'micro-memoize'
2
2
 
3
3
  import type { Chain, ChainStatic } from './chain.ts'
4
4
  import { Tree, getLeafHasher, proofFlagsToBits } from './hasher/index.ts'
5
5
  import {
6
+ type CCIPCommit,
6
7
  type CCIPExecution,
7
8
  type CCIPMessage,
9
+ type CCIPRequest,
8
10
  type CCIPVersion,
9
- type CommitReport,
10
11
  type ExecutionReport,
11
12
  type Lane,
13
+ type WithLogger,
12
14
  ExecutionState,
13
15
  } from './types.ts'
14
16
 
@@ -16,9 +18,10 @@ import {
16
18
  * Pure/sync function to calculate/generate OffRamp.executeManually report for messageIds
17
19
  *
18
20
  * @param messagesInBatch - Array containing all messages in batch, ordered
19
- * @param lane - Arguments for leafeHasher (lane info)
20
- * @param messageIds - list of messages (from batch) to prove for manual execution
21
+ * @param lane - Arguments for leafHasher (lane info)
22
+ * @param messageId - Message ID to prove for manual execution
21
23
  * @param merkleRoot - Optional merkleRoot of the CommitReport, for validation
24
+ * @param ctx - Context for logging
22
25
  * @returns ManualExec report arguments
23
26
  **/
24
27
  export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
@@ -26,8 +29,9 @@ export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
26
29
  lane: Lane<V>,
27
30
  messageId: string,
28
31
  merkleRoot?: string,
32
+ ctx?: WithLogger,
29
33
  ): Omit<ExecutionReport, 'offchainTokenData' | 'message'> {
30
- const hasher = getLeafHasher(lane)
34
+ const hasher = getLeafHasher(lane, ctx)
31
35
 
32
36
  const msgIdx = messagesInBatch.findIndex((message) => message.header.messageId === messageId)
33
37
  if (msgIdx < 0) {
@@ -56,8 +60,13 @@ export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
56
60
  }
57
61
  }
58
62
 
59
- export const discoverOffRamp = moize.default(
60
- async function discoverOffRamp_(source: Chain, dest: Chain, onRamp: string): Promise<string> {
63
+ export const discoverOffRamp = memoize(
64
+ async function discoverOffRamp_(
65
+ source: Chain,
66
+ dest: Chain,
67
+ onRamp: string,
68
+ { logger = console }: WithLogger = {},
69
+ ): Promise<string> {
61
70
  const sourceRouter = await source.getRouterForOnRamp(onRamp, dest.network.chainSelector)
62
71
  const sourceOffRamps = await source.getOffRampsForRouter(
63
72
  sourceRouter,
@@ -70,7 +79,7 @@ export const discoverOffRamp = moize.default(
70
79
  for (const offRamp of destOffRamps) {
71
80
  const offRampsOnRamp = await dest.getOnRampForOffRamp(offRamp, source.network.chainSelector)
72
81
  if (offRampsOnRamp === onRamp) {
73
- console.debug(
82
+ logger.debug(
74
83
  'discoverOffRamp: found, from',
75
84
  { sourceRouter, sourceOffRamps, destOnRamp, destOffRamps, offRampsOnRamp },
76
85
  '=',
@@ -83,49 +92,48 @@ export const discoverOffRamp = moize.default(
83
92
  throw new Error(`No matching offRamp found for "${onRamp}" on "${dest.network.name}"`)
84
93
  },
85
94
  {
86
- transformArgs: ([source, dest, onRamp]) => [
87
- (source as Chain).network.chainSelector,
88
- (dest as Chain).network.chainSelector,
89
- onRamp as string,
90
- ],
95
+ transformKey: ([source, dest, onRamp]) =>
96
+ [source.network.chainSelector, dest.network.chainSelector, onRamp] as const,
91
97
  },
92
98
  )
93
99
 
94
100
  /**
95
- * Generic implementation for fetching ExecutionReceipts for given requests
96
- * If more than one request is given, may yield them interleaved
97
- * Completes as soon as there's no more work to be done
98
- * 2 possible behaviors:
101
+ * Generic implementation for fetching ExecutionReceipts for given requests.
102
+ * If more than one request is given, may yield them interleaved.
103
+ * Completes as soon as there's no more work to be done.
104
+ *
105
+ * Two possible behaviors:
99
106
  * - if `startBlock|startTime` is given, pages forward from that block up;
100
107
  * completes when success (final) receipt is found for all requests (or reach latest block)
101
108
  * - otherwise, pages backwards and returns only the most recent receipt per request;
102
109
  * completes when receipts for all requests were seen
103
110
  *
104
- * @param dest - provider to page through
105
- * @param requests - CCIP requests to search executions for
106
- * @param hints.fromBlock - A block from where to start paging forward;
107
- * otherwise, page backwards and completes on first (most recent) receipt
108
- * @param hints.page - getLogs pagination range param
109
- * @param hints.commit - Special param to help narrow down search on suppported chains (e.g. Solana)
110
- **/
111
+ * @param dest - Provider to page through.
112
+ * @param offRamp - OffRamp contract address.
113
+ * @param request - CCIP request to search executions for.
114
+ * @param commit - Optional commit info to narrow down search.
115
+ * @param hints - Optional hints (e.g., `page` for getLogs pagination range).
116
+ */
111
117
  export async function* fetchExecutionReceipts(
112
118
  dest: Chain,
113
119
  offRamp: string,
114
- messageIds: Set<string>,
115
- hints?: { startBlock?: number; startTime?: number; page?: number; commit?: CommitReport },
120
+ request: CCIPRequest,
121
+ commit?: CCIPCommit,
122
+ hints?: { page?: number },
116
123
  ): AsyncGenerator<CCIPExecution> {
117
- const onlyLast = !hints?.startBlock && !hints?.startTime // backwards
124
+ const onlyLast = !commit?.log.blockNumber && !request.tx.timestamp // backwards
118
125
  for await (const log of dest.getLogs({
119
- ...hints,
126
+ startBlock: commit?.log.blockNumber,
127
+ startTime: request.tx.timestamp,
120
128
  address: offRamp,
121
129
  topics: ['ExecutionStateChanged'],
130
+ ...hints,
122
131
  })) {
123
132
  const receipt = (dest.constructor as ChainStatic).decodeReceipt(log)
124
- if (!receipt || !messageIds.has(receipt.messageId)) continue
125
- if (onlyLast || receipt.state === ExecutionState.Success) messageIds.delete(receipt.messageId)
133
+ if (!receipt || receipt.messageId !== request.message.header.messageId) continue
126
134
 
127
- const timestamp = await dest.getBlockTimestamp(log.blockNumber)
135
+ const timestamp = log.tx?.timestamp ?? (await dest.getBlockTimestamp(log.blockNumber))
128
136
  yield { receipt, log, timestamp }
129
- if (!messageIds.size) break
137
+ if (onlyLast || receipt.state === ExecutionState.Success) break
130
138
  }
131
139
  }
package/src/extra-args.ts CHANGED
@@ -1,32 +1,63 @@
1
1
  import { type BytesLike, id } from 'ethers'
2
2
 
3
- import { ChainFamily } from './chain.ts'
4
3
  import { supportedChains } from './supported-chains.ts'
4
+ import { ChainFamily } from './types.ts'
5
5
 
6
+ /** Tag identifier for EVMExtraArgsV1 encoding. */
6
7
  export const EVMExtraArgsV1Tag = id('CCIP EVMExtraArgsV1').substring(0, 10) as '0x97a657c9'
8
+ /** Tag identifier for EVMExtraArgsV2 encoding. */
7
9
  export const EVMExtraArgsV2Tag = id('CCIP EVMExtraArgsV2').substring(0, 10) as '0x181dcf10'
10
+ /** Tag identifier for SVMExtraArgsV1 encoding. */
8
11
  export const SVMExtraArgsV1Tag = id('CCIP SVMExtraArgsV1').substring(0, 10) as '0x1f3b3aba'
12
+ /** Tag identifier for SuiExtraArgsV1 encoding. */
9
13
  export const SuiExtraArgsV1Tag = id('CCIP SuiExtraArgsV1').substring(0, 10) as '0x21ea4ca9'
10
14
 
15
+ /**
16
+ * EVM extra arguments version 1 with gas limit only.
17
+ */
11
18
  export type EVMExtraArgsV1 = {
19
+ /** Gas limit for execution on the destination chain. */
12
20
  gasLimit: bigint
13
21
  }
14
- // aka GenericExtraArgsV2
22
+
23
+ /**
24
+ * EVM extra arguments version 2 with out-of-order execution support.
25
+ * Also known as GenericExtraArgsV2.
26
+ */
15
27
  export type EVMExtraArgsV2 = EVMExtraArgsV1 & {
28
+ /** Whether to allow out-of-order message execution. */
16
29
  allowOutOfOrderExecution: boolean
17
30
  }
31
+
32
+ /**
33
+ * Solana (SVM) extra arguments version 1.
34
+ */
18
35
  export type SVMExtraArgsV1 = {
36
+ /** Compute units for Solana execution. */
19
37
  computeUnits: bigint
38
+ /** Bitmap indicating which accounts are writable. */
20
39
  accountIsWritableBitmap: bigint
40
+ /** Whether to allow out-of-order message execution. */
21
41
  allowOutOfOrderExecution: boolean
42
+ /** Token receiver address on Solana. */
22
43
  tokenReceiver: string
44
+ /** Additional account addresses required for execution. */
23
45
  accounts: string[]
24
46
  }
47
+
48
+ /**
49
+ * Sui extra arguments version 1.
50
+ */
25
51
  export type SuiExtraArgsV1 = EVMExtraArgsV2 & {
52
+ /** Token receiver address on Sui. */
26
53
  tokenReceiver: string
54
+ /** Object IDs required for the receiver. */
27
55
  receiverObjectIds: string[]
28
56
  }
29
57
 
58
+ /**
59
+ * Union type of all supported extra arguments formats.
60
+ */
30
61
  export type ExtraArgs = EVMExtraArgsV1 | EVMExtraArgsV2 | SVMExtraArgsV1 | SuiExtraArgsV1
31
62
 
32
63
  /**
@@ -41,10 +72,11 @@ export function encodeExtraArgs(args: ExtraArgs, from: ChainFamily = ChainFamily
41
72
  }
42
73
 
43
74
  /**
44
- * Parses extra arguments from CCIP messages
45
- * @param data - extra arguments bytearray data
46
- * @returns extra arguments object if found
47
- **/
75
+ * Parses extra arguments from CCIP messages.
76
+ * @param data - Extra arguments bytearray data.
77
+ * @param from - Optional chain family to narrow decoding attempts.
78
+ * @returns Extra arguments object if found, undefined otherwise.
79
+ */
48
80
  export function decodeExtraArgs(
49
81
  data: BytesLike,
50
82
  from?: ChainFamily,
package/src/gas.ts CHANGED
@@ -35,14 +35,12 @@ const ccipReceive = FunctionFragment.from({
35
35
  type Any2EVMMessage = Parameters<TypedContract<typeof RouterABI>['routeMessage']>[0]
36
36
 
37
37
  /**
38
- * Estimate CCIP gasLimit needed to execute a request on a contract receiver
39
- *
40
- * @param dest - Provider for the destination chain
41
- * @param request - CCIP request info
42
- * @param request.lane - Lane info
43
- * @param request.message - Message info
44
- * @returns estimated gasLimit as bigint
45
- **/
38
+ * Estimate CCIP gasLimit needed to execute a request on a contract receiver.
39
+ * @param source - Provider for the source chain.
40
+ * @param dest - Provider for the destination chain.
41
+ * @param request - CCIP request info containing `lane` and `message` details.
42
+ * @returns Estimated gasLimit as bigint.
43
+ */
46
44
  export async function estimateExecGasForRequest(
47
45
  source: Chain,
48
46
  dest: EVMChain,
@@ -60,7 +58,7 @@ export async function estimateExecGasForRequest(
60
58
  }
61
59
  },
62
60
  ) {
63
- const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp)
61
+ const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp, source)
64
62
  const destRouter = await dest.getRouterForOffRamp(offRamp, request.lane.sourceChainSelector)
65
63
 
66
64
  const destTokenAmounts = await Promise.all(
@@ -2,6 +2,7 @@ import { concat, hexlify, keccak256, toBeHex } from 'ethers'
2
2
 
3
3
  import type { CCIPMessage, CCIPVersion } from '../types.ts'
4
4
 
5
+ /** Function type for computing the leaf hash of a CCIP message. */
5
6
  export type LeafHasher<V extends CCIPVersion = CCIPVersion> = (message: CCIPMessage<V>) => string
6
7
 
7
8
  const INTERNAL_DOMAIN_SEPARATOR = toBeHex(1, 32)
@@ -10,8 +11,8 @@ export const ZERO_HASH = hexlify(new Uint8Array(32).fill(0xff))
10
11
 
11
12
  /**
12
13
  * Computes the Keccak-256 hash of the concatenation of two hash values.
13
- * @param a The first hash as a Hash type.
14
- * @param b The second hash as a Hash type.
14
+ * @param a - The first hash as a Hash type.
15
+ * @param b - The second hash as a Hash type.
15
16
  * @returns The Keccak-256 hash result as a Hash type.
16
17
  */
17
18
  export function hashInternal(a: string, b: string): string {