@chainlink/ccip-sdk 0.91.0 → 0.92.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 (273) hide show
  1. package/README.md +127 -80
  2. package/dist/aptos/hasher.d.ts.map +1 -1
  3. package/dist/aptos/hasher.js +7 -6
  4. package/dist/aptos/hasher.js.map +1 -1
  5. package/dist/aptos/index.d.ts +7 -2
  6. package/dist/aptos/index.d.ts.map +1 -1
  7. package/dist/aptos/index.js +29 -20
  8. package/dist/aptos/index.js.map +1 -1
  9. package/dist/aptos/logs.d.ts +5 -3
  10. package/dist/aptos/logs.d.ts.map +1 -1
  11. package/dist/aptos/logs.js +64 -27
  12. package/dist/aptos/logs.js.map +1 -1
  13. package/dist/aptos/token.d.ts.map +1 -1
  14. package/dist/aptos/token.js +2 -1
  15. package/dist/aptos/token.js.map +1 -1
  16. package/dist/aptos/types.js +6 -6
  17. package/dist/aptos/types.js.map +1 -1
  18. package/dist/chain.d.ts +36 -11
  19. package/dist/chain.d.ts.map +1 -1
  20. package/dist/chain.js +34 -2
  21. package/dist/chain.js.map +1 -1
  22. package/dist/commits.d.ts +2 -3
  23. package/dist/commits.d.ts.map +1 -1
  24. package/dist/commits.js +19 -8
  25. package/dist/commits.js.map +1 -1
  26. package/dist/errors/CCIPError.d.ts +48 -0
  27. package/dist/errors/CCIPError.d.ts.map +1 -0
  28. package/dist/errors/CCIPError.js +65 -0
  29. package/dist/errors/CCIPError.js.map +1 -0
  30. package/dist/errors/codes.d.ts +120 -0
  31. package/dist/errors/codes.d.ts.map +1 -0
  32. package/dist/errors/codes.js +156 -0
  33. package/dist/errors/codes.js.map +1 -0
  34. package/dist/errors/index.d.ts +26 -0
  35. package/dist/errors/index.d.ts.map +1 -0
  36. package/dist/errors/index.js +51 -0
  37. package/dist/errors/index.js.map +1 -0
  38. package/dist/errors/recovery.d.ts +6 -0
  39. package/dist/errors/recovery.d.ts.map +1 -0
  40. package/dist/errors/recovery.js +118 -0
  41. package/dist/errors/recovery.js.map +1 -0
  42. package/dist/errors/specialized.d.ts +637 -0
  43. package/dist/errors/specialized.d.ts.map +1 -0
  44. package/dist/errors/specialized.js +1298 -0
  45. package/dist/errors/specialized.js.map +1 -0
  46. package/dist/errors/utils.d.ts +11 -0
  47. package/dist/errors/utils.d.ts.map +1 -0
  48. package/dist/errors/utils.js +61 -0
  49. package/dist/errors/utils.js.map +1 -0
  50. package/dist/evm/abi/CommitStore_1_5.js +1 -1
  51. package/dist/evm/abi/LockReleaseTokenPool_1_5.js +1 -1
  52. package/dist/evm/abi/OffRamp_1_5.js +1 -1
  53. package/dist/evm/abi/OnRamp_1_5.js +1 -1
  54. package/dist/evm/abi/PriceRegistry_1_2.d.ts +443 -0
  55. package/dist/evm/abi/PriceRegistry_1_2.d.ts.map +1 -0
  56. package/dist/evm/abi/PriceRegistry_1_2.js +439 -0
  57. package/dist/evm/abi/PriceRegistry_1_2.js.map +1 -0
  58. package/dist/evm/const.d.ts +1 -0
  59. package/dist/evm/const.d.ts.map +1 -1
  60. package/dist/evm/const.js +2 -0
  61. package/dist/evm/const.js.map +1 -1
  62. package/dist/evm/hasher.d.ts.map +1 -1
  63. package/dist/evm/hasher.js +7 -6
  64. package/dist/evm/hasher.js.map +1 -1
  65. package/dist/evm/index.d.ts +9 -13
  66. package/dist/evm/index.d.ts.map +1 -1
  67. package/dist/evm/index.js +85 -68
  68. package/dist/evm/index.js.map +1 -1
  69. package/dist/evm/logs.d.ts.map +1 -1
  70. package/dist/evm/logs.js +47 -16
  71. package/dist/evm/logs.js.map +1 -1
  72. package/dist/evm/messages.d.ts +7 -6
  73. package/dist/evm/messages.d.ts.map +1 -1
  74. package/dist/evm/offchain.js +1 -1
  75. package/dist/evm/offchain.js.map +1 -1
  76. package/dist/evm/types.d.ts +10 -0
  77. package/dist/evm/types.d.ts.map +1 -0
  78. package/dist/evm/types.js +2 -0
  79. package/dist/evm/types.js.map +1 -0
  80. package/dist/execution.d.ts.map +1 -1
  81. package/dist/execution.js +9 -5
  82. package/dist/execution.js.map +1 -1
  83. package/dist/extra-args.d.ts.map +1 -1
  84. package/dist/extra-args.js +4 -3
  85. package/dist/extra-args.js.map +1 -1
  86. package/dist/gas.d.ts.map +1 -1
  87. package/dist/gas.js +3 -2
  88. package/dist/gas.js.map +1 -1
  89. package/dist/hasher/hasher.d.ts.map +1 -1
  90. package/dist/hasher/hasher.js +2 -1
  91. package/dist/hasher/hasher.js.map +1 -1
  92. package/dist/hasher/merklemulti.d.ts.map +1 -1
  93. package/dist/hasher/merklemulti.js +9 -8
  94. package/dist/hasher/merklemulti.js.map +1 -1
  95. package/dist/index.d.ts +5 -2
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +6 -2
  98. package/dist/index.js.map +1 -1
  99. package/dist/offchain.d.ts.map +1 -1
  100. package/dist/offchain.js +5 -8
  101. package/dist/offchain.js.map +1 -1
  102. package/dist/requests.d.ts +1 -1
  103. package/dist/requests.d.ts.map +1 -1
  104. package/dist/requests.js +37 -43
  105. package/dist/requests.js.map +1 -1
  106. package/dist/selectors.d.ts.map +1 -1
  107. package/dist/selectors.js +22 -0
  108. package/dist/selectors.js.map +1 -1
  109. package/dist/solana/cleanup.d.ts +2 -2
  110. package/dist/solana/cleanup.d.ts.map +1 -1
  111. package/dist/solana/cleanup.js +2 -3
  112. package/dist/solana/cleanup.js.map +1 -1
  113. package/dist/solana/exec.d.ts.map +1 -1
  114. package/dist/solana/exec.js +12 -12
  115. package/dist/solana/exec.js.map +1 -1
  116. package/dist/solana/hasher.d.ts.map +1 -1
  117. package/dist/solana/hasher.js +6 -5
  118. package/dist/solana/hasher.js.map +1 -1
  119. package/dist/solana/index.d.ts +30 -13
  120. package/dist/solana/index.d.ts.map +1 -1
  121. package/dist/solana/index.js +96 -143
  122. package/dist/solana/index.js.map +1 -1
  123. package/dist/solana/logs.d.ts +15 -0
  124. package/dist/solana/logs.d.ts.map +1 -0
  125. package/dist/solana/logs.js +106 -0
  126. package/dist/solana/logs.js.map +1 -0
  127. package/dist/solana/offchain.d.ts.map +1 -1
  128. package/dist/solana/offchain.js +6 -5
  129. package/dist/solana/offchain.js.map +1 -1
  130. package/dist/solana/patchBorsh.d.ts.map +1 -1
  131. package/dist/solana/patchBorsh.js +3 -2
  132. package/dist/solana/patchBorsh.js.map +1 -1
  133. package/dist/solana/send.d.ts.map +1 -1
  134. package/dist/solana/send.js +8 -7
  135. package/dist/solana/send.js.map +1 -1
  136. package/dist/solana/utils.d.ts +7 -8
  137. package/dist/solana/utils.d.ts.map +1 -1
  138. package/dist/solana/utils.js +23 -11
  139. package/dist/solana/utils.js.map +1 -1
  140. package/dist/sui/discovery.d.ts +18 -0
  141. package/dist/sui/discovery.d.ts.map +1 -0
  142. package/dist/sui/discovery.js +116 -0
  143. package/dist/sui/discovery.js.map +1 -0
  144. package/dist/sui/events.d.ts +36 -0
  145. package/dist/sui/events.d.ts.map +1 -0
  146. package/dist/sui/events.js +179 -0
  147. package/dist/sui/events.js.map +1 -0
  148. package/dist/sui/hasher.d.ts.map +1 -1
  149. package/dist/sui/hasher.js +6 -5
  150. package/dist/sui/hasher.js.map +1 -1
  151. package/dist/sui/index.d.ts +69 -41
  152. package/dist/sui/index.d.ts.map +1 -1
  153. package/dist/sui/index.js +402 -65
  154. package/dist/sui/index.js.map +1 -1
  155. package/dist/sui/manuallyExec/encoder.d.ts +8 -0
  156. package/dist/sui/manuallyExec/encoder.d.ts.map +1 -0
  157. package/dist/sui/manuallyExec/encoder.js +76 -0
  158. package/dist/sui/manuallyExec/encoder.js.map +1 -0
  159. package/dist/sui/manuallyExec/index.d.ts +37 -0
  160. package/dist/sui/manuallyExec/index.d.ts.map +1 -0
  161. package/dist/sui/manuallyExec/index.js +81 -0
  162. package/dist/sui/manuallyExec/index.js.map +1 -0
  163. package/dist/sui/objects.d.ts +46 -0
  164. package/dist/sui/objects.d.ts.map +1 -0
  165. package/dist/sui/objects.js +259 -0
  166. package/dist/sui/objects.js.map +1 -0
  167. package/dist/ton/bindings/offramp.d.ts +48 -0
  168. package/dist/ton/bindings/offramp.d.ts.map +1 -0
  169. package/dist/ton/bindings/offramp.js +63 -0
  170. package/dist/ton/bindings/offramp.js.map +1 -0
  171. package/dist/ton/bindings/onramp.d.ts +40 -0
  172. package/dist/ton/bindings/onramp.d.ts.map +1 -0
  173. package/dist/ton/bindings/onramp.js +51 -0
  174. package/dist/ton/bindings/onramp.js.map +1 -0
  175. package/dist/ton/bindings/router.d.ts +47 -0
  176. package/dist/ton/bindings/router.d.ts.map +1 -0
  177. package/dist/ton/bindings/router.js +51 -0
  178. package/dist/ton/bindings/router.js.map +1 -0
  179. package/dist/ton/exec.d.ts +18 -0
  180. package/dist/ton/exec.d.ts.map +1 -0
  181. package/dist/ton/exec.js +28 -0
  182. package/dist/ton/exec.js.map +1 -0
  183. package/dist/ton/hasher.d.ts +27 -0
  184. package/dist/ton/hasher.d.ts.map +1 -0
  185. package/dist/ton/hasher.js +134 -0
  186. package/dist/ton/hasher.js.map +1 -0
  187. package/dist/ton/index.d.ts +247 -0
  188. package/dist/ton/index.d.ts.map +1 -0
  189. package/dist/ton/index.js +781 -0
  190. package/dist/ton/index.js.map +1 -0
  191. package/dist/ton/logs.d.ts +26 -0
  192. package/dist/ton/logs.d.ts.map +1 -0
  193. package/dist/ton/logs.js +126 -0
  194. package/dist/ton/logs.js.map +1 -0
  195. package/dist/ton/types.d.ts +37 -0
  196. package/dist/ton/types.d.ts.map +1 -0
  197. package/dist/ton/types.js +92 -0
  198. package/dist/ton/types.js.map +1 -0
  199. package/dist/ton/utils.d.ts +67 -0
  200. package/dist/ton/utils.d.ts.map +1 -0
  201. package/dist/ton/utils.js +425 -0
  202. package/dist/ton/utils.js.map +1 -0
  203. package/dist/types.d.ts +4 -2
  204. package/dist/types.d.ts.map +1 -1
  205. package/dist/types.js +1 -0
  206. package/dist/types.js.map +1 -1
  207. package/dist/utils.d.ts +10 -0
  208. package/dist/utils.d.ts.map +1 -1
  209. package/dist/utils.js +52 -17
  210. package/dist/utils.js.map +1 -1
  211. package/package.json +12 -10
  212. package/src/aptos/hasher.ts +10 -6
  213. package/src/aptos/index.ts +50 -31
  214. package/src/aptos/logs.ts +85 -29
  215. package/src/aptos/token.ts +5 -1
  216. package/src/aptos/types.ts +6 -6
  217. package/src/chain.ts +83 -12
  218. package/src/commits.ts +23 -11
  219. package/src/errors/CCIPError.ts +86 -0
  220. package/src/errors/codes.ts +179 -0
  221. package/src/errors/index.ts +175 -0
  222. package/src/errors/recovery.ts +170 -0
  223. package/src/errors/specialized.ts +1655 -0
  224. package/src/errors/utils.ts +73 -0
  225. package/src/evm/abi/CommitStore_1_5.ts +1 -1
  226. package/src/evm/abi/LockReleaseTokenPool_1_5.ts +1 -1
  227. package/src/evm/abi/OffRamp_1_5.ts +1 -1
  228. package/src/evm/abi/OnRamp_1_5.ts +1 -1
  229. package/src/evm/abi/PriceRegistry_1_2.ts +438 -0
  230. package/src/evm/const.ts +2 -0
  231. package/src/evm/hasher.ts +7 -6
  232. package/src/evm/index.ts +104 -86
  233. package/src/evm/logs.ts +64 -16
  234. package/src/evm/messages.ts +14 -14
  235. package/src/evm/offchain.ts +1 -1
  236. package/src/evm/types.ts +11 -0
  237. package/src/execution.ts +13 -9
  238. package/src/extra-args.ts +4 -3
  239. package/src/gas.ts +10 -3
  240. package/src/hasher/hasher.ts +2 -1
  241. package/src/hasher/merklemulti.ts +18 -8
  242. package/src/index.ts +14 -2
  243. package/src/offchain.ts +10 -14
  244. package/src/requests.ts +51 -53
  245. package/src/selectors.ts +23 -0
  246. package/src/solana/cleanup.ts +2 -4
  247. package/src/solana/exec.ts +13 -13
  248. package/src/solana/hasher.ts +9 -5
  249. package/src/solana/index.ts +126 -200
  250. package/src/solana/logs.ts +155 -0
  251. package/src/solana/offchain.ts +10 -7
  252. package/src/solana/patchBorsh.ts +3 -2
  253. package/src/solana/send.ts +14 -7
  254. package/src/solana/utils.ts +31 -17
  255. package/src/sui/discovery.ts +163 -0
  256. package/src/sui/events.ts +328 -0
  257. package/src/sui/hasher.ts +6 -5
  258. package/src/sui/index.ts +528 -80
  259. package/src/sui/manuallyExec/encoder.ts +88 -0
  260. package/src/sui/manuallyExec/index.ts +137 -0
  261. package/src/sui/objects.ts +358 -0
  262. package/src/ton/bindings/offramp.ts +96 -0
  263. package/src/ton/bindings/onramp.ts +72 -0
  264. package/src/ton/bindings/router.ts +65 -0
  265. package/src/ton/exec.ts +44 -0
  266. package/src/ton/hasher.ts +184 -0
  267. package/src/ton/index.ts +989 -0
  268. package/src/ton/logs.ts +157 -0
  269. package/src/ton/types.ts +143 -0
  270. package/src/ton/utils.ts +514 -0
  271. package/src/types.ts +6 -2
  272. package/src/utils.ts +58 -23
  273. package/tsconfig.json +2 -1
