@chainlink/ccip-cli 0.91.1 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +60 -55
  2. package/dist/commands/manual-exec.d.ts +7 -1
  3. package/dist/commands/manual-exec.d.ts.map +1 -1
  4. package/dist/commands/manual-exec.js +16 -7
  5. package/dist/commands/manual-exec.js.map +1 -1
  6. package/dist/commands/parse.d.ts.map +1 -1
  7. package/dist/commands/parse.js +6 -5
  8. package/dist/commands/parse.js.map +1 -1
  9. package/dist/commands/send.d.ts +6 -1
  10. package/dist/commands/send.d.ts.map +1 -1
  11. package/dist/commands/send.js +27 -22
  12. package/dist/commands/send.js.map +1 -1
  13. package/dist/commands/show.d.ts +10 -1
  14. package/dist/commands/show.d.ts.map +1 -1
  15. package/dist/commands/show.js +85 -27
  16. package/dist/commands/show.js.map +1 -1
  17. package/dist/commands/supported-tokens.d.ts.map +1 -1
  18. package/dist/commands/supported-tokens.js +6 -6
  19. package/dist/commands/supported-tokens.js.map +1 -1
  20. package/dist/commands/types.d.ts +3 -2
  21. package/dist/commands/types.d.ts.map +1 -1
  22. package/dist/commands/utils.d.ts +27 -7
  23. package/dist/commands/utils.d.ts.map +1 -1
  24. package/dist/commands/utils.js +97 -35
  25. package/dist/commands/utils.js.map +1 -1
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/providers/aptos.d.ts.map +1 -1
  31. package/dist/providers/aptos.js +3 -5
  32. package/dist/providers/aptos.js.map +1 -1
  33. package/dist/providers/evm.d.ts.map +1 -1
  34. package/dist/providers/evm.js +2 -4
  35. package/dist/providers/evm.js.map +1 -1
  36. package/dist/providers/index.d.ts +6 -5
  37. package/dist/providers/index.d.ts.map +1 -1
  38. package/dist/providers/index.js +104 -71
  39. package/dist/providers/index.js.map +1 -1
  40. package/dist/providers/solana.d.ts.map +1 -1
  41. package/dist/providers/solana.js +5 -4
  42. package/dist/providers/solana.js.map +1 -1
  43. package/dist/providers/sui.d.ts +10 -0
  44. package/dist/providers/sui.d.ts.map +1 -0
  45. package/dist/providers/sui.js +15 -0
  46. package/dist/providers/sui.js.map +1 -0
  47. package/dist/providers/ton.d.ts +10 -0
  48. package/dist/providers/ton.d.ts.map +1 -0
  49. package/dist/providers/ton.js +55 -0
  50. package/dist/providers/ton.js.map +1 -0
  51. package/package.json +13 -13
  52. package/src/commands/manual-exec.ts +18 -6
  53. package/src/commands/parse.ts +9 -5
  54. package/src/commands/send.ts +36 -28
  55. package/src/commands/show.ts +94 -31
  56. package/src/commands/supported-tokens.ts +6 -9
  57. package/src/commands/types.ts +3 -2
  58. package/src/commands/utils.ts +118 -43
  59. package/src/index.ts +4 -4
  60. package/src/providers/aptos.ts +3 -5
  61. package/src/providers/evm.ts +2 -4
  62. package/src/providers/index.ts +120 -94
  63. package/src/providers/solana.ts +5 -6
  64. package/src/providers/sui.ts +15 -0
  65. package/src/providers/ton.ts +68 -0
@@ -1,9 +1,13 @@
1
- import { bigIntReplacer, supportedChains } from '@chainlink/ccip-sdk/src/index.ts'
1
+ import {
2
+ CCIPDataParseError,
3
+ bigIntReplacer,
4
+ supportedChains,
5
+ } from '@chainlink/ccip-sdk/src/index.ts'
2
6
  import type { Argv } from 'yargs'
