@chainlink/ccip-sdk 0.96.0 → 1.0.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 (247) hide show
  1. package/README.md +12 -9
  2. package/dist/api/index.d.ts +21 -8
  3. package/dist/api/index.d.ts.map +1 -1
  4. package/dist/api/index.js +42 -13
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/api/types.d.ts +0 -2
  7. package/dist/api/types.d.ts.map +1 -1
  8. package/dist/aptos/exec.d.ts +2 -2
  9. package/dist/aptos/exec.d.ts.map +1 -1
  10. package/dist/aptos/exec.js.map +1 -1
  11. package/dist/aptos/hasher.d.ts.map +1 -1
  12. package/dist/aptos/hasher.js +1 -1
  13. package/dist/aptos/hasher.js.map +1 -1
  14. package/dist/aptos/index.d.ts +17 -16
  15. package/dist/aptos/index.d.ts.map +1 -1
  16. package/dist/aptos/index.js +42 -73
  17. package/dist/aptos/index.js.map +1 -1
  18. package/dist/aptos/logs.d.ts +2 -2
  19. package/dist/aptos/logs.d.ts.map +1 -1
  20. package/dist/aptos/types.d.ts +2 -19
  21. package/dist/aptos/types.d.ts.map +1 -1
  22. package/dist/aptos/types.js +0 -11
  23. package/dist/aptos/types.js.map +1 -1
  24. package/dist/chain.d.ts +538 -158
  25. package/dist/chain.d.ts.map +1 -1
  26. package/dist/chain.js +132 -19
  27. package/dist/chain.js.map +1 -1
  28. package/dist/commits.d.ts +4 -6
  29. package/dist/commits.d.ts.map +1 -1
  30. package/dist/commits.js +4 -4
  31. package/dist/commits.js.map +1 -1
  32. package/dist/errors/CCIPError.d.ts +33 -4
  33. package/dist/errors/CCIPError.d.ts.map +1 -1
  34. package/dist/errors/CCIPError.js +33 -4
  35. package/dist/errors/CCIPError.js.map +1 -1
  36. package/dist/errors/codes.d.ts +3 -0
  37. package/dist/errors/codes.d.ts.map +1 -1
  38. package/dist/errors/codes.js +3 -1
  39. package/dist/errors/codes.js.map +1 -1
  40. package/dist/errors/index.d.ts +4 -4
  41. package/dist/errors/index.d.ts.map +1 -1
  42. package/dist/errors/index.js +4 -4
  43. package/dist/errors/index.js.map +1 -1
  44. package/dist/errors/recovery.d.ts.map +1 -1
  45. package/dist/errors/recovery.js +4 -1
  46. package/dist/errors/recovery.js.map +1 -1
  47. package/dist/errors/specialized.d.ts +1695 -120
  48. package/dist/errors/specialized.d.ts.map +1 -1
  49. package/dist/errors/specialized.js +1715 -123
  50. package/dist/errors/specialized.js.map +1 -1
  51. package/dist/errors/utils.d.ts.map +1 -1
  52. package/dist/errors/utils.js +0 -1
  53. package/dist/errors/utils.js.map +1 -1
  54. package/dist/evm/abi/OffRamp_2_0.d.ts +764 -0
  55. package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -0
  56. package/dist/evm/abi/OffRamp_2_0.js +744 -0
  57. package/dist/evm/abi/OffRamp_2_0.js.map +1 -0
  58. package/dist/evm/abi/OnRamp_2_0.d.ts +925 -0
  59. package/dist/evm/abi/OnRamp_2_0.d.ts.map +1 -0
  60. package/dist/evm/abi/OnRamp_2_0.js +992 -0
  61. package/dist/evm/abi/OnRamp_2_0.js.map +1 -0
  62. package/dist/evm/const.d.ts +12 -2
  63. package/dist/evm/const.d.ts.map +1 -1
  64. package/dist/evm/const.js +8 -2
  65. package/dist/evm/const.js.map +1 -1
  66. package/dist/evm/errors.d.ts.map +1 -1
  67. package/dist/evm/errors.js +7 -2
  68. package/dist/evm/errors.js.map +1 -1
  69. package/dist/evm/extra-args.d.ts.map +1 -1
  70. package/dist/evm/extra-args.js +5 -24
  71. package/dist/evm/extra-args.js.map +1 -1
  72. package/dist/evm/hasher.d.ts.map +1 -1
  73. package/dist/evm/hasher.js +23 -13
  74. package/dist/evm/hasher.js.map +1 -1
  75. package/dist/evm/index.d.ts +73 -16
  76. package/dist/evm/index.d.ts.map +1 -1
  77. package/dist/evm/index.js +241 -146
  78. package/dist/evm/index.js.map +1 -1
  79. package/dist/evm/logs.d.ts +1 -1
  80. package/dist/evm/logs.js +1 -1
  81. package/dist/evm/messages.d.ts +59 -5
  82. package/dist/evm/messages.d.ts.map +1 -1
  83. package/dist/evm/messages.js +210 -0
  84. package/dist/evm/messages.js.map +1 -1
  85. package/dist/evm/offchain.d.ts +1 -14
  86. package/dist/evm/offchain.d.ts.map +1 -1
  87. package/dist/evm/offchain.js +1 -133
  88. package/dist/evm/offchain.js.map +1 -1
  89. package/dist/evm/types.d.ts +7 -2
  90. package/dist/evm/types.d.ts.map +1 -1
  91. package/dist/evm/types.js +22 -1
  92. package/dist/evm/types.js.map +1 -1
  93. package/dist/execution.d.ts +62 -22
  94. package/dist/execution.d.ts.map +1 -1
  95. package/dist/execution.js +98 -61
  96. package/dist/execution.js.map +1 -1
  97. package/dist/extra-args.d.ts +13 -3
  98. package/dist/extra-args.d.ts.map +1 -1
  99. package/dist/extra-args.js +13 -3
  100. package/dist/extra-args.js.map +1 -1
  101. package/dist/gas.d.ts +25 -2
  102. package/dist/gas.d.ts.map +1 -1
  103. package/dist/gas.js +30 -4
  104. package/dist/gas.js.map +1 -1
  105. package/dist/index.d.ts +3 -2
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +2 -1
  108. package/dist/index.js.map +1 -1
  109. package/dist/offchain.d.ts +23 -6
  110. package/dist/offchain.d.ts.map +1 -1
  111. package/dist/offchain.js +92 -17
  112. package/dist/offchain.js.map +1 -1
  113. package/dist/requests.d.ts +85 -14
  114. package/dist/requests.d.ts.map +1 -1
  115. package/dist/requests.js +99 -17
  116. package/dist/requests.js.map +1 -1
  117. package/dist/selectors.d.ts.map +1 -1
  118. package/dist/selectors.js +12 -0
  119. package/dist/selectors.js.map +1 -1
  120. package/dist/shared/bcs-codecs.d.ts +61 -0
  121. package/dist/shared/bcs-codecs.d.ts.map +1 -0
  122. package/dist/shared/bcs-codecs.js +102 -0
  123. package/dist/shared/bcs-codecs.js.map +1 -0
  124. package/dist/shared/constants.d.ts +3 -0
  125. package/dist/shared/constants.d.ts.map +1 -0
  126. package/dist/shared/constants.js +3 -0
  127. package/dist/shared/constants.js.map +1 -0
  128. package/dist/solana/exec.d.ts +2 -2
  129. package/dist/solana/exec.d.ts.map +1 -1
  130. package/dist/solana/exec.js.map +1 -1
  131. package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.d.ts +1 -1
  132. package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.js +1 -1
  133. package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.d.ts +1 -1
  134. package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.js +1 -1
  135. package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.d.ts +1 -1
  136. package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.js +1 -1
  137. package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts +16 -1
  138. package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts.map +1 -1
  139. package/dist/solana/idl/1.6.0/CCIP_COMMON.js +16 -1
  140. package/dist/solana/idl/1.6.0/CCIP_COMMON.js.map +1 -1
  141. package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.d.ts +1 -1
  142. package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.js +1 -1
  143. package/dist/solana/idl/1.6.0/CCIP_ROUTER.d.ts +1 -1
  144. package/dist/solana/idl/1.6.0/CCIP_ROUTER.js +1 -1
  145. package/dist/solana/index.d.ts +85 -24
  146. package/dist/solana/index.d.ts.map +1 -1
  147. package/dist/solana/index.js +69 -37
  148. package/dist/solana/index.js.map +1 -1
  149. package/dist/solana/offchain.d.ts +1 -13
  150. package/dist/solana/offchain.d.ts.map +1 -1
  151. package/dist/solana/offchain.js +1 -66
  152. package/dist/solana/offchain.js.map +1 -1
  153. package/dist/solana/utils.d.ts +4 -4
  154. package/dist/solana/utils.d.ts.map +1 -1
  155. package/dist/solana/utils.js +1 -1
  156. package/dist/solana/utils.js.map +1 -1
  157. package/dist/sui/hasher.d.ts.map +1 -1
  158. package/dist/sui/hasher.js +1 -1
  159. package/dist/sui/hasher.js.map +1 -1
  160. package/dist/sui/index.d.ts +18 -18
  161. package/dist/sui/index.d.ts.map +1 -1
  162. package/dist/sui/index.js +38 -39
  163. package/dist/sui/index.js.map +1 -1
  164. package/dist/sui/manuallyExec/encoder.d.ts +2 -2
  165. package/dist/sui/manuallyExec/encoder.d.ts.map +1 -1
  166. package/dist/sui/manuallyExec/encoder.js.map +1 -1
  167. package/dist/sui/manuallyExec/index.d.ts +2 -2
  168. package/dist/sui/manuallyExec/index.d.ts.map +1 -1
  169. package/dist/ton/exec.d.ts +3 -3
  170. package/dist/ton/exec.d.ts.map +1 -1
  171. package/dist/ton/exec.js +1 -1
  172. package/dist/ton/exec.js.map +1 -1
  173. package/dist/ton/index.d.ts +14 -22
  174. package/dist/ton/index.d.ts.map +1 -1
  175. package/dist/ton/index.js +26 -35
  176. package/dist/ton/index.js.map +1 -1
  177. package/dist/ton/types.d.ts +3 -5
  178. package/dist/ton/types.d.ts.map +1 -1
  179. package/dist/ton/types.js.map +1 -1
  180. package/dist/types.d.ts +55 -20
  181. package/dist/types.d.ts.map +1 -1
  182. package/dist/types.js +6 -1
  183. package/dist/types.js.map +1 -1
  184. package/dist/utils.d.ts +65 -2
  185. package/dist/utils.d.ts.map +1 -1
  186. package/dist/utils.js +74 -2
  187. package/dist/utils.js.map +1 -1
  188. package/package.json +14 -10
  189. package/src/api/index.ts +53 -17
  190. package/src/api/types.ts +0 -2
  191. package/src/aptos/exec.ts +2 -2
  192. package/src/aptos/hasher.ts +1 -1
  193. package/src/aptos/index.ts +55 -100
  194. package/src/aptos/logs.ts +2 -2
  195. package/src/aptos/types.ts +2 -15
  196. package/src/chain.ts +594 -171
  197. package/src/commits.ts +9 -9
  198. package/src/errors/CCIPError.ts +33 -4
  199. package/src/errors/codes.ts +3 -1
  200. package/src/errors/index.ts +4 -0
  201. package/src/errors/recovery.ts +7 -1
  202. package/src/errors/specialized.ts +1726 -130
  203. package/src/errors/utils.ts +0 -1
  204. package/src/evm/abi/OffRamp_2_0.ts +743 -0
  205. package/src/evm/abi/OnRamp_2_0.ts +991 -0
  206. package/src/evm/const.ts +10 -3
  207. package/src/evm/errors.ts +6 -2
  208. package/src/evm/extra-args.ts +4 -21
  209. package/src/evm/hasher.ts +30 -18
  210. package/src/evm/index.ts +314 -176
  211. package/src/evm/logs.ts +1 -1
  212. package/src/evm/messages.ts +323 -11
  213. package/src/evm/offchain.ts +2 -191
  214. package/src/evm/types.ts +20 -2
  215. package/src/execution.ts +125 -86
  216. package/src/extra-args.ts +13 -3
  217. package/src/gas.ts +29 -3
  218. package/src/index.ts +10 -3
  219. package/src/offchain.ts +125 -28
  220. package/src/requests.ts +114 -19
  221. package/src/selectors.ts +12 -0
  222. package/src/shared/bcs-codecs.ts +132 -0
  223. package/src/shared/constants.ts +2 -0
  224. package/src/solana/exec.ts +4 -4
  225. package/src/solana/idl/1.6.0/BASE_TOKEN_POOL.ts +2 -2
  226. package/src/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.ts +2 -2
  227. package/src/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts +2 -2
  228. package/src/solana/idl/1.6.0/CCIP_COMMON.ts +32 -2
  229. package/src/solana/idl/1.6.0/CCIP_OFFRAMP.ts +2 -2
  230. package/src/solana/idl/1.6.0/CCIP_ROUTER.ts +2 -2
  231. package/src/solana/index.ts +110 -85
  232. package/src/solana/offchain.ts +3 -100
  233. package/src/solana/utils.ts +8 -5
  234. package/src/sui/hasher.ts +1 -1
  235. package/src/sui/index.ts +55 -59
  236. package/src/sui/manuallyExec/encoder.ts +2 -2
  237. package/src/sui/manuallyExec/index.ts +2 -2
  238. package/src/ton/exec.ts +4 -7
  239. package/src/ton/index.ts +45 -53
  240. package/src/ton/types.ts +4 -7
  241. package/src/types.ts +81 -37
  242. package/src/utils.ts +73 -2
  243. package/dist/aptos/utils.d.ts +0 -12
  244. package/dist/aptos/utils.d.ts.map +0 -1
  245. package/dist/aptos/utils.js +0 -15
  246. package/dist/aptos/utils.js.map +0 -1
  247. package/src/aptos/utils.ts +0 -24
