@chainlink/ccip-sdk 0.90.2 → 0.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +35 -26
  2. package/dist/aptos/exec.d.ts +4 -5
  3. package/dist/aptos/exec.d.ts.map +1 -1
  4. package/dist/aptos/exec.js +5 -14
  5. package/dist/aptos/exec.js.map +1 -1
  6. package/dist/aptos/hasher.d.ts +18 -0
  7. package/dist/aptos/hasher.d.ts.map +1 -1
  8. package/dist/aptos/hasher.js +18 -0
  9. package/dist/aptos/hasher.js.map +1 -1
  10. package/dist/aptos/index.d.ts +127 -28
  11. package/dist/aptos/index.d.ts.map +1 -1
  12. package/dist/aptos/index.js +199 -70
  13. package/dist/aptos/index.js.map +1 -1
  14. package/dist/aptos/logs.d.ts +18 -0
  15. package/dist/aptos/logs.d.ts.map +1 -1
  16. package/dist/aptos/logs.js +21 -3
  17. package/dist/aptos/logs.js.map +1 -1
  18. package/dist/aptos/send.d.ts +22 -5
  19. package/dist/aptos/send.d.ts.map +1 -1
  20. package/dist/aptos/send.js +23 -15
  21. package/dist/aptos/send.js.map +1 -1
  22. package/dist/aptos/token.d.ts +6 -0
  23. package/dist/aptos/token.d.ts.map +1 -1
  24. package/dist/aptos/token.js +6 -0
  25. package/dist/aptos/token.js.map +1 -1
  26. package/dist/aptos/types.d.ts +16 -1
  27. package/dist/aptos/types.d.ts.map +1 -1
  28. package/dist/aptos/types.js +13 -0
  29. package/dist/aptos/types.js.map +1 -1
  30. package/dist/aptos/utils.d.ts +1 -1
  31. package/dist/aptos/utils.js +1 -1
  32. package/dist/chain.d.ts +185 -99
  33. package/dist/chain.d.ts.map +1 -1
  34. package/dist/chain.js +38 -15
  35. package/dist/chain.js.map +1 -1
  36. package/dist/commits.d.ts +4 -10
  37. package/dist/commits.d.ts.map +1 -1
  38. package/dist/commits.js +2 -1
  39. package/dist/commits.js.map +1 -1
  40. package/dist/evm/const.d.ts +5 -0
  41. package/dist/evm/const.d.ts.map +1 -1
  42. package/dist/evm/const.js +5 -0
  43. package/dist/evm/const.js.map +1 -1
  44. package/dist/evm/errors.d.ts +5 -0
  45. package/dist/evm/errors.d.ts.map +1 -1
  46. package/dist/evm/errors.js +6 -1
  47. package/dist/evm/errors.js.map +1 -1
  48. package/dist/evm/hasher.d.ts +16 -2
  49. package/dist/evm/hasher.d.ts.map +1 -1
  50. package/dist/evm/hasher.js +17 -3
  51. package/dist/evm/hasher.js.map +1 -1
  52. package/dist/evm/index.d.ts +176 -31
  53. package/dist/evm/index.d.ts.map +1 -1
  54. package/dist/evm/index.js +312 -154
  55. package/dist/evm/index.js.map +1 -1
  56. package/dist/evm/logs.d.ts +20 -0
  57. package/dist/evm/logs.d.ts.map +1 -0
  58. package/dist/evm/logs.js +194 -0
  59. package/dist/evm/logs.js.map +1 -0
  60. package/dist/evm/messages.d.ts +11 -2
  61. package/dist/evm/messages.d.ts.map +1 -1
  62. package/dist/evm/messages.js +4 -2
  63. package/dist/evm/messages.js.map +1 -1
  64. package/dist/evm/offchain.d.ts +7 -2
  65. package/dist/evm/offchain.d.ts.map +1 -1
  66. package/dist/evm/offchain.js +12 -7
  67. package/dist/evm/offchain.js.map +1 -1
  68. package/dist/execution.d.ts +19 -62
  69. package/dist/execution.d.ts.map +1 -1
  70. package/dist/execution.js +28 -31
  71. package/dist/execution.js.map +1 -1
  72. package/dist/extra-args.d.ts +35 -5
  73. package/dist/extra-args.d.ts.map +1 -1
  74. package/dist/extra-args.js +10 -5
  75. package/dist/extra-args.js.map +1 -1
  76. package/dist/gas.d.ts +6 -8
  77. package/dist/gas.d.ts.map +1 -1
  78. package/dist/gas.js +7 -9
  79. package/dist/gas.js.map +1 -1
  80. package/dist/hasher/common.d.ts +3 -2
  81. package/dist/hasher/common.d.ts.map +1 -1
  82. package/dist/hasher/common.js +2 -2
  83. package/dist/hasher/common.js.map +1 -1
  84. package/dist/hasher/hasher.d.ts +8 -2
  85. package/dist/hasher/hasher.d.ts.map +1 -1
  86. package/dist/hasher/hasher.js +8 -3
  87. package/dist/hasher/hasher.js.map +1 -1
  88. package/dist/hasher/merklemulti.d.ts +11 -9
  89. package/dist/hasher/merklemulti.d.ts.map +1 -1
  90. package/dist/hasher/merklemulti.js +17 -16
  91. package/dist/hasher/merklemulti.js.map +1 -1
  92. package/dist/index.d.ts +16 -8
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +17 -7
  95. package/dist/index.js.map +1 -1
  96. package/dist/requests.d.ts +39 -25
  97. package/dist/requests.d.ts.map +1 -1
  98. package/dist/requests.js +42 -35
  99. package/dist/requests.js.map +1 -1
  100. package/dist/selectors.d.ts +1 -1
  101. package/dist/solana/cleanup.d.ts +14 -10
  102. package/dist/solana/cleanup.d.ts.map +1 -1
  103. package/dist/solana/cleanup.js +35 -33
  104. package/dist/solana/cleanup.js.map +1 -1
  105. package/dist/solana/exec.d.ts +19 -11
  106. package/dist/solana/exec.d.ts.map +1 -1
  107. package/dist/solana/exec.js +86 -163
  108. package/dist/solana/exec.js.map +1 -1
  109. package/dist/solana/hasher.d.ts +7 -2
  110. package/dist/solana/hasher.d.ts.map +1 -1
  111. package/dist/solana/hasher.js +7 -2
  112. package/dist/solana/hasher.js.map +1 -1
  113. package/dist/solana/index.d.ts +202 -84
  114. package/dist/solana/index.d.ts.map +1 -1
  115. package/dist/solana/index.js +367 -252
  116. package/dist/solana/index.js.map +1 -1
  117. package/dist/solana/offchain.d.ts +8 -18
  118. package/dist/solana/offchain.d.ts.map +1 -1
  119. package/dist/solana/offchain.js +29 -83
  120. package/dist/solana/offchain.js.map +1 -1
  121. package/dist/solana/patchBorsh.d.ts +5 -1
  122. package/dist/solana/patchBorsh.d.ts.map +1 -1
  123. package/dist/solana/patchBorsh.js +57 -46
  124. package/dist/solana/patchBorsh.js.map +1 -1
  125. package/dist/solana/send.d.ts +28 -10
  126. package/dist/solana/send.d.ts.map +1 -1
  127. package/dist/solana/send.js +44 -77
  128. package/dist/solana/send.js.map +1 -1
  129. package/dist/solana/types.d.ts +22 -1
  130. package/dist/solana/types.d.ts.map +1 -1
  131. package/dist/solana/types.js +12 -1
  132. package/dist/solana/types.js.map +1 -1
  133. package/dist/solana/utils.d.ts +58 -4
  134. package/dist/solana/utils.d.ts.map +1 -1
  135. package/dist/solana/utils.js +110 -7
  136. package/dist/solana/utils.js.map +1 -1
  137. package/dist/sui/hasher.d.ts +18 -0
  138. package/dist/sui/hasher.d.ts.map +1 -1
  139. package/dist/sui/hasher.js +18 -0
  140. package/dist/sui/hasher.js.map +1 -1
  141. package/dist/sui/index.d.ts +99 -12
  142. package/dist/sui/index.d.ts.map +1 -1
  143. package/dist/sui/index.js +108 -19
  144. package/dist/sui/index.js.map +1 -1
  145. package/dist/sui/types.d.ts +6 -0
  146. package/dist/sui/types.d.ts.map +1 -1
  147. package/dist/sui/types.js +5 -0
  148. package/dist/sui/types.js.map +1 -1
  149. package/dist/supported-chains.d.ts +2 -1
  150. package/dist/supported-chains.d.ts.map +1 -1
  151. package/dist/supported-chains.js.map +1 -1
  152. package/dist/types.d.ts +127 -16
  153. package/dist/types.d.ts.map +1 -1
  154. package/dist/types.js +18 -0
  155. package/dist/types.js.map +1 -1
  156. package/dist/utils.d.ts +67 -46
  157. package/dist/utils.d.ts.map +1 -1
  158. package/dist/utils.js +143 -21
  159. package/dist/utils.js.map +1 -1
  160. package/package.json +13 -9
  161. package/src/aptos/exec.ts +7 -18
  162. package/src/aptos/hasher.ts +18 -0
  163. package/src/aptos/index.ts +288 -110
  164. package/src/aptos/logs.ts +21 -3
  165. package/src/aptos/send.ts +25 -22
  166. package/src/aptos/token.ts +6 -0
  167. package/src/aptos/types.ts +26 -2
  168. package/src/aptos/utils.ts +1 -1
  169. package/src/chain.ts +243 -108
  170. package/src/commits.ts +6 -7
  171. package/src/evm/const.ts +5 -0
  172. package/src/evm/errors.ts +6 -1
  173. package/src/evm/hasher.ts +20 -4
  174. package/src/evm/index.ts +416 -214
  175. package/src/evm/logs.ts +255 -0
  176. package/src/evm/messages.ts +11 -5
  177. package/src/evm/offchain.ts +13 -4
  178. package/src/execution.ts +40 -32
  179. package/src/extra-args.ts +38 -6
  180. package/src/gas.ts +7 -9
  181. package/src/hasher/common.ts +3 -2
  182. package/src/hasher/hasher.ts +12 -4
  183. package/src/hasher/merklemulti.ts +17 -16
  184. package/src/index.ts +29 -23
  185. package/src/requests.ts +64 -46
  186. package/src/selectors.ts +1 -1
  187. package/src/solana/cleanup.ts +49 -34
  188. package/src/solana/exec.ts +128 -272
  189. package/src/solana/hasher.ts +13 -4
  190. package/src/solana/index.ts +483 -356
  191. package/src/solana/offchain.ts +32 -102
  192. package/src/solana/patchBorsh.ts +65 -50
  193. package/src/solana/send.ts +52 -111
  194. package/src/solana/types.ts +44 -3
  195. package/src/solana/utils.ts +143 -19
  196. package/src/sui/hasher.ts +18 -0
  197. package/src/sui/index.ts +143 -31
  198. package/src/sui/types.ts +6 -0
  199. package/src/supported-chains.ts +2 -1
  200. package/src/types.ts +130 -18
  201. package/src/utils.ts +168 -26
  202. package/tsconfig.json +2 -1