3
7
 
4
8
  import type { GlobalOpts } from '../index.ts'
5
9
  import { type Ctx, Format } from './types.ts'
6
- import { getCtx, prettyTable } from './utils.ts'
10
+ import { getCtx, logParsedError, prettyTable } from './utils.ts'
7
11
 
8
12
  export const command = ['parse <data>', 'parseBytes <data>', 'parseData <data>']
9
13
  export const describe =
@@ -26,12 +30,12 @@ export const builder = (yargs: Argv) =>
26
30
  * @param argv - Command line arguments.
27
31
  */
28
32
  export function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
29
- const [, ctx] = getCtx(argv)
33
+ const [ctx] = getCtx(argv)
30
34
  try {
31
35
  parseBytes(ctx, argv)
32
36
  } catch (err) {
33
37
  process.exitCode = 1
34
- ctx.logger.error(err)
38
+ if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
35
39
  }
36
40
  }
37
41
 
@@ -46,7 +50,7 @@ function parseBytes(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
46
50
  // pass
47
51
  }
48
52
  }
49
- if (!parsed) throw new Error('Unknown data')
53
+ if (!parsed) throw new CCIPDataParseError(argv.data)
50
54
 
51
55
  switch (argv.format) {
52
56
  case Format.log: {
@@ -4,8 +4,10 @@ import {
4
4
  type ChainStatic,
5
5
  type EVMChain,
6
6
  type ExtraArgs,
7
+ CCIPArgumentInvalidError,
8
+ CCIPChainFamilyUnsupportedError,
9
+ CCIPTokenNotFoundError,
7
10
  ChainFamily,
8
- bigIntReplacer,
9
11
  estimateExecGasForRequest,
10
12
  getDataBytes,
11
13
  networkInfo,
@@ -15,14 +17,9 @@ import { type BytesLike, dataLength, formatUnits, toUtf8Bytes } from 'ethers'
15
17
  import type { Argv } from 'yargs'
16
18
 
17
19
  import type { GlobalOpts } from '../index.ts'
18
- import { type Ctx, Format } from './types.ts'
19
- import {
20
- getCtx,
21
- logParsedError,
22
- parseTokenAmounts,
23
- prettyRequest,
24
- withDateTimestamp,
25
- } from './utils.ts'
20
+ import { showRequests } from './show.ts'
21
+ import type { Ctx } from './types.ts'
22
+ import { getCtx, logParsedError, parseTokenAmounts } from './utils.ts'
26
23
  import { fetchChainsFromRpcs, loadChainWallet } from '../providers/index.ts'
27
24
 
28
25
  export const command = 'send <source> <router> <dest>'
@@ -127,6 +124,11 @@ export const builder = (yargs: Argv) =>
127
124
  describe:
128
125
  "Approve the maximum amount of tokens to transfer; default=false approves only what's needed",
129
126
  },
127
+ wait: {
128
+ type: 'boolean',
129
+ default: false,
130
+ describe: 'Wait for execution',
131
+ },
130
132
  })
131
133
  .check(
132
134
  ({ 'transfer-tokens': transferTokens }) =>
@@ -138,13 +140,13 @@ export const builder = (yargs: Argv) =>
138
140
  * @param argv - Command line arguments.
139
141
  */
140
142
  export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
141
- const [controller, ctx] = getCtx(argv)
143
+ const [ctx, destroy] = getCtx(argv)
142
144
  return sendMessage(ctx, argv)
143
145
  .catch((err) => {
144
146
  process.exitCode = 1
145
147
  if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
146
148
  })
147
- .finally(() => controller.abort('Exited'))
149
+ .finally(destroy)
148
150
  }
149
151
 