package/src/execution.ts CHANGED
@@ -1,23 +1,13 @@
1
1
  import { memoize } from 'micro-memoize'
2
2
 
3
- import type { Chain, ChainStatic } from './chain.ts'
3
+ import type { Chain } from './chain.ts'
4
4
  import {
5
5
  CCIPMerkleRootMismatchError,
6
6
  CCIPMessageNotInBatchError,
7
7
  CCIPOffRampNotFoundError,
8
8
  } from './errors/index.ts'
9
9
  import { Tree, getLeafHasher, proofFlagsToBits } from './hasher/index.ts'
10
- import {
11
- type CCIPCommit,
12
- type CCIPExecution,
13
- type CCIPMessage,
14
- type CCIPRequest,
15
- type CCIPVersion,
16
- type ExecutionReport,
17
- type Lane,
18
- type WithLogger,
19
- ExecutionState,
20
- } from './types.ts'
10
+ import type { CCIPMessage, CCIPVersion, Lane, WithLogger } from './types.ts'
21
11
 
22
12
  /**
23
13
  * Pure/sync function to calculate/generate OffRamp.executeManually report for messageIds
@@ -28,6 +18,38 @@ import {
28
18
  * @param merkleRoot - Optional merkleRoot of the CommitReport, for validation
29
19
  * @param ctx - Context for logging
30
20
  * @returns ManualExec report arguments
21
+ * @throws CCIPMessageNotInBatchError - When the messageId is not found in the provided batch
22
+ * @throws CCIPMerkleRootMismatchError - When calculated merkle root doesn't match the provided one
23
+ *
24
+ * @remarks
25
+ * This is a pure/sync function that performs no I/O - all data must be pre-fetched.
26
+ * It builds a merkle tree from the messages, generates a proof for the target messageId,
27
+ * and optionally validates against the provided merkleRoot.
28
+ *
29
+ * The returned proof can be used with `execute` to manually execute a stuck message.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { calculateManualExecProof, EVMChain } from '@chainlink/ccip-sdk'
34
+ *
35
+ * // Fetch the request and all messages in its batch
36
+ * const request = (await source.getMessagesInTx(txHash))[0]
37
+ * const verifications = await dest.getVerifications({ offRamp, request })
38
+ * const messages = await source.getMessagesInBatch(request, commit.report)
39
+ *
40
+ * // Calculate proof for manual execution
41
+ * const proof = calculateManualExecProof(
42
+ * messages,
43
+ * request.lane,
44
+ * request.message.messageId,
45
+ * commit.report.merkleRoot
46
+ * )
47
+ * console.log('Merkle root:', proof.merkleRoot)
48
+ * console.log('Proofs:', proof.proofs)
49
+ * ```
50
+ * @see {@link discoverOffRamp} - Find the OffRamp for manual execution
51
+ * @see {@link execute} - Execute the report on destination chain
52
+ * @see {@link generateUnsignedExecute} - Build unsigned execution tx
31
53
  **/