package/src/aptos/logs.ts CHANGED
@@ -8,7 +8,17 @@ import {
8
8
  import { memoize } from 'micro-memoize'
9
9
 
10
10
  import type { LogFilter } from '../chain.ts'
11
+ import {
12
+ CCIPAptosAddressModuleRequiredError,
13
+ CCIPAptosTopicInvalidError,
14
+ CCIPAptosTransactionTypeUnexpectedError,
15
+ CCIPLogsWatchRequiresFinalityError,
16
+ CCIPLogsWatchRequiresStartError,
17
+ } from '../errors/index.ts'
11
18
  import type { Log_ } from '../types.ts'
19
+ import { sleep } from '../utils.ts'
20
+
21
+ const DEFAULT_POLL_INTERVAL = 5e3
12
22
 
13
23
  const eventToHandler = {
14
24
  CCIPMessageSent: 'OnRampState/ccip_message_sent_events',
@@ -30,29 +40,24 @@ export async function getUserTxByVersion(
30
40
  ledgerVersion: version,
31
41
  })
32
42
  if (tx.type !== TransactionResponseType.User)
33
- throw new Error(`Unexpected transaction type="${tx.type}"`)
43
+ throw new CCIPAptosTransactionTypeUnexpectedError(tx.type)
34
44
  return tx
35
45
  }