150
152
  async function sendMessage(
@@ -185,7 +187,10 @@ async function sendMessage(
185
187
  tokenReceiver = receiver
186
188
  receiver = '11111111111111111111111111111111'
187
189
  } else {
188
- throw new Error('--token-receiver is required when sending tokens with data')
190
+ throw new CCIPArgumentInvalidError(
191
+ 'token-receiver',
192
+ 'required when sending tokens with data to Solana',
193
+ )
189
194
  }
190
195
 
191
196
  if (argv.account) {
@@ -198,13 +203,16 @@ async function sendMessage(
198
203
  })
199
204
  } else accounts = [] as string[]
200
205
  } else if (argv.tokenReceiver || argv.account?.length) {
201
- throw new Error('--token-receiver and --account intended only for Solana dest')
206
+ throw new CCIPArgumentInvalidError(
207
+ 'token-receiver/account',
208
+ 'only valid for Solana destination',
209
+ )
202
210
  }
203
211
 
204
212
  let walletAddress, wallet
205
213
  if (!receiver) {
206
214
  if (sourceNetwork.family !== destNetwork.family)
207
- throw new Error('--receiver is required when sending to a different chain family')
215
+ throw new CCIPArgumentInvalidError('receiver', 'required for cross-family transfers')
208
216
  ;[walletAddress, wallet] = await loadChainWallet(source, argv)
209
217
  receiver = walletAddress // send to self if same family
210
218
  }
@@ -212,7 +220,9 @@ async function sendMessage(
212
220
  if (argv.estimateGasLimit != null || argv.onlyEstimate) {
213
221
  // TODO: implement for all chain families
214
222
  if (destNetwork.family !== ChainFamily.EVM)
215
- throw new Error(`Estimating gasLimit supported only on EVM, got=${destNetwork.family}`)
223
+ throw new CCIPChainFamilyUnsupportedError(destNetwork.family, {
224
+ context: { feature: 'gas estimation' },
225
+ })
216
226
  const dest = (await getChain(destNetwork.chainSelector)) as unknown as EVMChain
217
227
  const onRamp = await source.getOnRampForRouter(argv.router, destNetwork.chainSelector)
218
228
  const lane = {
@@ -270,7 +280,7 @@ async function sendMessage(
270
280
  feeTokenInfo = info
271
281
  break
272
282
  }
273
- if (!feeTokenInfo) throw new Error(`Fee token "${argv.feeToken}" not found`)
283
+ if (!feeTokenInfo) throw new CCIPTokenNotFoundError(argv.feeToken)
274
284
  }
275
285
  } else {
276
286
  const nativeToken = await source.getNativeTokenForRouter(argv.router)
@@ -316,18 +326,16 @@ async function sendMessage(
316
326
  ', tx =>',
317
327
  request.tx.hash,
318
328
  ', messageId =>',
319
- request.message.header.messageId,
329
+ request.message.messageId,
320
330
  )
321
331
 
322
- switch (argv.format) {
323
- case Format.log:
324
- logger.log(`message ${request.log.index} =`, withDateTimestamp(request))
325
- break
326
- case Format.pretty:
327
- await prettyRequest.call(ctx, source, request)
328
- break
329
- case Format.json:
330
- logger.info(JSON.stringify(request, bigIntReplacer, 2))
331
- break
332
- }
332
+ await showRequests(ctx, {
333
+ ...argv,
334
+ txHash: request.tx.hash,
335
+ 'tx-hash': request.tx.hash,
336
+ 'id-from-source': undefined,
337
+ idFromSource: undefined,
338
+ 'log-index': undefined,
339
+ logIndex: undefined,
340
+ })
333
341
  }
@@ -1,10 +1,11 @@
1
- import util from 'util'
2
-
3
1
  import {
4
2
  type CCIPRequest,
5
3
  type ChainTransaction,
4
+ CCIPExecTxRevertedError,
5
+ CCIPNotImplementedError,
6
6
  bigIntReplacer,
7
7
  discoverOffRamp,
8
+ isSupportedTxHash,
8
9
  networkInfo,
9
10
  } from '@chainlink/ccip-sdk/src/index.ts'
10
11
  import type { Argv } from 'yargs'
@@ -17,6 +18,7 @@ import {
17
18
  prettyCommit,
18
19
  prettyReceipt,
19
20
  prettyRequest,
21
+ prettyTable,
20
22
  selectRequest,
21
23
  withDateTimestamp,
22
24
  } from './utils.ts'
@@ -37,6 +39,7 @@ export const builder = (yargs: Argv) =>
37
39
  demandOption: true,
38
40
  describe: 'transaction hash of the request (source) message',
39
41
  })
