@chainlink/ccip-cli 0.93.0 → 0.95.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 (37) hide show
  1. package/README.md +96 -40
  2. package/dist/commands/manual-exec.d.ts.map +1 -1
  3. package/dist/commands/manual-exec.js +11 -11
  4. package/dist/commands/manual-exec.js.map +1 -1
  5. package/dist/commands/send.d.ts +6 -10
  6. package/dist/commands/send.d.ts.map +1 -1
  7. package/dist/commands/send.js +72 -89
  8. package/dist/commands/send.js.map +1 -1
  9. package/dist/commands/show.d.ts.map +1 -1
  10. package/dist/commands/show.js +2 -4
  11. package/dist/commands/show.js.map +1 -1
  12. package/dist/commands/supported-tokens.d.ts +2 -2
  13. package/dist/commands/supported-tokens.d.ts.map +1 -1
  14. package/dist/commands/supported-tokens.js +22 -10
  15. package/dist/commands/supported-tokens.js.map +1 -1
  16. package/dist/commands/token.d.ts +26 -0
  17. package/dist/commands/token.d.ts.map +1 -0
  18. package/dist/commands/token.js +105 -0
  19. package/dist/commands/token.js.map +1 -0
  20. package/dist/commands/utils.d.ts.map +1 -1
  21. package/dist/commands/utils.js +21 -7
  22. package/dist/commands/utils.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +2 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/providers/index.d.ts.map +1 -1
  27. package/dist/providers/index.js +3 -3
  28. package/dist/providers/index.js.map +1 -1
  29. package/package.json +17 -17
  30. package/src/commands/manual-exec.ts +13 -23
  31. package/src/commands/send.ts +78 -107
  32. package/src/commands/show.ts +1 -4
  33. package/src/commands/supported-tokens.ts +21 -10
  34. package/src/commands/token.ts +132 -0
  35. package/src/commands/utils.ts +23 -6
  36. package/src/index.ts +2 -2
  37. package/src/providers/index.ts +7 -3
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Token balance query command.
3
+ * Queries native or token balance for an address.
4
+ */
5
+
6
+ import { type ChainStatic, networkInfo } from '@chainlink/ccip-sdk/src/index.ts'
7
+ import { formatUnits } from 'ethers'
8
+ import type { Argv } from 'yargs'
9
+
10
+ import { type Ctx, Format } from './types.ts'
11
+ import { getCtx, logParsedError, prettyTable } from './utils.ts'
12
+ import type { GlobalOpts } from '../index.ts'
13
+ import { fetchChainsFromRpcs } from '../providers/index.ts'
14
+
15
+ export const command = 'token'
16
+ export const describe = 'Query token balance for an address'
17
+
18
+ /**
19
+ * Yargs builder for the token command.
20
+ * @param yargs - Yargs instance.
21
+ * @returns Configured yargs instance with command options.
22
+ */
23
+ export const builder = (yargs: Argv) =>
24
+ yargs
25
+ .option('network', {
26
+ alias: 'n',
27
+ type: 'string',
28
+ demandOption: true,
29
+ describe: 'Network: chainId or name (e.g., ethereum-mainnet, solana-devnet)',
30
+ })
31
+ .option('holder', {
32
+ alias: 'H',
33
+ type: 'string',
34
+ demandOption: true,
35
+ describe: 'Wallet address to query balance for',
36
+ })
37
+ .option('token', {
38
+ alias: 't',
39
+ type: 'string',
40
+ demandOption: false,
41
+ describe: 'Token address (omit for native token balance)',
42
+ })
43
+ .example([
44
+ ['ccip-cli token -n ethereum-mainnet -H 0x1234...abcd', 'Query native ETH balance'],
45
+ [
46
+ 'ccip-cli token -n ethereum-mainnet -H 0x1234... -t 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
47
+ 'Query USDC token balance',
48
+ ],
49
+ [
50
+ 'ccip-cli token -n solana-devnet -H EPUjBP3Xf76K1VKsDSc6GupBWE8uykNksCLJgXZn87CB',
51
+ 'Query native SOL balance',
52
+ ],
53
+ ])
54
+
55
+ /**
56
+ * Handler for the token command.
57
+ * @param argv - Command line arguments.
58
+ */
59
+ export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
60
+ const [ctx, destroy] = getCtx(argv)
61
+ return queryTokenBalance(ctx, argv)
62
+ .catch((err) => {
63
+ process.exitCode = 1
64
+ if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
65
+ })
66
+ .finally(destroy)
67
+ }
68
+
69
+ async function queryTokenBalance(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
70
+ const { logger } = ctx
71
+ const networkName = networkInfo(argv.network).name
72
+ const getChain = fetchChainsFromRpcs(ctx, argv)
73
+ const chain = await getChain(networkName)
74
+
75
+ const balance = await chain.getBalance({
76
+ holder: argv.holder,
77
+ token: argv.token,
78
+ })
79
+
80
+ // Get token info for formatting (only for tokens, not native)
81
+ let tokenInfo
82
+ if (argv.token) {
83
+ argv.token = (chain.constructor as ChainStatic).getAddress(argv.token)
84
+ tokenInfo = await chain.getTokenInfo(argv.token)
85
+ }
86
+
87
+ const tokenLabel = tokenInfo?.symbol ?? 'native'
88
+ const formatted = formatUnits(
89
+ balance,
90
+ tokenInfo ? tokenInfo.decimals : (chain.constructor as ChainStatic).decimals,
91
+ )
92
+
93
+ switch (argv.format) {
94
+ case Format.json:
95
+ logger.log(
96
+ JSON.stringify(
97
+ {
98
+ network: networkName,
99
+ holder: argv.holder,
100
+ token: tokenLabel,
101
+ balance: balance.toString(),
102
+ formatted,
103
+ ...tokenInfo,
104
+ },
105
+ null,
106
+ 2,
107
+ ),
108
+ )
109
+ return
110
+ case Format.log:
111
+ logger.log(
112
+ `Balance of`,
113
+ tokenInfo ? argv.token : tokenLabel,
114
+ ':',
115
+ balance,
116
+ `=`,
117
+ tokenInfo ? `${formatted} ${tokenLabel}` : formatted,
118
+ )
119
+ return
120
+ case Format.pretty:
121
+ default:
122
+ prettyTable.call(ctx, {
123
+ network: networkName,
124
+ holder: argv.holder,
125
+ token: argv.token ?? tokenLabel,
126
+ balance,
127
+ formatted,
128
+ ...tokenInfo,
129
+ })
130
+ return
131
+ }
132
+ }
@@ -12,6 +12,7 @@ import {
12
12
  CCIPErrorCode,
13
13
  ExecutionState,
14
14
  getCCIPExplorerUrl,
15
+ getDataBytes,
15
16
  networkInfo,
16
17
  supportedChains,
17
18
  } from '@chainlink/ccip-sdk/src/index.ts'