36
46
 
37
47
  /**
38
48
  * Gets the timestamp for a given transaction version.
39
49
  * @param provider - Aptos provider instance.
40
- * @param version - Version number or 'finalized'.
41
- * @returns Timestamp in seconds.
50
+ * @param version - Positive version number, negative block depth finality, or 'finalized'.
51
+ * @returns Epoch timestamp in seconds.
42
52
  */
43
53
  export async function getVersionTimestamp(
44
54
  provider: Aptos,
45
55
  version: number | 'finalized',
46
56
  ): Promise<number> {
47
- if (version === 'finalized') {
48
- const info = await provider.getLedgerInfo()
49
- const tx = await provider.getTransactionByVersion({
50
- ledgerVersion: +info.ledger_version,
51
- })
52
- return +(tx as UserTransactionResponse).timestamp / 1e6
53
- }
54
- const tx = await getUserTxByVersion(provider, version)
55
- return +tx.timestamp / 1e6
57
+ if (typeof version !== 'number') version = 0
58
+ if (version <= 0) version = +(await provider.getLedgerInfo()).ledger_version + version
59
+ const tx = await provider.getTransactionByVersion({ ledgerVersion: version })
60
+ return +(tx as UserTransactionResponse).timestamp / 1e6
56
61
  }
57
62
 
