@chainlink/ccip-cli 0.92.1 → 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.
- package/dist/commands/lane-latency.d.ts +26 -0
- package/dist/commands/lane-latency.d.ts.map +1 -0
- package/dist/commands/lane-latency.js +73 -0
- package/dist/commands/lane-latency.js.map +1 -0
- package/dist/commands/manual-exec.d.ts.map +1 -1
- package/dist/commands/manual-exec.js +20 -269
- package/dist/commands/manual-exec.js.map +1 -1
- package/dist/commands/send.js +11 -2
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +36 -16
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +2 -1
- package/dist/commands/utils.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/aptos.js +1 -1
- package/dist/providers/aptos.js.map +1 -1
- package/dist/providers/evm.js +1 -1
- package/dist/providers/evm.js.map +1 -1
- package/dist/providers/index.d.ts +4 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +11 -8
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ton.d.ts +8 -5
- package/dist/providers/ton.d.ts.map +1 -1
- package/dist/providers/ton.js +100 -26
- package/dist/providers/ton.js.map +1 -1
- package/package.json +10 -6
- package/src/commands/lane-latency.ts +93 -0
- package/src/commands/manual-exec.ts +18 -267
- package/src/commands/send.ts +11 -8
- package/src/commands/show.ts +40 -22
- package/src/commands/utils.ts +6 -4
- package/src/index.ts +8 -4
- package/src/providers/aptos.ts +1 -1
- package/src/providers/evm.ts +1 -1
- package/src/providers/index.ts +18 -14
- package/src/providers/ton.ts +109 -27
- package/tsconfig.json +3 -2
package/dist/providers/ton.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
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
|
|
37
|
+
publicKey,
|
|
20
38
|
});
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
const contract = WalletContractV4.create({
|
|
34
|
-
workchain: 0,
|
|
35
|
-
publicKey: keyPair.publicKey,
|
|
36
|
-
});
|
|
37
|
-
return { contract, keyPair };
|
|
100
|
+
keyPair = keyPairFromSecretKey(secretKey);
|
|
38
101
|
}
|
|
39
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
53
|
-
"@ledgerhq/hw-app-solana": "^7.6.
|
|
54
|
-
"@ledgerhq/hw-transport-node-hid": "^6.29.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
220
|
+
logger.log('receipt =', withDateTimestamp(receipt))
|
|
276
221
|
break
|
|
277
222
|
case Format.pretty:
|
|
278
|
-
|
|
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
|
-
|
|
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
|