@chainlink/ccip-sdk 0.95.0 → 0.97.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 (217) hide show
  1. package/README.md +2 -2
  2. package/dist/all-chains.d.ts +23 -0
  3. package/dist/all-chains.d.ts.map +1 -0
  4. package/dist/all-chains.js +24 -0
  5. package/dist/all-chains.js.map +1 -0
  6. package/dist/api/index.d.ts +31 -19
  7. package/dist/api/index.d.ts.map +1 -1
  8. package/dist/api/index.js +46 -25
  9. package/dist/api/index.js.map +1 -1
  10. package/dist/api/types.d.ts +24 -30
  11. package/dist/api/types.d.ts.map +1 -1
  12. package/dist/aptos/exec.d.ts +2 -2
  13. package/dist/aptos/exec.d.ts.map +1 -1
  14. package/dist/aptos/exec.js.map +1 -1
  15. package/dist/aptos/hasher.d.ts.map +1 -1
  16. package/dist/aptos/hasher.js +1 -1
  17. package/dist/aptos/hasher.js.map +1 -1
  18. package/dist/aptos/index.d.ts +43 -15
  19. package/dist/aptos/index.d.ts.map +1 -1
  20. package/dist/aptos/index.js +112 -105
  21. package/dist/aptos/index.js.map +1 -1
  22. package/dist/aptos/types.d.ts +2 -19
  23. package/dist/aptos/types.d.ts.map +1 -1
  24. package/dist/aptos/types.js +0 -11
  25. package/dist/aptos/types.js.map +1 -1
  26. package/dist/chain.d.ts +734 -174
  27. package/dist/chain.d.ts.map +1 -1
  28. package/dist/chain.js +216 -31
  29. package/dist/chain.js.map +1 -1
  30. package/dist/commits.d.ts +4 -6
  31. package/dist/commits.d.ts.map +1 -1
  32. package/dist/commits.js +4 -4
  33. package/dist/commits.js.map +1 -1
  34. package/dist/errors/CCIPError.d.ts +33 -4
  35. package/dist/errors/CCIPError.d.ts.map +1 -1
  36. package/dist/errors/CCIPError.js +33 -4
  37. package/dist/errors/CCIPError.js.map +1 -1
  38. package/dist/errors/codes.d.ts +5 -0
  39. package/dist/errors/codes.d.ts.map +1 -1
  40. package/dist/errors/codes.js +5 -1
  41. package/dist/errors/codes.js.map +1 -1
  42. package/dist/errors/index.d.ts +2 -2
  43. package/dist/errors/index.d.ts.map +1 -1
  44. package/dist/errors/index.js +2 -2
  45. package/dist/errors/index.js.map +1 -1
  46. package/dist/errors/recovery.d.ts.map +1 -1
  47. package/dist/errors/recovery.js +6 -1
  48. package/dist/errors/recovery.js.map +1 -1
  49. package/dist/errors/specialized.d.ts +1702 -121
  50. package/dist/errors/specialized.d.ts.map +1 -1
  51. package/dist/errors/specialized.js +1729 -125
  52. package/dist/errors/specialized.js.map +1 -1
  53. package/dist/errors/utils.d.ts.map +1 -1
  54. package/dist/errors/utils.js +0 -1
  55. package/dist/errors/utils.js.map +1 -1
  56. package/dist/evm/abi/OffRamp_2_0.d.ts +764 -0
  57. package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -0
  58. package/dist/evm/abi/OffRamp_2_0.js +744 -0
  59. package/dist/evm/abi/OffRamp_2_0.js.map +1 -0
  60. package/dist/evm/abi/OnRamp_2_0.d.ts +925 -0
  61. package/dist/evm/abi/OnRamp_2_0.d.ts.map +1 -0
  62. package/dist/evm/abi/OnRamp_2_0.js +992 -0
  63. package/dist/evm/abi/OnRamp_2_0.js.map +1 -0
  64. package/dist/evm/const.d.ts +12 -2
  65. package/dist/evm/const.d.ts.map +1 -1
  66. package/dist/evm/const.js +8 -2
  67. package/dist/evm/const.js.map +1 -1
  68. package/dist/evm/errors.d.ts.map +1 -1
  69. package/dist/evm/errors.js +7 -2
  70. package/dist/evm/errors.js.map +1 -1
  71. package/dist/evm/extra-args.d.ts +25 -0
  72. package/dist/evm/extra-args.d.ts.map +1 -0
  73. package/dist/evm/extra-args.js +309 -0
  74. package/dist/evm/extra-args.js.map +1 -0
  75. package/dist/evm/gas.d.ts.map +1 -1
  76. package/dist/evm/gas.js +7 -12
  77. package/dist/evm/gas.js.map +1 -1
  78. package/dist/evm/hasher.d.ts.map +1 -1
  79. package/dist/evm/hasher.js +23 -13
  80. package/dist/evm/hasher.js.map +1 -1
  81. package/dist/evm/index.d.ts +140 -35
  82. package/dist/evm/index.d.ts.map +1 -1
  83. package/dist/evm/index.js +306 -226
  84. package/dist/evm/index.js.map +1 -1
  85. package/dist/evm/messages.d.ts +59 -5
  86. package/dist/evm/messages.d.ts.map +1 -1
  87. package/dist/evm/messages.js +210 -0
  88. package/dist/evm/messages.js.map +1 -1
  89. package/dist/evm/offchain.js.map +1 -1
  90. package/dist/evm/types.d.ts +7 -2
  91. package/dist/evm/types.d.ts.map +1 -1
  92. package/dist/evm/types.js +22 -1
  93. package/dist/evm/types.js.map +1 -1
  94. package/dist/execution.d.ts +62 -22
  95. package/dist/execution.d.ts.map +1 -1
  96. package/dist/execution.js +102 -51
  97. package/dist/execution.js.map +1 -1
  98. package/dist/extra-args.d.ts +113 -4
  99. package/dist/extra-args.d.ts.map +1 -1
  100. package/dist/extra-args.js +38 -3
  101. package/dist/extra-args.js.map +1 -1
  102. package/dist/gas.d.ts +31 -5
  103. package/dist/gas.d.ts.map +1 -1
  104. package/dist/gas.js +43 -9
  105. package/dist/gas.js.map +1 -1
  106. package/dist/index.d.ts +11 -10
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +8 -8
  109. package/dist/index.js.map +1 -1
  110. package/dist/requests.d.ts +101 -22
  111. package/dist/requests.d.ts.map +1 -1
  112. package/dist/requests.js +115 -24
  113. package/dist/requests.js.map +1 -1
  114. package/dist/selectors.d.ts.map +1 -1
  115. package/dist/selectors.js +24 -0
  116. package/dist/selectors.js.map +1 -1
  117. package/dist/shared/bcs-codecs.d.ts +61 -0
  118. package/dist/shared/bcs-codecs.d.ts.map +1 -0
  119. package/dist/shared/bcs-codecs.js +102 -0
  120. package/dist/shared/bcs-codecs.js.map +1 -0
  121. package/dist/shared/constants.d.ts +3 -0
  122. package/dist/shared/constants.d.ts.map +1 -0
  123. package/dist/shared/constants.js +3 -0
  124. package/dist/shared/constants.js.map +1 -0
  125. package/dist/solana/exec.d.ts +2 -2
  126. package/dist/solana/exec.d.ts.map +1 -1
  127. package/dist/solana/exec.js.map +1 -1
  128. package/dist/solana/index.d.ts +148 -30
  129. package/dist/solana/index.d.ts.map +1 -1
  130. package/dist/solana/index.js +137 -44
  131. package/dist/solana/index.js.map +1 -1
  132. package/dist/sui/hasher.d.ts.map +1 -1
  133. package/dist/sui/hasher.js +1 -1
  134. package/dist/sui/hasher.js.map +1 -1
  135. package/dist/sui/index.d.ts +49 -19
  136. package/dist/sui/index.d.ts.map +1 -1
  137. package/dist/sui/index.js +76 -43
  138. package/dist/sui/index.js.map +1 -1
  139. package/dist/sui/manuallyExec/encoder.d.ts +2 -2
  140. package/dist/sui/manuallyExec/encoder.d.ts.map +1 -1
  141. package/dist/sui/manuallyExec/encoder.js.map +1 -1
  142. package/dist/sui/manuallyExec/index.d.ts +2 -2
  143. package/dist/sui/manuallyExec/index.d.ts.map +1 -1
  144. package/dist/ton/exec.d.ts +2 -2
  145. package/dist/ton/exec.d.ts.map +1 -1
  146. package/dist/ton/exec.js.map +1 -1
  147. package/dist/ton/index.d.ts +66 -27
  148. package/dist/ton/index.d.ts.map +1 -1
  149. package/dist/ton/index.js +172 -47
  150. package/dist/ton/index.js.map +1 -1
  151. package/dist/ton/send.d.ts +52 -0
  152. package/dist/ton/send.d.ts.map +1 -0
  153. package/dist/ton/send.js +166 -0
  154. package/dist/ton/send.js.map +1 -0
  155. package/dist/ton/types.d.ts +2 -2
  156. package/dist/ton/types.d.ts.map +1 -1
  157. package/dist/ton/types.js.map +1 -1
  158. package/dist/types.d.ts +148 -12
  159. package/dist/types.d.ts.map +1 -1
  160. package/dist/types.js +6 -1
  161. package/dist/types.js.map +1 -1
  162. package/dist/utils.d.ts +79 -4
  163. package/dist/utils.d.ts.map +1 -1
  164. package/dist/utils.js +92 -7
  165. package/dist/utils.js.map +1 -1
  166. package/package.json +16 -11
  167. package/src/all-chains.ts +26 -0
  168. package/src/api/index.ts +58 -34
  169. package/src/api/types.ts +24 -31
  170. package/src/aptos/exec.ts +2 -2
  171. package/src/aptos/hasher.ts +1 -1
  172. package/src/aptos/index.ts +127 -129
  173. package/src/aptos/types.ts +2 -15
  174. package/src/chain.ts +837 -191
  175. package/src/commits.ts +9 -9
  176. package/src/errors/CCIPError.ts +33 -4
  177. package/src/errors/codes.ts +5 -1
  178. package/src/errors/index.ts +2 -1
  179. package/src/errors/recovery.ts +9 -1
  180. package/src/errors/specialized.ts +1745 -132
  181. package/src/errors/utils.ts +0 -1
  182. package/src/evm/abi/OffRamp_2_0.ts +743 -0
  183. package/src/evm/abi/OnRamp_2_0.ts +991 -0
  184. package/src/evm/const.ts +10 -3
  185. package/src/evm/errors.ts +6 -2
  186. package/src/evm/extra-args.ts +360 -0
  187. package/src/evm/gas.ts +14 -13
  188. package/src/evm/hasher.ts +30 -18
  189. package/src/evm/index.ts +376 -281
  190. package/src/evm/messages.ts +323 -11
  191. package/src/evm/offchain.ts +2 -2
  192. package/src/evm/types.ts +20 -2
  193. package/src/execution.ts +126 -71
  194. package/src/extra-args.ts +118 -4
  195. package/src/gas.ts +44 -11
  196. package/src/index.ts +14 -11
  197. package/src/requests.ts +128 -24
  198. package/src/selectors.ts +24 -0
  199. package/src/shared/bcs-codecs.ts +132 -0
  200. package/src/shared/constants.ts +2 -0
  201. package/src/solana/exec.ts +4 -4
  202. package/src/solana/index.ts +170 -82
  203. package/src/sui/hasher.ts +1 -1
  204. package/src/sui/index.ts +88 -56
  205. package/src/sui/manuallyExec/encoder.ts +2 -2
  206. package/src/sui/manuallyExec/index.ts +2 -2
  207. package/src/ton/exec.ts +2 -2
  208. package/src/ton/index.ts +220 -58
  209. package/src/ton/send.ts +222 -0
  210. package/src/ton/types.ts +2 -2
  211. package/src/types.ts +173 -30
  212. package/src/utils.ts +91 -7
  213. package/dist/aptos/utils.d.ts +0 -12
  214. package/dist/aptos/utils.d.ts.map +0 -1
  215. package/dist/aptos/utils.js +0 -15
  216. package/dist/aptos/utils.js.map +0 -1
  217. package/src/aptos/utils.ts +0 -24