58
63
  type ResEvent = AptosEvent & { version: string }
@@ -84,12 +89,16 @@ async function binarySearchFirst(
84
89
  }
85
90
 
86
91
  async function* fetchEventsForward(
87
- provider: Aptos,
88
- opts: LogFilter,
92
+ { provider }: { provider: Aptos },
93
+ opts: LogFilter & { pollInterval?: number },
89
94
  eventHandlerField: string,
90
95
  stateAddr: string,
91
96
  limit = 100,
92
97
  ): AsyncGenerator<ResEvent> {
98
+ if (opts.watch && typeof opts.endBlock === 'number' && opts.endBlock > 0)
99
+ throw new CCIPLogsWatchRequiresFinalityError(opts.endBlock)
100
+ opts.endBlock ||= 'latest'
101
+
93
102
  const fetchBatch = memoize(
94
103
  async (start?: number) => {
95
104
  const { data }: { data: ResEvent[] } = await getAptosFullNode({
@@ -101,7 +110,7 @@ async function* fetchEventsForward(
101
110
  if (!start) fetchBatch.cache.set([+data[0].sequence_number], Promise.resolve(data))
102
111
  return data
103
112
  },
104
- { maxArgs: 1, maxSize: 100 },
113
+ { maxArgs: 1, maxSize: 100, async: true },
105
114
  )
106
115
 
107
116
  const initialBatch = await fetchBatch()
@@ -124,8 +133,25 @@ async function* fetchEventsForward(
124
133
  start = end - limit + 1
125
134
  }
126
135
 
127
- let first = true
128
- for (; start < end; start += limit) {
136
+ let notAfter =
137
+ typeof opts.endBlock !== 'number'
138
+ ? undefined
139
+ : opts.endBlock < 0
140
+ ? memoize(
141
+ async () =>
142
+ +(await provider.getLedgerInfo()).ledger_version + (opts.endBlock as number),
143
+ {
144
+ async: true,
145
+ maxArgs: 0,
146
+ expires: opts.pollInterval || DEFAULT_POLL_INTERVAL,
147
+ },
148
+ )
149
+ : opts.endBlock
150
+
151
+ let first = true,
152
+ catchedUp = false
153
+ while (opts.watch || !catchedUp) {
154
+ const lastReq = performance.now()
129
155
  const data = await fetchBatch(start)
130
156
  if (
131
157
  first &&
@@ -139,17 +165,40 @@ async function* fetchEventsForward(
139
165
  })
140
166
  data.splice(0, actualStart - 1)
141
167
  }
168
+
169
+ if (!first && catchedUp && typeof opts.endBlock === 'number' && opts.endBlock < 0)
170
+ notAfter = +(await provider.getLedgerInfo()).ledger_version + opts.endBlock
171
+
142
172
  first = false
173
+
143
174
  for (const ev of data) {
144
175
  if (opts.startBlock && +ev.version < opts.startBlock) continue
145
- if (opts.endBlock && +ev.version > opts.endBlock) return
176
+ // there may be an unknown interval between yields, so we support memoized negative finality
177
+ if (
178
+ notAfter &&
179
+ +ev.version > (typeof notAfter === 'function' ? await notAfter() : notAfter)
180
+ ) {
181
+ catchedUp = true
182
+ break
183
+ }
184
+ const start_: number = +ev.sequence_number
185
+ start = start_ + 1
146
186
  yield ev
147
187
  }
188
+ catchedUp ||= start >= end
189
+ if (opts.watch && catchedUp) {
190
+ let break$ = sleep(
191
+ Math.max((opts.pollInterval || DEFAULT_POLL_INTERVAL) - (performance.now() - lastReq), 1),
192
+ ).then(() => false)
193
+ if (opts.watch instanceof Promise)
194
+ break$ = Promise.race([break$, opts.watch.then(() => true)])
195
+ if (await break$) break
196
+ }
148
197
  }
149
198
  }
150
199
 
151
200
  async function* fetchEventsBackward(
152
- provider: Aptos,
201
+ { provider }: { provider: Aptos },
153
202
  opts: LogFilter,
154
203
  eventHandlerField: string,
155
204
  stateAddr: string,
@@ -157,6 +206,12 @@ async function* fetchEventsBackward(
157
206
  ): AsyncGenerator<ResEvent> {
158
207
  let start
159
208
  let cont = true
209
+ const notAfter =
210
+ typeof opts.endBlock !== 'number'
211
+ ? undefined
212
+ : opts.endBlock < 0
213
+ ? +(await provider.getLedgerInfo()).ledger_version + opts.endBlock
214
+ : opts.endBlock
160
215
  do {
161
216
  const { data } = await getAptosFullNode<object, ResEvent[]>({
162
217
  aptosConfig: provider.config,
@@ -170,7 +225,7 @@ async function* fetchEventsBackward(
170
225
  else start = Math.max(+data[0].sequence_number - limit, 1)
171
226
 
172
227
  for (const ev of data.reverse()) {
173
- if (opts.endBlock && +ev.version > opts.endBlock) continue
228
+ if (notAfter && +ev.version > notAfter) continue
174
229
  if (+ev.sequence_number <= 1) cont = false
175
230
  yield ev
176
231
  }
@@ -184,20 +239,19 @@ async function* fetchEventsBackward(
184
239
  * @returns Async generator of log entries.
185
240
  */
186
241
  export async function* streamAptosLogs(
187
- provider: Aptos,
242
+ ctx: { provider: Aptos },
188
243
  opts: LogFilter & { versionAsHash?: boolean },
189
244
  ): AsyncGenerator<Log_> {
190
245
  const limit = 100
191
- if (!opts.address || !opts.address.includes('::'))
192
- throw new Error('address with module is required')
246
+ if (!opts.address || !opts.address.includes('::')) throw new CCIPAptosAddressModuleRequiredError()
193
247
  if (opts.topics?.length !== 1 || typeof opts.topics[0] !== 'string')
194
- throw new Error('single string topic required')
248
+ throw new CCIPAptosTopicInvalidError()
195
249
  let eventHandlerField = opts.topics[0]
196
250
  if (!eventHandlerField.includes('/')) {
197
251
  eventHandlerField = (eventToHandler as Record<string, string>)[eventHandlerField]
198
- if (!eventHandlerField) throw new Error(`Unknown topic event handler="${opts.topics[0]}"`)
252
+ if (!eventHandlerField) throw new CCIPAptosTopicInvalidError(opts.topics[0])
199
253
  }
200
- const [stateAddr] = await provider.view<[string]>({
254
+ const [stateAddr] = await ctx.provider.view<[string]>({
201
255
  payload: {
202
256
  function: `${opts.address}::get_state_address` as `0x${string}::${string}::get_state_address`,
203
257
  },
@@ -205,10 +259,12 @@ export async function* streamAptosLogs(
205
259
 
206
260
  let eventsIter
207
261
  if (opts.startBlock || opts.startTime) {
208
- eventsIter = fetchEventsForward(provider, opts, eventHandlerField, stateAddr, limit)
262
+ eventsIter = fetchEventsForward(ctx, opts, eventHandlerField, stateAddr, limit)
263
+ } else if (opts.watch) {
264
+ throw new CCIPLogsWatchRequiresStartError()
209
265
  } else {
210
266
  // backwards, just paginate down to lowest sequence number
211
- eventsIter = fetchEventsBackward(provider, opts, eventHandlerField, stateAddr, limit)
267
+ eventsIter = fetchEventsBackward(ctx, opts, eventHandlerField, stateAddr, limit)
212
268
  }
213
269
 
214
270
  let topics
@@ -221,7 +277,7 @@ export async function* streamAptosLogs(
221
277
  blockNumber: +ev.version,
222
278
  transactionHash: opts?.versionAsHash
223
279
  ? `${ev.version}`
224
- : (await getUserTxByVersion(provider, +ev.version)).hash,
280
+ : (await getUserTxByVersion(ctx.provider, +ev.version)).hash,
225
281
  data: ev.data as Record<string, unknown>,
226
282
  }
227
283
  }
@@ -1,6 +1,7 @@
1
1
  import type { Aptos } from '@aptos-labs/ts-sdk'
2
2
 
3
3
  import type { TokenInfo } from '../chain.ts'
4
+ import { CCIPError } from '../errors/CCIPError.ts'
4
5
 
5
6
  /**
6
7
  * Retrieves token metadata (symbol and decimals) from Aptos.
@@ -152,5 +153,8 @@ export async function getTokenInfo(provider: Aptos, token: string): Promise<Toke
152
153
  }
153
154
  }
154
155
 
155
- throw lastErr ?? new Error(`Could not view token info for ${token}`)
156
+ throw CCIPError.from(
157
+ lastErr ?? `Could not view token info for ${token}`,
158
+ 'TOKEN_POOL_INFO_NOT_FOUND',
159
+ )
156
160
  }
@@ -78,12 +78,12 @@ export function serializeExecutionReport(
78
78
  ): Uint8Array {
79
79
  const message = execReport.message
80
80
  return ExecutionReportCodec.serialize({
81
- sourceChainSelector: message.header.sourceChainSelector,
82
- messageId: getBytes(message.header.messageId),
83
- headerSourceChainSelector: message.header.sourceChainSelector,
84
- destChainSelector: message.header.destChainSelector,
85
- sequenceNumber: message.header.sequenceNumber,
86
- nonce: message.header.nonce,
81
+ sourceChainSelector: message.sourceChainSelector,
82
+ messageId: getBytes(message.messageId),
83
+ headerSourceChainSelector: message.sourceChainSelector,
84
+ destChainSelector: message.destChainSelector,
85
+ sequenceNumber: message.sequenceNumber,
86
+ nonce: message.nonce,
87
87
  sender: getAddressBytes(message.sender),
88
88
  data: getBytes(message.data),
89
89
  receiver: getAddressBytes(message.receiver),
package/src/chain.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { BytesLike } from 'ethers'
2
- import type { PickDeep } from 'type-fest'
2
+ import type { PickDeep, SetOptional } from 'type-fest'
3
3
 
4
4
  import type { UnsignedAptosTx } from './aptos/types.ts'
5
5
  import { fetchCommitReport } from './commits.ts'
6
- import type { UnsignedEVMTx } from './evm/index.ts'
6
+ import { CCIPChainFamilyMismatchError, CCIPTransactionNotFinalizedError } from './errors/index.ts'
7
+ import type { UnsignedEVMTx } from './evm/types.ts'
7
8
  import type {
8
9
  EVMExtraArgsV1,
9
10
  EVMExtraArgsV2,
@@ -13,6 +14,7 @@ import type {
13
14
  } from './extra-args.ts'
14
15
  import type { LeafHasher } from './hasher/common.ts'
15
16
  import type { UnsignedSolanaTx } from './solana/types.ts'
17
+ import type { UnsignedTONTx } from './ton/types.ts'
16
18
  import {
17
19
  type AnyMessage,
18
20
  type CCIPCommit,
@@ -43,9 +45,13 @@ export type LogFilter = {
43
45
  /** Starting Unix timestamp (inclusive). */
44
46
  startTime?: number
45
47
  /** Ending block number (inclusive). */
46
- endBlock?: number
47
- /** Optional hint signature for end of iteration. */
48
+ endBlock?: number | 'finalized' | 'latest'
49
+ /** Solana: optional hint txHash for end of iteration. */
48
50
  endBefore?: string
51
+ /** watch mode: polls for new logs after fetching since start (required), until endBlock finality tag
52
+ * (e.g. endBlock=finalized polls only finalized logs); can be a promise to cancel loop
53
+ */
54
+ watch?: boolean | Promise<unknown>
49
55
  /** Contract address to filter logs by. */
50
56
  address?: string
51
57
  /** Topics to filter logs by. */
@@ -100,6 +106,7 @@ export type UnsignedTx = {
100
106
  [ChainFamily.EVM]: UnsignedEVMTx
101
107
  [ChainFamily.Solana]: UnsignedSolanaTx
102
108
  [ChainFamily.Aptos]: UnsignedAptosTx
109
+ [ChainFamily.TON]: UnsignedTONTx
103
110
  [ChainFamily.Sui]: never // TODO
104
111
  }
105
112
 
@@ -117,7 +124,11 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
117
124
  */
118
125
  constructor(network: NetworkInfo, { logger = console }: WithLogger = {}) {
119
126
  if (network.family !== (this.constructor as ChainStatic).family)
120
- throw new Error(`Invalid network family for ${this.constructor.name}: ${network.family}`)
127
+ throw new CCIPChainFamilyMismatchError(
128
+ this.constructor.name,
129
+ (this.constructor as ChainStatic).family,
130
+ network.family,
131
+ )
121
132
  this.network = network as NetworkInfo<F>
122
133
  this.logger = logger
123
134
  }
@@ -132,7 +143,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
132
143
 
133
144
  /**
134
145
  * Fetch the timestamp of a given block
135
- * @param block - block number or 'finalized'
146
+ * @param block - positive block number, negative finality depth or 'finalized' tag
136
147
  * @returns timestamp of the block, in seconds
137
148
  */
138
149
  abstract getBlockTimestamp(block: number | 'finalized'): Promise<number>
@@ -142,6 +153,49 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
142
153
  * @returns generic transaction details
143
154
  */
144
155
  abstract getTransaction(hash: string): Promise<ChainTransaction>
156
+ /**
157
+ * Confirm a log tx is finalized or wait for it to be finalized
158
+ * Throws if it isn't included (e.g. a reorg)
159
+ */
160
+ async waitFinalized(
161
+ {
162
+ log,
163
+ tx,
164
+ }: SetOptional<
165
+ PickDeep<
166
+ CCIPRequest,
167
+ | `log.${'address' | 'blockNumber' | 'transactionHash' | 'topics' | 'tx.timestamp'}`
168
+ | 'tx.timestamp'
169
+ >,
170
+ 'tx'
171
+ >,
172
+ finality: number | 'finalized' = 'finalized',
173
+ cancel$?: Promise<unknown>,
174
+ ): Promise<true> {
175
+ const timestamp = log.tx?.timestamp ?? tx?.timestamp
176
+ if (!timestamp || Date.now() / 1e3 - timestamp > 60) {
177
+ // only try to fetch tx if request is old enough (>60s)
178
+ const [trans, finalizedTs] = await Promise.all([
179
+ this.getTransaction(log.transactionHash),
180
+ this.getBlockTimestamp(finality),
181
+ ])
182
+ if (trans.timestamp <= finalizedTs) return true
183
+ }
184
+ for await (const l of this.getLogs({
185
+ address: log.address,
186
+ startBlock: log.blockNumber,
187
+ endBlock: finality,
188
+ topics: [log.topics[0]],
189
+ watch: cancel$ ?? true,
190
+ })) {
191
+ if (l.transactionHash === log.transactionHash) {
192
+ return true
193
+ } else if (l.blockNumber > log.blockNumber) {
194
+ break
195
+ }
196
+ }
197
+ throw new CCIPTransactionNotFinalizedError(log.transactionHash)
198
+ }
145
199
  /**
146
200
  * An async generator that yields logs based on the provided options.
147
201
  * @param opts - Options object containing:
@@ -189,7 +243,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
189
243
  abstract fetchAllMessagesInBatch<
190
244
  R extends PickDeep<
191
245
  CCIPRequest,
192
- 'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.header.sequenceNumber'
246
+ 'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.sequenceNumber'
193
247
  >,
194
248
  >(
195
249
  request: R,
@@ -374,8 +428,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
374
428
  **/
375
429
  async fetchCommitReport(
376
430
  commitStore: string,
377
- request: PickDeep<CCIPRequest, 'lane' | 'message.header.sequenceNumber' | 'tx.timestamp'>,
378
- hints?: { startBlock?: number; page?: number },
431
+ request: PickDeep<CCIPRequest, 'lane' | 'message.sequenceNumber' | 'tx.timestamp'>,
432
+ hints?: Pick<LogFilter, 'page' | 'watch'> & { startBlock?: number },
379
433
  ): Promise<CCIPCommit> {
380
434
  return fetchCommitReport(this, commitStore, request, hints)
381
435
  }
@@ -390,9 +444,9 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
390
444
  */
391
445
  async *fetchExecutionReceipts(
392
446
  offRamp: string,
393
- request: PickDeep<CCIPRequest, 'lane' | 'message.header.messageId' | 'tx.timestamp'>,
447
+ request: PickDeep<CCIPRequest, 'lane' | 'message.messageId' | 'tx.timestamp'>,
394
448
  commit?: CCIPCommit,
395
- hints?: { page?: number },
449
+ hints?: Pick<LogFilter, 'page' | 'watch'>,
396
450
  ): AsyncIterableIterator<CCIPExecution> {
397
451
  const onlyLast = !commit?.log.blockNumber && !request.tx.timestamp // backwards
398
452
  for await (const log of this.getLogs({
@@ -403,7 +457,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
403
457
  ...hints,
404
458
  })) {
405
459
  const receipt = (this.constructor as ChainStatic).decodeReceipt(log)
406
- if (!receipt || receipt.messageId !== request.message.header.messageId) continue
460
+ if (!receipt || receipt.messageId !== request.message.messageId) continue
407
461
 
408
462
  const timestamp = log.tx?.timestamp ?? (await this.getBlockTimestamp(log.blockNumber))
409
463
  yield { receipt, log, timestamp }
@@ -514,6 +568,23 @@ export type ChainStatic<F extends ChainFamily = ChainFamily> = Function & {
514
568
  * @returns Address in this chain family's format
515
569
  */
516
570
  getAddress(bytes: BytesLike): string
571
+ /**
572
+ * Validates a transaction hash format for this chain family
573
+ */
574
+ isTxHash(v: unknown): v is string
575
+ /**
576
+ * Format an address for human-friendly display.
577
+ * Defaults to getAddress if not overridden.
578
+ * @param address - Address string in any recognized format
579
+ * @returns Human-friendly address string for display
580
+ */
581
+ formatAddress?(address: string): string
582
+ /**
583
+ * Format a transaction hash for human-friendly display.
584
+ * @param hash - Transaction hash string
585
+ * @returns Human-friendly hash string for display
586
+ */
587
+ formatTxHash?(hash: string): string
517
588
  /**
518
589
  * Create a leaf hasher for this dest chain and lane
519
590
  * @param lane - source, dest and onramp lane info
package/src/commits.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { PickDeep } from 'type-fest'
2
2
 
3
- import type { Chain, ChainStatic } from './chain.ts'
3
+ import type { Chain, ChainStatic, LogFilter } from './chain.ts'
4
+ import { CCIPCommitNotFoundError } from './errors/index.ts'
4
5
  import { type CCIPCommit, type CCIPRequest, CCIPVersion } from './types.ts'
5
6
 
6
7
  /**
@@ -19,10 +20,10 @@ export async function fetchCommitReport(
19
20
  commitStore: string,
20
21
  {
21
22
  lane,
22
- message: { header },
23
+ message,
23
24
  tx: { timestamp: requestTimestamp },
24
- }: PickDeep<CCIPRequest, 'lane' | 'message.header.sequenceNumber' | 'tx.timestamp'>,
25
- hints?: { startBlock?: number; page?: number },
25
+ }: PickDeep<CCIPRequest, 'lane' | 'message.sequenceNumber' | 'tx.timestamp'>,
26
+ hints?: Pick<LogFilter, 'page' | 'watch'> & { startBlock?: number },
26
27
  ): Promise<CCIPCommit> {
27
28
  for await (const log of dest.getLogs({
28
29
  ...hints,
@@ -30,14 +31,25 @@ export async function fetchCommitReport(
30
31
  address: commitStore,
31
32
  topics: [lane.version < CCIPVersion.V1_6 ? 'ReportAccepted' : 'CommitReportAccepted'],
32
33
  })) {
33
- const report = (dest.constructor as ChainStatic).decodeCommits(log, lane)?.[0]
34
- if (!report || report.maxSeqNr < header.sequenceNumber) continue
35
- // since we walk forward from some startBlock/startTime, give up if we find a newer report
36
- if (report.minSeqNr > header.sequenceNumber) break
37
- return { report, log }
34
+ const reports = (dest.constructor as ChainStatic).decodeCommits(log, lane)
35
+ if (!reports) continue
36
+ const validReports = reports.filter((r) => {
37
+ if (!r || r.maxSeqNr < message.sequenceNumber) return
38
+ // we could give up since we walk forward from some startBlock/startTime, but there might be some out-of-order logs
39
+ if (r.minSeqNr > message.sequenceNumber) return
40
+ return true
41
+ })
42
+
43
+ if (validReports.length === 0) continue
44
+
45
+ return {
46
+ log,
47
+ report: validReports[0],
48
+ }
38
49
  }
39
50
 
40
- throw new Error(
41
- `Could not find commit after ${hints?.startBlock ?? requestTimestamp} for sequenceNumber=${header.sequenceNumber}`,
51
+ throw new CCIPCommitNotFoundError(
52
+ hints?.startBlock ?? String(requestTimestamp),
53
+ message.sequenceNumber,
42
54
  )
43
55
  }
@@ -0,0 +1,86 @@
1
+ import type { CCIPErrorCode } from './codes.ts'
2
+ import { getDefaultRecovery } from './recovery.ts'
3
+
4
+ /** Options for CCIPError constructor. */
5
+ export interface CCIPErrorOptions {
6
+ /** Original error (ES2022 cause). */
7
+ cause?: Error
8
+ /** Structured context (IDs, addresses). */
9
+ context?: Record<string, unknown>
10
+ /** True if retry may succeed. */
11
+ isTransient?: boolean
12
+ /** Retry delay in ms. */
13
+ retryAfterMs?: number
14
+ /** Recovery suggestion. */
15
+ recovery?: string
16
+ }
17
+
18
+ /**
19
+ * Base error class for CCIP SDK.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * if (CCIPError.isCCIPError(error) && error.isTransient) {
24
+ * await sleep(error.retryAfterMs ?? 5000)
25
+ * }
26
+ * ```
27
+ */
28
+ export class CCIPError extends Error {
29
+ /** Brand for cross-module identification (dual package hazard). */
30
+ readonly _isCCIPError = true as const
31
+ /** Machine-readable error code. */
32
+ readonly code: CCIPErrorCode
33
+ /** Structured context (IDs, addresses). */
34
+ readonly context: Record<string, unknown>
35
+ /** True if retry may succeed. */
36
+ readonly isTransient: boolean
37
+ /** Retry delay in ms. */
38
+ readonly retryAfterMs?: number
39
+ /** Recovery suggestion. */
40
+ readonly recovery?: string
41
+
42
+ override readonly name: string = 'CCIPError'
43
+
44
+ /** Creates CCIPError with code, message, and options. */
45
+ constructor(code: CCIPErrorCode, message: string, options?: CCIPErrorOptions) {
46
+ super(message, { cause: options?.cause })
47
+ Object.setPrototypeOf(this, new.target.prototype)
48
+
49
+ this.code = code
50
+ this.context = options?.context ?? {}
51
+ this.isTransient = options?.isTransient ?? false
52
+ this.retryAfterMs = options?.retryAfterMs
53
+ this.recovery = options?.recovery ?? getDefaultRecovery(code)
54
+
55
+ Error.captureStackTrace?.(this, this.constructor)
56
+ }
57
+
58
+ /** Type guard. Prefer over instanceof (handles dual package hazard). */
59
+ static isCCIPError(error: unknown): error is CCIPError {
60
+ return error instanceof CCIPError || !!(error as { _isCCIPError?: boolean })?._isCCIPError
61
+ }
62
+
63
+ /** Wrap unknown catch value in CCIPError. */
64
+ static from(error: unknown, code?: CCIPErrorCode): CCIPError {
65
+ if (error instanceof CCIPError) return error
66
+ if (error instanceof Error) {
67
+ return new CCIPError(code ?? 'UNKNOWN', error.message, { cause: error })
68
+ }
69
+ return new CCIPError(code ?? 'UNKNOWN', String(error))
70
+ }
71
+
72
+ /** Serialize for logging (JSON.stringify loses non-enumerable props). */
73
+ toJSON(): Record<string, unknown> {
74
+ return {
75
+ name: this.name,
76
+ message: this.message,
77
+ code: this.code,
78
+ context: this.context,
79
+ isTransient: this.isTransient,
80
+ retryAfterMs: this.retryAfterMs,
81
+ recovery: this.recovery,
82
+ stack: this.stack,
83
+ cause: this.cause instanceof Error ? this.cause.message : this.cause,
84
+ }
85
+ }
86
+ }