@chainlink/ccip-cli 0.92.0 → 0.93.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 (50) hide show
  1. package/dist/commands/lane-latency.d.ts +26 -0
  2. package/dist/commands/lane-latency.d.ts.map +1 -0
  3. package/dist/commands/lane-latency.js +73 -0
  4. package/dist/commands/lane-latency.js.map +1 -0
  5. package/dist/commands/manual-exec.d.ts.map +1 -1
  6. package/dist/commands/manual-exec.js +20 -269
  7. package/dist/commands/manual-exec.js.map +1 -1
  8. package/dist/commands/send.d.ts +1 -1
  9. package/dist/commands/send.js +11 -2
  10. package/dist/commands/send.js.map +1 -1
  11. package/dist/commands/show.d.ts.map +1 -1
  12. package/dist/commands/show.js +36 -16
  13. package/dist/commands/show.js.map +1 -1
  14. package/dist/commands/types.d.ts +1 -1
  15. package/dist/commands/utils.d.ts.map +1 -1
  16. package/dist/commands/utils.js +2 -1
  17. package/dist/commands/utils.js.map +1 -1
  18. package/dist/index.d.ts +6 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +7 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/providers/aptos.js +1 -1
  23. package/dist/providers/aptos.js.map +1 -1
  24. package/dist/providers/evm.js +1 -1
  25. package/dist/providers/evm.js.map +1 -1
  26. package/dist/providers/index.d.ts +6 -1
  27. package/dist/providers/index.d.ts.map +1 -1
  28. package/dist/providers/index.js +11 -8
  29. package/dist/providers/index.js.map +1 -1
  30. package/dist/providers/sui.d.ts.map +1 -1
  31. package/dist/providers/sui.js +1 -2
  32. package/dist/providers/sui.js.map +1 -1
  33. package/dist/providers/ton.d.ts +9 -3
  34. package/dist/providers/ton.d.ts.map +1 -1
  35. package/dist/providers/ton.js +100 -26
  36. package/dist/providers/ton.js.map +1 -1
  37. package/package.json +10 -6
  38. package/src/commands/lane-latency.ts +93 -0
  39. package/src/commands/manual-exec.ts +18 -267
  40. package/src/commands/send.ts +11 -8
  41. package/src/commands/show.ts +40 -22
  42. package/src/commands/types.ts +1 -1
  43. package/src/commands/utils.ts +6 -4
  44. package/src/index.ts +8 -4
  45. package/src/providers/aptos.ts +1 -1
  46. package/src/providers/evm.ts +1 -1
  47. package/src/providers/index.ts +18 -14
  48. package/src/providers/sui.ts +1 -2
  49. package/src/providers/ton.ts +106 -30
  50. package/tsconfig.json +3 -2
@@ -1,27 +1,95 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import { CCIPArgumentInvalidError, CCIPWalletInvalidError, } from '@chainlink/ccip-sdk';
2
+ import { CCIPArgumentInvalidError, CCIPWalletInvalidError, bytesToBuffer, } from '@chainlink/ccip-sdk';
3
+ import HIDTransport from '@ledgerhq/hw-transport-node-hid';
3
4
  import { keyPairFromSecretKey, mnemonicToPrivateKey } from '@ton/crypto';
4
- import { WalletContractV4 } from '@ton/ton';
5
+ import { Address, SendMode, WalletContractV4, internal, toNano } from '@ton/ton';
6
+ import { TonTransport } from '@ton-community/ton-ledger';
5
7
  /**
6
8
  * Loads a TON wallet from the provided options.
9
+ * @param client - TON client instance
7
10
  * @param wallet - wallet options (as passed from yargs argv)
11
+ * @param isTestnet - whether the wallet is on the testnet
8
12
  * @returns Promise to TONWallet instance
9
13
  */