package/src/evm/index.ts CHANGED
@@ -1,27 +1,23 @@
1
- import { parseAbi } from 'abitype'
2
1
  import {
3
2
  type BytesLike,
4
3
  type Interface,
5
4
  type JsonRpcApiProvider,
6
5
  type Log,
6
+ type Result,
7
7
  type Signer,
8
8
  type TransactionReceipt,
9
9
  type TransactionRequest,
10
10
  type TransactionResponse,
11
11
  Contract,
12
12
  JsonRpcProvider,
13
- Result,
14
13
  WebSocketProvider,
15
14
  ZeroAddress,
16
- concat,
17
- dataSlice,
18
- encodeBase58,
19
15
  getAddress,
20
16
  hexlify,
21
17
  isBytesLike,
22
18
  isHexString,
19
+ keccak256,
23
20
  toBeHex,
24
- toBigInt,
25
21
  zeroPadValue,
26
22
  } from 'ethers'
27
23
  import type { TypedContract } from 'ethers-abitype'
@@ -37,43 +33,36 @@ import {
37
33
  } from '../chain.ts'
38
34
  import {
39
35
  CCIPAddressInvalidEvmError,
36
+ CCIPApiClientNotAvailableError,
40
37
  CCIPBlockNotFoundError,
41
38
  CCIPContractNotRouterError,
42
39
  CCIPContractTypeInvalidError,
43
40
  CCIPDataFormatUnsupportedError,
44
41
  CCIPExecTxNotConfirmedError,
45
42
  CCIPExecTxRevertedError,
46
- CCIPExtraArgsParseError,
47
43
  CCIPHasherVersionUnsupportedError,
48
44
  CCIPLogDataInvalidError,
49
- CCIPMessageDecodeError,
45
+ CCIPNotImplementedError,
50
46
  CCIPSourceChainUnsupportedError,
51
47
  CCIPTokenNotConfiguredError,
48
+ CCIPTokenPoolChainConfigNotFoundError,
52
49
  CCIPTransactionNotFoundError,
53
50
  CCIPVersionFeatureUnavailableError,
54
51
  CCIPVersionRequiresLaneError,
55
52
  CCIPVersionUnsupportedError,
56
53
  CCIPWalletInvalidError,
57
54
  } from '../errors/index.ts'
58
- import {
59
- type EVMExtraArgsV1,
60
- type EVMExtraArgsV2,
61
- type ExtraArgs,
62
- type SVMExtraArgsV1,
63
- type SuiExtraArgsV1,
64
- EVMExtraArgsV1Tag,
65
- EVMExtraArgsV2Tag,
66
- SVMExtraArgsV1Tag,
67
- SuiExtraArgsV1Tag,
68
- } from '../extra-args.ts'
55
+ import type { ExtraArgs } from '../extra-args.ts'
69
56
  import type { LeafHasher } from '../hasher/common.ts'
70
57
  import { supportedChains } from '../supported-chains.ts'