package/src/evm/index.ts CHANGED
@@ -1,19 +1,15 @@
1
- import util from 'util'
2
-
3
1
  import { parseAbi } from 'abitype'
4
2
  import {
5
3
  type BytesLike,
4
+ type Interface,
6
5
  type JsonRpcApiProvider,
7
6
  type Log,
8
- type Provider,
9
7
  type Signer,
10
8
  type TransactionReceipt,
11
- AbstractSigner,
12
- BaseWallet,
9
+ type TransactionRequest,
13
10
  Contract,
14
11
  JsonRpcProvider,
15
12
  Result,
16
- SigningKey,
17
13
  WebSocketProvider,
18
14
  ZeroAddress,
19
15
  concat,
@@ -24,19 +20,15 @@ import {
24
20
  hexlify,
25
21
  isBytesLike,
26
22
  isHexString,
23
+ toBeHex,
27
24
  toBigInt,
28
25
  zeroPadValue,
29
26
  } from 'ethers'
30
27
  import type { TypedContract } from 'ethers-abitype'
31
- import moize, { type Key } from 'moize'
28
+ import { memoize } from 'micro-memoize'
29
+ import type { PickDeep, SetRequired } from 'type-fest'
32
30
 
33
- import {
34
- type ChainTransaction,
35
- type LogFilter,
36
- type TokenPoolRemote,
37
- Chain,
38
- ChainFamily,
39
- } from '../chain.ts'
31
+ import { type LogFilter, type TokenPoolRemote, Chain } from '../chain.ts'
40
32
  import {
41
33
  type EVMExtraArgsV1,
42
34
  type EVMExtraArgsV2,
@@ -52,8 +44,11 @@ import type { LeafHasher } from '../hasher/common.ts'
52
44
  import { supportedChains } from '../supported-chains.ts'
53
45
  import {
54
46
  type AnyMessage,
47
+ type CCIPCommit,
48
+ type CCIPExecution,
55
49
  type CCIPMessage,
56
50
  type CCIPRequest,
51
+ type ChainTransaction,
57
52
  type CommitReport,
58
53
  type ExecutionReceipt,
59
54
  type ExecutionReport,
@@ -62,17 +57,18 @@ import {
62
57
  type Log_,
63
58
  type NetworkInfo,
64
59
  type OffchainTokenData,
60
+ type WithLogger,
65
61
  CCIPVersion,
62
+ ChainFamily,
66
63
  } from '../types.ts'
67
64
  import {
68
- blockRangeGenerator,
69
65
  decodeAddress,
70
66
  decodeOnRampAddress,
71
67
  getAddressBytes,
72
68
  getDataBytes,
73
- getSomeBlockNumberBefore,
74
69
  networkInfo,
75
70
  parseTypeAndVersion,
71
+ util,
76
72
  } from '../utils.ts'
77
73
  import type Token_ABI from './abi/BurnMintERC677Token.ts'
78
74
  import type FeeQuoter_ABI from './abi/FeeQuoter_1_6.ts'
@@ -87,23 +83,35 @@ import OnRamp_1_6_ABI from './abi/OnRamp_1_6.ts'
87
83
  import type Router_ABI from './abi/Router.ts'
88
84
  import type TokenAdminRegistry_1_5_ABI from './abi/TokenAdminRegistry_1_5.ts'
89
85
  import {
90
- DEFAULT_APPROVE_GAS_LIMIT,
91
86
  DEFAULT_GAS_LIMIT,
92
87
  commitsFragments,
93
88
  defaultAbiCoder,
94
- getAllFragmentsMatchingEvents,
95
89
  interfaces,
96
90
  receiptsFragments,
97
91
  requestsFragments,
98
92
  } from './const.ts'
99
93
  import { parseData } from './errors.ts'
100
94
  import { getV12LeafHasher, getV16LeafHasher } from './hasher.ts'
95
+ import { getEvmLogs } from './logs.ts'
101
96
  import {
102
97
  type CCIPMessage_V1_6_EVM,
103
98
  type CleanAddressable,
104
99
  parseSourceTokenData,
105
100
  } from './messages.ts'
106
101
  import { encodeEVMOffchainTokenData, fetchEVMOffchainTokenData } from './offchain.ts'
102
+ import {
103
+ fetchAllMessagesInBatch,
104
+ fetchCCIPRequestById,
105
+ fetchCCIPRequestsInTx,
106
+ } from '../requests.ts'
107
+
108
+ /**
109
+ * Type representing a set of unsigned EVM transactions
110
+ */
111
+ export type UnsignedEVMTx = {
112
+ family: typeof ChainFamily.EVM
113
+ transactions: Pick<TransactionRequest, 'from' | 'to' | 'data'>[]
114
+ }
107
115
 
108
116
  const VersionedContractABI = parseAbi(['function typeAndVersion() view returns (string)'])
109
117
 
@@ -134,81 +142,67 @@ function resultsToMessage(result: Result): Record<string, unknown> {
134
142
  } as unknown as CCIPMessage
135
143
  }
136
144
 
145
+ /** typeguard for ethers Signer interface (used for `wallet`s) */
146
+ function isSigner(wallet: unknown): wallet is Signer {
147
+ return (
148
+ typeof wallet === 'object' &&
149
+ wallet !== null &&
150
+ 'signTransaction' in wallet &&
151
+ 'getAddress' in wallet
152
+ )
153
+ }
154
+
155
+ /**
156
+ * EVM chain implementation supporting Ethereum-compatible networks.
157
+ */
137
158
  export class EVMChain extends Chain<typeof ChainFamily.EVM> {
159
+ static {
160
+ supportedChains[ChainFamily.EVM] = EVMChain
161
+ }
138
162
  static readonly family = ChainFamily.EVM
139
163
  static readonly decimals = 18
140
164
 
141
- readonly network: NetworkInfo<typeof ChainFamily.EVM>
142
- readonly provider: JsonRpcApiProvider
165
+ provider: JsonRpcApiProvider
166
+ readonly destroy$: Promise<void>
143
167
 
144
- constructor(provider: JsonRpcApiProvider, network: NetworkInfo) {
145
- if (network.family !== ChainFamily.EVM)
146
- throw new Error(`Invalid network family for EVMChain: ${network.family}`)
147
- super()
168
+ /**
169
+ * Creates a new EVMChain instance.
170
+ * @param provider - JSON-RPC provider for the EVM network.
171
+ * @param network - Network information for this chain.
172
+ */
173
+ constructor(provider: JsonRpcApiProvider, network: NetworkInfo, ctx?: WithLogger) {
174
+ super(network, ctx)
148
175
 
149
- this.network = network
150
176
  this.provider = provider
177
+ this.destroy$ = new Promise<void>((resolve) => (this.destroy = resolve))
178
+ void this.destroy$.finally(() => provider.destroy())
151
179
 
152
- this.typeAndVersion = moize.default(this.typeAndVersion.bind(this))
153
- this.getBlockTimestamp = moize.default(this.getBlockTimestamp.bind(this), {
180
+ this.typeAndVersion = memoize(this.typeAndVersion.bind(this))
181
+
182
+ this.provider.getBlock = memoize(provider.getBlock.bind(provider), {
154
183
  maxSize: 100,
155
- updateCacheForKey: (key: Key[]) => typeof key[key.length - 1] !== 'number',
184
+ maxArgs: 1,
185
+ async: true,
186
+ forceUpdate: ([block]) => typeof block !== 'number',
156
187
  })
157
- this.getTransaction = moize.default(this.getTransaction.bind(this), {
188
+ this.getTransaction = memoize(this.getTransaction.bind(this), {
158
189
  maxSize: 100,
159
- transformArgs: (args: Key[]) =>
190
+ transformKey: (args) =>
160
191
  typeof args[0] !== 'string'
161
192
  ? [(args[0] as unknown as TransactionReceipt).hash]
162
193
  : (args as unknown as string[]),
163
194
  })
164
- this.getTokenForTokenPool = moize.default(this.getTokenForTokenPool.bind(this))
165
- this.getNativeTokenForRouter = moize.default(this.getNativeTokenForRouter.bind(this), {
195
+ this.getTokenForTokenPool = memoize(this.getTokenForTokenPool.bind(this))
196
+ this.getNativeTokenForRouter = memoize(this.getNativeTokenForRouter.bind(this), {
166
197
  maxArgs: 1,
167
- isPromise: true,
198
+ async: true,
168
199
  })
169
- this.getTokenInfo = moize.default(this.getTokenInfo.bind(this))
170
- this.getWallet = moize.default(this.getWallet.bind(this), { maxSize: 1, maxArgs: 0 })
171
- this.getTokenAdminRegistryFor = moize.default(this.getTokenAdminRegistryFor.bind(this), {
172
- isPromise: true,
200
+ this.getTokenInfo = memoize(this.getTokenInfo.bind(this))
201
+ this.getTokenAdminRegistryFor = memoize(this.getTokenAdminRegistryFor.bind(this), {
202
+ async: true,
173
203
  maxArgs: 1,
174
204
  })
175
- }
176
-
177
- // overwrite EVMChain.getWallet to implement custom wallet loading
178
- // some signers don't like to be `.connect`ed, so pass provider as first param
179
- static getWallet(_provider: Provider, _opts: { wallet?: unknown }): Promise<Signer> {
180
- throw new Error('static EVM wallet loading not available')
181
- }
182
-
183
- // cached wallet/signer getter
184
- async getWallet(opts: { wallet?: unknown } = {}): Promise<Signer> {
185
- if (
186
- typeof opts.wallet === 'number' ||
187
- (typeof opts.wallet === 'string' && opts.wallet.match(/^(\d+|0x[a-fA-F0-9]{40})$/))
188
- ) {
189
- // if given a number, numeric string or address, use ethers `provider.getSigner` (e.g. geth or MM)
190
- return this.provider.getSigner(
191
- typeof opts.wallet === 'string' && opts.wallet.match(/^0x[a-fA-F0-9]{40}$/)
192
- ? opts.wallet
193
- : Number(opts.wallet),
194
- )
195
- } else if (typeof opts.wallet === 'string') {
196
- // support receiving private key directly (not recommended)
197
- try {
198
- return Promise.resolve(
199
- new BaseWallet(
200
- new SigningKey((opts.wallet.startsWith('0x') ? '' : '0x') + opts.wallet),
201
- this.provider,
202
- ),
203
- )
204
- } catch (_) {
205
- // pass
206
- }
207
- } else if (opts.wallet instanceof AbstractSigner) {
208
- // if given a signer, return/cache it
209
- return opts.wallet
210
- }
211
- return (this.constructor as typeof EVMChain).getWallet(this.provider, opts)
205
+ this.getFeeTokens = memoize(this.getFeeTokens.bind(this), { async: true, maxArgs: 1 })
212
206
  }
213
207
 
214
208
  /**
@@ -218,10 +212,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
218
212
  return (await this.provider.listAccounts()).map(({ address }) => address)
219
213
  }
220
214
 
221
- async getWalletAddress(opts?: { wallet?: unknown }): Promise<string> {
222
- return (await this.getWallet(opts)).getAddress()
223
- }
224
-
215
+ /**
216
+ * Creates a JSON-RPC provider from a URL.
217
+ * @param url - WebSocket (wss://) or HTTP (https://) endpoint URL.
218
+ * @returns A ready JSON-RPC provider.
219
+ */
225
220
  static async _getProvider(url: string): Promise<JsonRpcApiProvider> {
226
221
  let provider: JsonRpcApiProvider
227
222
  let providerReady: Promise<JsonRpcApiProvider>
@@ -246,37 +241,45 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
246
241
  return providerReady
247
242
  }
248
243
 
249
- static async fromProvider(provider: JsonRpcApiProvider): Promise<EVMChain> {
244
+ /**
245
+ * Creates an EVMChain instance from an existing provider.
246
+ * @param provider - JSON-RPC provider instance.
247
+ * @param ctx - context containing logger.
248
+ * @returns A new EVMChain instance.
249
+ */
250
+ static async fromProvider(provider: JsonRpcApiProvider, ctx?: WithLogger): Promise<EVMChain> {
250
251
  try {
251
- return new EVMChain(provider, networkInfo(Number((await provider.getNetwork()).chainId)))
252
+ return new EVMChain(provider, networkInfo(Number((await provider.getNetwork()).chainId)), ctx)
252
253
  } catch (err) {
253
254
  provider.destroy()
254
255
  throw err
255
256
  }
256
257
  }
257
258
 
258
- static async fromUrl(url: string): Promise<EVMChain> {
259
- return this.fromProvider(await this._getProvider(url))
260
- }
261
-
262
- // eslint-disable-next-line @typescript-eslint/require-await
263
- async destroy(): Promise<void> {
264
- this.provider.destroy()
259
+ /**
260
+ * Creates an EVMChain instance from an RPC URL.
261
+ * @param url - WebSocket (wss://) or HTTP (https://) endpoint URL.
262
+ * @param ctx - context containing logger.
263
+ * @returns A new EVMChain instance.
264
+ */
265
+ static async fromUrl(url: string, ctx?: WithLogger): Promise<EVMChain> {
266
+ return this.fromProvider(await this._getProvider(url), ctx)
265
267
  }
266
268
 
269
+ /** {@inheritDoc Chain.getBlockTimestamp} */
267
270
  async getBlockTimestamp(block: number | 'finalized'): Promise<number> {
268
- const res = await this.provider.getBlock(block)
271
+ const res = await this.provider.getBlock(block) // cached
269
272
  if (!res) throw new Error(`Block not found: ${block}`)
270
273
  return res.timestamp
271
274
  }
272
275
 
276
+ /** {@inheritDoc Chain.getTransaction} */
273
277
  async getTransaction(hash: string | TransactionReceipt): Promise<ChainTransaction> {
274
278
  const tx = typeof hash === 'string' ? await this.provider.getTransactionReceipt(hash) : hash
275
279
  if (!tx) throw new Error(`Transaction not found: ${hash as string}`)
276
280
  const timestamp = await this.getBlockTimestamp(tx.blockNumber)
277
281
  const chainTx = {
278
282
  ...tx,
279
- chain: this,
280
283
  timestamp,
281
284
  logs: [] as Log_[],
282
285
  }
@@ -285,47 +288,62 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
285
288
  return chainTx
286
289
  }
287
290
 
288
- async *getLogs(filter: LogFilter): AsyncIterableIterator<Log> {
289
- const endBlock = filter.endBlock ?? (await this.provider.getBlockNumber())
290
- if (
291
- filter.topics?.length &&
292
- filter.topics.every((t: string | string[]): t is string => typeof t === 'string')
293
- ) {
294
- const topics = new Set(
295
- filter.topics
296
- .filter(isHexString)
297
- .concat(Object.keys(getAllFragmentsMatchingEvents(filter.topics)) as `0x${string}`[])
298
- .flat(),
299
- )
300
- if (!topics.size) {
301
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
302
- throw new Error(`Could not find matching topics: ${filter.topics}`)
291
+ /** {@inheritDoc Chain.getLogs} */
292
+ async *getLogs(filter: LogFilter & { onlyFallback?: boolean }): AsyncIterableIterator<Log> {
293
+ yield* getEvmLogs(filter, this)
294
+ }
295
+
296
+ /** {@inheritDoc Chain.fetchRequestsInTx} */
297
+ async fetchRequestsInTx(tx: string | ChainTransaction): Promise<CCIPRequest[]> {
298
+ return fetchCCIPRequestsInTx(this, typeof tx === 'string' ? await this.getTransaction(tx) : tx)
299
+ }
300
+
301
+ /** {@inheritDoc Chain.fetchRequestById} */
302
+ override fetchRequestById(
303
+ messageId: string,
304
+ onRamp?: string,
305
+ opts?: { page?: number },
306
+ ): Promise<CCIPRequest> {
307
+ return fetchCCIPRequestById(this, messageId, { address: onRamp, ...opts })
308
+ }
309
+
310
+ /** {@inheritDoc Chain.fetchAllMessagesInBatch} */
311
+ async fetchAllMessagesInBatch<
312
+ R extends PickDeep<
313
+ CCIPRequest,
314
+ 'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.header.sequenceNumber'
315
+ >,
316
+ >(
317
+ request: R,
318
+ commit: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
319
+ opts?: { page?: number },
320
+ ): Promise<R['message'][]> {
321
+ let opts_: Parameters<EVMChain['getLogs']>[0] | undefined
322
+ if (request.lane.version >= CCIPVersion.V1_6) {
323
+ // specialized getLogs filter for v1.6 CCIPMessageSent events, to filter by dest
324
+ opts_ = {
325
+ ...opts,
326
+ topics: [[request.log.topics[0]], [toBeHex(request.lane.destChainSelector, 32)]],
303
327
  }
304
- filter.topics = [Array.from(topics)]
305
- }
306
- if (!filter.startBlock && filter.startTime) {
307
- filter.startBlock = await getSomeBlockNumberBefore(
308
- this.getBlockTimestamp.bind(this),
309
- endBlock,
310
- filter.startTime,
311
- )
312
- }
313
- for (const blockRange of blockRangeGenerator({ ...filter, endBlock })) {
314
- console.debug('evm getLogs:', {
315
- ...blockRange,
316
- ...(filter.address ? { address: filter.address } : {}),
317
- ...(filter.topics?.length ? { topics: filter.topics } : {}),
318
- })
319
- const logs = await this.provider.getLogs({
320
- ...blockRange,
321
- ...(filter.address ? { address: filter.address } : {}),
322
- ...(filter.topics?.length ? { topics: filter.topics } : {}),
323
- })
324
- if (!filter.startBlock) logs.reverse()
325
- yield* logs
326
328
  }
329
+ return fetchAllMessagesInBatch(this, request, commit, opts_)
330
+ }
331
+
332
+ /** {@inheritDoc Chain.typeAndVersion} */
333
+ async typeAndVersion(address: string) {
334
+ const contract = new Contract(
335
+ address,
336
+ VersionedContractABI,
337
+ this.provider,
338
+ ) as unknown as TypedContract<typeof VersionedContractABI>
339
+ return parseTypeAndVersion(await contract.typeAndVersion())
327
340
  }
328
341
 
342
+ /**
343
+ * Decodes a CCIP message from a log event.
344
+ * @param log - Log event with topics and data.
345
+ * @returns Decoded CCIPMessage or undefined if not a valid CCIP message.
346
+ */
329
347
  static decodeMessage(log: {
330
348
  topics?: readonly string[]
331
349
  data: unknown
@@ -383,11 +401,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
383
401
  ...tokenAmount,
384
402
  }
385
403
  } catch (_) {
386
- console.debug(
387
- 'legacy sourceTokenData:',
388
- i,
389
- (message as { sourceTokenData: string[] }).sourceTokenData[i],
390
- )
404
+ // legacy sourceTokenData
391
405
  }
392
406
  }
393
407
  if (typeof tokenAmount.destExecData === 'string' && tokenAmount.destGasAmount == null) {
@@ -428,6 +442,12 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
428
442
  return message as CCIPMessage
429
443
  }
430
444
 
445
+ /**
446
+ * Decodes commit reports from a log event.
447
+ * @param log - Log event with topics and data.
448
+ * @param lane - Lane info (required for CCIP v1.5 and earlier).
449
+ * @returns Array of CommitReport or undefined if not a valid commit event.
450
+ */
431
451
  static decodeCommits(
432
452
  log: { topics?: readonly string[]; data: unknown },
433
453
  lane?: Omit<Lane, 'destChainSelector'>,
@@ -478,6 +498,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
478
498
  }
479
499
  }
480
500
 
501
+ /**
502
+ * Decodes an execution receipt from a log event.
503
+ * @param log - Log event with topics and data.
504
+ * @returns ExecutionReceipt or undefined if not a valid execution event.
505
+ */
481
506
  static decodeReceipt(log: {
482
507
  topics?: readonly string[]
483
508
  data: unknown
@@ -502,6 +527,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
502
527
  }
503
528
  }
504
529
 
530
+ /**
531
+ * Decodes extra arguments from a CCIP message.
532
+ * @param extraArgs - Encoded extra arguments bytes.
533
+ * @returns Decoded extra arguments with tag, or undefined if unknown format.
534
+ */
505
535
  static decodeExtraArgs(
506
536
  extraArgs: BytesLike,
507
537
  ):
@@ -542,6 +572,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
542
572
  }
543
573
  }
544
574
 
575
+ /**
576
+ * Encodes extra arguments for a CCIP message.
577
+ * @param args - Extra arguments to encode.
578
+ * @returns Encoded extra arguments as hex string.
579
+ */
545
580
  static encodeExtraArgs(args: ExtraArgs): string {
546
581
  if (!args) return '0x'
547
582
  if ('computeUnits' in args) {
@@ -581,6 +616,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
581
616
  return '0x'
582
617
  }
583
618
 
619
+ /**
620
+ * Converts bytes to a checksummed EVM address.
621
+ * @param bytes - Bytes to convert (must be 20 bytes or 32 bytes with leading zeros).
622
+ * @returns Checksummed EVM address.
623
+ */
584
624
  static getAddress(bytes: BytesLike): string {
585
625
  bytes = getBytes(bytes)
586
626
  if (bytes.length < 20) throw new Error(`Invalid address: ${hexlify(bytes)}`)
@@ -594,15 +634,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
594
634
  return getAddress(hexlify(bytes))
595
635
  }
596
636
 
597
- async typeAndVersion(address: string) {
598
- const contract = new Contract(
599
- address,
600
- VersionedContractABI,
601
- this.provider,
602
- ) as unknown as TypedContract<typeof VersionedContractABI>
603
- return parseTypeAndVersion(await contract.typeAndVersion())
604
- }
605
-
637
+ /**
638
+ * Gets lane configuration from an OnRamp contract.
639
+ * @param onRamp - OnRamp contract address.
640
+ * @returns Lane configuration.
641
+ */
606
642
  async getLaneForOnRamp(onRamp: string): Promise<Lane> {
607
643
  const [, version] = await this.typeAndVersion(onRamp)
608
644
  const onRampABI = version === CCIPVersion.V1_2 ? EVM2EVMOnRamp_1_2_ABI : EVM2EVMOnRamp_1_5_ABI
@@ -623,6 +659,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
623
659
  }
624
660
  }
625
661
 
662
+ /** {@inheritDoc Chain.getRouterForOnRamp} */
626
663
  async getRouterForOnRamp(onRamp: string, destChainSelector: bigint): Promise<string> {
627
664
  const [, version] = await this.typeAndVersion(onRamp)
628
665
  let onRampABI
@@ -651,6 +688,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
651
688
  }
652
689
  }
653
690
 
691
+ /** {@inheritDoc Chain.getRouterForOffRamp} */
654
692
  async getRouterForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
655
693
  const [, version] = await this.typeAndVersion(offRamp)
656
694
  let offRampABI, router
@@ -684,6 +722,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
684
722
  return router as string
685
723
  }
686
724
 
725
+ /** {@inheritDoc Chain.getNativeTokenForRouter} */
687
726
  async getNativeTokenForRouter(router: string): Promise<string> {
688
727
  const contract = new Contract(
689
728
  router,
@@ -693,6 +732,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
693
732
  return contract.getWrappedNative() as Promise<string>
694
733
  }
695
734
 
735
+ /** {@inheritDoc Chain.getOffRampsForRouter} */
696
736
  async getOffRampsForRouter(router: string, sourceChainSelector: bigint): Promise<string[]> {
697
737
  const contract = new Contract(
698
738
  router,
@@ -705,6 +745,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
705
745
  .map(({ offRamp }) => offRamp) as string[]
706
746
  }
707
747
 
748
+ /** {@inheritDoc Chain.getOnRampForRouter} */
708
749
  async getOnRampForRouter(router: string, destChainSelector: bigint): Promise<string> {
709
750
  const contract = new Contract(
710
751
  router,
@@ -714,6 +755,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
714
755
  return contract.getOnRamp(destChainSelector) as Promise<string>
715
756
  }
716
757
 
758
+ /** {@inheritDoc Chain.getOnRampForOffRamp} */
717
759
  async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
718
760
  const [, version] = await this.typeAndVersion(offRamp)
719
761
  let offRampABI
@@ -746,6 +788,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
746
788
  }
747
789
  }
748
790
 
791
+ /** {@inheritDoc Chain.getCommitStoreForOffRamp} */
749
792
  async getCommitStoreForOffRamp(offRamp: string): Promise<string> {
750
793
  const [, version] = await this.typeAndVersion(offRamp)
751
794
  let offRampABI
@@ -771,6 +814,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
771
814
  }
772
815
  }
773
816
 
817
+ /** {@inheritDoc Chain.getTokenForTokenPool} */
774
818
  async getTokenForTokenPool(tokenPool: string): Promise<string> {
775
819
  const contract = new Contract(
776
820
  tokenPool,
@@ -780,6 +824,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
780
824
  return contract.getToken() as Promise<string>
781
825
  }
782
826
 
827
+ /** {@inheritDoc Chain.getTokenInfo} */
783
828
  async getTokenInfo(token: string): Promise<{ decimals: number; symbol: string; name: string }> {
784
829
  const contract = new Contract(
785
830
  token,
@@ -794,12 +839,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
794
839
  return { symbol, decimals: Number(decimals), name }
795
840
  }
796
841
 
797
- static getDestLeafHasher({
798
- sourceChainSelector,
799
- destChainSelector,
800
- onRamp,
801
- version,
802
- }: Lane): LeafHasher {
842
+ /**
843
+ * Gets the leaf hasher for computing Merkle proofs on the destination chain.
844
+ * @param lane - Lane configuration.
845
+ * @param ctx - Context object containing logger.
846
+ * @returns Leaf hasher function.
847
+ */
848
+ static getDestLeafHasher(
849
+ { sourceChainSelector, destChainSelector, onRamp, version }: Lane,
850
+ ctx?: WithLogger,
851
+ ): LeafHasher {
803
852
  switch (version) {
804
853
  case CCIPVersion.V1_2:
805
854
  case CCIPVersion.V1_5:
@@ -807,12 +856,17 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
807
856
  throw new Error(`Unsupported source chain: ${sourceChainSelector}`)
808
857
  return getV12LeafHasher(sourceChainSelector, destChainSelector, onRamp) as LeafHasher
809
858
  case CCIPVersion.V1_6:
810
- return getV16LeafHasher(sourceChainSelector, destChainSelector, onRamp) as LeafHasher
859
+ return getV16LeafHasher(sourceChainSelector, destChainSelector, onRamp, ctx) as LeafHasher
811
860
  default:
812
861
  throw new Error(`Unsupported hasher version for EVM: ${version as string}`)
813
862
  }
814
863
  }
815
864
 
865
+ /**
866
+ * Gets any available OnRamp for the given router.
867
+ * @param router - Router contract address.
868
+ * @returns OnRamp contract address.
869
+ */
816
870
  async _getSomeOnRampFor(router: string): Promise<string> {
817
871
  // when given a router, we take any onRamp we can find, as usually they all use same registry
818
872
  const someOtherNetwork = this.network.isTestnet
@@ -825,6 +879,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
825
879
  return this.getOnRampForRouter(router, networkInfo(someOtherNetwork).chainSelector)
826
880
  }
827
881
 
882
+ /** {@inheritDoc Chain.getTokenAdminRegistryFor} */
828
883
  async getTokenAdminRegistryFor(address: string): Promise<string> {
829
884
  let [type, version, typeAndVersion] = await this.typeAndVersion(address)
830
885
  if (type === 'TokenAdminRegistry') {
@@ -850,6 +905,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
850
905
  return tokenAdminRegistry as string
851
906
  }
852
907
 
908
+ /**
909
+ * Gets the FeeQuoter contract address for a given Router or Ramp.
910
+ * @param address - Router or Ramp contract address.
911
+ * @returns FeeQuoter contract address.
912
+ */
853
913
  async getFeeQuoterFor(address: string): Promise<string> {
854
914
  let [type, version, typeAndVersion] = await this.typeAndVersion(address)
855
915
  if (type === 'FeeQuoter') {
@@ -872,6 +932,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
872
932
  return feeQuoter as string
873
933
  }
874
934
 
935
+ /** {@inheritDoc Chain.getFee} */
875
936
  async getFee(router_: string, destChainSelector: bigint, message: AnyMessage): Promise<bigint> {
876
937
  const router = new Contract(
877
938
  router_,
@@ -887,12 +948,24 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
887
948
  })
888
949
  }
889
950
 
890
- async sendMessage(
891
- router_: string,
951
+ /**
952
+ * Generate unsigned txs for ccipSend'ing a message
953
+ * @param sender - sender address
954
+ * @param router - address of the Router contract
955
+ * @param destChainSelector - chainSelector of destination chain
956
+ * @param message - AnyMessage to send; if `fee` is not present, it'll be calculated
957
+ * @param approveMax - if tokens approvals are needed, opt into approving maximum allowance
958
+ * @returns Array containing 0 or more unsigned token approvals txs (if needed at the time of
959
+ * generation), followed by a ccipSend TransactionRequest
960
+ */
961
+ async generateUnsignedSendMessage(
962
+ sender: string,
963
+ router: string,
892
964
  destChainSelector: bigint,
893
- message: AnyMessage & { fee: bigint },
894
- opts?: { wallet?: unknown; approveMax?: boolean },
895
- ): Promise<ChainTransaction> {
965
+ message: AnyMessage & { fee?: bigint },
966
+ opts?: { approveMax?: boolean },
967
+ ): Promise<UnsignedEVMTx> {
968
+ if (!message.fee) message.fee = await this.getFee(router, destChainSelector, message)
896
969
  const feeToken = message.feeToken ?? ZeroAddress
897
970
  const receiver = zeroPadValue(getAddressBytes(message.receiver), 32)
898
971
  const data = hexlify(message.data)
@@ -908,34 +981,28 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
908
981
  if (feeToken !== ZeroAddress)
909
982
  amountsToApprove[feeToken] = (amountsToApprove[feeToken] ?? 0n) + message.fee
910
983
 
911
- const wallet = await this.getWallet(opts) // moized wallet arg (if called previously)
912
-
913
- // approve all tokens (including fee token) in parallel
914
- let nonce = await this.provider.getTransactionCount(await this.getWalletAddress())
915
- await Promise.all(
916
- Object.entries(amountsToApprove).map(async ([token, amount]) => {
917
- const contract = new Contract(token, interfaces.Token, wallet) as unknown as TypedContract<
918
- typeof Token_ABI
919
- >
920
- const allowance = await contract.allowance(await wallet.getAddress(), router_)
921
- if (allowance < amount) {
984
+ const approveTxs = (
985
+ await Promise.all(
986
+ Object.entries(amountsToApprove).map(async ([token, amount]) => {
987
+ const contract = new Contract(
988
+ token,
989
+ interfaces.Token,
990
+ this.provider,
991
+ ) as unknown as TypedContract<typeof Token_ABI>
992
+ const allowance = await contract.allowance(sender, router)
993
+ if (allowance >= amount) return
922
994
  const amnt = opts?.approveMax ? 2n ** 256n - 1n : amount
923
- // optimization: hardcode nonce and gasLimit to send all approvals in parallel without estimating
924
- console.info('Approving', amnt, 'of', token, 'tokens for router', router_)
925
- const tx = await contract.approve(router_, amnt, {
926
- nonce: nonce++,
927
- gasLimit: DEFAULT_APPROVE_GAS_LIMIT,
928
- })
929
- console.info('=>', tx.hash)
930
- await tx.wait(1, 60_000)
931
- }
932
- }),
933
- )
995
+ return contract.approve.populateTransaction(router, amnt, { from: sender })
996
+ }),
997
+ )
998
+ ).filter((tx) => tx != null)
934
999
 
935
- const router = new Contract(router_, interfaces.Router, wallet) as unknown as TypedContract<
936
- typeof Router_ABI
937
- >
938
- const tx = await router.ccipSend(
1000
+ const contract = new Contract(
1001
+ router,
1002
+ interfaces.Router,
1003
+ this.provider,
1004
+ ) as unknown as TypedContract<typeof Router_ABI>
1005
+ const sendTx = await contract.ccipSend.populateTransaction(
939
1006
  destChainSelector,
940
1007
  {
941
1008
  receiver,
@@ -945,26 +1012,85 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
945
1012
  feeToken,
946
1013
  },
947
1014
  {
948
- nonce: nonce++,
1015
+ from: sender,
949
1016
  // if native fee, include it in value; otherwise, it's transferedFrom feeToken
950
- ...(feeToken === ZeroAddress ? { value: message.fee } : {}),
1017
+ ...(feeToken === ZeroAddress && { value: message.fee }),
951
1018
  },
952
1019
  )
953
- const receipt = await tx.wait(1)
954
- return this.getTransaction(receipt!)
1020
+ const txRequests = [...approveTxs, sendTx] as SetRequired<typeof sendTx, 'from'>[]
1021
+ return {
1022
+ family: ChainFamily.EVM,
1023
+ transactions: txRequests,
1024
+ }
1025
+ }
1026
+
1027
+ /** {@inheritDoc Chain.sendMessage} */
1028
+ async sendMessage(
1029
+ router_: string,
1030
+ destChainSelector: bigint,
1031
+ message: AnyMessage & { fee?: bigint },
1032
+ opts: { wallet: unknown; approveMax?: boolean },
1033
+ ): Promise<CCIPRequest> {
1034
+ const wallet = opts.wallet
1035
+ if (!isSigner(wallet)) throw new Error(`Wallet must be a Signer, got=${util.inspect(wallet)}`)
1036
+
1037
+ const sender = await wallet.getAddress()
1038
+ const txs = await this.generateUnsignedSendMessage(
1039
+ sender,
1040
+ router_,
1041
+ destChainSelector,
1042
+ message,
1043
+ opts,
1044
+ )
1045
+ const approveTxs = txs.transactions.slice(0, txs.transactions.length - 1)
1046
+ let sendTx: TransactionRequest = txs.transactions[txs.transactions.length - 1]
1047
+
1048
+ // approve all tokens (including feeToken, if needed) in parallel
1049
+ let nonce = await this.provider.getTransactionCount(sender)
1050
+ const responses = await Promise.all(
1051
+ approveTxs.map(async (tx: TransactionRequest) => {
1052
+ tx.nonce = nonce++
1053
+ tx = await wallet.populateTransaction(tx)
1054
+ tx.from = undefined
1055
+ const signed = await wallet.signTransaction(tx)
1056
+ const response = await this.provider.broadcastTransaction(signed)
1057
+ this.logger.debug('approve =>', response.hash)
1058
+ return response
1059
+ }),
1060
+ )
1061
+ if (responses.length) await responses[responses.length - 1].wait(1, 60_000) // wait last tx nonce to be mined
1062
+
1063
+ sendTx.nonce = nonce++
1064
+ // sendTx.gasLimit = await this.provider.estimateGas(sendTx)
1065
+ sendTx = await wallet.populateTransaction(sendTx)
1066
+ sendTx.from = undefined // some signers don't like receiving pre-populated `from`
1067
+ const signed = await wallet.signTransaction(sendTx)
1068
+ const response = await this.provider.broadcastTransaction(signed)
1069
+ this.logger.debug('ccipSend =>', response.hash)
1070
+ await response.wait(1, 60_000)
1071
+ return (await this.fetchRequestsInTx(await this.getTransaction(response.hash)))[0]
955
1072
  }
956
1073
 
1074
+ /** {@inheritDoc Chain.fetchOffchainTokenData} */
957
1075
  fetchOffchainTokenData(request: CCIPRequest): Promise<OffchainTokenData[]> {
958
- return fetchEVMOffchainTokenData(request)
1076
+ return fetchEVMOffchainTokenData(request, this)
959
1077
  }
960
1078
 
961
- async executeReport(
1079
+ /**
1080
+ * Generate unsigned tx to manuallyExecute a message
1081
+ * @param _payer - not used in EVM
1082
+ * @param offRamp - address of the OffRamp contract
1083
+ * @param execReport - execution report
1084
+ * @param opts - gas limit overrides for ccipReceive and tokenPool calls options
1085
+ * @returns array containing one unsigned `manuallyExecute` TransactionRequest object
1086
+ */
1087
+ async generateUnsignedExecuteReport(
1088
+ _payer: string,
962
1089
  offRamp: string,
963
1090
  execReport: ExecutionReport,
964
- opts?: { wallet?: string; gasLimit?: number; tokensGasLimit?: number },
965
- ) {
1091
+ opts: { gasLimit?: number; tokensGasLimit?: number },
1092
+ ): Promise<UnsignedEVMTx> {
966
1093
  const [_, version] = await this.typeAndVersion(offRamp)
967
- const wallet = await this.getWallet(opts)
968
1094
 
969
1095
  let manualExecTx
970
1096
  const offchainTokenData = execReport.offchainTokenData.map(encodeEVMOffchainTokenData)
@@ -974,10 +1100,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
974
1100
  const contract = new Contract(
975
1101
  offRamp,
976
1102
  EVM2EVMOffRamp_1_2_ABI,
977
- wallet,
1103
+ this.provider,
978
1104
  ) as unknown as TypedContract<typeof EVM2EVMOffRamp_1_2_ABI>
979
1105
  const gasOverride = BigInt(opts?.gasLimit ?? 0)
980
- manualExecTx = await contract.manuallyExecute(
1106
+ manualExecTx = await contract.manuallyExecute.populateTransaction(
981
1107
  {
982
1108
  ...execReport,
983
1109
  proofs: execReport.proofs.map((d) => hexlify(d)),
@@ -992,9 +1118,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
992
1118
  const contract = new Contract(
993
1119
  offRamp,
994
1120
  EVM2EVMOffRamp_1_5_ABI,
995
- wallet,
1121
+ this.provider,
996
1122
  ) as unknown as TypedContract<typeof EVM2EVMOffRamp_1_5_ABI>
997
- manualExecTx = await contract.manuallyExecute(
1123
+ manualExecTx = await contract.manuallyExecute.populateTransaction(
998
1124
  {
999
1125
  ...execReport,
1000
1126
  proofs: execReport.proofs.map((d) => hexlify(d)),
@@ -1027,10 +1153,12 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1027
1153
  sender,
1028
1154
  tokenAmounts,
1029
1155
  }
1030
- const contract = new Contract(offRamp, OffRamp_1_6_ABI, wallet) as unknown as TypedContract<
1031
- typeof OffRamp_1_6_ABI
1032
- >
1033
- manualExecTx = await contract.manuallyExecute(
1156
+ const contract = new Contract(
1157
+ offRamp,
1158
+ OffRamp_1_6_ABI,
1159
+ this.provider,
1160
+ ) as unknown as TypedContract<typeof OffRamp_1_6_ABI>
1161
+ manualExecTx = await contract.manuallyExecute.populateTransaction(
1034
1162
  [
1035
1163
  {
1036
1164
  ...execReport,
@@ -1056,20 +1184,48 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1056
1184
  default:
1057
1185
  throw new Error(`Unsupported version: ${version}`)
1058
1186
  }
1059
- const receipt = await this.provider.waitForTransaction(manualExecTx.hash, 1, 60e3)
1060
- if (!receipt?.hash) throw new Error(`Could not confirm exec tx: ${manualExecTx.hash}`)
1061
- if (!receipt.status) throw new Error(`Exec transaction reverted: ${manualExecTx.hash}`)
1187
+ return { family: ChainFamily.EVM, transactions: [manualExecTx] }
1188
+ }
1189
+
1190
+ /** {@inheritDoc Chain.executeReport} */
1191
+ async executeReport(
1192
+ offRamp: string,
1193
+ execReport: ExecutionReport,
1194
+ opts: { wallet: unknown; gasLimit?: number; tokensGasLimit?: number },
1195
+ ) {
1196
+ const wallet = opts.wallet
1197
+ if (!isSigner(wallet)) throw new Error(`Wallet must be a Signer, got=${util.inspect(wallet)}`)
1198
+
1199
+ const unsignedTxs = await this.generateUnsignedExecuteReport(
1200
+ await wallet.getAddress(),
1201
+ offRamp,
1202
+ execReport,
1203
+ opts,
1204
+ )
1205
+ const unsignedTx = await wallet.populateTransaction(unsignedTxs.transactions[0])
1206
+ unsignedTx.from = undefined // some signers don't like receiving pre-populated `from`
1207
+ const signed = await wallet.signTransaction(unsignedTx)
1208
+ const response = await this.provider.broadcastTransaction(signed)
1209
+ this.logger.debug('ccipSend =>', response.hash)
1210
+ const receipt = await response.wait(1, 60_000)
1211
+ if (!receipt?.hash) throw new Error(`Could not confirm exec tx: ${response.hash}`)
1212
+ if (!receipt.status) throw new Error(`Exec transaction reverted: ${response.hash}`)
1062
1213
  return this.getTransaction(receipt)
1063
1214
  }
1064
1215
 
1216
+ /**
1217
+ * Parses raw data into typed structures.
1218
+ * @param data - Raw data to parse.
1219
+ * @returns Parsed data.
1220
+ */
1065
1221
  static parse(data: unknown) {
1066
1222
  return parseData(data)
1067
1223
  }
1068
1224
 
1069
1225
  /**
1070
- * Get the supported tokens for a given contract address
1071
- *
1072
- * @param address Router, OnRamp, OffRamp or TokenAdminRegistry contract
1226
+ * Get the supported tokens for a given contract address.
1227
+ * @param registry - Router, OnRamp, OffRamp or TokenAdminRegistry contract address.
1228
+ * @param opts - Optional parameters.
1073
1229
  * @returns An array of supported token addresses.
1074
1230
  */
1075
1231
  async getSupportedTokens(registry: string, opts?: { page?: number }): Promise<string[]> {
@@ -1089,6 +1245,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1089
1245
  return res as string[]
1090
1246
  }
1091
1247
 
1248
+ /** {@inheritDoc Chain.getRegistryTokenConfig} */
1092
1249
  async getRegistryTokenConfig(
1093
1250
  registry: string,
1094
1251
  token: string,
@@ -1117,6 +1274,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1117
1274
  }
1118
1275
  }
1119
1276
 
1277
+ /** {@inheritDoc Chain.getTokenPoolConfigs} */
1120
1278
  async getTokenPoolConfigs(tokenPool: string): Promise<{
1121
1279
  token: string
1122
1280
  router: string
@@ -1141,6 +1299,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1141
1299
  })
1142
1300
  }
1143
1301
 
1302
+ /** {@inheritDoc Chain.getTokenPoolRemotes} */
1144
1303
  async getTokenPoolRemotes(
1145
1304
  tokenPool: string,
1146
1305
  remoteChainSelector?: bigint,
@@ -1216,24 +1375,36 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1216
1375
  ),
1217
1376
  )
1218
1377
  }
1219
- async listFeeTokens(router: string) {
1378
+
1379
+ /** {@inheritDoc Chain.getFeeTokens} */
1380
+ async getFeeTokens(router: string) {
1220
1381
  const onRamp = await this._getSomeOnRampFor(router)
1221
1382
  const [_, version] = await this.typeAndVersion(onRamp)
1222
1383
  let tokens
1223
- let onRampABI
1384
+ let onRampIface: Interface
1224
1385
  switch (version) {
1225
1386
  case CCIPVersion.V1_2:
1226
- onRampABI = EVM2EVMOnRamp_1_2_ABI
1387
+ onRampIface = interfaces.EVM2EVMOnRamp_v1_2
1227
1388
  // falls through
1228
1389
  case CCIPVersion.V1_5: {
1229
- onRampABI ??= EVM2EVMOnRamp_1_5_ABI
1230
- const contract = new Contract(onRamp, onRampABI, this.provider) as unknown as TypedContract<
1231
- typeof onRampABI
1232
- >
1233
- tokens = await Promise.all([
1234
- this.getNativeTokenForRouter(router),
1235
- contract.getStaticConfig().then(({ linkToken }) => linkToken),
1236
- ])
1390
+ onRampIface ??= interfaces.EVM2EVMOnRamp_v1_5
1391
+ const fragment = onRampIface.getEvent('FeeConfigSet')!
1392
+ const tokens_ = new Set()
1393
+ for await (const log of this.getLogs({
1394
+ address: onRamp,
1395
+ topics: [fragment.topicHash],
1396
+ startBlock: 1,
1397
+ onlyFallback: true,
1398
+ })) {
1399
+ ;(
1400
+ onRampIface.decodeEventLog(fragment, log.data, log.topics) as unknown as {
1401
+ feeConfig: { token: string; enabled: boolean }[]
1402
+ }
1403
+ ).feeConfig.forEach(({ token, enabled }) =>
1404
+ enabled ? tokens_.add(token) : tokens_.delete(token),
1405
+ )
1406
+ }
1407
+ tokens = Array.from(tokens_)
1237
1408
  break
1238
1409
  }
1239
1410
  case CCIPVersion.V1_6: {
@@ -1257,6 +1428,37 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1257
1428
  ),
1258
1429
  )
1259
1430
  }
1260
- }
1261
1431
 
1262
- supportedChains[ChainFamily.EVM] = EVMChain
1432
+ /** {@inheritDoc Chain.fetchExecutionReceipts} */
1433
+ override async *fetchExecutionReceipts(
1434
+ offRamp: string,
1435
+ request: PickDeep<CCIPRequest, 'lane' | 'message.header.messageId' | 'tx.timestamp'>,
1436
+ commit?: CCIPCommit,
1437
+ opts?: { page?: number },
1438
+ ): AsyncIterableIterator<CCIPExecution> {
1439
+ let opts_: Parameters<EVMChain['getLogs']>[0] | undefined = opts
1440
+ if (request.lane.version < CCIPVersion.V1_6) {
1441
+ opts_ = {
1442
+ ...opts,
1443
+ topics: [
1444
+ interfaces.EVM2EVMOffRamp_v1_5.getEvent('ExecutionStateChanged')!.topicHash,
1445
+ null,
1446
+ request.message.header.messageId,
1447
+ ],
1448
+ // onlyFallback: false,
1449
+ }
1450
+ } else /* >= V1.6 */ {
1451
+ opts_ = {
1452
+ ...opts,
1453
+ topics: [
1454
+ interfaces.OffRamp_v1_6.getEvent('ExecutionStateChanged')!.topicHash,
1455
+ toBeHex(request.lane.sourceChainSelector, 32),
1456
+ null,
1457
+ request.message.header.messageId,
1458
+ ],
1459
+ // onlyFallback: false,
1460
+ }
1461
+ }
1462
+ yield* super.fetchExecutionReceipts(offRamp, request, commit, opts_)
1463
+ }
1464
+ }