10
- export async function loadTonWallet({ wallet: walletOpt, } = {}) {
14
+ export async function loadTonWallet(client, { wallet: walletOpt } = {}, isTestnet) {
11
15
  if (typeof walletOpt !== 'string')
12
16
  throw new CCIPWalletInvalidError(walletOpt);
13
- // Handle mnemonic phrase
14
- if (walletOpt.includes(' ')) {
15
- const mnemonic = walletOpt.trim().split(' ');
16
- const keyPair = await mnemonicToPrivateKey(mnemonic);
17
+ if (walletOpt === 'ledger' || walletOpt.startsWith('ledger:')) {
18
+ const transport = await HIDTransport.default.create();
19
+ const ton = new TonTransport(transport);
20
+ let derivationPath = walletOpt.split(':')[1];
21
+ if (!derivationPath)
22
+ derivationPath = `44'/607'/${isTestnet ? '1' : '0'}'/0/0/0`;
23
+ else if (!isNaN(Number(derivationPath)))
24
+ derivationPath = `44'/607'/${isTestnet ? '1' : '0'}'/0/${derivationPath}/0`;
25
+ const match = derivationPath.match(/^(?:m\/)?(\d+)'?\/(\d+)'?\/(\d+)'?\/(\d+)'?\/(\d+)'?\/(\d+)'?$/);
26
+ if (!match)
27
+ throw new CCIPWalletInvalidError(walletOpt);
28
+ const path = match.slice(1).map((x) => parseInt(x));
29
+ const { address, publicKey } = await ton.getAddress(path, {
30
+ chain: 0,
31
+ bounceable: false,
32
+ testOnly: isTestnet,
33
+ });
34
+ console.info('Ledger TON:', address, ', derivationPath:', derivationPath);
17
35
  const contract = WalletContractV4.create({
18
36
  workchain: 0,
19
- publicKey: keyPair.publicKey,
37
+ publicKey,
20
38
  });
21
- return { contract, keyPair };
39
+ const openedWallet = client.open(contract);
40
+ return {
41
+ getAddress: () => address,
42
+ sendTransaction: async ({ value, body, ...args }) => {
43
+ const seqno = await openedWallet.getSeqno();
44
+ const to = Address.parse(args.to);
45
+ if (!value) {
46
+ const { source_fees } = await client.estimateExternalMessageFee(to, {
47
+ ignoreSignature: true,
48
+ body,
49
+ initCode: null,
50
+ initData: null,
51
+ });
52
+ value =
53
+ BigInt(source_fees.storage_fee +
54
+ source_fees.gas_fee +
55
+ source_fees.fwd_fee +
56
+ source_fees.in_fwd_fee) + toNano('0.0001'); // buffer
57
+ }
58
+ const signed = await ton.signTransaction(path, {
59
+ seqno,
60
+ amount: value,
61
+ sendMode: SendMode.IGNORE_ERRORS | SendMode.PAY_GAS_SEPARATELY,
62
+ timeout: Math.floor(Date.now() / 1000 + 60),
63
+ bounce: false,
64
+ ...args,
65
+ to,
66
+ payload: {
67
+ type: 'unsafe',
68
+ message: body,
69
+ },
70
+ });
71
+ await openedWallet.send(signed);
72
+ return seqno;
73
+ },
74
+ };
75
+ }
76
+ let keyPair;
77
+ if (existsSync(walletOpt)) {
78
+ // Handle file path
79
+ const content = readFileSync(walletOpt, 'utf8').trim();
80
+ const secretKey = bytesToBuffer(content);
81
+ if (secretKey.length !== 64) {
82
+ throw new CCIPArgumentInvalidError('wallet', 'Invalid private key in file: must be 64 bytes');
83
+ }
84
+ keyPair = keyPairFromSecretKey(secretKey);
85
+ }
86
+ else if (walletOpt.includes(' ')) {
87
+ // Handle mnemonic phrase
88
+ const mnemonic = walletOpt.trim().split(' ');
89
+ keyPair = await mnemonicToPrivateKey(mnemonic);
22
90
  }
23
- // Handle hex private key
24
- if (walletOpt.startsWith('0x')) {
91
+ else if (walletOpt.startsWith('0x')) {
92
+ // Handle hex private key
25
93
  const secretKey = Buffer.from(walletOpt.slice(2), 'hex');
26
94
  if (secretKey.length === 32) {
27
95
  throw new CCIPArgumentInvalidError('wallet', '32-byte seeds not supported. Use 64-byte secret key or mnemonic.');
@@ -29,26 +97,32 @@ export async function loadTonWallet({ wallet: walletOpt, } = {}) {
29
97
  if (secretKey.length !== 64) {
30
98
  throw new CCIPArgumentInvalidError('wallet', 'must be 64 bytes (or use mnemonic)');
31
99
  }
32
- const keyPair = keyPairFromSecretKey(secretKey);
33
- const contract = WalletContractV4.create({
34
- workchain: 0,
35
- publicKey: keyPair.publicKey,
36
- });
37
- return { contract, keyPair };
100
+ keyPair = keyPairFromSecretKey(secretKey);
38
101
  }
39
- // Handle file path
40
- if (existsSync(walletOpt)) {
41
- const content = readFileSync(walletOpt, 'utf8').trim();
42
- const secretKey = Buffer.from(content.startsWith('0x') ? content.slice(2) : content, 'hex');
43
- if (secretKey.length !== 64) {
44
- throw new CCIPArgumentInvalidError('wallet', 'Invalid private key in file: must be 64 bytes');
45
- }
46
- const keyPair = keyPairFromSecretKey(secretKey);
102
+ if (keyPair) {
47
103
  const contract = WalletContractV4.create({
48
104
  workchain: 0,
49
105
  publicKey: keyPair.publicKey,
50
106
  });
51
- return { contract, keyPair };
107
+ const openedWallet = client.open(contract);
108
+ return {
109
+ getAddress: () => contract.address.toString(),
110
+ sendTransaction: async (args) => {
111
+ const seqno = await openedWallet.getSeqno();
112
+ const signed = await openedWallet.createTransfer({
113
+ ...keyPair,
114
+ seqno,
115
+ messages: [
116
+ internal({
117
+ value: toNano('0.3'), // TODO: FIXME: estimate proper value for execution costs instead of hardcoding.
118
+ ...args,
119
+ }),
120
+ ],
121
+ });
122
+ await openedWallet.send(signed);
123
+ return seqno;
124
+ },
125
+ };
52
126
  }
53
127
  throw new CCIPArgumentInvalidError('wallet', 'Wallet not specified');
54
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ton.js","sourceRoot":"","sources":["../../src/providers/ton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAElD,OAAO,EACL,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,+CAA+C,CAAA;AAEtD,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAE3C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAClC,MAAM,EAAE,SAAS,MACO,EAAE;IAC1B,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,MAAM,IAAI,sBAAsB,CAAC,SAAS,CAAC,CAAA;IAE9E,yBAAyB;IACzB,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC5C,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAA;QACpD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC;YACvC,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAA;QACF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;IAC9B,CAAC;IAED,yBAAyB;IACzB,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;QACxD,IAAI,SAAS,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,wBAAwB,CAChC,QAAQ,EACR,kEAAkE,CACnE,CAAA;QACH,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,wBAAwB,CAAC,QAAQ,EAAE,oCAAoC,CAAC,CAAA;QACpF,CAAC;QACD,MAAM,OAAO,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC;YACvC,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAA;QACF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;IAC9B,CAAC;IAED,mBAAmB;IACnB,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QACtD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAC3F,IAAI,SAAS,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,wBAAwB,CAAC,QAAQ,EAAE,+CAA+C,CAAC,CAAA;QAC/F,CAAC;QACD,MAAM,OAAO,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC;YACvC,SAAS,EAAE,CAAC;YACZ,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAA;QACF,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;IAC9B,CAAC;IAED,MAAM,IAAI,wBAAwB,CAAC,QAAQ,EAAE,sBAAsB,CAAC,CAAA;AACtE,CAAC"}
1
+ {"version":3,"file":"ton.js","sourceRoot":"","sources":["../../src/providers/ton.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlink/ccip-cli",
3
- "version": "0.92.0",
3
+ "version": "0.93.0",
4
4
  "description": "CCIP Command Line Interface, based on @chainlink/ccip-sdk",
5
5
  "author": "Chainlink devs",
6
6
  "license": "MIT",
@@ -41,21 +41,25 @@
41
41
  "prettier": "^3.7.4",
42
42
  "tsx": "4.21.0",
43
43
  "typescript": "5.9.3",
44
- "typescript-eslint": "8.50.0"
44
+ "typescript-eslint": "8.51.0"
45
45
  },
46
46
  "dependencies": {
47
47
  "@aptos-labs/ts-sdk": "^5.2.0",
48
- "@chainlink/ccip-sdk": "^0.92.0",
48
+ "@chainlink/ccip-sdk": "^0.93.0",
49
49
  "@coral-xyz/anchor": "^0.29.0",
50
50
  "@ethers-ext/signer-ledger": "^6.0.0-beta.1",
51
51
  "@inquirer/prompts": "8.1.0",
52
- "@ledgerhq/hw-app-aptos": "^6.34.10",
53
- "@ledgerhq/hw-app-solana": "^7.6.1",
54
- "@ledgerhq/hw-transport-node-hid": "^6.29.15",
52
+ "@ledgerhq/hw-app-aptos": "^6.34.11",
53
+ "@ledgerhq/hw-app-solana": "^7.6.2",
54
+ "@ledgerhq/hw-transport-node-hid": "^6.29.16",
55
55
  "@solana/web3.js": "^1.98.4",
56
+ "@ton-community/ton-ledger": "^7.3.0",
56
57
  "bs58": "^6.0.0",
57
58
  "ethers": "6.16.0",
58
59
  "type-fest": "^5.3.1",
59
60
  "yargs": "18.0.0"
61
+ },
62
+ "overrides": {
63
+ "@ledgerhq/types-live": "6.90.0"
60
64
  }
61
65
  }
@@ -0,0 +1,93 @@
1
+ import {
2
+ CCIPAPIClient,
3
+ CCIPApiClientNotAvailableError,
4
+ bigIntReplacer,
5
+ networkInfo,
6
+ } from '@chainlink/ccip-sdk/src/index.ts'
7
+ import type { Argv } from 'yargs'
8
+
9
+ import type { GlobalOpts } from '../index.ts'
10
+ import { type Ctx, Format } from './types.ts'
11
+ import { formatDuration, getCtx, logParsedError, prettyTable } from './utils.ts'
12
+
13
+ export const command = ['laneLatency <source> <dest>', 'lane-latency <source> <dest>']
14
+ export const describe = 'Query real-time lane latency between source and destination chains'
15
+ export const aliases = ['latency']
16
+
17
+ /**
18
+ * Yargs builder for the lane-latency command.
19
+ * @param yargs - Yargs instance.
20
+ * @returns Configured yargs instance with command options.
21
+ */
22
+ export const builder = (yargs: Argv) =>
23
+ yargs
24
+ .positional('source', {
25
+ type: 'string',
26
+ demandOption: true,
27
+ describe: 'Source network (chainId, selector, or name). Example: ethereum-mainnet',
28
+ })
29
+ .positional('dest', {
30
+ type: 'string',
31
+ demandOption: true,
32
+ describe: 'Destination network (chainId, selector, or name). Example: arbitrum-mainnet',
33
+ })
34
+ .option('api-url', {
35
+ type: 'string',
36
+ describe: 'Custom CCIP API URL (defaults to api.ccip.chain.link)',
37
+ })
38
+
39
+ /**
40
+ * Handler for the lane-latency command.
41
+ * @param argv - Command line arguments.
42
+ */
43
+ export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
44
+ const [ctx, destroy] = getCtx(argv)
45
+ return getLaneLatencyCmd(ctx, argv)
46
+ .catch((err) => {
47
+ process.exitCode = 1
48
+ if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
49
+ })
50
+ .finally(destroy)
51
+ }
52
+
53
+ /** Exported for testing */
54
+ export async function getLaneLatencyCmd(ctx: Ctx, argv: Parameters<typeof handler>[0]) {
55
+ const { logger } = ctx
56
+
57
+ // Respect --no-api flag - this command requires API access
58
+ if (argv.noApi) {
59
+ throw new CCIPApiClientNotAvailableError({
60
+ context: {
61
+ reason:
62
+ 'The lane-latency command requires API access. Remove --no-api flag to use this command.',
63
+ },
64
+ })
65
+ }
66
+
67
+ const sourceNetwork = networkInfo(argv.source)
68
+ const destNetwork = networkInfo(argv.dest)
69
+
70
+ const apiClient = new CCIPAPIClient(argv.apiUrl, { logger })
71
+
72
+ const result = await apiClient.getLaneLatency(
73
+ sourceNetwork.chainSelector,
74
+ destNetwork.chainSelector,
75
+ )
76
+
77
+ switch (argv.format) {
78
+ case Format.json:
79
+ logger.log(JSON.stringify(result, bigIntReplacer, 2))
80
+ break
81
+ case Format.log:
82
+ logger.log('Lane Latency:', result)
83
+ break
84
+ default: {
85
+ prettyTable.call(ctx, {
86
+ Source: `${sourceNetwork.name} [${sourceNetwork.chainSelector}]`,
87
+ Destination: `${destNetwork.name} [${destNetwork.chainSelector}]`,
88
+ 'Estimated Delivery': `~${formatDuration(result.totalMs / 1000)}`,
89
+ 'Latency (ms)': result.totalMs.toLocaleString(),
90
+ })
91
+ }
92
+ }
93
+ }
@@ -1,11 +1,9 @@
1
1
  import {
2
2
  type CCIPRequest,
3
3
  type CCIPVersion,
4
- type ChainStatic,
5
4
  type EVMChain,
6
5
  type ExecutionReport,
7
6
  CCIPChainFamilyUnsupportedError,
8
- CCIPReceiptNotFoundError,
9
7
  ChainFamily,
10
8
  bigIntReplacer,
11
9
  calculateManualExecProof,
@@ -132,7 +130,7 @@ async function manualExec(
132
130
  // messageId not yet implemented for Solana
133
131
  const [getChain, tx$] = fetchChainsFromRpcs(ctx, argv, argv.txHash)
134
132
  const [source, tx] = await tx$
135
- const request = await selectRequest(await source.fetchRequestsInTx(tx), 'to know more', argv)
133
+ const request = await selectRequest(await source.getMessagesInTx(tx), 'to know more', argv)
136
134
 
137
135
  switch (argv.format) {
138
136
  case Format.log: {
@@ -151,13 +149,14 @@ async function manualExec(
151
149
  const dest = await getChain(request.lane.destChainSelector)
152
150
  const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp, source)
153
151
  const commitStore = await dest.getCommitStoreForOffRamp(offRamp)
154
- const commit = await dest.fetchCommitReport(commitStore, request, argv)
152
+ const commit = await dest.getCommitReport({ ...argv, commitStore, request })
155
153
 
156
154
  switch (argv.format) {
157
155
  case Format.log:
158
156
  logger.log('commit =', commit)
159
157
  break
160
158
  case Format.pretty:
159
+ logger.info('Commit (dest):')
161
160
  await prettyCommit.call(ctx, dest, commit, request)
162
161
  break
163
162
  case Format.json:
@@ -165,7 +164,7 @@ async function manualExec(
165
164
  break
166
165
  }
167
166
 
168
- const messagesInBatch = await source.fetchAllMessagesInBatch(request, commit.report, argv)
167
+ const messagesInBatch = await source.getMessagesInBatch(request, commit.report, argv)
169
168
  const execReportProof = calculateManualExecProof(
170
169
  messagesInBatch,
171
170
  request.lane,
@@ -174,7 +173,7 @@ async function manualExec(
174
173
  dest,
175
174
  )
176
175
 
177
- const offchainTokenData = await source.fetchOffchainTokenData(request)
176
+ const offchainTokenData = await source.getOffchainTokenData(request)
178
177
  const execReport: ExecutionReport = {
179
178
  ...execReportProof,
180
179
  message: request.message,
@@ -214,274 +213,26 @@ async function manualExec(
214
213
  }
215
214
 
216
215
  const [, wallet] = await loadChainWallet(dest, argv)
217
- const manualExecTx = await dest.executeReport(offRamp, execReport, { ...argv, wallet })
216
+ const receipt = await dest.executeReport({ ...argv, offRamp, execReport, wallet })
218
217
 
219
- logger.info('🚀 manualExec tx =', manualExecTx.hash, 'to offRamp =', offRamp)
220
-
221
- let found = false
222
- for (const log of manualExecTx.logs) {
223
- const execReceipt = (dest.constructor as ChainStatic).decodeReceipt(log)
224
- if (!execReceipt) continue
225
- const timestamp = await dest.getBlockTimestamp(log.blockNumber)
226
- const receipt = { receipt: execReceipt, log, timestamp }
227
- switch (argv.format) {
228
- case Format.log:
229
- logger.log('receipt =', withDateTimestamp(receipt))
230
- break
231
- case Format.pretty:
232
- if (!found) logger.info('Receipts (dest):')
233
- prettyReceipt.call(
234
- ctx,
235
- receipt,
236
- request,
237
- receipt.log.tx?.from ??
238
- (await dest.getTransaction(receipt.log.transactionHash).catch(() => null))?.from,
239
- )
240
- break
241
- case Format.json:
242
- logger.info(JSON.stringify(execReceipt, bigIntReplacer, 2))
243
- break
244
- }
245
- found = true
246
- }
247
- if (!found) throw new CCIPReceiptNotFoundError(manualExecTx.hash)
248
- }
249
-
250
- /*
251
- export async function manualExecSenderQueue(
252
- providers: Providers,
253
- txHash: string,
254
- argv: {
255
- gasLimit?: number
256
- tokensGasLimit?: number
257
- logIndex?: number
258
- execFailed?: boolean
259
- format: Format
260
- page: number
261
- wallet?: string
262
- },
263
- ) {
264
- const tx = await providers.getTxReceipt(txHash)
265
- const source = tx.provider
266
-
267
- let firstRequest
268
- if (argv.logIndex != null) {
269
- firstRequest = await fetchCCIPMessageInLog(tx, argv.logIndex)
270
- } else {
271
- firstRequest = await selectRequest(await fetchCCIPMessagesInTx(tx), 'to execute')
272
- }
273
218
  switch (argv.format) {
274
219
  case Format.log:
275
- console.log(`message ${firstRequest.log.index} =`, withDateTimestamp(firstRequest))
220
+ logger.log('receipt =', withDateTimestamp(receipt))
276
221
  break
277
222
  case Format.pretty:
278
- await prettyRequest(source, firstRequest)
223
+ logger.info('Receipt (dest):')
224
+ prettyReceipt.call(
225
+ ctx,
226
+ receipt,
227
+ request,
228
+ receipt.log.tx?.from ??
229
+ (await dest.getTransaction(receipt.log.transactionHash).catch(() => null))?.from,
230
+ )
279
231
  break
280
232
  case Format.json:
281
- console.info(JSON.stringify(firstRequest, bigIntReplacer, 2))
233
+ logger.info(JSON.stringify(receipt, bigIntReplacer, 2))
282
234
  break
283
235
  }
284
-
285
- const dest = await providers.forChainId(chainIdFromSelector(firstRequest.lane.destChainSelector))
286
-
287
- const requests: Omit<CCIPRequest, 'timestamp' | 'tx'>[] = []
288
- for await (const request of fetchRequestsForSender(source, firstRequest)) {
289
- requests.push(request)
290
- if (requests.length >= MAX_QUEUE) break
291
- }
292
- console.info('Found', requests.length, `requests for "${firstRequest.message.sender}"`)
293
- if (!requests.length) return
294
-
295
- let startBlock = await getSomeBlockNumberBefore(dest, firstRequest.timestamp)
296
- const wallet = (await getWallet(argv)).connect(dest)
297
- const offRampContract = await discoverOffRamp(wallet, firstRequest.lane, {
298
- fromBlock: startBlock,
299
- page: argv.page,
300
- })
301
- const senderNonce = await offRampContract.getSenderNonce(firstRequest.message.sender)
302
- const origRequestsCnt = requests.length,
303
- last = requests[requests.length - 1]
304
- while (requests.length && requests[0].message.header.sequenceNumber <= senderNonce) {
305
- requests.shift()
306
- }
307
- console.info(
308
- 'Found',
309
- requests.length,
310
- `requests for "${firstRequest.message.sender}", removed `,
311
- origRequestsCnt - requests.length,
312
- 'already executed before senderNonce =',
313
- senderNonce,
314
- '. Last source txHash =',
315
- last.log.transactionHash,
316
- )
317
- if (!requests.length) return
318
- let nonce = await wallet.getNonce()
319
-
320
- let lastBatch:
321
- | readonly [CCIPCommit, Omit<CCIPRequest<CCIPVersion>, 'tx' | 'timestamp'>[]]
322
- | undefined
323
- const txsPending = []
324
- for (let i = 0; i < requests.length; ) {
325
- let commit, batch
326
- if (!lastBatch || requests[i].message.header.sequenceNumber > lastBatch[0].report.maxSeqNr) {
327
- commit = await fetchCommitReport(dest, requests[i], {
328
- startBlock,
329
- page: argv.page,
330
- })
331
- startBlock = commit.log.blockNumber + 1
332
-
333
- batch = await fetchAllMessagesInBatch(
334
- source,
335
- requests[i].lane.destChainSelector,
336
- requests[i].log,
337
- commit.report,
338
- { page: argv.page },
339
- )
340
- lastBatch = [commit, batch]
341
- } else {
342
- ;[commit, batch] = lastBatch
343
- }
344
-
345
- const msgIdsToExec = [] as string[]
346
- while (
347
- i < requests.length &&
348
- requests[i].message.header.sequenceNumber <= commit.report.maxSeqNr &&
349
- msgIdsToExec.length < MAX_EXECS_IN_BATCH
350
- ) {
351
- msgIdsToExec.push(requests[i++].message.header.messageId)
352
- }
353
-
354
- const manualExecReport = calculateManualExecProof(
355
- batch.map(({ message }) => message),
356
- firstRequest.lane,
357
- msgIdsToExec,
358
- commit.report.merkleRoot,
359
- )
360
- const requestsToExec = manualExecReport.messages.map(
361
- ({ header }) =>
362
- requests.find(({ message }) => message.header.messageId === header.messageId)!,
363
- )
364
- const offchainTokenData = await Promise.all(
365
- requestsToExec.map(async (request) => {
366
- const tx = await lazyCached(`tx ${request.log.transactionHash}`, () =>
367
- source.getTransactionReceipt(request.log.transactionHash).then((res) => {
368
- if (!res) throw new Error(`Tx not found: ${request.log.transactionHash}`)
369
- return res
370
- }),
371
- )
372
- return fetchOffchainTokenData({ ...request, tx })
373
- }),
374
- )
375
- const execReport = { ...manualExecReport, offchainTokenData }
376
- const getGasLimitOverride = (message: { gasLimit: bigint } | { extraArgs: string }): bigint => {
377
- if (argv.gasLimit != null) {
378
- const argvGasLimit = BigInt(argv.gasLimit)
379
- let msgGasLimit
380
- if ('gasLimit' in message) {
381
- msgGasLimit = message.gasLimit
382
- } else {
383
- const parsedArgs = parseExtraArgs(message.extraArgs, source.network.family)
384
- if (!parsedArgs || !('gasLimit' in parsedArgs) || !parsedArgs.gasLimit) {
385
- throw new Error(`Missing gasLimit argument`)
386
- }
387
- msgGasLimit = BigInt(parsedArgs.gasLimit)
388
- }
389
- if (argvGasLimit > msgGasLimit) {
390
- return argvGasLimit
391
- }
392
- }
393
- return 0n
394
- }
395
-
396
- let manualExecTx
397
- if (firstRequest.lane.version === CCIPVersion.V1_2) {
398
- const gasOverrides = manualExecReport.messages.map((message) =>
399
- getGasLimitOverride(message as CCIPMessage<typeof CCIPVersion.V1_2>),
400
- )
401
- manualExecTx = await (
402
- offRampContract as CCIPContract<typeof CCIPContractType.OffRamp, typeof CCIPVersion.V1_2>
403
- ).manuallyExecute(
404
- execReport as {
405
- offchainTokenData: string[][]
406
- messages: CCIPMessage<typeof CCIPVersion.V1_2>[]
407
- proofs: string[]
408
- proofFlagBits: bigint
409
- },
410
- gasOverrides,
411
- { nonce: nonce++, gasLimit: argv.gasLimit ? argv.gasLimit : undefined },
412
- )
413
- } else if (firstRequest.lane.version === CCIPVersion.V1_5) {
414
- const gasOverrides = manualExecReport.messages.map((message) => ({
415
- receiverExecutionGasLimit: getGasLimitOverride(
416
- message as CCIPMessage<typeof CCIPVersion.V1_5>,
417
- ),
418
- tokenGasOverrides: message.tokenAmounts.map(() => BigInt(argv.tokensGasLimit ?? 0)),
419
- }))
420
- manualExecTx = await (
421
- offRampContract as CCIPContract<typeof CCIPContractType.OffRamp, typeof CCIPVersion.V1_5>
422
- ).manuallyExecute(
423
- execReport as {
424
- offchainTokenData: string[][]
425
- messages: CCIPMessage<typeof CCIPVersion.V1_5>[]
426
- proofs: string[]
427
- proofFlagBits: bigint
428
- },
429
- gasOverrides,
430
- { nonce: nonce++, gasLimit: argv.gasLimit ? argv.gasLimit : undefined },
431
- )
432
- } else {
433
- const gasOverrides = manualExecReport.messages.map((message) => ({
434
- receiverExecutionGasLimit: getGasLimitOverride(
435
- message as CCIPMessage<typeof CCIPVersion.V1_6>,
436
- ),
437
- tokenGasOverrides: message.tokenAmounts.map(() => BigInt(argv.tokensGasLimit ?? 0)),
438
- }))
439
- manualExecTx = await (
440
- offRampContract as CCIPContract<typeof CCIPContractType.OffRamp, typeof CCIPVersion.V1_6>
441
- ).manuallyExecute(
442
- [
443
- {
444
- sourceChainSelector: firstRequest.lane.sourceChainSelector,
445
- messages: execReport.messages as (CCIPMessage<typeof CCIPVersion.V1_6> & {
446
- gasLimit: bigint
447
- })[],
448
- proofs: execReport.proofs,
449
- proofFlagBits: execReport.proofFlagBits,
450
- offchainTokenData: execReport.offchainTokenData,
451
- },
452
- ],
453
- [gasOverrides],
454
- { nonce: nonce++, gasLimit: argv.gasLimit ? argv.gasLimit : undefined },
455
- )
456
- }
457
-
458
- const toExec = requests[i - 1] // log only request data for last msg in msgIdsToExec
459
- console.log(
460
- `🚀 [${i}/${requests.length}, ${batch.length} batch, ${msgIdsToExec.length} to exec]`,
461
- 'source tx =',
462
- toExec.log.transactionHash,
463
- 'msgId =',
464
- toExec.message.header.messageId,
465
- 'nonce =',
466
- toExec.message.header.nonce,
467
- 'manualExec tx =',
468
- manualExecTx.hash,
469
- 'to =',
470
- manualExecTx.to,
471
- 'gasLimit =',
472
- manualExecTx.gasLimit,
473
- )
474
- txsPending.push(manualExecTx)
475
- if (txsPending.length >= MAX_PENDING_TXS) {
476
- console.debug(
477
- 'awaiting',
478
- txsPending.length,
479
- 'txs:',
480
- txsPending.map((tx) => tx.hash),
481
- )
482
- await txsPending[txsPending.length - 1].wait()
483
- txsPending.length = 0
484
- }
485
- }
486
236
  }
487
- */
237
+
238
+ // TODO: re-implement executing `sender` queue