32
54
  export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
33
55
  messagesInBatch: readonly CCIPMessage<V>[],
@@ -35,7 +57,7 @@ export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
35
57
  messageId: string,
36
58
  merkleRoot?: string,
37
59
  ctx?: WithLogger,
38
- ): Omit<ExecutionReport, 'offchainTokenData' | 'message'> {
60
+ ) {
39
61
  const hasher = getLeafHasher(lane, ctx)
40
62
 
41
63
  const msgIdx = messagesInBatch.findIndex((message) => message.messageId === messageId)
@@ -64,6 +86,30 @@ export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
64
86
  }
65
87
  }
66
88
 
89
+ /**
90
+ * Discover the OffRamp address for a given OnRamp and destination chain.
91
+ * Results are memoized for performance.
92
+ *
93
+ * @param source - Source chain instance.
94
+ * @param dest - Destination chain instance.
95
+ * @param onRamp - OnRamp contract address on source chain.
96
+ * @param ctx - Optional context with logger.
97
+ * @returns OffRamp address on destination chain.
98
+ * @throws CCIPOffRampNotFoundError - When no matching OffRamp is found for the OnRamp
99
+ * @example
100
+ * ```typescript
101
+ * import { discoverOffRamp, EVMChain } from '@chainlink/ccip-sdk'
102
+ *
103
+ * const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
104
+ * const dest = await EVMChain.fromUrl('https://rpc.fuji.avax.network')
105
+ *
106
+ * const offRamp = await discoverOffRamp(source, dest, onRampAddress)
107
+ * console.log('OffRamp on destination:', offRamp)
108
+ * ```
109
+ * @see {@link calculateManualExecProof} - Use with OffRamp for manual execution
110
+ * @see {@link execute} - Execute on destination chain
111
+ * @see {@link getExecutionReceipts} - Check execution status
112
+ */
67
113
  export const discoverOffRamp = memoize(
68
114
  async function discoverOffRamp_(
69
115
  source: Chain,
@@ -77,42 +123,76 @@ export const discoverOffRamp = memoize(
77
123
  dest.network.chainSelector,
78
124
  )
79
125
  for (const offRamp of sourceOffRamps) {
80
- let destOnRamp: string
126
+ let destOnRamps
81
127
  try {
82
- destOnRamp = await source.getOnRampForOffRamp(offRamp, dest.network.chainSelector)
83
- } catch {
84
- logger.debug('discoverOffRamp: skipping offRamp', offRamp, '(no valid source chain config)')
85
- continue
86
- }
87
- const destRouter = await dest.getRouterForOnRamp(destOnRamp, source.network.chainSelector)
88
- const destOffRamps = await dest.getOffRampsForRouter(destRouter, source.network.chainSelector)
89
- for (const offRamp of destOffRamps) {
90
- let offRampsOnRamp: string
91
- try {
92
- offRampsOnRamp = await dest.getOnRampForOffRamp(offRamp, source.network.chainSelector)
93
- } catch {
94
- logger.debug(
95
- 'discoverOffRamp: skipping dest offRamp',
96
- offRamp,
97
- '(no valid source chain config)',
98
- )
99
- continue
100
- }
128
+ destOnRamps = await source.getOnRampsForOffRamp(offRamp, dest.network.chainSelector)
129
+ } catch (err) {
101
130
  logger.debug(
102
- 'discoverOffRamp: found, from',
103
- {
104
- sourceOnRamp: onRamp,
105
- sourceRouter,
106
- sourceOffRamps,
107
- destOnRamp,
108
- destOffRamps,
109
- offRampsOnRamp,
110
- },
111
- '=',
131
+ 'discoverOffRamp: skipping offRamp',
112
132
  offRamp,
133
+ '(no valid source chain config)',
134
+ err,
135
+ )
136
+ continue
137
+ }
138
+ for (const destOnRamp of destOnRamps) {
139
+ const destRouter = await dest.getRouterForOnRamp(destOnRamp, source.network.chainSelector)
140
+ const destOffRamps = await dest.getOffRampsForRouter(
141
+ destRouter,
142
+ source.network.chainSelector,
113
143
  )
114
- if (offRampsOnRamp === onRamp) {
115
- return offRamp
144
+ for (const offRamp of destOffRamps) {
145
+ let offRampsOnRamps
146
+ try {
147
+ offRampsOnRamps = await dest.getOnRampsForOffRamp(offRamp, source.network.chainSelector)
148
+ } catch (err) {
149
+ logger.debug(
150
+ 'discoverOffRamp: skipping dest offRamp',
151
+ offRamp,
152
+ '(no valid source chain config)',
153
+ err,
154
+ )
155
+ continue
156
+ }
157
+ for (const offRampsOnRamp of offRampsOnRamps) {
158
+ logger.debug(
159
+ 'discoverOffRamp: found, from',
160
+ {
161
+ sourceOnRamp: onRamp,
162
+ sourceRouter,
163
+ sourceOffRamps,
164
+ destOnRamp,
165
+ destOffRamps,
166
+ offRampsOnRamp,
167
+ },
168
+ '=',
169
+ offRamp,
170
+ )
171
+ for (const offRamp of destOffRamps) {
172
+ const offRampsOnRamps = await dest.getOnRampsForOffRamp(
173
+ offRamp,
174
+ source.network.chainSelector,
175
+ )
176
+ for (const offRampsOnRamp of offRampsOnRamps) {
177
+ logger.debug(
178
+ 'discoverOffRamp: found, from',
179
+ {
180
+ sourceOnRamp: onRamp,
181
+ sourceRouter,
182
+ sourceOffRamps,
183
+ destOnRamp,
184
+ destOffRamps,
185
+ offRampsOnRamps,
186
+ },
187
+ '=',
188
+ offRamp,
189
+ )
190
+ if (offRampsOnRamp === onRamp) {
191
+ return offRamp
192
+ }
193
+ }
194
+ }
195
+ }
116
196
  }
117
197
  }
118
198
  }
@@ -123,44 +203,3 @@ export const discoverOffRamp = memoize(
123
203
  [source.network.chainSelector, dest.network.chainSelector, onRamp] as const,
124
204
  },
125
205
  )
126
-
127
- /**
128
- * Generic implementation for fetching ExecutionReceipts for given requests.
129
- * If more than one request is given, may yield them interleaved.
130
- * Completes as soon as there's no more work to be done.
131
- *
132
- * Two possible behaviors:
133
- * - if `startBlock|startTime` is given, pages forward from that block up;
134
- * completes when success (final) receipt is found for all requests (or reach latest block)
135
- * - otherwise, pages backwards and returns only the most recent receipt per request;
136
- * completes when receipts for all requests were seen
137
- *
138
- * @param dest - Provider to page through.
139
- * @param offRamp - OffRamp contract address.
140
- * @param request - CCIP request to search executions for.
141
- * @param commit - Optional commit info to narrow down search.
142
- * @param hints - Optional hints (e.g., `page` for getLogs pagination range).
143
- */
144
- export async function* getExecutionReceipts(
145
- dest: Chain,
146
- offRamp: string,
147
- request: CCIPRequest,
148
- commit?: CCIPCommit,
149
- hints?: { page?: number },
150
- ): AsyncGenerator<CCIPExecution> {
151
- const onlyLast = !commit?.log.blockNumber && !request.tx.timestamp // backwards
152
- for await (const log of dest.getLogs({
153
- startBlock: commit?.log.blockNumber,
154
- startTime: request.tx.timestamp,
155
- address: offRamp,
156
- topics: ['ExecutionStateChanged'],
157
- ...hints,
158
- })) {
159
- const receipt = (dest.constructor as ChainStatic).decodeReceipt(log)
160
- if (!receipt || receipt.messageId !== request.message.messageId) continue
161
-
162
- const timestamp = log.tx?.timestamp ?? (await dest.getBlockTimestamp(log.blockNumber))
163
- yield { receipt, log, timestamp }
164
- if (onlyLast || receipt.state === ExecutionState.Success) break
165
- }
166
- }
package/src/extra-args.ts CHANGED
@@ -9,7 +9,7 @@ export const EVMExtraArgsV1Tag = id('CCIP EVMExtraArgsV1').substring(0, 10) as '
9
9
  /** Tag identifier for EVMExtraArgsV2 encoding. */
10
10
  export const EVMExtraArgsV2Tag = id('CCIP EVMExtraArgsV2').substring(0, 10) as '0x181dcf10'
11
11
  /** Tag identifier for GenericExtraArgsV3 encoding (tightly packed binary format). */
12
- export const GenericExtraArgsV3Tag = '0x302326cb' as const
12
+ export const GenericExtraArgsV3Tag = id('CCIP GenericExtraArgsV3').substring(0, 10) as '0xa69dd4aa'
13
13
  /** Tag identifier for SVMExtraArgsV1 encoding. */
14
14
  export const SVMExtraArgsV1Tag = id('CCIP SVMExtraArgsV1').substring(0, 10) as '0x1f3b3aba'
15
15
  /** Tag identifier for SuiExtraArgsV1 encoding. */
@@ -145,6 +145,7 @@ export type ExtraArgs =
145
145
  * Encodes extra arguments for CCIP messages.
146
146
  * The args are *to* a dest network, but are encoded as a message *from* this source chain.
147
147
  * E.g. Solana uses Borsh to encode extraArgs in its produced requests, even those targeting EVM.
148
+ *
148
149
  * @param args - Extra arguments to encode
149
150
  * @param from - Source chain family for encoding format (defaults to EVM)
150
151
  * @returns Encoded extra arguments as hex string
@@ -152,12 +153,16 @@ export type ExtraArgs =
152
153
  *
153
154
  * @example
154
155
  * ```typescript
156
+ * import { encodeExtraArgs } from '@chainlink/ccip-sdk'
157
+ *
155
158
  * const encoded = encodeExtraArgs({
156
159
  * gasLimit: 200_000n,
157
160
  * allowOutOfOrderExecution: true,
158
161
  * })
159
- * // Returns: '0x181dcf10...'
162
+ * console.log('Encoded:', encoded) // '0x181dcf10...'
160
163
  * ```
164
+ *
165
+ * @see {@link decodeExtraArgs} - Decode extra arguments from bytes
161
166
  */
162
167
  export function encodeExtraArgs(args: ExtraArgs, from: ChainFamily = ChainFamily.EVM): string {
163
168
  const chain = supportedChains[from]
@@ -175,11 +180,16 @@ export function encodeExtraArgs(args: ExtraArgs, from: ChainFamily = ChainFamily
175
180
  *
176
181
  * @example
177
182
  * ```typescript
183
+ * import { decodeExtraArgs } from '@chainlink/ccip-sdk'
184
+ *
178
185
  * const decoded = decodeExtraArgs('0x181dcf10...')
179
186
  * if (decoded?._tag === 'EVMExtraArgsV2') {
180
- * console.log(decoded.gasLimit, decoded.allowOutOfOrderExecution)
187
+ * console.log('Gas limit:', decoded.gasLimit)
188
+ * console.log('Out of order:', decoded.allowOutOfOrderExecution)
181
189
  * }
182
190
  * ```
191
+ *
192
+ * @see {@link encodeExtraArgs} - Encode extra arguments to bytes
183
193
  */
184
194
  export function decodeExtraArgs(
185
195
  data: BytesLike,
package/src/gas.ts CHANGED
@@ -4,6 +4,7 @@ import type { Chain } from './chain.ts'
4
4
  import {
5
5
  CCIPContractTypeInvalidError,
6
6
  CCIPMethodUnsupportedError,
7
+ CCIPOnRampRequiredError,
7
8
  CCIPTokenDecimalsInsufficientError,
8
9
  } from './errors/index.ts'
9
10
  import { discoverOffRamp } from './execution.ts'
@@ -50,11 +51,34 @@ export type EstimateReceiveExecutionOpts = {
50
51
 
51
52
  /**
52
53
  * Estimate CCIP gasLimit needed to execute a request on a contract receiver.
53
- * @param opts - Options for estimation: source and dest chains, router or ramp address, and message
54
- * @returns Estimated gasLimit.
54
+ *
55
+ * @param opts - {@link EstimateReceiveExecutionOpts} for estimation
56
+ * @returns Estimated gasLimit
57
+ *
55
58
  * @throws {@link CCIPMethodUnsupportedError} if dest chain doesn't support estimation
56
59
  * @throws {@link CCIPContractTypeInvalidError} if routerOrRamp is not a valid contract type
57
60
  * @throws {@link CCIPTokenDecimalsInsufficientError} if dest token has insufficient decimals
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * import { estimateReceiveExecution, EVMChain } from '@chainlink/ccip-sdk'
65
+ *
66
+ * const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
67
+ * const dest = await EVMChain.fromUrl('https://rpc.fuji.avax.network')
68
+ *
69
+ * const gasLimit = await estimateReceiveExecution({
70
+ * source,
71
+ * dest,
72
+ * routerOrRamp: '0xRouter...',
73
+ * message: {
74
+ * sender: '0x...',
75
+ * receiver: '0x...',
76
+ * data: '0x...',
77
+ * tokenAmounts: [],
78
+ * },
79
+ * })
80
+ * console.log('Estimated gas:', gasLimit)
81
+ * ```
58
82
  */
59
83
  export async function estimateReceiveExecution({
60
84
  source,
@@ -78,7 +102,9 @@ export async function estimateReceiveExecution({
78
102
  if (!tnv[0].includes('OffRamp'))
79
103
  throw new CCIPContractTypeInvalidError(routerOrRamp, tnv[2], ['OffRamp'])
80
104
  offRamp = routerOrRamp
81
- onRamp = await dest.getOnRampForOffRamp(offRamp, source.network.chainSelector)
105
+ const onRamps = await dest.getOnRampsForOffRamp(offRamp, source.network.chainSelector)
106
+ if (!onRamps.length) throw new CCIPOnRampRequiredError()
107
+ onRamp = onRamps[onRamps.length - 1]!
82
108
  } catch {
83
109
  throw sourceErr // re-throw original error
84
110
  }
package/src/index.ts CHANGED
@@ -13,7 +13,12 @@ export type {
13
13
  CCIPAPIClientContext,
14
14
  LaneLatencyResponse,
15
15
  } from './api/index.ts'
16
- export { CCIPAPIClient, DEFAULT_API_BASE_URL } from './api/index.ts'
16
+ export {
17
+ CCIPAPIClient,
18
+ DEFAULT_API_BASE_URL,
19
+ SDK_VERSION,
20
+ SDK_VERSION_HEADER,
21
+ } from './api/index.ts'
17
22
 
18
23
  export type {
19
24
  ApiRetryConfig,
@@ -41,16 +46,18 @@ export {
41
46
  encodeExtraArgs,
42
47
  } from './extra-args.ts'
43
48
  export { estimateReceiveExecution } from './gas.ts'
49
+ export { getOffchainTokenData } from './offchain.ts'
44
50
  export { decodeMessage, getMessagesForSender, sourceToDestTokenAddresses } from './requests.ts'
45
51
  export {
46
- type CCIPCommit,
47
52
  type CCIPExecution,
48
53
  type CCIPMessage,
49
54
  type CCIPRequest,
55
+ type CCIPVerifications,
56
+ type ChainLog,
50
57
  type ChainTransaction,
51
58
  type CommitReport,
59
+ type ExecutionInput,
52
60
  type ExecutionReceipt,
53
- type ExecutionReport,
54
61
  type Lane,
55
62
  type Logger,
56
63
  type MessageInput,
package/src/offchain.ts CHANGED
@@ -1,56 +1,79 @@
1
- import { keccak256 } from 'ethers'
1
+ import { type BytesLike, dataLength, dataSlice, getBytes, toNumber } from 'ethers'
2
+ import type { PickDeep } from 'type-fest'
2
3
 
3
4
  import {
4
5
  CCIPLbtcAttestationNotApprovedError,
5
6
  CCIPLbtcAttestationNotFoundError,
6
7
  CCIPUsdcAttestationError,
7
8
  } from './errors/index.ts'
8
- import { NetworkType } from './types.ts'
9
+ import { parseSourceTokenData } from './evm/messages.ts'
10
+ import { type CCIPRequest, type OffchainTokenData, type WithLogger, NetworkType } from './types.ts'
11
+ import { networkInfo } from './utils.ts'
9
12
 
10
13
  const CIRCLE_API_URL = {
11
- mainnet: 'https://iris-api.circle.com/v1',
12
- testnet: 'https://iris-api-sandbox.circle.com/v1',
14
+ mainnet: 'https://iris-api.circle.com',
15
+ testnet: 'https://iris-api-sandbox.circle.com',
13
16
  }
14
17
 
15
18
  type CctpAttestationResponse =
16
19
  | { error: 'string' }
17
- | { status: 'pending_confirmations' }
18
- | { status: 'complete'; attestation: string }
19
-
20
- const LOMBARD_API_URL = {
21
- mainnet: 'https://mainnet.prod.lombard.finance',
22
- testnet: 'https://gastald-testnet.prod.lombard.finance',
23
- }
24
-
25
- type LombardAttestation =
26
- | { status: 'NOTARIZATION_STATUS_SESSION_APPROVED'; message_hash: string; attestation: string }
27
- | { status: string; message_hash: string }
28
- type LombardAttestationsResponse = { attestations: Array<LombardAttestation> }
20
+ | {
21
+ messages: {
22
+ status: 'pending_confirmations' | 'complete'
23
+ eventNonce?: string
24
+ attestation: string
25
+ message: string
26
+ }[]
27
+ }
29
28
 
30
29
  /**
31
- * Returns the USDC attestation for a given MessageSent Log
32
- * https://developers.circle.com/stablecoins/reference/getattestation
30
+ * Returns the USDC attestation for a given tokenAmount.extraData and txHash
31
+ * https://developers.circle.com/cctp/quickstarts/transfer-usdc-ethereum-to-arc#3-3-retrieve-attestation
33
32
  *
34
- * @param message - payload of USDC MessageSent(bytes message) event
33
+ * @param opts - CCTPv2 options
35
34
  * @param networkType - network type (mainnet or testnet)
36
- * @returns USDC/CCTP attestation bytes
35
+ * @returns USDC/CCTP attestation and message
37
36
  */
38
37
  export async function getUsdcAttestation(
39
- message: string,
38
+ opts: {
39
+ /** CCTP sourceDomain */
40
+ sourceDomain: number
41
+ /** CCTP burn eventNonce */
42
+ nonce: number
43
+ /** burn txHash, same as CCIP request */
44
+ txHash: string
45
+ },
40
46
  networkType: NetworkType,
41
- ): Promise<string> {
42
- const msgHash = keccak256(message)
43
-
47
+ ): Promise<{ attestation: string; message: string }> {
48
+ const { sourceDomain, nonce, txHash } = opts
44
49
  const circleApiBaseUrl =
45
50
  networkType === NetworkType.Mainnet ? CIRCLE_API_URL.mainnet : CIRCLE_API_URL.testnet
46
- const res = await fetch(`${circleApiBaseUrl}/attestations/${msgHash}`)
51
+ const res = await fetch(
52
+ `${circleApiBaseUrl}/v2/messages/${sourceDomain}?transactionHash=${txHash}`,
53
+ )
47
54
  const json = (await res.json()) as CctpAttestationResponse
48
- if (!('status' in json) || json.status !== 'complete' || !json.attestation) {
49
- throw new CCIPUsdcAttestationError(msgHash, json)
55
+ let att
56
+ if ('messages' in json) {
57
+ att = json.messages.find((m) => m.status === 'complete' && m.eventNonce === nonce.toString())
50
58
  }
51
- return json.attestation
59
+ if (!att?.message) throw new CCIPUsdcAttestationError(txHash, json, { context: opts })
60
+ return att
61
+ }
62
+
63
+ const LOMBARD_API_URL = {
64
+ mainnet: 'https://mainnet.prod.lombard.finance',
65
+ testnet: 'https://gastald-testnet.prod.lombard.finance',
52
66
  }
53
67
 
68
+ type LombardAttestation =
69
+ | {
70
+ status: 'NOTARIZATION_STATUS_SESSION_APPROVED'
71
+ message_hash: string
72
+ attestation: string
73
+ }
74
+ | { status: string; message_hash: string }
75
+ type LombardAttestationsResponse = { attestations: Array<LombardAttestation> }
76
+
54
77
  /**
55
78
  * Returns the LBTC attestation for a given payload hash
56
79
  *
@@ -86,3 +109,77 @@ export async function getLbtcAttestation(
86
109
  }
87
110
  return attestation
88
111
  }
112
+
113
+ /**
114
+ * Fetch CCIPv1 offchain token data for USDC and LBTC tokenAmounts
115
+ * @param request - CCIPRequest containing tx.hash and message
116
+ * @returns Promise resolving to an OffchainTokenData for each tokenAmount
117
+ */
118
+ export async function getOffchainTokenData(
119
+ request: PickDeep<CCIPRequest, 'tx.hash' | `message`>,
120
+ { logger = console }: WithLogger = {},
121
+ ): Promise<OffchainTokenData[]> {
122
+ const { networkType } = networkInfo(request.message.sourceChainSelector)
123
+
124
+ function looksUsdcData(extraData: BytesLike) {
125
+ if (dataLength(extraData) !== 64) return
126
+ // USDCTokenPool's extraData is a packed `SourceTokenDataPayloadV1{uint64 nonce, uint32 sourceDomain}`,
127
+ // which we need to query CCTPv2 (by sourceDomain and txHash) and to filter by nonce among messages,
128
+ // if more than one in tx
129
+ let nonce, sourceDomain
130
+ try {
131
+ // those toNumber conversions throw early in case the bytearray don't look like small numbers
132
+ nonce = toNumber(dataSlice(extraData, 0, 32))
133
+ sourceDomain = toNumber(dataSlice(extraData, 32, 32 + 32))
134
+ return { nonce, sourceDomain } // maybe USDC
135
+ } catch {
136
+ // not USDC
137
+ }
138
+ }
139
+
140
+ function looksLbtcData(extraData: BytesLike) {
141
+ // LBTC returns `message_hash`/`payloadHash` directly as `bytes32 extraData`
142
+ if (
143
+ dataLength(extraData) === 32 &&
144
+ getBytes(extraData, 'extraData').filter(Boolean).length > 20 // looks like a hash
145
+ )
146
+ return true
147
+ }
148
+
149
+ return Promise.all(
150
+ request.message.tokenAmounts.map(async (tokenAmount, i) => {
151
+ let extraData
152
+ if ('extraData' in tokenAmount) {
153
+ extraData = tokenAmount.extraData
154
+ } else if ('sourceTokenData' in request.message) {
155
+ // v1.2..v1.5
156
+ if (dataLength(request.message.sourceTokenData[i]!) === 64) {
157
+ extraData = request.message.sourceTokenData[i]
158
+ } else {
159
+ ;({ extraData } = parseSourceTokenData(request.message.sourceTokenData[i]!))
160
+ }
161
+ }
162
+ if (!extraData) return
163
+ const usdcOpts = looksUsdcData(extraData)
164
+ if (usdcOpts) {
165
+ try {
166
+ const usdcAttestation = await getUsdcAttestation(
167
+ { ...usdcOpts, txHash: request.tx.hash },
168
+ networkType,
169
+ )
170
+ return { _tag: 'usdc', extraData, ...usdcAttestation }
171
+ } catch (err) {
172
+ // maybe not a USDC transfer, or not ready
173
+ logger.warn(`❌ CCTP: Failed to fetch attestation for message:`, request.message, err)
174
+ }
175
+ } else if (looksLbtcData(extraData)) {
176
+ try {
177
+ const lbtcAttestation = await getLbtcAttestation(extraData, networkType)
178
+ return { _tag: 'lbtc', extraData, ...lbtcAttestation }
179
+ } catch (err) {
180
+ logger.warn(`❌ LBTC: Failed to fetch attestation for message:`, extraData, err)
181
+ }
182
+ }
183
+ }),
184
+ )
185
+ }