71
58
  import {
72
59
  type CCIPExecution,
73
60
  type CCIPMessage,
74
61
  type CCIPRequest,
62
+ type CCIPVerifications,
75
63
  type ChainTransaction,
76
64
  type CommitReport,
65
+ type ExecutionInput,
77
66
  type ExecutionReceipt,
78
67
  type ExecutionState,
79
68
  type Lane,
@@ -100,56 +89,44 @@ import type TokenPool_ABI from './abi/LockReleaseTokenPool_1_6_1.ts'
100
89
  import EVM2EVMOffRamp_1_2_ABI from './abi/OffRamp_1_2.ts'
101
90
  import EVM2EVMOffRamp_1_5_ABI from './abi/OffRamp_1_5.ts'
102
91
  import OffRamp_1_6_ABI from './abi/OffRamp_1_6.ts'
92
+ import OffRamp_2_0_ABI from './abi/OffRamp_2_0.ts'
103
93
  import EVM2EVMOnRamp_1_2_ABI from './abi/OnRamp_1_2.ts'
104
94
  import EVM2EVMOnRamp_1_5_ABI from './abi/OnRamp_1_5.ts'
105
- import OnRamp_1_6_ABI from './abi/OnRamp_1_6.ts'
95
+ import type OnRamp_1_6_ABI from './abi/OnRamp_1_6.ts'
96
+ import type OnRamp_2_0_ABI from './abi/OnRamp_2_0.ts'
106
97
  import type Router_ABI from './abi/Router.ts'
107
98
  import type TokenAdminRegistry_1_5_ABI from './abi/TokenAdminRegistry_1_5.ts'
108
99
  import {
109
- DEFAULT_GAS_LIMIT,
100
+ CCV_INDEXER_URL,
101
+ VersionedContractABI,
110
102
  commitsFragments,
111
- defaultAbiCoder,
112
103
  interfaces,
113
104
  receiptsFragments,
114
105
  requestsFragments,
115
106
  } from './const.ts'
116
107
  import { parseData } from './errors.ts'
108
+ import {
109
+ decodeExtraArgs as decodeExtraArgs_,
110
+ encodeExtraArgs as encodeExtraArgs_,
111
+ } from './extra-args.ts'
117
112
  import { estimateExecGas } from './gas.ts'
118
113
  import { getV12LeafHasher, getV16LeafHasher } from './hasher.ts'
119
114
  import { getEvmLogs } from './logs.ts'
120
115
  import {
121
116
  type CCIPMessage_V1_6_EVM,
117
+ type CCIPMessage_V2_0,
122
118
  type CleanAddressable,
123
- parseSourceTokenData,
119
+ type MessageV1,
120
+ type TokenTransferV1,
121
+ decodeMessageV1,
124
122
  } from './messages.ts'
123
+ export { decodeMessageV1 }
124
+ export type { MessageV1, TokenTransferV1 }
125
125
  import { encodeEVMOffchainTokenData, fetchEVMOffchainTokenData } from './offchain.ts'
126
- import { buildMessageForDest, getMessagesInBatch } from '../requests.ts'
127
- import type { UnsignedEVMTx } from './types.ts'
126
+ import { buildMessageForDest, decodeMessage, getMessagesInBatch } from '../requests.ts'
127
+ import { type UnsignedEVMTx, resultToObject } from './types.ts'
128
128
  export type { UnsignedEVMTx }
129
129
 
130
- const VersionedContractABI = parseAbi(['function typeAndVersion() view returns (string)'])
131
-
132
- const EVMExtraArgsV1 = 'tuple(uint256 gasLimit)'
133
- const EVMExtraArgsV2 = 'tuple(uint256 gasLimit, bool allowOutOfOrderExecution)'
134
- const SVMExtraArgsV1 =
135
- 'tuple(uint32 computeUnits, uint64 accountIsWritableBitmap, bool allowOutOfOrderExecution, bytes32 tokenReceiver, bytes32[] accounts)'
136
- const SuiExtraArgsV1 =
137
- 'tuple(uint256 gasLimit, bool allowOutOfOrderExecution, bytes32 tokenReceiver, bytes32[] receiverObjectIds)'
138
-
139
- function resultToObject<T>(o: T): T {
140
- if (o instanceof Promise) return o.then(resultToObject) as T
141
- if (!(o instanceof Result)) return o
142
- if (o.length === 0) return o.toArray() as T
143
- try {
144
- const obj = o.toObject()
145
- if (!Object.keys(obj).every((k) => /^_+\d*$/.test(k)))
146
- return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, resultToObject(v)])) as T
147
- } catch (_) {
148
- // fallthrough
149
- }
150
- return o.toArray().map(resultToObject) as T
151
- }
152
-
153
130
  /** typeguard for ethers Signer interface (used for `wallet`s) */