42
+ .check(({ txHash }) => isSupportedTxHash(txHash))
40
43
  .options({
41
44
  'log-index': {
42
45
  type: 'number',
@@ -48,6 +51,10 @@ export const builder = (yargs: Argv) =>
48
51
  describe:
49
52
  'Search by messageId instead of txHash; requires `[onRamp@]sourceNetwork` (onRamp address may be required in some chains)',
50
53
  },
54
+ wait: {
55
+ type: 'boolean',
56
+ describe: 'Wait for (first) execution',
57
+ },
51
58
  })
52
59
 
53
60
  /**
@@ -55,16 +62,19 @@ export const builder = (yargs: Argv) =>
55
62
  * @param argv - Command line arguments.
56
63
  */
57
64
  export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
58
- const [controller, ctx] = getCtx(argv)
65
+ const [ctx, destroy] = getCtx(argv)
59
66
  return showRequests(ctx, argv)
60
67
  .catch((err) => {
61
68
  process.exitCode = 1
62
69
  if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
63
70
  })
64
- .finally(() => controller.abort('Exited'))
71
+ .finally(destroy)
65
72
  }
66
73
 
67
- async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
74
+ /**
75
+ * Show details of a request.
76
+ */
77
+ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
68
78
  const { logger } = ctx
69
79
  let source, getChain, tx: ChainTransaction, request: CCIPRequest
70
80
  // messageId not yet implemented for Solana
@@ -77,7 +87,7 @@ async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
77
87
  const sourceNetwork = networkInfo(idFromSource)
78
88
  source = await getChain(sourceNetwork.chainId)
79
89
  if (!source.fetchRequestById)
80
- throw new Error(`fetchRequestById not implemented for ${source.constructor.name}`)
90
+ throw new CCIPNotImplementedError(`fetchRequestById for ${source.constructor.name}`)
81
91
  request = await source.fetchRequestById(argv.txHash, onRamp, argv)
82
92
  } else {
83
93
  const [getChain_, tx$] = fetchChainsFromRpcs(ctx, argv, argv.txHash)
@@ -86,52 +96,104 @@ async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
86
96
  request = await selectRequest(await source.fetchRequestsInTx(tx), 'to know more', argv)
87
97
  }
88
98
 
89
- const offchainTokenData = await source.fetchOffchainTokenData(request)
90
-
91
99
  switch (argv.format) {
92
100
  case Format.log: {
93
- logger.log(
94
- `message ${request.log.index} =`,
95
- withDateTimestamp(request),
96
- '\nattestations =',
97
- offchainTokenData,
98
- )
101
+ logger.log(`message ${request.log.index} =`, withDateTimestamp(request))
99
102
  break
100
103
  }
101
104
  case Format.pretty:
102
- await prettyRequest.call(ctx, source, request, offchainTokenData)
105
+ await prettyRequest.call(ctx, source, request)
103
106
  break
104
107
  case Format.json:
105
- logger.info(JSON.stringify({ ...request, offchainTokenData }, bigIntReplacer, 2))
108
+ logger.info(JSON.stringify(request, bigIntReplacer, 2))
106
109
  break
107
110
  }
