@chainlink/ccip-cli 0.96.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 (38) hide show
  1. package/dist/commands/lane-latency.d.ts +17 -0
  2. package/dist/commands/lane-latency.d.ts.map +1 -1
  3. package/dist/commands/lane-latency.js +18 -1
  4. package/dist/commands/lane-latency.js.map +1 -1
  5. package/dist/commands/manual-exec.d.ts +20 -0
  6. package/dist/commands/manual-exec.d.ts.map +1 -1
  7. package/dist/commands/manual-exec.js +34 -18
  8. package/dist/commands/manual-exec.js.map +1 -1
  9. package/dist/commands/parse.d.ts +17 -0
  10. package/dist/commands/parse.d.ts.map +1 -1
  11. package/dist/commands/parse.js +17 -0
  12. package/dist/commands/parse.js.map +1 -1
  13. package/dist/commands/send.d.ts +20 -0
  14. package/dist/commands/send.d.ts.map +1 -1
  15. package/dist/commands/send.js +22 -4
  16. package/dist/commands/send.js.map +1 -1
  17. package/dist/commands/show.d.ts +22 -6
  18. package/dist/commands/show.d.ts.map +1 -1
  19. package/dist/commands/show.js +77 -43
  20. package/dist/commands/show.js.map +1 -1
  21. package/dist/commands/utils.d.ts +7 -7
  22. package/dist/commands/utils.d.ts.map +1 -1
  23. package/dist/commands/utils.js +89 -43
  24. package/dist/commands/utils.js.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/loadtest.d.ts +2 -0
  27. package/dist/loadtest.d.ts.map +1 -0
  28. package/dist/loadtest.js +132 -0
  29. package/dist/loadtest.js.map +1 -0
  30. package/package.json +8 -8
  31. package/src/commands/lane-latency.ts +19 -1
  32. package/src/commands/manual-exec.ts +35 -27
  33. package/src/commands/parse.ts +18 -0
  34. package/src/commands/send.ts +23 -4
  35. package/src/commands/show.ts +85 -44
  36. package/src/commands/utils.ts +105 -43
  37. package/src/index.ts +1 -1
  38. package/src/loadtest.ts +171 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlink/ccip-cli",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "CCIP Command Line Interface, based on @chainlink/ccip-sdk",
5
5
  "author": "Chainlink devs",
6
6
  "license": "MIT",
@@ -31,27 +31,27 @@
31
31
  "!**/__mocks__"
32
32
  ],
33
33
  "devDependencies": {
34
- "@eslint/js": "^9.39.2",
35
- "@types/node": "25.2.2",
34
+ "@eslint/js": "^9.39.3",
35
+ "@types/node": "25.3.0",
36
36
  "@types/yargs": "17.0.35",
37
- "eslint": "^9.39.2",
37
+ "eslint": "^9.39.3",
38
38
  "eslint-config-prettier": "10.1.8",
39
39
  "eslint-import-resolver-typescript": "4.4.4",
40
40
  "eslint-plugin-import": "^2.32.0",
41
- "eslint-plugin-jsdoc": "^62.5.4",
41
+ "eslint-plugin-jsdoc": "^62.7.0",
42
42
  "eslint-plugin-prettier": "^5.5.5",
43
43
  "eslint-plugin-tsdoc": "^0.5.0",
44
44
  "prettier": "^3.8.1",
45
45
  "tsx": "4.21.0",
46
46
  "typescript": "5.9.3",
47
- "typescript-eslint": "8.55.0"
47
+ "typescript-eslint": "8.56.0"
48
48
  },