154
131
  function isSigner(wallet: unknown): wallet is Signer {
155
132
  return (
@@ -180,6 +157,26 @@ async function submitTransaction(
180
157
 
181
158
  /**
182
159
  * EVM chain implementation supporting Ethereum-compatible networks.
160
+ *
161
+ * Provides methods for sending CCIP cross-chain messages, querying message
162
+ * status, fetching fee quotes, and manually executing pending messages on
163
+ * Ethereum Virtual Machine compatible chains.
164
+ *
165
+ * @example Create from RPC URL
166
+ * ```typescript
167
+ * import { EVMChain } from '@chainlink/ccip-sdk'
168
+ *
169
+ * const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
170
+ * console.log(`Connected to: ${chain.network.name}`)
171
+ * ```
172
+ *
173
+ * @example Query messages in a transaction
174
+ * ```typescript
175
+ * const requests = await chain.getMessagesInTx('0xabc123...')
176
+ * for (const req of requests) {
177
+ * console.log(`Message ID: ${req.message.messageId}`)
178
+ * }
179
+ * ```
183
180
  */
184
181
  export class EVMChain extends Chain<typeof ChainFamily.EVM> {
185
182
  static {
@@ -190,6 +187,13 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
190
187
 
191
188
  provider: JsonRpcApiProvider
192
189
  readonly destroy$: Promise<void>
190
+ private noncesPromises: Record<string, Promise<unknown>>
191
+ /**
192
+ * Cache of current nonces per wallet address.
193
+ * Used internally by {@link sendMessage} and {@link execute} to manage transaction ordering.
194
+ * Can be inspected for debugging or manually adjusted if needed.
195
+ */
196
+ nonces: Record<string, number>
193
197
 
194
198
  /**
195
199
  * Creates a new EVMChain instance.
@@ -199,6 +203,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
199
203
  constructor(provider: JsonRpcApiProvider, network: NetworkInfo, ctx?: ChainContext) {
200
204
  super(network, ctx)
201
205
 
206
+ this.noncesPromises = {}
207
+ this.nonces = {}
208
+
202
209
  this.provider = provider
203
210
  this.destroy$ = new Promise<void>((resolve) => (this.destroy = resolve))
204
211
  void this.destroy$.finally(() => provider.destroy())
@@ -238,6 +245,22 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
238
245
  return (await this.provider.listAccounts()).map(({ address }) => address)
239
246
  }
240
247
 
248
+ /**
249
+ * Get the next nonce for a wallet address and increment the internal counter.
250
+ * Fetches from the network on first call, then uses cached value.
251
+ * @param address - Wallet address to get nonce for
252
+ * @returns The next available nonce
253
+ */
254
+ async nextNonce(address: string): Promise<number> {
255
+ await (this.noncesPromises[address] ??= this.provider
256
+ .getTransactionCount(address)
257
+ .then((nonce) => {
258
+ this.nonces[address] = nonce
259
+ return nonce
260
+ }))
261
+ return this.nonces[address]!++
262
+ }
263
+
241
264
  /**
242
265
  * Creates a JSON-RPC provider from a URL.
243
266
  * @param url - WebSocket (wss://) or HTTP (https://) endpoint URL.
@@ -282,9 +305,20 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
282
305
 
283
306
  /**
284
307
  * Creates an EVMChain instance from an RPC URL.
308
+ *
285
309
  * @param url - WebSocket (wss://) or HTTP (https://) endpoint URL.
286
- * @param ctx - context containing logger.
287
- * @returns A new EVMChain instance.
310
+ * @param ctx - Optional context containing logger and API client configuration.
311
+ * @returns A new EVMChain instance connected to the specified network.
312
+ * @throws {@link CCIPChainNotFoundError} if chain cannot be identified from chainId
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * // HTTP connection
317
+ * const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
318
+ *
319
+ * // With custom logger
320
+ * const chain = await EVMChain.fromUrl(url, { logger: customLogger })
321
+ * ```
288
322
  */
289
323
  static async fromUrl(url: string, ctx?: ChainContext): Promise<EVMChain> {
290
324
  return this.fromProvider(await this._getProvider(url), ctx)
@@ -320,15 +354,15 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
320
354
  }
321
355
 
322
356
  /** {@inheritDoc Chain.getMessagesInBatch} */
323
- getMessagesInBatch<
357
+ override getMessagesInBatch<
324
358
  R extends PickDeep<
325
359
  CCIPRequest,
326
360
  'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.sequenceNumber'
327
361
  >,
328
362
  >(
329
363
  request: R,
330
- commit: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
331
- opts?: { page?: number },
364
+ range: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
365
+ opts?: Pick<LogFilter, 'page'>,
332
366
  ): Promise<R['message'][]> {
333
367
  let opts_: Parameters<EVMChain['getLogs']>[0] | undefined
334
368
  if (request.lane.version >= CCIPVersion.V1_6) {
@@ -338,7 +372,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
338
372
  topics: [[request.log.topics[0]!], [toBeHex(request.lane.destChainSelector, 32)]],
339
373
  }
340
374
  }
341
- return getMessagesInBatch(this, request, commit, opts_)
375
+ return getMessagesInBatch(this, request, range, opts_)
342
376
  }
343
377
 
344
378
  /** {@inheritDoc Chain.typeAndVersion} */
@@ -348,13 +382,17 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
348
382
  VersionedContractABI,
349
383
  this.provider,
350
384
  ) as unknown as TypedContract<typeof VersionedContractABI>
351
- return parseTypeAndVersion(await contract.typeAndVersion())
385
+ const res = parseTypeAndVersion(await contract.typeAndVersion())
386
+ if (res[1].startsWith('1.7.')) res[1] = CCIPVersion.V2_0
387
+ return res
352
388
  }
353
389
 
354
390
  /**
355
391
  * Decodes a CCIP message from a log event.
356
392
  * @param log - Log event with topics and data.
357
393
  * @returns Decoded CCIPMessage or undefined if not a valid CCIP message.
394
+ * @throws {@link CCIPLogDataInvalidError} if log data is not valid bytes
395
+ * @throws {@link CCIPMessageDecodeError} if message cannot be decoded
358
396
  */
359
397
  static decodeMessage(log: {
360
398
  topics?: readonly string[]
@@ -376,79 +414,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
376
414
  const result = interfaces.OnRamp_v1_6.decodeEventLog(fragment, log.data, log.topics)
377
415
  message = resultToObject(result) as Record<string, unknown>
378
416
  if (message.message) message = message.message as Record<string, unknown> | undefined
417
+ else if (message.encodedMessage) {
418
+ Object.assign(message, decodeMessageV1(message.encodedMessage as BytesLike))
419
+ }
379
420
  if (message) break
380
421
  } catch (_) {
381
422
  // try next fragment
382
423
  }
383
424
  }
384
425
  if (!message) return
385
- if (!isHexString(message.sender, 20)) throw new CCIPMessageDecodeError('invalid sender')
386
-
387
- if (message.header) {
388
- // CCIPMessage_V1_6
389
- Object.assign(message, message.header)
390
- delete message.header
391
- }
392
-
393
- const sourceFamily = networkInfo(
394
- (message as { sourceChainSelector: bigint }).sourceChainSelector,
395
- ).family
396
- let destFamily: ChainFamily = ChainFamily.EVM
397
- if ((message as { destChainSelector?: bigint }).destChainSelector) {
398
- destFamily = networkInfo((message as { destChainSelector: bigint }).destChainSelector).family
399
- }
400
- // conversions to make any message version be compatible with latest v1.6
401
- message.tokenAmounts = (message.tokenAmounts as Record<string, string | bigint | number>[]).map(
402
- (tokenAmount, i) => {
403
- if ('sourceTokenData' in message) {
404
- // CCIPMessage_V1_2_EVM
405
- try {
406
- tokenAmount = {
407
- ...parseSourceTokenData(
408
- (message as { sourceTokenData: string[] }).sourceTokenData[i]!,
409
- ),
410
- ...tokenAmount,
411
- }
412
- } catch (_) {
413
- // legacy sourceTokenData
414
- }
415
- }
416
- if (typeof tokenAmount.destExecData === 'string' && tokenAmount.destGasAmount == null) {
417
- // CCIPMessage_V1_6_EVM
418
- tokenAmount.destGasAmount = toBigInt(getDataBytes(tokenAmount.destExecData))
419
- }
420
- // Can be undefined if the message is from before v1.5 and failed to parse sourceTokenData
421
- if (tokenAmount.sourcePoolAddress) {
422
- tokenAmount.sourcePoolAddress = decodeAddress(
423
- tokenAmount.sourcePoolAddress as string,
424
- sourceFamily,
425
- )
426
- }
427
- if (tokenAmount.destTokenAddress) {
428
- tokenAmount.destTokenAddress = decodeAddress(
429
- tokenAmount.destTokenAddress as string,
430
- destFamily,
431
- )
432
- }
433
- return tokenAmount
434
- },
435
- )
436
- message.sender = decodeAddress(message.sender, sourceFamily)
437
- message.feeToken = decodeAddress(message.feeToken as string, sourceFamily)
438
- message.receiver = decodeAddress(message.receiver as string, destFamily)
439
- if (message.extraArgs) {
440
- // v1.6+
441
- const parsed = this.decodeExtraArgs(message.extraArgs as string)
442
- if (!parsed) throw new CCIPExtraArgsParseError(message.extraArgs as string)
443
- const { _tag, ...rest } = parsed
444
- // merge parsed extraArgs to any family in message root object
445
- Object.assign(message, rest)
446
- } else if (message.nonce === 0n) {
447
- // v1.2..v1.5 targets EVM only; extraArgs is not explicit, gasLimit is already in
448
- // message body, allowOutOfOrderExecution (in v1.5) was present only as nonce=0
449
- message.allowOutOfOrderExecution = true
450
- }
451
- return message as CCIPMessage
426
+ return decodeMessage(message)
452
427
  }
453
428
 
454
429
  /**
@@ -456,6 +431,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
456
431
  * @param log - Log event with topics and data.
457
432
  * @param lane - Lane info (required for CCIP v1.5 and earlier).
458
433
  * @returns Array of CommitReport or undefined if not a valid commit event.
434
+ * @throws {@link CCIPLogDataInvalidError} if log data is not valid bytes
435
+ * @throws {@link CCIPVersionRequiresLaneError} if CCIP v1.5 event but no lane provided
459
436
  */
460
437
  static decodeCommits(
461
438
  log: { topics?: readonly string[]; data: unknown },
@@ -511,6 +488,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
511
488
  * Decodes an execution receipt from a log event.
512
489
  * @param log - Log event with topics and data.
513
490
  * @returns ExecutionReceipt or undefined if not a valid execution event.
491
+ * @throws {@link CCIPLogDataInvalidError} if log data is not valid bytes
514
492
  */
515
493
  static decodeReceipt(log: {
516
494
  topics?: readonly string[]
@@ -542,43 +520,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
542
520
  * @param extraArgs - Encoded extra arguments bytes.
543
521
  * @returns Decoded extra arguments with tag, or undefined if unknown format.
544
522
  */
545
- static decodeExtraArgs(
546
- extraArgs: BytesLike,
547
- ):
548
- | (EVMExtraArgsV1 & { _tag: 'EVMExtraArgsV1' })
549
- | (EVMExtraArgsV2 & { _tag: 'EVMExtraArgsV2' })
550
- | (SVMExtraArgsV1 & { _tag: 'SVMExtraArgsV1' })
551
- | (SuiExtraArgsV1 & { _tag: 'SuiExtraArgsV1' })
552
- | undefined {
553
- const data = getDataBytes(extraArgs),
554
- tag = dataSlice(data, 0, 4)
555
- switch (tag) {
556
- case EVMExtraArgsV1Tag: {
557
- const args = defaultAbiCoder.decode([EVMExtraArgsV1], dataSlice(data, 4))
558
- return { ...(resultToObject(args[0]) as EVMExtraArgsV1), _tag: 'EVMExtraArgsV1' }
559
- }
560
- case EVMExtraArgsV2Tag: {
561
- const args = defaultAbiCoder.decode([EVMExtraArgsV2], dataSlice(data, 4))
562
- return { ...(resultToObject(args[0]) as EVMExtraArgsV2), _tag: 'EVMExtraArgsV2' }
563
- }
564
- case SVMExtraArgsV1Tag: {
565
- const args = defaultAbiCoder.decode([SVMExtraArgsV1], dataSlice(data, 4))
566
- const parsed = resultToObject(args[0]) as SVMExtraArgsV1
567
- parsed.tokenReceiver = encodeBase58(parsed.tokenReceiver)
568
- parsed.accounts = parsed.accounts.map((a: string) => encodeBase58(a))
569
- return { ...parsed, _tag: 'SVMExtraArgsV1' }
570
- }
571
- case SuiExtraArgsV1Tag: {
572
- const args = defaultAbiCoder.decode([SuiExtraArgsV1], dataSlice(data, 4))
573
- const parsed = resultToObject(args[0]) as SuiExtraArgsV1
574
- return {
575
- ...parsed,
576
- _tag: 'SuiExtraArgsV1',
577
- }
578
- }
579
- default:
580
- return undefined
581
- }
523
+ static decodeExtraArgs(extraArgs: BytesLike) {
524
+ return decodeExtraArgs_(extraArgs)
582
525
  }
583
526
 
584
527
  /**
@@ -587,48 +530,14 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
587
530
  * @returns Encoded extra arguments as hex string.
588
531
  */
589
532
  static encodeExtraArgs(args: ExtraArgs | undefined): string {
590
- if (!args) return '0x'
591
- if ('computeUnits' in args) {
592
- return concat([
593
- SVMExtraArgsV1Tag,
594
- defaultAbiCoder.encode(
595
- [SVMExtraArgsV1],
596
- [
597
- {
598
- ...args,
599
- tokenReceiver: getAddressBytes(args.tokenReceiver),
600
- accounts: args.accounts.map((a) => getAddressBytes(a)),
601
- },
602
- ],
603
- ),
604
- ])
605
- } else if ('receiverObjectIds' in args) {
606
- return concat([
607
- SuiExtraArgsV1Tag,
608
- defaultAbiCoder.encode(
609
- [SuiExtraArgsV1],
610
- [
611
- {
612
- ...args,
613
- tokenReceiver: zeroPadValue(getAddressBytes(args.tokenReceiver), 32),
614
- receiverObjectIds: args.receiverObjectIds.map((a) => getDataBytes(a)),
615
- },
616
- ],
617
- ),
618
- ])
619
- } else if ('allowOutOfOrderExecution' in args) {
620
- if ((args as Partial<typeof args>).gasLimit == null) args.gasLimit = DEFAULT_GAS_LIMIT
621
- return concat([EVMExtraArgsV2Tag, defaultAbiCoder.encode([EVMExtraArgsV2], [args])])
622
- } else if ((args as Partial<typeof args>).gasLimit != null) {
623
- return concat([EVMExtraArgsV1Tag, defaultAbiCoder.encode([EVMExtraArgsV1], [args])])
624
- }
625
- return '0x'
533
+ return encodeExtraArgs_(args)
626
534
  }
627
535
 
628
536
  /**
629
537
  * Converts bytes to a checksummed EVM address.
630
538
  * @param bytes - Bytes to convert (must be 20 bytes or 32 bytes with leading zeros).
631
539
  * @returns Checksummed EVM address.
540
+ * @throws {@link CCIPAddressInvalidEvmError} if bytes cannot be converted to a valid EVM address
632
541
  */
633
542
  static getAddress(bytes: BytesLike): string {
634
543
  if (isHexString(bytes, 20)) return getAddress(bytes)
@@ -655,6 +564,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
655
564
  * Gets lane configuration from an OnRamp contract.
656
565
  * @param onRamp - OnRamp contract address.
657
566
  * @returns Lane configuration.
567
+ * @throws {@link CCIPContractTypeInvalidError} if contract doesn't have destChainSelector
658
568
  */
659
569
  async getLaneForOnRamp(onRamp: string): Promise<Lane> {
660
570
  const [, version] = await this.typeAndVersion(onRamp)
@@ -674,7 +584,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
674
584
  }
675
585
  }
676
586
 
677
- /** {@inheritDoc Chain.getRouterForOnRamp} */
587
+ /**
588
+ * {@inheritDoc Chain.getRouterForOnRamp}
589
+ * @throws {@link CCIPVersionUnsupportedError} if OnRamp version is not supported
590
+ */
678
591
  async getRouterForOnRamp(onRamp: string, destChainSelector: bigint): Promise<string> {
679
592
  const [, version] = await this.typeAndVersion(onRamp)
680
593
  let onRampABI
@@ -691,19 +604,32 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
691
604
  return router as string
692
605
  }
693
606
  case CCIPVersion.V1_6: {
694
- onRampABI = OnRamp_1_6_ABI
695
- const contract = new Contract(onRamp, onRampABI, this.provider) as unknown as TypedContract<
696
- typeof onRampABI
697
- >
607
+ const contract = new Contract(
608
+ onRamp,
609
+ interfaces.OnRamp_v1_6,
610
+ this.provider,
611
+ ) as unknown as TypedContract<typeof OnRamp_1_6_ABI>
698
612
  const [, , router] = await contract.getDestChainConfig(destChainSelector)
699
613
  return router as string
700
614
  }
615
+ case CCIPVersion.V2_0: {
616
+ const contract = new Contract(
617
+ onRamp,
618
+ interfaces.OnRamp_v2_0,
619
+ this.provider,
620
+ ) as unknown as TypedContract<typeof OnRamp_2_0_ABI>
621
+ const { router } = await contract.getDestChainConfig(destChainSelector)
622
+ return router as string
623
+ }
701
624
  default:
702
625
  throw new CCIPVersionUnsupportedError(version)
703
626
  }
704
627
  }
705
628
 
706
- /** {@inheritDoc Chain.getRouterForOffRamp} */
629
+ /**
630
+ * {@inheritDoc Chain.getRouterForOffRamp}
631
+ * @throws {@link CCIPVersionUnsupportedError} if OffRamp version is not supported
632
+ */
707
633
  async getRouterForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
708
634
  const [, version] = await this.typeAndVersion(offRamp)
709
635
  let offRampABI, router
@@ -721,8 +647,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
721
647
  ;({ router } = await contract.getDynamicConfig())
722
648
  break
723
649
  }
724
- case CCIPVersion.V1_6: {
650
+ case CCIPVersion.V1_6:
725
651
  offRampABI = OffRamp_1_6_ABI
652
+ // falls through
653
+ case CCIPVersion.V2_0: {
654
+ offRampABI = OffRamp_2_0_ABI
726
655
  const contract = new Contract(
727
656
  offRamp,
728
657
  offRampABI,
@@ -770,8 +699,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
770
699
  return contract.getOnRamp(destChainSelector) as Promise<string>
771
700
  }
772
701
 
773
- /** {@inheritDoc Chain.getOnRampForOffRamp} */
774
- async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
702
+ /**
703
+ * {@inheritDoc Chain.getOnRampsForOffRamp}
704
+ * @throws {@link CCIPVersionUnsupportedError} if OffRamp version is not supported
705
+ */
706
+ async getOnRampsForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string[]> {
775
707
  const [, version] = await this.typeAndVersion(offRamp)
776
708
  let offRampABI
777
709
  switch (version) {
@@ -786,7 +718,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
786
718
  this.provider,
787
719
  ) as unknown as TypedContract<typeof offRampABI>
788
720
  const { onRamp } = await contract.getStaticConfig()
789
- return onRamp as string
721
+ return [onRamp as string]
790
722
  }
791
723
  case CCIPVersion.V1_6: {
792
724
  offRampABI = OffRamp_1_6_ABI
@@ -796,14 +728,40 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
796
728
  this.provider,
797
729
  ) as unknown as TypedContract<typeof offRampABI>
798
730
  const { onRamp } = await contract.getSourceChainConfig(sourceChainSelector)
799
- return decodeOnRampAddress(onRamp, networkInfo(sourceChainSelector).family)
731
+ if (!onRamp || onRamp.match(/^(0x)?0*$/i)) return []
732
+ return [decodeOnRampAddress(onRamp, networkInfo(sourceChainSelector).family)]
733
+ }
734
+ case CCIPVersion.V2_0: {
735
+ offRampABI = OffRamp_2_0_ABI
736
+ const contract = new Contract(
737
+ offRamp,
738
+ offRampABI,
739
+ this.provider,
740
+ ) as unknown as TypedContract<typeof offRampABI>
741
+ const { onRamps } = await contract.getSourceChainConfig(sourceChainSelector)
742
+ const sourceFamily = networkInfo(sourceChainSelector).family
743
+ return onRamps.map((onRamp) => decodeOnRampAddress(onRamp, sourceFamily))
800
744
  }
801
745
  default:
802
746
  throw new CCIPVersionUnsupportedError(version)
803
747
  }
804
748
  }
805
749
 
806
- /** {@inheritDoc Chain.getCommitStoreForOffRamp} */
750
+ /**
751
+ * Fetch the CommitStore set in OffRamp config (CCIP v1.5 and earlier).
752
+ * For CCIP v1.6 and later, it should return the offRamp address.
753
+ *
754
+ * @param offRamp - OffRamp contract address
755
+ * @returns Promise resolving to CommitStore address
756
+ *
757
+ * @example Get commit store
758
+ * ```typescript
759
+ * const commitStore = await dest.getCommitStoreForOffRamp(offRampAddress)
760
+ * // For v1.6+, commitStore === offRampAddress
761
+ * ```
762
+ * @throws {@link CCIPVersionUnsupportedError} if OffRamp version is not supported
763
+ * @internal
764
+ */
807
765
  async getCommitStoreForOffRamp(offRamp: string): Promise<string> {
808
766
  const [, version] = await this.typeAndVersion(offRamp)
809
767
  let offRampABI
@@ -821,11 +779,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
821
779
  const { commitStore } = await contract.getStaticConfig()
822
780
  return commitStore as string
823
781
  }
824
- case CCIPVersion.V1_6: {
825
- return offRamp
826
- }
827
782
  default:
828
- throw new CCIPVersionUnsupportedError(version)
783
+ return offRamp
829
784
  }