108
- if (request.tx.error) throw new Error(`Request tx reverted: ${util.inspect(request.tx.error)}`)
111
+ if (request.tx.error)
112
+ throw new CCIPExecTxRevertedError(request.log.transactionHash, {
113
+ context: { error: request.tx.error },
114
+ })
115
+
116
+ if (argv.wait === false) return // `false` used by call at end of `send` command without `--wait`
117
+
118
+ let cancelWaitFinalized: (() => void) | undefined
119
+ const finalized$ = (async () => {
120
+ if (argv.wait) {
121
+ logger.info('Waiting for finalization...')
122
+ await source.waitFinalized(
123
+ request,
124
+ undefined,
125
+ new Promise<void>((resolve) => (cancelWaitFinalized = resolve)),
126
+ )
127
+ logger.info(`Transaction "${request.log.transactionHash}" finalized ✅`)
128
+ }
129
+
130
+ const offchainTokenData = await source.fetchOffchainTokenData(request)
131
+ if (offchainTokenData?.length && offchainTokenData.some((d) => !!d)) {
132
+ switch (argv.format) {
133
+ case Format.log: {
134
+ logger.log('attestations =', offchainTokenData)
135
+ break
136
+ }
137
+ case Format.pretty:
138
+ logger.info('Attestations:')
139
+ for (const attestation of offchainTokenData) {
140
+ const { _tag: type, ...rest } = attestation!
141
+ prettyTable.call(ctx, { type, ...rest })
142
+ }
143
+ break
144
+ case Format.json:
145
+ logger.info(JSON.stringify({ attestations: offchainTokenData }, bigIntReplacer, 2))
146
+ break
147
+ }
148
+ }
149
+
150
+ if (argv.wait) logger.info('Waiting for Commit (dest)...')
151
+ else logger.info('Commit (dest):')
152
+ })()
109
153
 
110
154
  const dest = await getChain(request.lane.destChainSelector)
111
155
  const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp, source)
112
156
  const commitStore = await dest.getCommitStoreForOffRamp(offRamp)
113
157
 
114
- const commit = await dest.fetchCommitReport(commitStore, request, argv)
115
- switch (argv.format) {
116
- case Format.log:
117
- logger.log('commit =', commit)
118
- break
119
- case Format.pretty:
120
- await prettyCommit.call(ctx, dest, commit, request)
121
- break
122
- case Format.json:
123
- logger.info(JSON.stringify(commit, bigIntReplacer, 2))
124
- break
125
- }
158
+ let cancelWaitCommit: (() => void) | undefined
159
+ const commit$ = (async () => {
160
+ const commit = await dest.fetchCommitReport(commitStore, request, {
161
+ ...argv,
162
+ watch: argv.wait && new Promise<void>((resolve) => (cancelWaitCommit = resolve)),
163
+ })
164
+ cancelWaitFinalized?.()
165
+ if (!commit) return
166
+ await finalized$
167
+ switch (argv.format) {
168
+ case Format.log:
169
+ logger.log('commit =', commit)
170
+ break
171
+ case Format.pretty:
172
+ await prettyCommit.call(ctx, dest, commit, request)
173
+ break
174
+ case Format.json:
175
+ logger.info(JSON.stringify(commit, bigIntReplacer, 2))
176
+ break
177
+ }
178
+ if (argv.wait) logger.info('Waiting for Receipt (dest):')
179
+ else logger.info('Receipts (dest):')
180
+ return commit
181
+ })()
126
182
 
127
183
  let found = false