49
49
  "dependencies": {
50
50
  "@aptos-labs/ts-sdk": "^5.2.1",
51
- "@chainlink/ccip-sdk": "^0.96.0",
51
+ "@chainlink/ccip-sdk": "^0.97.0",
52
52
  "@coral-xyz/anchor": "^0.29.0",
53
53
  "@ethers-ext/signer-ledger": "^6.0.0-beta.1",
54
- "@inquirer/prompts": "8.2.0",
54
+ "@inquirer/prompts": "8.3.0",
55
55
  "@ledgerhq/hw-app-aptos": "^6.35.0",
56
56
  "@ledgerhq/hw-app-solana": "^7.7.0",
57
57
  "@ledgerhq/hw-transport-node-hid": "^6.30.0",
@@ -1,3 +1,21 @@
1
+ /**
2
+ * CCIP CLI Lane Latency Command
3
+ *
4
+ * Queries real-time lane latency statistics between source and destination chains
5
+ * using the CCIP API. Shows average, median, and percentile latencies.
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Get latency between Ethereum and Arbitrum
10
+ * ccip-cli laneLatency ethereum-mainnet arbitrum-mainnet
11
+ *
12
+ * # Use custom API URL
13
+ * ccip-cli laneLatency sepolia fuji --api-url https://custom-api.example.com
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
1
19
  import {
2
20
  CCIPAPIClient,
3
21
  CCIPApiClientNotAvailableError,
@@ -67,7 +85,7 @@ export async function getLaneLatencyCmd(ctx: Ctx, argv: Parameters<typeof handle
67
85
  const sourceNetwork = networkInfo(argv.source)
68
86
  const destNetwork = networkInfo(argv.dest)
69
87
 
70
- const apiClient = new CCIPAPIClient(argv.apiUrl, { logger })
88
+ const apiClient = CCIPAPIClient.fromUrl(argv.apiUrl, { logger })
71
89
 
72
90
  const result = await apiClient.getLaneLatency(
73
91
  sourceNetwork.chainSelector,
@@ -1,7 +1,26 @@
1
+ /**
2
+ * CCIP CLI Manual Execution Command
3
+ *
4
+ * Manually executes pending or failed CCIP messages on the destination chain.
5
+ * Use this when automatic execution fails or is delayed.
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Execute a stuck message
10
+ * ccip-cli manualExec 0xSourceTxHash... --wallet $PRIVATE_KEY
11
+ *
12
+ * # Execute with custom gas limit
13
+ * ccip-cli manualExec 0xSourceTxHash... --gas-limit 500000
14
+ *
15
+ * # Execute all messages in sender queue
16
+ * ccip-cli manualExec 0xSourceTxHash... --sender-queue
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
1
22
  import {
2
- type ExecutionReport,
3
23
  bigIntReplacer,
4
- calculateManualExecProof,
5
24
  discoverOffRamp,
6
25
  estimateReceiveExecution,
7
26
  isSupportedTxHash,
@@ -13,9 +32,9 @@ import { type Ctx, Format } from './types.ts'
13
32
  import {
14
33
  getCtx,
15
34
  logParsedError,
16
- prettyCommit,
17
35
  prettyReceipt,
18
36
  prettyRequest,
37
+ prettyVerifications,
19
38
  selectRequest,
20
39
  withDateTimestamp,
21
40
  } from './utils.ts'
@@ -134,7 +153,7 @@ async function manualExec(
134
153
  break
135
154
  }
136
155
  case Format.pretty:
137
- await prettyRequest.call(ctx, source, request)
156
+ await prettyRequest.call(ctx, request, source)
138
157
  break
139
158
  case Format.json:
140
159
  logger.info(JSON.stringify(request, bigIntReplacer, 2))
@@ -143,38 +162,21 @@ async function manualExec(
143
162
 
144
163
  const dest = await getChain(request.lane.destChainSelector)
145
164
  const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp, source)
146
- const commitStore = await dest.getCommitStoreForOffRamp(offRamp)
147
- const commit = await dest.getCommitReport({ ...argv, commitStore, request })
148
165
 
166
+ const verifications = await dest.getVerifications({ ...argv, offRamp, request })
149
167
  switch (argv.format) {
150
168
  case Format.log:
151
- logger.log('commit =', commit)
169
+ logger.log('commit =', verifications)
152
170
  break
153
171
  case Format.pretty:
154
172
  logger.info('Commit (dest):')
155
- await prettyCommit.call(ctx, dest, commit, request)
173
+ await prettyVerifications.call(ctx, dest, verifications, request)
156
174
  break
157
175
  case Format.json:
158
- logger.info(JSON.stringify(commit, bigIntReplacer, 2))
176
+ logger.info(JSON.stringify(verifications, bigIntReplacer, 2))
159
177
  break
160
178
  }
161
179
 
162
- const messagesInBatch = await source.getMessagesInBatch(request, commit.report, argv)
163
- const execReportProof = calculateManualExecProof(
164
- messagesInBatch,
165
- request.lane,
166
- request.message.messageId,
167
- commit.report.merkleRoot,
168
- dest,
169
- )
170
-
171
- const offchainTokenData = await source.getOffchainTokenData(request)
172
- const execReport: ExecutionReport = {
173
- ...execReportProof,
174
- message: request.message,
175
- offchainTokenData,
176
- }
177
-
178
180
  if (argv.estimateGasLimit != null) {
179
181
  let estimated = await estimateReceiveExecution({
180
182
  source,
@@ -185,7 +187,11 @@ async function manualExec(
185
187
  logger.info('Estimated gasLimit override:', estimated)
186
188
  estimated += Math.ceil((estimated * argv.estimateGasLimit) / 100)
187
189
  const origLimit = Number(
188
- 'gasLimit' in request.message ? request.message.gasLimit : request.message.computeUnits,
190
+ 'ccipReceiveGasLimit' in request.message
191
+ ? request.message.ccipReceiveGasLimit
192
+ : 'gasLimit' in request.message
193
+ ? request.message.gasLimit
194
+ : request.message.computeUnits,
189
195
  )
190
196
  if (origLimit >= estimated) {
191
197
  logger.warn(
@@ -202,8 +208,10 @@ async function manualExec(
202
208
  }
203
209
  }
204
210
 
211
+ const input = await source.getExecutionInput({ ...argv, request, verifications })
212
+
205
213
  const [, wallet] = await loadChainWallet(dest, argv)
206
- const receipt = await dest.executeReport({ ...argv, offRamp, execReport, wallet })
214
+ const receipt = await dest.execute({ ...argv, offRamp, input, wallet })
207
215
 
208
216
  switch (argv.format) {
209
217
  case Format.log:
@@ -1,3 +1,21 @@
1
+ /**
2
+ * CCIP CLI Parse Command
3
+ *
4
+ * Parses and decodes various data formats including errors, revert reasons,
5
+ * function calls, and event data. Supports hex, base64, and chain-specific formats.
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Parse a revert reason
10
+ * ccip-cli parse 0x08c379a0...
11
+ *
12
+ * # Parse event data
13
+ * ccip-cli parse 0xEventData...
14
+ * ```
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
1
19
  import {
2
20
  CCIPDataParseError,
3
21
  bigIntReplacer,
@@ -1,3 +1,24 @@
1
+ /**
2
+ * CCIP CLI Send Command
3
+ *
4
+ * Sends a cross-chain message via CCIP. Supports data payloads, token transfers,
5
+ * custom gas limits, and various fee payment options.
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Send a message with data
10
+ * ccip-cli send -s sepolia -d fuji -r 0xRouter... --to 0xReceiver... --data "hello"
11
+ *
12
+ * # Send tokens
13
+ * ccip-cli send -s sepolia -d fuji -r 0xRouter... -t 0xToken=1.5
14
+ *
15
+ * # Pay fee in LINK
16
+ * ccip-cli send -s sepolia -d fuji -r 0xRouter... --fee-token LINK
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
1
22
  import {
2
23
  type ChainStatic,
3
24
  type ExtraArgs,
@@ -327,10 +348,8 @@ async function sendMessage(
327
348
  )
328
349
  await showRequests(ctx, {
329
350
  ...argv,
330
- txHash: request.tx.hash,
331
- 'tx-hash': request.tx.hash,
332
- 'id-from-source': undefined,
333
- idFromSource: undefined,
351
+ txHashOrId: request.tx.hash,
352
+ 'tx-hash-or-id': request.tx.hash,
334
353
  'log-index': undefined,
335
354
  logIndex: undefined,
336
355
  })
@@ -1,14 +1,37 @@
1
+ /**
2
+ * CCIP CLI Show Command
3
+ *
4
+ * Displays detailed information about a CCIP message, including its status,
5
+ * commit report, and execution receipts across source and destination chains.
6
+ *
7
+ * @example
8
+ * ```bash
9
+ * # Show message details
10
+ * ccip-cli show 0xSourceTxHash...
11
+ *
12
+ * # Wait for execution
13
+ * ccip-cli show 0xSourceTxHash... --wait
14
+ *
15
+ * # Output as JSON
16
+ * ccip-cli show 0xSourceTxHash... --format json
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
1
22
  import {
2
- type CCIPRequest,
3
- type ChainTransaction,
23
+ type Chain,
24
+ CCIPAPIClient,
4
25
  CCIPExecTxRevertedError,
26
+ CCIPMessageIdNotFoundError,
27
+ CCIPTransactionNotFoundError,
5
28
  ExecutionState,
6
29
  MessageStatus,
7
30
  bigIntReplacer,
8
31
  discoverOffRamp,
9
32
  isSupportedTxHash,
10
- networkInfo,
11
33
  } from '@chainlink/ccip-sdk/src/index.ts'
34
+ import { isHexString } from 'ethers'
12
35
  import type { Argv } from 'yargs'
13
36
 
14
37
  import type { GlobalOpts } from '../index.ts'
@@ -16,16 +39,16 @@ import { type Ctx, Format } from './types.ts'
16
39
  import {
17
40
  getCtx,
18
41
  logParsedError,
19
- prettyCommit,
20
42
  prettyReceipt,
21
43
  prettyRequest,
22
44
  prettyTable,
45
+ prettyVerifications,
23
46
  selectRequest,
24
47
  withDateTimestamp,
25
48
  } from './utils.ts'
26
49
  import { fetchChainsFromRpcs } from '../providers/index.ts'
27
50
 
28
- export const command = ['show <tx-hash>', '* <tx-hash>']
51
+ export const command = ['show <tx-hash-or-id>', '* <tx-hash-or-id>']
29
52
  export const describe = 'Show details of a CCIP request'
30
53
 
31
54
  /**
@@ -35,23 +58,18 @@ export const describe = 'Show details of a CCIP request'
35
58
  */
36
59
  export const builder = (yargs: Argv) =>
37
60
  yargs
38
- .positional('tx-hash', {
61
+ .positional('tx-hash-or-id', {
39
62
  type: 'string',
40
63
  demandOption: true,
41
- describe: 'transaction hash of the request (source) message',
64
+ describe: 'transaction hash or message ID (32-byte hex) of the CCIP request',
42
65
  })
43
- .check(({ txHash }) => isSupportedTxHash(txHash))
66
+ .check(({ txHashOrId }) => isSupportedTxHash(txHashOrId))
44
67
  .options({
45
68
  'log-index': {
46
69
  type: 'number',
47
70
  describe:
48
71
  'Pre-select a message request by logIndex, if more than one in tx; by default, a selection menu is shown',
49
72
  },
50
- 'id-from-source': {
51
- type: 'string',
52
- describe:
53
- 'Search by messageId instead of txHash; requires `[onRamp@]sourceNetwork` (onRamp address may be required in some chains)',
54
- },
55
73
  wait: {
56
74
  type: 'boolean',
57
75
  describe: 'Wait for (first) execution',
@@ -77,22 +95,42 @@ export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']>
77
95
  */
78
96
  export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
79
97
  const { logger } = ctx
80
- let source, getChain, tx: ChainTransaction, request: CCIPRequest
81
- // messageId not yet implemented for Solana
82
- if (argv.idFromSource) {
83
- getChain = fetchChainsFromRpcs(ctx, argv)
84
- let idFromSource, onRamp
85
- if (argv.idFromSource.includes('@')) {
86
- ;[onRamp, idFromSource] = argv.idFromSource.split('@') as [string, string]
87
- } else idFromSource = argv.idFromSource
88
- const sourceNetwork = networkInfo(idFromSource)
89
- source = await getChain(sourceNetwork.chainId)
90
- request = await source.getMessageById(argv.txHash, { ...argv, onRamp })
91
- } else {
92
- const [getChain_, tx$] = fetchChainsFromRpcs(ctx, argv, argv.txHash)
93
- getChain = getChain_
94
- ;[source, tx] = await tx$
95
- request = await selectRequest(await source.getMessagesInTx(tx), 'to know more', argv)
98
+ const [getChain, tx$] = fetchChainsFromRpcs(ctx, argv, argv.txHashOrId)
99
+
100
+ let source: Chain | undefined
101
+ let request$ = (async () => {
102
+ const [source_, tx] = await tx$
103
+ source = source_
104
+ return selectRequest(await source_.getMessagesInTx(tx), 'to know more', argv)
105
+ })()
106
+
107
+ if (argv.noApi !== true) {
108
+ const apiClient = CCIPAPIClient.fromUrl(undefined, ctx)
109
+ if (isHexString(argv.txHashOrId, 32)) {
110
+ request$ = Promise.any([request$, apiClient.getMessageById(argv.txHashOrId)])
111
+ }
112
+ }
113
+ let request
114
+ try {
115
+ request = await request$
116
+ } catch (err) {
117
+ if (err instanceof AggregateError && err.errors.length === 2) {
118
+ if (!(err.errors[0] instanceof CCIPTransactionNotFoundError)) throw err.errors[0] as Error
119
+ else if (!(err.errors[1] instanceof CCIPMessageIdNotFoundError)) throw err.errors[1] as Error
120
+ }
121
+ throw err
122
+ }
123
+ if (!source) {
124
+ try {
125
+ source = await getChain(request.lane.sourceChainSelector)
126
+ } catch (err) {
127
+ logger.debug(
128
+ 'Fetched messageId from API, but failed find a source',
129
+ request.lane.sourceChainSelector,
130
+ 'RPC endpoint:',
131
+ err,
132
+ )
133
+ }
96
134
  }
97
135
 
98
136
  switch (argv.format) {
@@ -101,7 +139,7 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
101
139
  break
102
140
  }
103
141
  case Format.pretty:
104
- await prettyRequest.call(ctx, source, request)
142
+ await prettyRequest.call(ctx, request, source)
105
143
  break
106
144
  case Format.json:
107
145
  logger.info(JSON.stringify(request, bigIntReplacer, 2))
@@ -112,6 +150,7 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
112
150
  context: { error: request.tx.error },
113
151
  })
114
152
 
153
+ if (!source) return
115
154
  if (argv.wait === false) return // `false` used by call at end of `send` command without `--wait`
116
155
 
117
156
  let cancelWaitFinalized: (() => void) | undefined
@@ -152,15 +191,14 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
152
191
 
153
192
  const dest = await getChain(request.lane.destChainSelector)
154
193
  const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp, source)
155
- const commitStore = await dest.getCommitStoreForOffRamp(offRamp)
156
194
 
157
- let cancelWaitCommit: (() => void) | undefined
158
- const commit$ = (async () => {
159
- const commit = await dest.getCommitReport({
160
- commitStore,
195
+ let cancelWaitVerifications: (() => void) | undefined
196
+ const verifications$ = (async () => {
197
+ const verifications = await dest.getVerifications({
198
+ offRamp,
161
199
  request,
162
200
  ...argv,
163
- watch: argv.wait && new Promise<void>((resolve) => (cancelWaitCommit = resolve)),
201
+ watch: argv.wait && new Promise<void>((resolve) => (cancelWaitVerifications = resolve)),
164
202
  })
165
203
  cancelWaitFinalized?.()
166
204
  await finalized$
@@ -168,20 +206,23 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
168
206
  logger.info(`[${MessageStatus.Committed}] Commit report accepted on destination chain`)
169
207
  switch (argv.format) {
170
208
  case Format.log:
171
- logger.log('commit =', commit)
209
+ logger.log('commit =', verifications)
172
210
  break
173
211
  case Format.pretty:
174
- await prettyCommit.call(ctx, dest, commit, request)
212
+ await prettyVerifications.call(ctx, dest, verifications, request)
175
213
  break
176
214
  case Format.json:
177
- logger.info(JSON.stringify(commit, bigIntReplacer, 2))
215
+ logger.info(JSON.stringify(verifications, bigIntReplacer, 2))
178
216
  break
179
217
  }
180
218
  if (argv.wait)
181
219
  logger.info(`[${MessageStatus.Blessed}] Waiting for execution on destination chain...`)
182
220
  else logger.info('Receipts (dest):')
183
- return commit
184
- })()
221
+ return verifications
222
+ })().catch((err) => {
223
+ logger.debug('getVerifications error:', err)
224
+ return undefined
225
+ })
185
226
 
186
227
  let found = false
187
228
  for await (const receipt of dest.getExecutionReceipts({
@@ -190,11 +231,11 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
190
231
  messageId: request.message.messageId,
191
232
  sourceChainSelector: request.message.sourceChainSelector,
192
233
  startTime: request.tx.timestamp,
193
- commit: !argv.wait ? await commit$ : undefined,
234
+ verifications: !argv.wait ? await verifications$ : undefined,
194
235
  watch: argv.wait && ctx.destroy$,
195
236
  })) {
196
- cancelWaitCommit?.()
197
- await commit$
237
+ cancelWaitVerifications?.()
238
+ await verifications$
198
239
  const status =
199
240
  receipt.receipt.state === ExecutionState.Success
200
241
  ? MessageStatus.Success