@@ -19,11 +20,11 @@ import { select } from '@inquirer/prompts'
19
20
  import {
20
21
  dataLength,
21
22
  formatUnits,
22
- getBytes,
23
23
  hexlify,
24
24
  isBytesLike,
25
25
  isHexString,
26
26
  parseUnits,
27
+ toBigInt,
27
28
  toUtf8String,
28
29
  } from 'ethers'
29
30
  import type { PickDeep } from 'type-fest'
@@ -215,6 +216,26 @@ function omit<T extends Record<string, unknown>, K extends string>(
215
216
  return result
216
217
  }
217
218
 
219
+ // while formatData just breaks 0x bytes into 32B chunks for readability, this function first
220
+ // tests if the data looks like a UTF-8 string (with length prefix) and decode that before
221
+ function formatDataString(data: string): Record<string, string> {
222
+ const bytes = getDataBytes(data)
223
+ const isPrintableChars = (bytes_: Uint8Array) => bytes_.every((b) => 32 <= b && b <= 126)
224
+ if (bytes.length > 64 && toBigInt(bytes.subarray(0, 32)) === 32n) {
225
+ const len = toBigInt(bytes.subarray(32, 64))
226
+ if (
227
+ len < 512 &&
228
+ bytes.length - 64 === Math.ceil(Number(len) / 32) * 32 &&
229
+ isPrintableChars(bytes.subarray(64, 64 + Number(len))) &&
230
+ bytes.subarray(64 + Number(len)).every((b) => b === 0)
231
+ ) {
232
+ return { data: toUtf8String(bytes.subarray(64, 64 + Number(len))) }
233
+ }
234
+ }
235
+ if (bytes.length > 0 && isPrintableChars(bytes)) return { data: toUtf8String(bytes) }
236
+ return formatData('data', data)
237
+ }
238
+
218
239
  /**
219
240
  * Prints a CCIP request in a human-readable format.
220
241
  * @param source - Source chain instance.
@@ -289,11 +310,7 @@ export async function prettyRequest(this: Ctx, source: Chain, request: CCIPReque
289
310
  'tokens',
290
311
  await Promise.all(request.message.tokenAmounts.map(formatToken.bind(null, source))),
291
312
  ),
292
- ...(isBytesLike(request.message.data) &&
293
- dataLength(request.message.data) > 0 &&
294
- getBytes(request.message.data).every((b) => 32 <= b && b <= 126) // printable characters
295
- ? { data: toUtf8String(request.message.data) }
296
- : formatData('data', request.message.data)),
313
+ ...formatDataString(request.message.data),
297
314
  ...('accounts' in request.message ? formatArray('accounts', request.message.accounts) : {}),
298
315
  ...rest,
299
316
  })
package/src/index.ts CHANGED
@@ -11,13 +11,13 @@ import { Format } from './commands/index.ts'
11
11
  util.inspect.defaultOptions.depth = 6 // print down to tokenAmounts in requests
12
12
  // generate:nofail
13
13
  // `const VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'`
14
- const VERSION = '0.93.0-e6b317b'
14
+ const VERSION = '0.95.0-5f1a7cb'
15
15
  // generate:end
16
16
 
17
17
  const globalOpts = {
18
18
  rpcs: {
19
19
  type: 'array',
20
- alias: ['r', 'rpc'],
20
+ alias: 'rpc',
21
21
  describe: 'List of RPC endpoint URLs, ws[s] or http[s]',
22
22
  string: true,
23
23
  },
@@ -8,10 +8,10 @@ import {
8
8
  type EVMChain,
9
9
  type TONChain,
10
10
  CCIPChainFamilyUnsupportedError,
11
- CCIPNetworkFamilyUnsupportedError,
12
11
  CCIPRpcNotFoundError,
13
12
  CCIPTransactionNotFoundError,
14
13
  ChainFamily,
14
+ NetworkType,
15
15
  networkInfo,
16
16
  supportedChains,
17
17
  } from '@chainlink/ccip-sdk/src/index.ts'
@@ -94,7 +94,7 @@ export function fetchChainsFromRpcs(
94
94
  const loadChainFamily = (F: ChainFamily, txHash?: string) =>
95
95
  (initFamily$[F] ??= (endpoints$ ??= collectEndpoints.call(ctx, argv)).then((endpoints) => {
96
96
  const C = supportedChains[F]
97
- if (!C) throw new CCIPNetworkFamilyUnsupportedError(F)
97
+ if (!C) throw new CCIPChainFamilyUnsupportedError(F)
98
98
  ctx.logger.debug('Racing', endpoints.size, 'RPC endpoints for', F)
99
99
 
100
100
  const chains$: Promise<Chain>[] = []
@@ -214,7 +214,11 @@ export async function loadChainWallet(chain: Chain, argv: { wallet?: unknown; rp
214
214
  wallet = loadSuiWallet(argv)
215
215
  return [wallet.toSuiAddress(), wallet] as const
216
216
  case ChainFamily.TON:
217
- wallet = await loadTonWallet((chain as TONChain).provider, argv, chain.network.isTestnet)
217
+ wallet = await loadTonWallet(
218
+ (chain as TONChain).provider,
219
+ argv,
220
+ chain.network.networkType === NetworkType.Testnet,
221
+ )
218
222
  return [wallet.getAddress(), wallet] as const
219
223
  default:
220
224
  // TypeScript exhaustiveness check - this should never be reached