830
785
  }
831
786
 
@@ -875,6 +830,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
875
830
  * @param lane - Lane configuration.
876
831
  * @param ctx - Context object containing logger.
877
832
  * @returns Leaf hasher function.
833
+ * @throws {@link CCIPSourceChainUnsupportedError} if source chain is not EVM for v1.2/v1.5
834
+ * @throws {@link CCIPHasherVersionUnsupportedError} if lane version is not supported
878
835
  */
879
836
  static getDestLeafHasher(
880
837
  { sourceChainSelector, destChainSelector, onRamp, version }: Lane,
@@ -911,7 +868,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
911
868
  return this.getOnRampForRouter(router, networkInfo(someOtherNetwork).chainSelector)
912
869
  }
913
870
 
914
- /** {@inheritDoc Chain.getTokenAdminRegistryFor} */
871
+ /**
872
+ * {@inheritDoc Chain.getTokenAdminRegistryFor}
873
+ * @throws {@link CCIPContractNotRouterError} if address is not a Router, OnRamp, or OffRamp
874
+ */
915
875
  async getTokenAdminRegistryFor(address: string): Promise<string> {
916
876
  let [type, version, typeAndVersion] = await this.typeAndVersion(address)
917
877
  if (type === 'TokenAdminRegistry') {
@@ -942,6 +902,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
942
902
  * @internal
943
903
  * @param address - Router or Ramp contract address.
944
904
  * @returns FeeQuoter contract address.
905
+ * @throws {@link CCIPContractNotRouterError} if address is not a Router, OnRamp, or OffRamp
906
+ * @throws {@link CCIPVersionFeatureUnavailableError} if contract version is below v1.6
945
907
  */
946
908
  async getFeeQuoterFor(address: string): Promise<string> {
947
909
  let [type, version, typeAndVersion] = await this.typeAndVersion(address)
@@ -1064,7 +1026,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1064
1026
  }
1065
1027
  }
1066
1028
 
1067
- /** {@inheritDoc Chain.sendMessage} */
1029
+ /**
1030
+ * {@inheritDoc Chain.sendMessage}
1031
+ * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Signer
1032
+ */
1068
1033
  async sendMessage(opts: Parameters<Chain['sendMessage']>[0]): Promise<CCIPRequest> {
1069
1034
  const wallet = opts.wallet
1070
1035
  if (!isSigner(wallet)) throw new CCIPWalletInvalidError(wallet)
@@ -1075,27 +1040,37 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1075
1040
  let sendTx: TransactionRequest = txs.transactions[txs.transactions.length - 1]!
1076
1041
 
1077
1042
  // approve all tokens (including feeToken, if needed) in parallel
1078
- let nonce = await this.provider.getTransactionCount(sender)
1079
1043
  const responses = await Promise.all(
1080
1044
  approveTxs.map(async (tx: TransactionRequest) => {
1081
- tx.nonce = nonce++
1082
- tx = await wallet.populateTransaction(tx)
1083
- tx.from = undefined
1084
- const response = await submitTransaction(wallet, tx, this.provider)
1085
- this.logger.debug('approve =>', response.hash)
1086
- return response
1045
+ tx.nonce = await this.nextNonce(sender)
1046
+ try {
1047
+ tx = await wallet.populateTransaction(tx)
1048
+ tx.from = undefined
1049
+ const response = await submitTransaction(wallet, tx, this.provider)
1050
+ this.logger.debug('approve =>', response.hash)
1051
+ return response
1052
+ } catch (err) {
1053
+ this.nonces[sender]!--
1054
+ throw err
1055
+ }
1087
1056
  }),
1088
1057
  )
1089
1058
  if (responses.length) await responses[responses.length - 1]!.wait(1, 60_000) // wait last tx nonce to be mined
1090
1059
 
1091
- sendTx.nonce = nonce++
1092
- // sendTx.gasLimit = await this.provider.estimateGas(sendTx)
1093
- sendTx = await wallet.populateTransaction(sendTx)
1094
- sendTx.from = undefined // some signers don't like receiving pre-populated `from`
1095
- const response = await submitTransaction(wallet, sendTx, this.provider)
1060
+ sendTx.nonce = await this.nextNonce(sender)
1061
+ let response
1062
+ try {
1063
+ // sendTx.gasLimit = await this.provider.estimateGas(sendTx)
1064
+ sendTx = await wallet.populateTransaction(sendTx)
1065
+ sendTx.from = undefined // some signers don't like receiving pre-populated `from`
1066
+ response = await submitTransaction(wallet, sendTx, this.provider)
1067
+ } catch (err) {
1068
+ this.nonces[sender]!--
1069
+ throw err
1070
+ }
1096
1071
  this.logger.debug('ccipSend =>', response.hash)
1097
- await response.wait(1, 60_000)
1098
- return (await this.getMessagesInTx(await this.getTransaction(response.hash)))[0]!
1072
+ const tx = (await response.wait(1, 60_000))!
1073
+ return (await this.getMessagesInTx(await this.getTransaction(tx)))[0]!
1099
1074
  }
1100
1075
 
1101
1076
  /** {@inheritDoc Chain.getOffchainTokenData} */
@@ -1104,32 +1079,70 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1104
1079
  }