128
- for await (const receipt of dest.fetchExecutionReceipts(offRamp, request, commit, argv)) {
184
+ for await (const receipt of dest.fetchExecutionReceipts(
185
+ offRamp,
186
+ request,
187
+ !argv.wait ? await commit$ : undefined,
188
+ { ...argv, watch: argv.wait && ctx.destroy$ },
189
+ )) {
190
+ cancelWaitCommit?.()
191
+ await commit$
129
192
  switch (argv.format) {
130
193
  case Format.log:
131
194
  logger.log('receipt =', withDateTimestamp(receipt))
132
195
  break
133
196
  case Format.pretty:
134
- if (!found) logger.info('Receipts (dest):')
135
197
  prettyReceipt.call(
136
198
  ctx,
137
199
  receipt,
@@ -145,6 +207,7 @@ async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
145
207
  break
146
208
  }
147
209
  found = true
210
+ if (argv.wait) break
148
211
  }
149
212
  if (!found) logger.warn(`No execution receipt found for request`)
150
213
  }
@@ -24,6 +24,7 @@
24
24
  import {
25
25
  type Chain,
26
26
  type RateLimiterState,
27
+ CCIPTokenNotConfiguredError,
27
28
  bigIntReplacer,
28
29
  networkInfo,
29
30
  } from '@chainlink/ccip-sdk/src/index.ts'
@@ -70,13 +71,13 @@ export const builder = (yargs: Argv) =>
70
71
  * @param argv - Command line arguments.
71
72
  */
72
73
  export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
73
- const [controller, ctx] = getCtx(argv)
74
+ const [ctx, destroy] = getCtx(argv)
74
75
  return getSupportedTokens(ctx, argv)
75
76
  .catch((err) => {
76
77
  process.exitCode = 1
77
78
  if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
78
79
  })
79
- .finally(() => controller.abort('Exited'))
80
+ .finally(destroy)
80
81
  }
81
82
 
82
83
  async function getSupportedTokens(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
@@ -111,8 +112,7 @@ async function getSupportedTokens(ctx: Ctx, argv: Parameters<typeof handler>[0])
111
112
  if (!info) return // format != pretty
112
113
  registryConfig = await source.getRegistryTokenConfig(registry, info.token)
113
114
  tokenPool = registryConfig.tokenPool
114
- if (!tokenPool)
115
- throw new Error(`TokenPool not set in tokenAdminRegistry=${registry} for token=${info.token}`)
115
+ if (!tokenPool) throw new CCIPTokenNotConfiguredError(info.token, registry)
116
116
  poolConfigs = await source.getTokenPoolConfigs(tokenPool)
117
117
  } else {
118
118
  if (!argv.token) {
@@ -131,10 +131,7 @@ async function getSupportedTokens(ctx: Ctx, argv: Parameters<typeof handler>[0])
131
131
 
132
132
  registryConfig = await source.getRegistryTokenConfig(registry, argv.token)
133
133
  tokenPool = registryConfig.tokenPool
134
- if (!tokenPool)
135
- throw new Error(
136
- `TokenPool not set in tokenAdminRegistry=${registry} for token=${argv.token}`,
137
- )
134
+ if (!tokenPool) throw new CCIPTokenNotConfiguredError(argv.token, registry)
138
135
  poolConfigs = await source.getTokenPoolConfigs(tokenPool)
139
136
  }
140
137
 
@@ -212,7 +209,7 @@ async function listTokens({ logger }: Ctx, source: Chain, registry: string, argv
212
209
  if (argv.format !== Format.pretty) return // Format.pretty interactive search and details
213
210
 
214
211
  return search({
215
- message: 'Select a token to know more:',
212
+ message: 'Select a supported token to know more:',
216
213
  pageSize: 20,
217
214
  source: (term) => {
218
215
  const filtered = infos.filter(
@@ -13,9 +13,10 @@ export type Format = (typeof Format)[keyof typeof Format]
13
13
  * Command context
14
14
  */
15
15
  export type Ctx = {
16
- destroy$: AbortSignal
16
+ destroy$: Promise<unknown>
17
17
  logger: Logger & {
18
- table: (tabularData: unknown, properties?: readonly string[]) => void
18
+ table: (tabularData: unknown, properties?: string[]) => void
19
19
  log: (...args: unknown[]) => void
20
20
  }
21
+ verbose?: boolean
21
22
  }