1105
1080
 
1106
1081
  /**
1107
- * {@inheritDoc Chain.generateUnsignedExecuteReport}
1082
+ * {@inheritDoc Chain.generateUnsignedExecute}
1108
1083
  * @returns array containing one unsigned `manuallyExecute` TransactionRequest object
1084
+ * @throws {@link CCIPVersionUnsupportedError} if OffRamp version is not supported
1109
1085
  */
1110
- async generateUnsignedExecuteReport({
1111
- offRamp,
1112
- execReport,
1113
- ...opts
1114
- }: Parameters<Chain['generateUnsignedExecuteReport']>[0]): Promise<UnsignedEVMTx> {
1115
- const [_, version] = await this.typeAndVersion(offRamp)
1086
+ async generateUnsignedExecute(
1087
+ opts: Parameters<Chain['generateUnsignedExecute']>[0],
1088
+ ): Promise<UnsignedEVMTx> {
1089
+ let input: ExecutionInput, offRamp: string
1090
+ if (!('input' in opts)) {
1091
+ if (!this.apiClient) throw new CCIPApiClientNotAvailableError()
1092
+ ;({ offRamp, ...input } = await this.apiClient.getExecutionInput(opts.messageId))
1093
+ } else {
1094
+ ;({ offRamp, input } = opts)
1095
+ }
1096
+ if ('verifications' in input) {
1097
+ const contract = new Contract(
1098
+ offRamp,
1099
+ interfaces.OffRamp_v2_0,
1100
+ this.provider,
1101
+ ) as unknown as TypedContract<typeof OffRamp_2_0_ABI>
1102
+
1103
+ const message = decodeMessageV1(input.encodedMessage)
1104
+ const messageId = keccak256(input.encodedMessage)
1105
+ // `execute` doesn't revert on failure, so we need to estimate using `executeSingleMessage`
1106
+ const txGasLimit = await contract.executeSingleMessage.estimateGas(
1107
+ {
1108
+ ...message,
1109
+ executionGasLimit: BigInt(message.executionGasLimit),
1110
+ ccipReceiveGasLimit: BigInt(message.ccipReceiveGasLimit),
1111
+ finality: BigInt(message.finality),
1112
+ },
1113
+ messageId,
1114
+ input.verifications.map(({ destAddress }) => destAddress),
1115
+ input.verifications.map(({ ccvData }) => hexlify(ccvData)),
1116
+ BigInt(opts.gasLimit ?? 0),
1117
+ { from: offRamp }, // internal method
1118
+ )
1119
+ const execTx = await contract.execute.populateTransaction(
1120
+ input.encodedMessage,
1121
+ input.verifications.map(({ destAddress }) => destAddress),
1122
+ input.verifications.map(({ ccvData }) => hexlify(ccvData)),
1123
+ BigInt(opts.gasLimit ?? 0),
1124
+ )
1125
+ execTx.gasLimit = txGasLimit + 40000n // plus `execute`'s overhead
1126
+ return { family: ChainFamily.EVM, transactions: [execTx] }
1127
+ }
1116
1128
 
1117
1129
  let manualExecTx
1118
- const offchainTokenData = execReport.offchainTokenData.map(encodeEVMOffchainTokenData)
1130
+ const [_, version] = await this.typeAndVersion(offRamp)
1131
+ const offchainTokenData = input.offchainTokenData.map(encodeEVMOffchainTokenData)
1119
1132
 
1120
1133
  switch (version) {
1121
1134
  case CCIPVersion.V1_2: {
1122
1135
  const contract = new Contract(
1123
1136
  offRamp,
1124
- EVM2EVMOffRamp_1_2_ABI,
1137
+ interfaces.EVM2EVMOffRamp_v1_2,
1125
1138
  this.provider,
1126
1139
  ) as unknown as TypedContract<typeof EVM2EVMOffRamp_1_2_ABI>
1127
1140
  const gasOverride = BigInt(opts.gasLimit ?? 0)
1128
1141
  manualExecTx = await contract.manuallyExecute.populateTransaction(
1129
1142
  {
1130
- ...execReport,
1131
- proofs: execReport.proofs.map((d) => hexlify(d)),
1132
- messages: [execReport.message as CCIPMessage<typeof CCIPVersion.V1_2>],
1143
+ ...input,
1144
+ proofs: input.proofs.map((d) => hexlify(d)),
1145
+ messages: [input.message as CCIPMessage<typeof CCIPVersion.V1_2>],
1133
1146
  offchainTokenData: [offchainTokenData],
1134
1147
  },
1135
1148
  [gasOverride],
@@ -1139,20 +1152,20 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1139
1152
  case CCIPVersion.V1_5: {
1140
1153
  const contract = new Contract(
1141
1154
  offRamp,
1142
- EVM2EVMOffRamp_1_5_ABI,
1155
+ interfaces.EVM2EVMOffRamp_v1_5,
1143
1156
  this.provider,
1144
1157
  ) as unknown as TypedContract<typeof EVM2EVMOffRamp_1_5_ABI>
1145
1158
  manualExecTx = await contract.manuallyExecute.populateTransaction(
1146
1159
  {
1147
- ...execReport,
1148
- proofs: execReport.proofs.map((d) => hexlify(d)),
1149
- messages: [execReport.message as CCIPMessage<typeof CCIPVersion.V1_5>],
1160
+ ...input,
1161
+ proofs: input.proofs.map((d) => hexlify(d)),
1162
+ messages: [input.message as CCIPMessage<typeof CCIPVersion.V1_5>],
1150
1163
  offchainTokenData: [offchainTokenData],
1151
1164
  },
1152
1165
  [
1153
1166
  {
1154
1167
  receiverExecutionGasLimit: BigInt(opts.gasLimit ?? 0),
1155
- tokenGasOverrides: execReport.message.tokenAmounts.map(() =>
1168
+ tokenGasOverrides: input.message.tokenAmounts.map(() =>
1156
1169
  BigInt(opts.tokensGasLimit ?? opts.gasLimit ?? 0),
1157
1170
  ),
1158
1171
  },
@@ -1162,30 +1175,32 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1162
1175
  }
1163
1176
  case CCIPVersion.V1_6: {
1164
1177
  // normalize message
1165
- const sender = zeroPadValue(getAddressBytes(execReport.message.sender), 32)
1166
- const tokenAmounts = (execReport.message as CCIPMessage_V1_6_EVM).tokenAmounts.map(
1167
- (ta) => ({
1168
- ...ta,
1169
- sourcePoolAddress: zeroPadValue(getAddressBytes(ta.sourcePoolAddress), 32),
1170
- extraData: hexlify(getDataBytes(ta.extraData)),
1171
- }),
1172
- )
1178
+ const senderBytes = getAddressBytes(input.message.sender)
1179
+ // Addresses ≤32 bytes (EVM 20B, Aptos/Solana/Sui 32B) are zero-padded to 32 bytes;
1180
+ // Addresses >32 bytes (e.g., TON 36B) are used as raw bytes without padding
1181
+ const sender =
1182
+ senderBytes.length <= 32 ? zeroPadValue(senderBytes, 32) : hexlify(senderBytes)
1183
+ const tokenAmounts = (input.message as CCIPMessage_V1_6_EVM).tokenAmounts.map((ta) => ({
1184
+ ...ta,
1185
+ sourcePoolAddress: zeroPadValue(getAddressBytes(ta.sourcePoolAddress), 32),
1186
+ extraData: hexlify(getDataBytes(ta.extraData)),
1187
+ }))
1173
1188
  const message = {
1174
- ...(execReport.message as CCIPMessage_V1_6_EVM),
1189
+ ...(input.message as CCIPMessage_V1_6_EVM),
1175
1190
  sender,
1176
1191
  tokenAmounts,
1177
1192
  }
1178
1193
  const contract = new Contract(
1179
1194
  offRamp,
1180
- OffRamp_1_6_ABI,
1195
+ interfaces.OffRamp_v1_6,
1181
1196
  this.provider,
1182
1197
  ) as unknown as TypedContract<typeof OffRamp_1_6_ABI>
1183
1198
  manualExecTx = await contract.manuallyExecute.populateTransaction(
1184
1199
  [
1185
1200
  {
1186
- ...execReport,
1187
- proofs: execReport.proofs.map((p) => hexlify(p)),
1188
- sourceChainSelector: execReport.message.sourceChainSelector,
1201
+ ...input,
1202
+ proofs: input.proofs.map((p) => hexlify(p)),
1203
+ sourceChainSelector: input.message.sourceChainSelector,
1189
1204
  messages: [
1190
1205
  {
1191
1206
  ...message,
@@ -1205,7 +1220,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1205
1220
  [
1206
1221
  {
1207
1222
  receiverExecutionGasLimit: BigInt(opts.gasLimit ?? 0),
1208
- tokenGasOverrides: execReport.message.tokenAmounts.map(() =>
1223
+ tokenGasOverrides: input.message.tokenAmounts.map(() =>
1209
1224
  BigInt(opts.tokensGasLimit ?? opts.gasLimit ?? 0),
1210
1225
  ),
1211
1226
  },
@@ -1220,19 +1235,29 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1220
1235
  return { family: ChainFamily.EVM, transactions: [manualExecTx] }
1221
1236
  }
1222
1237
 
1223
- /** {@inheritDoc Chain.executeReport} */
1224
- async executeReport(opts: Parameters<Chain['executeReport']>[0]) {
1238
+ /**
1239
+ * {@inheritDoc Chain.execute}
1240
+ * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Signer
1241
+ * @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm
1242
+ * @throws {@link CCIPExecTxRevertedError} if execution transaction reverts
1243
+ */
1244
+ async execute(opts: Parameters<Chain['execute']>[0]) {
1225
1245
  const wallet = opts.wallet
1226
1246
  if (!isSigner(wallet)) throw new CCIPWalletInvalidError(wallet)
1227
1247
 
1228
- const unsignedTxs = await this.generateUnsignedExecuteReport({
1248
+ const unsignedTxs = await this.generateUnsignedExecute({
1229
1249
  ...opts,
1230
1250
  payer: await wallet.getAddress(),
1231
1251
  })
1232
- const unsignedTx = await wallet.populateTransaction(unsignedTxs.transactions[0]!)
1233
- unsignedTx.from = undefined // some signers don't like receiving pre-populated `from`
1234
- const response = await submitTransaction(wallet, unsignedTx, this.provider)
1252
+
1253
+ const unsignedTx: TransactionRequest = unsignedTxs.transactions[0]!
1254
+ unsignedTx.nonce = await this.nextNonce(await wallet.getAddress())
1255
+ const populatedTx = await wallet.populateTransaction(unsignedTx)
1256
+ populatedTx.from = undefined // some signers don't like receiving pre-populated `from`
1257
+
1258
+ const response = await submitTransaction(wallet, populatedTx, this.provider)
1235
1259
  this.logger.debug('manuallyExecute =>', response.hash)
1260
+
1236
1261
  const receipt = await response.wait(1, 60_000)
1237
1262
  if (!receipt?.hash) throw new CCIPExecTxNotConfirmedError(response.hash)
1238
1263
  if (!receipt.status) throw new CCIPExecTxRevertedError(response.hash)
@@ -1272,7 +1297,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1272
1297
  return res as string[]
1273
1298
  }
1274
1299
 
1275
- /** {@inheritDoc Chain.getRegistryTokenConfig} */
1300
+ /**
1301
+ * {@inheritDoc Chain.getRegistryTokenConfig}
1302
+ * @throws {@link CCIPTokenNotConfiguredError} if token is not configured in registry
1303
+ */
1276
1304
  async getRegistryTokenConfig(
1277
1305
  registry: string,
1278
1306
  token: string,
@@ -1301,8 +1329,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1301
1329
  }
1302
1330
  }
1303
1331
 
1304
- /** {@inheritDoc Chain.getTokenPoolConfigs} */
1305
- async getTokenPoolConfigs(tokenPool: string): Promise<{
1332
+ /** {@inheritDoc Chain.getTokenPoolConfig} */
1333
+ async getTokenPoolConfig(tokenPool: string): Promise<{
1306
1334
  token: string
1307
1335
  router: string
1308
1336
  typeAndVersion: string
@@ -1387,23 +1415,28 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1387
1415
  return Promise.all([supportedChains, remotePools, remoteInfo]).then(
1388
1416
  ([supportedChains, remotePools, remoteInfo]) =>
1389
1417
  Object.fromEntries(
1390
- supportedChains.map(
1391
- (chain, i) =>
1392
- [
1393
- chain.name,
1394
- {
1395
- remoteToken: decodeAddress(remoteInfo[i]![0], chain.family),
1396
- remotePools: remotePools[i]!.map((pool) => decodeAddress(pool, chain.family)),
1397
- inboundRateLimiterState: remoteInfo[i]![1].isEnabled ? remoteInfo[i]![1] : null,
1398
- outboundRateLimiterState: remoteInfo[i]![2].isEnabled ? remoteInfo[i]![2] : null,
1399
- },
1400
- ] as const,
1401
- ),
1418
+ supportedChains.map((chain, i) => {
1419
+ const remoteTokenRaw = remoteInfo[i]![0]
1420
+ if (!remoteTokenRaw || remoteTokenRaw.match(/^(0x)?0*$/))
1421
+ throw new CCIPTokenPoolChainConfigNotFoundError(tokenPool, tokenPool, chain.name)
1422
+ return [
1423
+ chain.name,
1424
+ {
1425
+ remoteToken: decodeAddress(remoteTokenRaw, chain.family),
1426
+ remotePools: remotePools[i]!.map((pool) => decodeAddress(pool, chain.family)),
1427
+ inboundRateLimiterState: remoteInfo[i]![1].isEnabled ? remoteInfo[i]![1] : null,
1428
+ outboundRateLimiterState: remoteInfo[i]![2].isEnabled ? remoteInfo[i]![2] : null,
1429
+ },
1430
+ ] as const
1431
+ }),
1402
1432
  ),
1403
1433
  )
1404
1434
  }
1405
1435
 
1406
- /** {@inheritDoc Chain.getFeeTokens} */
1436
+ /**
1437
+ * {@inheritDoc Chain.getFeeTokens}
1438
+ * @throws {@link CCIPVersionUnsupportedError} if OnRamp version is not supported
1439
+ */
1407
1440
  async getFeeTokens(router: string) {
1408
1441
  const onRamp = await this._getSomeOnRampFor(router)
1409
1442
  const [_, version] = await this.typeAndVersion(onRamp)
@@ -1456,6 +1489,64 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1456
1489
  )
1457
1490
  }
1458
1491
 
1492
+ /** {@inheritDoc Chain.getVerifications} */
1493
+ override async getVerifications(
1494
+ opts: Parameters<Chain['getVerifications']>[0],
1495
+ ): Promise<CCIPVerifications> {
1496
+ const { offRamp, request } = opts
1497
+ if (request.lane.version >= CCIPVersion.V2_0) {
1498
+ const message = request.message as CCIPMessage_V2_0
1499
+ if (!message.encodedMessage)
1500
+ throw new CCIPNotImplementedError(`CCIPAPIClient getMessageById v2 encodedMessage`)
1501
+ const contract = new Contract(
1502
+ offRamp,
1503
+ interfaces.OffRamp_v2_0,
1504
+ this.provider,
1505
+ ) as unknown as TypedContract<typeof OffRamp_2_0_ABI>
1506
+ const ccvs = await contract.getCCVsForMessage(message.encodedMessage)
1507
+ const [requiredCCVs, optionalCCVs, optionalThreshold] = ccvs.map(
1508
+ resultToObject,
1509
+ ) as unknown as CleanAddressable<typeof ccvs>
1510
+ const verificationPolicy = {
1511
+ requiredCCVs,
1512
+ optionalCCVs,
1513
+ optionalThreshold: Number(optionalThreshold),
1514
+ }
1515
+
1516
+ if (this.apiClient) {
1517
+ const apiRes = await this.apiClient.getMessageById(request.message.messageId)
1518
+ if ('verifiers' in apiRes.message) {
1519
+ const verifiers = apiRes.message.verifiers as {
1520
+ items: {
1521
+ destAddress: string
1522
+ sourceAddress: string
1523
+ verification: { data: string; timestamp: string }
1524
+ }[]
1525
+ }
1526
+ return {
1527
+ verificationPolicy,
1528
+ verifications: verifiers.items.map((item) => ({
1529
+ destAddress: item.destAddress,
1530
+ sourceAddress: item.sourceAddress,
1531
+ ccvData: item.verification.data,
1532
+ timestamp: new Date(item.verification.timestamp).getTime() / 1e3,
1533
+ })),
1534
+ }
1535
+ }
1536
+ }
1537
+
1538
+ const url = `${CCV_INDEXER_URL}/v1/verifierresults/${request.message.messageId}`
1539
+ const res = await fetch(url)
1540
+ const json = await res.json()
1541
+ return json as CCIPVerifications
1542
+ } else if (request.lane.version < CCIPVersion.V1_6) {
1543
+ // v1.2..v1.5 EVM (only) have separate CommitStore
1544
+ opts.offRamp = await this.getCommitStoreForOffRamp(opts.offRamp)
1545
+ }
1546
+ // fallback <=v1.6
1547
+ return super.getVerifications(opts)
1548
+ }
1549
+
1459
1550
  /** {@inheritDoc Chain.getExecutionReceipts} */
1460
1551
  override async *getExecutionReceipts(
1461
1552
  opts: Parameters<Chain['getExecutionReceipts']>[0],
@@ -1475,10 +1566,14 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1475
1566
  // onlyFallback: false,
1476
1567
  }
1477
1568
  } else /* >= V1.6 */ {
1569
+ const topicHash =
1570
+ version === CCIPVersion.V1_6
1571
+ ? interfaces.OffRamp_v1_6.getEvent('ExecutionStateChanged')!.topicHash
1572
+ : interfaces.OffRamp_v2_0.getEvent('ExecutionStateChanged')!.topicHash
1478
1573
  opts_ = {
1479
1574
  ...opts,
1480
1575
  topics: [
1481
- interfaces.OffRamp_v1_6.getEvent('ExecutionStateChanged')!.topicHash,
1576
+ topicHash,
1482
1577
  sourceChainSelector ? toBeHex(sourceChainSelector, 32) : null,
1483
1578
  null,
1484
1579
  messageId ?? null,