genlayer 0.38.13 → 0.38.15
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/.github/workflows/publish.yml +1 -3
- package/CHANGELOG.md +8 -0
- package/dist/index.js +297 -15
- package/package.json +2 -2
- package/src/commands/transactions/finalize.ts +45 -0
- package/src/commands/transactions/index.ts +19 -0
- package/tests/actions/finalize.test.ts +109 -0
- package/tests/commands/finalize.test.ts +83 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.38.15 (2026-04-14)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* preserve platform-specific optional deps in lockfile ([af7436b](https://github.com/genlayerlabs/genlayer-cli/commit/af7436b8d4f8f56582c38dd8006efa4946ea8964))
|
|
8
|
+
|
|
9
|
+
## [0.38.14](https://github.com/genlayerlabs/genlayer-cli/compare/v0.38.13...v0.38.14) (2026-04-02)
|
|
10
|
+
|
|
3
11
|
## 0.38.13 (2026-04-01)
|
|
4
12
|
|
|
5
13
|
## 0.38.12 (2026-04-01)
|
package/dist/index.js
CHANGED
|
@@ -20078,7 +20078,7 @@ var require_cli_table3 = __commonJS({
|
|
|
20078
20078
|
import { program } from "commander";
|
|
20079
20079
|
|
|
20080
20080
|
// package.json
|
|
20081
|
-
var version = "0.38.
|
|
20081
|
+
var version = "0.38.15";
|
|
20082
20082
|
var package_default = {
|
|
20083
20083
|
name: "genlayer",
|
|
20084
20084
|
version,
|
|
@@ -20148,7 +20148,7 @@ var package_default = {
|
|
|
20148
20148
|
dotenv: "^17.0.0",
|
|
20149
20149
|
ethers: "^6.13.4",
|
|
20150
20150
|
"fs-extra": "^11.3.0",
|
|
20151
|
-
"genlayer-js": "^0.
|
|
20151
|
+
"genlayer-js": "^0.28.5",
|
|
20152
20152
|
inquirer: "^12.0.0",
|
|
20153
20153
|
keytar: "^7.9.0",
|
|
20154
20154
|
"node-fetch": "^3.0.0",
|
|
@@ -33738,7 +33738,7 @@ init_toHex();
|
|
|
33738
33738
|
init_keccak256();
|
|
33739
33739
|
init_formatEther();
|
|
33740
33740
|
|
|
33741
|
-
// node_modules/genlayer-js/dist/chunk-
|
|
33741
|
+
// node_modules/genlayer-js/dist/chunk-SGAVFNGA.js
|
|
33742
33742
|
var chains_exports = {};
|
|
33743
33743
|
__export2(chains_exports, {
|
|
33744
33744
|
localnet: () => localnet,
|
|
@@ -37755,6 +37755,7 @@ var localnet = defineChain({
|
|
|
37755
37755
|
stakingContract: null,
|
|
37756
37756
|
feeManagerContract: null,
|
|
37757
37757
|
roundsStorageContract: null,
|
|
37758
|
+
appealsContract: null,
|
|
37758
37759
|
defaultNumberOfInitialValidators: 5,
|
|
37759
37760
|
defaultConsensusMaxRotations: 3
|
|
37760
37761
|
});
|
|
@@ -41768,6 +41769,7 @@ var studionet = defineChain({
|
|
|
41768
41769
|
stakingContract: null,
|
|
41769
41770
|
feeManagerContract: null,
|
|
41770
41771
|
roundsStorageContract: null,
|
|
41772
|
+
appealsContract: null,
|
|
41771
41773
|
defaultNumberOfInitialValidators: 5,
|
|
41772
41774
|
defaultConsensusMaxRotations: 3
|
|
41773
41775
|
});
|
|
@@ -47261,6 +47263,7 @@ var testnetAsimov = defineChain({
|
|
|
47261
47263
|
stakingContract: STAKING_CONTRACT,
|
|
47262
47264
|
feeManagerContract: null,
|
|
47263
47265
|
roundsStorageContract: null,
|
|
47266
|
+
appealsContract: null,
|
|
47264
47267
|
defaultNumberOfInitialValidators: 5,
|
|
47265
47268
|
defaultConsensusMaxRotations: 3
|
|
47266
47269
|
});
|
|
@@ -47294,6 +47297,70 @@ var ROUNDS_STORAGE_CONTRACT = {
|
|
|
47294
47297
|
stateMutability: "view",
|
|
47295
47298
|
inputs: [{ name: "_txId", type: "bytes32" }],
|
|
47296
47299
|
outputs: [{ name: "", type: "uint256" }]
|
|
47300
|
+
},
|
|
47301
|
+
{
|
|
47302
|
+
type: "function",
|
|
47303
|
+
name: "getRoundData",
|
|
47304
|
+
stateMutability: "view",
|
|
47305
|
+
inputs: [
|
|
47306
|
+
{ name: "_txId", type: "bytes32" },
|
|
47307
|
+
{ name: "_round", type: "uint256" }
|
|
47308
|
+
],
|
|
47309
|
+
outputs: [{
|
|
47310
|
+
name: "",
|
|
47311
|
+
type: "tuple",
|
|
47312
|
+
components: [
|
|
47313
|
+
{ name: "round", type: "uint256" },
|
|
47314
|
+
{ name: "leaderIndex", type: "uint256" },
|
|
47315
|
+
{ name: "votesCommitted", type: "uint256" },
|
|
47316
|
+
{ name: "votesRevealed", type: "uint256" },
|
|
47317
|
+
{ name: "appealBond", type: "uint256" },
|
|
47318
|
+
{ name: "rotationsLeft", type: "uint256" },
|
|
47319
|
+
{ name: "result", type: "uint8" },
|
|
47320
|
+
{ name: "roundValidators", type: "address[]" },
|
|
47321
|
+
{ name: "validatorVotes", type: "uint8[]" },
|
|
47322
|
+
{ name: "validatorVotesHash", type: "bytes32[]" },
|
|
47323
|
+
{ name: "validatorResultHash", type: "bytes32[]" }
|
|
47324
|
+
]
|
|
47325
|
+
}]
|
|
47326
|
+
},
|
|
47327
|
+
{
|
|
47328
|
+
type: "function",
|
|
47329
|
+
name: "getLastRoundData",
|
|
47330
|
+
stateMutability: "view",
|
|
47331
|
+
inputs: [{ name: "_txId", type: "bytes32" }],
|
|
47332
|
+
outputs: [
|
|
47333
|
+
{ name: "", type: "uint256" },
|
|
47334
|
+
{
|
|
47335
|
+
name: "",
|
|
47336
|
+
type: "tuple",
|
|
47337
|
+
components: [
|
|
47338
|
+
{ name: "round", type: "uint256" },
|
|
47339
|
+
{ name: "leaderIndex", type: "uint256" },
|
|
47340
|
+
{ name: "votesCommitted", type: "uint256" },
|
|
47341
|
+
{ name: "votesRevealed", type: "uint256" },
|
|
47342
|
+
{ name: "appealBond", type: "uint256" },
|
|
47343
|
+
{ name: "rotationsLeft", type: "uint256" },
|
|
47344
|
+
{ name: "result", type: "uint8" },
|
|
47345
|
+
{ name: "roundValidators", type: "address[]" },
|
|
47346
|
+
{ name: "validatorVotes", type: "uint8[]" },
|
|
47347
|
+
{ name: "validatorVotesHash", type: "bytes32[]" },
|
|
47348
|
+
{ name: "validatorResultHash", type: "bytes32[]" }
|
|
47349
|
+
]
|
|
47350
|
+
}
|
|
47351
|
+
]
|
|
47352
|
+
}
|
|
47353
|
+
]
|
|
47354
|
+
};
|
|
47355
|
+
var APPEALS_CONTRACT = {
|
|
47356
|
+
address: "0xbb8C35AA878D09b9830aFF9e5aAC6492BFbd5471",
|
|
47357
|
+
abi: [
|
|
47358
|
+
{
|
|
47359
|
+
type: "function",
|
|
47360
|
+
name: "canAppeal",
|
|
47361
|
+
stateMutability: "view",
|
|
47362
|
+
inputs: [{ name: "_txId", type: "bytes32" }],
|
|
47363
|
+
outputs: [{ name: "", type: "bool" }]
|
|
47297
47364
|
}
|
|
47298
47365
|
]
|
|
47299
47366
|
};
|
|
@@ -50640,6 +50707,7 @@ var testnetBradbury = defineChain({
|
|
|
50640
50707
|
stakingContract: STAKING_CONTRACT2,
|
|
50641
50708
|
feeManagerContract: FEE_MANAGER_CONTRACT,
|
|
50642
50709
|
roundsStorageContract: ROUNDS_STORAGE_CONTRACT,
|
|
50710
|
+
appealsContract: APPEALS_CONTRACT,
|
|
50643
50711
|
defaultNumberOfInitialValidators: 5,
|
|
50644
50712
|
defaultConsensusMaxRotations: 3
|
|
50645
50713
|
});
|
|
@@ -51410,10 +51478,10 @@ function extractGenCallResult(result) {
|
|
|
51410
51478
|
}
|
|
51411
51479
|
var contractActions = (client, publicClient) => {
|
|
51412
51480
|
return {
|
|
51413
|
-
/** Retrieves the source code of a deployed contract.
|
|
51481
|
+
/** Retrieves the source code of a deployed contract. Studio only. */
|
|
51414
51482
|
getContractCode: async (address) => {
|
|
51415
|
-
if (client.chain.
|
|
51416
|
-
throw new Error(`getContractCode is only available on
|
|
51483
|
+
if (!client.chain.isStudio) {
|
|
51484
|
+
throw new Error(`getContractCode is only available on Studio networks (current chain: ${client.chain.name})`);
|
|
51417
51485
|
}
|
|
51418
51486
|
const result = await client.request({
|
|
51419
51487
|
method: "gen_getContractCode",
|
|
@@ -51422,10 +51490,10 @@ var contractActions = (client, publicClient) => {
|
|
|
51422
51490
|
const codeBytes = b64ToArray(result);
|
|
51423
51491
|
return new TextDecoder().decode(codeBytes);
|
|
51424
51492
|
},
|
|
51425
|
-
/** Gets the schema (methods and constructor) of a deployed contract.
|
|
51493
|
+
/** Gets the schema (methods and constructor) of a deployed contract. Studio only. */
|
|
51426
51494
|
getContractSchema: async (address) => {
|
|
51427
|
-
if (client.chain.
|
|
51428
|
-
throw new Error(`getContractSchema is only available on
|
|
51495
|
+
if (!client.chain.isStudio) {
|
|
51496
|
+
throw new Error(`getContractSchema is only available on Studio networks (current chain: ${client.chain.name})`);
|
|
51429
51497
|
}
|
|
51430
51498
|
const schema = await client.request({
|
|
51431
51499
|
method: "gen_getContractSchema",
|
|
@@ -51433,10 +51501,10 @@ var contractActions = (client, publicClient) => {
|
|
|
51433
51501
|
});
|
|
51434
51502
|
return schema;
|
|
51435
51503
|
},
|
|
51436
|
-
/** Generates a schema for contract code without deploying it.
|
|
51504
|
+
/** Generates a schema for contract code without deploying it. Studio only. */
|
|
51437
51505
|
getContractSchemaForCode: async (contractCode) => {
|
|
51438
|
-
if (client.chain.
|
|
51439
|
-
throw new Error(`
|
|
51506
|
+
if (!client.chain.isStudio) {
|
|
51507
|
+
throw new Error(`getContractSchemaForCode is only available on Studio networks (current chain: ${client.chain.name})`);
|
|
51440
51508
|
}
|
|
51441
51509
|
const schema = await client.request({
|
|
51442
51510
|
method: "gen_getContractSchemaForCode",
|
|
@@ -51600,6 +51668,54 @@ var contractActions = (client, publicClient) => {
|
|
|
51600
51668
|
});
|
|
51601
51669
|
return minBond;
|
|
51602
51670
|
},
|
|
51671
|
+
/** Returns the current consensus round number for a transaction. */
|
|
51672
|
+
getRoundNumber: async (args) => {
|
|
51673
|
+
if (!client.chain.roundsStorageContract?.address) {
|
|
51674
|
+
throw new Error("getRoundNumber not supported on this chain (missing roundsStorageContract)");
|
|
51675
|
+
}
|
|
51676
|
+
return publicClient.readContract({
|
|
51677
|
+
address: client.chain.roundsStorageContract.address,
|
|
51678
|
+
abi: client.chain.roundsStorageContract.abi,
|
|
51679
|
+
functionName: "getRoundNumber",
|
|
51680
|
+
args: [args.txId]
|
|
51681
|
+
});
|
|
51682
|
+
},
|
|
51683
|
+
/** Returns detailed data for a specific consensus round. */
|
|
51684
|
+
getRoundData: async (args) => {
|
|
51685
|
+
if (!client.chain.roundsStorageContract?.address) {
|
|
51686
|
+
throw new Error("getRoundData not supported on this chain (missing roundsStorageContract)");
|
|
51687
|
+
}
|
|
51688
|
+
return publicClient.readContract({
|
|
51689
|
+
address: client.chain.roundsStorageContract.address,
|
|
51690
|
+
abi: client.chain.roundsStorageContract.abi,
|
|
51691
|
+
functionName: "getRoundData",
|
|
51692
|
+
args: [args.txId, args.round]
|
|
51693
|
+
});
|
|
51694
|
+
},
|
|
51695
|
+
/** Returns the current round number and its data for a transaction. */
|
|
51696
|
+
getLastRoundData: async (args) => {
|
|
51697
|
+
if (!client.chain.roundsStorageContract?.address) {
|
|
51698
|
+
throw new Error("getLastRoundData not supported on this chain (missing roundsStorageContract)");
|
|
51699
|
+
}
|
|
51700
|
+
return publicClient.readContract({
|
|
51701
|
+
address: client.chain.roundsStorageContract.address,
|
|
51702
|
+
abi: client.chain.roundsStorageContract.abi,
|
|
51703
|
+
functionName: "getLastRoundData",
|
|
51704
|
+
args: [args.txId]
|
|
51705
|
+
});
|
|
51706
|
+
},
|
|
51707
|
+
/** Checks if a transaction can be appealed. */
|
|
51708
|
+
canAppeal: async (args) => {
|
|
51709
|
+
if (!client.chain.appealsContract?.address) {
|
|
51710
|
+
throw new Error("canAppeal not supported on this chain (missing appealsContract)");
|
|
51711
|
+
}
|
|
51712
|
+
return publicClient.readContract({
|
|
51713
|
+
address: client.chain.appealsContract.address,
|
|
51714
|
+
abi: client.chain.appealsContract.abi,
|
|
51715
|
+
functionName: "canAppeal",
|
|
51716
|
+
args: [args.txId]
|
|
51717
|
+
});
|
|
51718
|
+
},
|
|
51603
51719
|
/** Appeals a consensus transaction to trigger a new round of validation. */
|
|
51604
51720
|
appealTransaction: async (args) => {
|
|
51605
51721
|
const { account, txId } = args;
|
|
@@ -51626,12 +51742,51 @@ var contractActions = (client, publicClient) => {
|
|
|
51626
51742
|
}
|
|
51627
51743
|
const senderAccount = account || client.account;
|
|
51628
51744
|
const encodedData = _encodeSubmitAppealData({ client, txId });
|
|
51629
|
-
|
|
51745
|
+
await _sendConsensusCall({
|
|
51630
51746
|
client,
|
|
51631
51747
|
publicClient,
|
|
51632
51748
|
encodedData,
|
|
51633
51749
|
senderAccount,
|
|
51634
|
-
value
|
|
51750
|
+
value,
|
|
51751
|
+
operationName: "Appeal"
|
|
51752
|
+
});
|
|
51753
|
+
return txId;
|
|
51754
|
+
},
|
|
51755
|
+
/** Finalizes a single GenLayer transaction that is ready to be finalized. Returns the EVM transaction hash. */
|
|
51756
|
+
finalizeTransaction: async (args) => {
|
|
51757
|
+
const { account, txId } = args;
|
|
51758
|
+
const senderAccount = account || client.account;
|
|
51759
|
+
const encodedData = encodeFunctionData({
|
|
51760
|
+
abi: client.chain.consensusMainContract?.abi,
|
|
51761
|
+
functionName: "finalizeTransaction",
|
|
51762
|
+
args: [txId]
|
|
51763
|
+
});
|
|
51764
|
+
return _sendConsensusCall({
|
|
51765
|
+
client,
|
|
51766
|
+
publicClient,
|
|
51767
|
+
encodedData,
|
|
51768
|
+
senderAccount,
|
|
51769
|
+
operationName: "Finalize"
|
|
51770
|
+
});
|
|
51771
|
+
},
|
|
51772
|
+
/** Batch-finalizes idle GenLayer transactions (those stuck without progressing). Returns the EVM transaction hash. */
|
|
51773
|
+
finalizeIdlenessTxs: async (args) => {
|
|
51774
|
+
const { account, txIds } = args;
|
|
51775
|
+
if (txIds.length === 0) {
|
|
51776
|
+
throw new Error("finalizeIdlenessTxs requires at least one txId.");
|
|
51777
|
+
}
|
|
51778
|
+
const senderAccount = account || client.account;
|
|
51779
|
+
const encodedData = encodeFunctionData({
|
|
51780
|
+
abi: client.chain.consensusMainContract?.abi,
|
|
51781
|
+
functionName: "finalizeIdlenessTxs",
|
|
51782
|
+
args: [txIds]
|
|
51783
|
+
});
|
|
51784
|
+
return _sendConsensusCall({
|
|
51785
|
+
client,
|
|
51786
|
+
publicClient,
|
|
51787
|
+
encodedData,
|
|
51788
|
+
senderAccount,
|
|
51789
|
+
operationName: "Finalize idleness"
|
|
51635
51790
|
});
|
|
51636
51791
|
}
|
|
51637
51792
|
};
|
|
@@ -51745,6 +51900,69 @@ var _encodeSubmitAppealData = ({
|
|
|
51745
51900
|
args: [txId]
|
|
51746
51901
|
});
|
|
51747
51902
|
};
|
|
51903
|
+
var _sendConsensusCall = async ({
|
|
51904
|
+
client,
|
|
51905
|
+
publicClient,
|
|
51906
|
+
encodedData,
|
|
51907
|
+
senderAccount,
|
|
51908
|
+
value = 0n,
|
|
51909
|
+
operationName = "Consensus call"
|
|
51910
|
+
}) => {
|
|
51911
|
+
if (!client.chain.consensusMainContract?.address) {
|
|
51912
|
+
throw new Error("Consensus main contract not initialized.");
|
|
51913
|
+
}
|
|
51914
|
+
const validatedAccount = validateAccount(senderAccount);
|
|
51915
|
+
const nonce = await client.getCurrentNonce({ address: validatedAccount.address });
|
|
51916
|
+
let estimatedGas;
|
|
51917
|
+
try {
|
|
51918
|
+
estimatedGas = await client.estimateTransactionGas({
|
|
51919
|
+
to: client.chain.consensusMainContract.address,
|
|
51920
|
+
data: encodedData,
|
|
51921
|
+
value
|
|
51922
|
+
});
|
|
51923
|
+
} catch (err) {
|
|
51924
|
+
console.error("Gas estimation failed, using default 200_000:", err);
|
|
51925
|
+
estimatedGas = 200000n;
|
|
51926
|
+
}
|
|
51927
|
+
const gasPriceHex = await client.request({ method: "eth_gasPrice" });
|
|
51928
|
+
if (validatedAccount.type === "local") {
|
|
51929
|
+
if (!validatedAccount.signTransaction) {
|
|
51930
|
+
throw new Error("Local account does not support signTransaction.");
|
|
51931
|
+
}
|
|
51932
|
+
const txRequest = {
|
|
51933
|
+
account: validatedAccount,
|
|
51934
|
+
to: client.chain.consensusMainContract.address,
|
|
51935
|
+
data: encodedData,
|
|
51936
|
+
value,
|
|
51937
|
+
gas: estimatedGas,
|
|
51938
|
+
gasPrice: BigInt(gasPriceHex),
|
|
51939
|
+
nonce,
|
|
51940
|
+
chainId: client.chain.id
|
|
51941
|
+
};
|
|
51942
|
+
const serializedTransaction = await validatedAccount.signTransaction(txRequest);
|
|
51943
|
+
const evmHash2 = await client.sendRawTransaction({ serializedTransaction });
|
|
51944
|
+
const receipt2 = await publicClient.waitForTransactionReceipt({ hash: evmHash2 });
|
|
51945
|
+
if (receipt2.status === "reverted") {
|
|
51946
|
+
throw new Error(`${operationName} reverted: EVM tx ${evmHash2}`);
|
|
51947
|
+
}
|
|
51948
|
+
return evmHash2;
|
|
51949
|
+
}
|
|
51950
|
+
const evmHash = await client.request({
|
|
51951
|
+
method: "eth_sendTransaction",
|
|
51952
|
+
params: [{
|
|
51953
|
+
from: validatedAccount.address,
|
|
51954
|
+
to: client.chain.consensusMainContract.address,
|
|
51955
|
+
data: encodedData,
|
|
51956
|
+
value: value ? `0x${value.toString(16)}` : void 0,
|
|
51957
|
+
gas: `0x${estimatedGas.toString(16)}`
|
|
51958
|
+
}]
|
|
51959
|
+
});
|
|
51960
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: evmHash });
|
|
51961
|
+
if (receipt.status === "reverted") {
|
|
51962
|
+
throw new Error(`${operationName} reverted: EVM tx ${evmHash}`);
|
|
51963
|
+
}
|
|
51964
|
+
return evmHash;
|
|
51965
|
+
};
|
|
51748
51966
|
var isAddTransactionAbiMismatchError = (error) => {
|
|
51749
51967
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
51750
51968
|
const serializedError = typeof error === "object" && error !== null ? JSON.stringify(error, (_key, value) => {
|
|
@@ -51874,6 +52092,9 @@ var _sendTransaction = async ({
|
|
|
51874
52092
|
method: "eth_sendTransaction",
|
|
51875
52093
|
params: [formattedRequest]
|
|
51876
52094
|
});
|
|
52095
|
+
if (client.chain.isStudio) {
|
|
52096
|
+
return evmTxHash;
|
|
52097
|
+
}
|
|
51877
52098
|
const externalReceipt = await publicClient.waitForTransactionReceipt({ hash: evmTxHash });
|
|
51878
52099
|
if (externalReceipt.status === "reverted") {
|
|
51879
52100
|
throw new Error(`Transaction reverted: EVM tx ${evmTxHash} to consensus contract ${client.chain.consensusMainContract?.address} was reverted.`);
|
|
@@ -52158,7 +52379,7 @@ var receiptActions = (client, publicClient) => ({
|
|
|
52158
52379
|
const requestedStatus = transactionsStatusNameToNumber[status];
|
|
52159
52380
|
if (transactionStatusString === requestedStatus || status === "ACCEPTED" && isDecidedState(transactionStatusString)) {
|
|
52160
52381
|
let finalTransaction = transaction;
|
|
52161
|
-
if (client.chain.
|
|
52382
|
+
if (client.chain.isStudio) {
|
|
52162
52383
|
finalTransaction = decodeLocalnetTransaction(transaction);
|
|
52163
52384
|
}
|
|
52164
52385
|
if (!fullTransaction) {
|
|
@@ -53017,6 +53238,21 @@ var PROVIDER_METHODS = /* @__PURE__ */ new Set([
|
|
|
53017
53238
|
"personal_sign",
|
|
53018
53239
|
"eth_signTypedData_v4"
|
|
53019
53240
|
]);
|
|
53241
|
+
var assertChainMatch = async (provider, chainConfig) => {
|
|
53242
|
+
if (chainConfig.isStudio) return;
|
|
53243
|
+
const expectedChainIdHex = `0x${chainConfig.id.toString(16)}`;
|
|
53244
|
+
try {
|
|
53245
|
+
const currentChainId = await provider.request({ method: "eth_chainId" });
|
|
53246
|
+
if (currentChainId !== expectedChainIdHex) {
|
|
53247
|
+
const currentId = parseInt(currentChainId, 16);
|
|
53248
|
+
throw new Error(
|
|
53249
|
+
`Wallet is on chain ${currentId} but client is configured for chain ${chainConfig.id} (${chainConfig.name}). Call client.connect("${chainConfig.name}") or switch your wallet to the correct network before sending transactions.`
|
|
53250
|
+
);
|
|
53251
|
+
}
|
|
53252
|
+
} catch (err) {
|
|
53253
|
+
if (err instanceof Error && err.message.startsWith("Wallet is on chain")) throw err;
|
|
53254
|
+
}
|
|
53255
|
+
};
|
|
53020
53256
|
var getCustomTransportConfig = (config, chainConfig) => {
|
|
53021
53257
|
const isAddress2 = typeof config.account !== "object";
|
|
53022
53258
|
return {
|
|
@@ -53025,6 +53261,9 @@ var getCustomTransportConfig = (config, chainConfig) => {
|
|
|
53025
53261
|
const provider = config.provider || (typeof window !== "undefined" ? window.ethereum : void 0);
|
|
53026
53262
|
if (provider) {
|
|
53027
53263
|
try {
|
|
53264
|
+
if (method === "eth_sendTransaction" || method === "eth_signTransaction") {
|
|
53265
|
+
await assertChainMatch(provider, chainConfig);
|
|
53266
|
+
}
|
|
53028
53267
|
return await provider.request({ method, params });
|
|
53029
53268
|
} catch (err) {
|
|
53030
53269
|
console.warn(`Error using provider for method ${method}:`, err);
|
|
@@ -56011,6 +56250,41 @@ var TraceAction = class extends BaseAction {
|
|
|
56011
56250
|
}
|
|
56012
56251
|
};
|
|
56013
56252
|
|
|
56253
|
+
// src/commands/transactions/finalize.ts
|
|
56254
|
+
var FinalizeAction = class extends BaseAction {
|
|
56255
|
+
constructor() {
|
|
56256
|
+
super();
|
|
56257
|
+
}
|
|
56258
|
+
async finalize({ txId, rpc }) {
|
|
56259
|
+
const client = await this.getClient(rpc);
|
|
56260
|
+
this.startSpinner(`Finalizing transaction ${txId}...`);
|
|
56261
|
+
try {
|
|
56262
|
+
const evmHash = await client.finalizeTransaction({ txId });
|
|
56263
|
+
this.succeedSpinner("Transaction finalized", { txId, evmTransactionHash: evmHash });
|
|
56264
|
+
} catch (error) {
|
|
56265
|
+
this.failSpinner("Error finalizing transaction", error);
|
|
56266
|
+
}
|
|
56267
|
+
}
|
|
56268
|
+
async finalizeBatch({ txIds, rpc }) {
|
|
56269
|
+
if (txIds.length === 0) {
|
|
56270
|
+
this.failSpinner("At least one txId is required.");
|
|
56271
|
+
return;
|
|
56272
|
+
}
|
|
56273
|
+
const client = await this.getClient(rpc);
|
|
56274
|
+
this.startSpinner(`Finalizing ${txIds.length} idle transaction(s)...`);
|
|
56275
|
+
try {
|
|
56276
|
+
const evmHash = await client.finalizeIdlenessTxs({ txIds });
|
|
56277
|
+
this.succeedSpinner("Idle transactions finalized", {
|
|
56278
|
+
count: txIds.length,
|
|
56279
|
+
txIds,
|
|
56280
|
+
evmTransactionHash: evmHash
|
|
56281
|
+
});
|
|
56282
|
+
} catch (error) {
|
|
56283
|
+
this.failSpinner("Error finalizing idle transactions", error);
|
|
56284
|
+
}
|
|
56285
|
+
}
|
|
56286
|
+
};
|
|
56287
|
+
|
|
56014
56288
|
// src/commands/transactions/index.ts
|
|
56015
56289
|
function parseIntOption(value, fallback2) {
|
|
56016
56290
|
const parsed = parseInt(value, 10);
|
|
@@ -56034,6 +56308,14 @@ function initializeTransactionsCommands(program2) {
|
|
|
56034
56308
|
const traceAction = new TraceAction();
|
|
56035
56309
|
await traceAction.trace({ txId, ...options });
|
|
56036
56310
|
});
|
|
56311
|
+
program2.command("finalize <txId>").description("Finalize a transaction that is ready to be finalized (public call)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (txId, options) => {
|
|
56312
|
+
const finalizeAction = new FinalizeAction();
|
|
56313
|
+
await finalizeAction.finalize({ txId, ...options });
|
|
56314
|
+
});
|
|
56315
|
+
program2.command("finalize-batch <txIds...>").description("Finalize a batch of idle transactions in a single call (public call)").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (txIds, options) => {
|
|
56316
|
+
const finalizeAction = new FinalizeAction();
|
|
56317
|
+
await finalizeAction.finalizeBatch({ txIds, ...options });
|
|
56318
|
+
});
|
|
56037
56319
|
return program2;
|
|
56038
56320
|
}
|
|
56039
56321
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genlayer",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.15",
|
|
4
4
|
"description": "GenLayer Command Line Tool",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"dotenv": "^17.0.0",
|
|
68
68
|
"ethers": "^6.13.4",
|
|
69
69
|
"fs-extra": "^11.3.0",
|
|
70
|
-
"genlayer-js": "^0.
|
|
70
|
+
"genlayer-js": "^0.28.5",
|
|
71
71
|
"inquirer": "^12.0.0",
|
|
72
72
|
"keytar": "^7.9.0",
|
|
73
73
|
"node-fetch": "^3.0.0",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {TransactionHash} from "genlayer-js/types";
|
|
2
|
+
import {BaseAction} from "../../lib/actions/BaseAction";
|
|
3
|
+
|
|
4
|
+
export interface FinalizeOptions {
|
|
5
|
+
rpc?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class FinalizeAction extends BaseAction {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async finalize({txId, rpc}: {txId: TransactionHash; rpc?: string}): Promise<void> {
|
|
14
|
+
const client = await this.getClient(rpc);
|
|
15
|
+
|
|
16
|
+
this.startSpinner(`Finalizing transaction ${txId}...`);
|
|
17
|
+
try {
|
|
18
|
+
const evmHash = await client.finalizeTransaction({txId});
|
|
19
|
+
this.succeedSpinner("Transaction finalized", {txId, evmTransactionHash: evmHash});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
this.failSpinner("Error finalizing transaction", error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async finalizeBatch({txIds, rpc}: {txIds: TransactionHash[]; rpc?: string}): Promise<void> {
|
|
26
|
+
if (txIds.length === 0) {
|
|
27
|
+
this.failSpinner("At least one txId is required.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const client = await this.getClient(rpc);
|
|
32
|
+
|
|
33
|
+
this.startSpinner(`Finalizing ${txIds.length} idle transaction(s)...`);
|
|
34
|
+
try {
|
|
35
|
+
const evmHash = await client.finalizeIdlenessTxs({txIds});
|
|
36
|
+
this.succeedSpinner("Idle transactions finalized", {
|
|
37
|
+
count: txIds.length,
|
|
38
|
+
txIds,
|
|
39
|
+
evmTransactionHash: evmHash,
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.failSpinner("Error finalizing idle transactions", error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -3,6 +3,7 @@ import {TransactionStatus, TransactionHash} from "genlayer-js/types";
|
|
|
3
3
|
import {ReceiptAction, ReceiptOptions} from "./receipt";
|
|
4
4
|
import {AppealAction, AppealOptions, AppealBondOptions} from "./appeal";
|
|
5
5
|
import {TraceAction, TraceOptions} from "./trace";
|
|
6
|
+
import {FinalizeAction, FinalizeOptions} from "./finalize";
|
|
6
7
|
|
|
7
8
|
function parseIntOption(value: string, fallback: number): number {
|
|
8
9
|
const parsed = parseInt(value, 10);
|
|
@@ -56,5 +57,23 @@ export function initializeTransactionsCommands(program: Command) {
|
|
|
56
57
|
await traceAction.trace({txId, ...options});
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
program
|
|
61
|
+
.command("finalize <txId>")
|
|
62
|
+
.description("Finalize a transaction that is ready to be finalized (public call)")
|
|
63
|
+
.option("--rpc <rpcUrl>", "RPC URL for the network")
|
|
64
|
+
.action(async (txId: TransactionHash, options: FinalizeOptions) => {
|
|
65
|
+
const finalizeAction = new FinalizeAction();
|
|
66
|
+
await finalizeAction.finalize({txId, ...options});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
program
|
|
70
|
+
.command("finalize-batch <txIds...>")
|
|
71
|
+
.description("Finalize a batch of idle transactions in a single call (public call)")
|
|
72
|
+
.option("--rpc <rpcUrl>", "RPC URL for the network")
|
|
73
|
+
.action(async (txIds: TransactionHash[], options: FinalizeOptions) => {
|
|
74
|
+
const finalizeAction = new FinalizeAction();
|
|
75
|
+
await finalizeAction.finalizeBatch({txIds, ...options});
|
|
76
|
+
});
|
|
77
|
+
|
|
59
78
|
return program;
|
|
60
79
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {describe, test, vi, beforeEach, afterEach, expect} from "vitest";
|
|
2
|
+
import {createClient, createAccount} from "genlayer-js";
|
|
3
|
+
import type {TransactionHash} from "genlayer-js/types";
|
|
4
|
+
import {FinalizeAction} from "../../src/commands/transactions/finalize";
|
|
5
|
+
|
|
6
|
+
vi.mock("genlayer-js", async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof import("genlayer-js")>();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
createClient: vi.fn(),
|
|
11
|
+
createAccount: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("FinalizeAction", () => {
|
|
16
|
+
let finalizeAction: FinalizeAction;
|
|
17
|
+
const mockClient = {
|
|
18
|
+
finalizeTransaction: vi.fn(),
|
|
19
|
+
finalizeIdlenessTxs: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const mockPrivateKey = "mocked_private_key";
|
|
23
|
+
const mockTxId = "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash;
|
|
24
|
+
const mockTxId2 = "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd" as TransactionHash;
|
|
25
|
+
const mockEvmHash = "0xdeadbeef" as `0x${string}`;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
vi.mocked(createClient).mockReturnValue(mockClient as any);
|
|
30
|
+
vi.mocked(createAccount).mockReturnValue({privateKey: mockPrivateKey} as any);
|
|
31
|
+
finalizeAction = new FinalizeAction();
|
|
32
|
+
vi.spyOn(finalizeAction as any, "getAccount").mockResolvedValue({privateKey: mockPrivateKey});
|
|
33
|
+
|
|
34
|
+
vi.spyOn(finalizeAction as any, "startSpinner").mockImplementation(() => {});
|
|
35
|
+
vi.spyOn(finalizeAction as any, "succeedSpinner").mockImplementation(() => {});
|
|
36
|
+
vi.spyOn(finalizeAction as any, "failSpinner").mockImplementation(() => {});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
vi.restoreAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("finalize calls client.finalizeTransaction and reports the EVM hash", async () => {
|
|
44
|
+
vi.mocked(mockClient.finalizeTransaction).mockResolvedValue(mockEvmHash);
|
|
45
|
+
|
|
46
|
+
await finalizeAction.finalize({txId: mockTxId});
|
|
47
|
+
|
|
48
|
+
expect(mockClient.finalizeTransaction).toHaveBeenCalledWith({txId: mockTxId});
|
|
49
|
+
expect(finalizeAction["succeedSpinner"]).toHaveBeenCalledWith(
|
|
50
|
+
"Transaction finalized",
|
|
51
|
+
{txId: mockTxId, evmTransactionHash: mockEvmHash},
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("finalize surfaces underlying errors via failSpinner", async () => {
|
|
56
|
+
vi.mocked(mockClient.finalizeTransaction).mockRejectedValue(new Error("boom"));
|
|
57
|
+
|
|
58
|
+
await finalizeAction.finalize({txId: mockTxId});
|
|
59
|
+
|
|
60
|
+
expect(finalizeAction["failSpinner"]).toHaveBeenCalledWith(
|
|
61
|
+
"Error finalizing transaction",
|
|
62
|
+
expect.any(Error),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("finalize uses custom RPC URL when provided", async () => {
|
|
67
|
+
vi.mocked(mockClient.finalizeTransaction).mockResolvedValue(mockEvmHash);
|
|
68
|
+
|
|
69
|
+
await finalizeAction.finalize({txId: mockTxId, rpc: "https://custom.com"});
|
|
70
|
+
|
|
71
|
+
expect(createClient).toHaveBeenCalledWith(
|
|
72
|
+
expect.objectContaining({endpoint: "https://custom.com"}),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("finalizeBatch calls client.finalizeIdlenessTxs with all ids", async () => {
|
|
77
|
+
vi.mocked(mockClient.finalizeIdlenessTxs).mockResolvedValue(mockEvmHash);
|
|
78
|
+
|
|
79
|
+
await finalizeAction.finalizeBatch({txIds: [mockTxId, mockTxId2]});
|
|
80
|
+
|
|
81
|
+
expect(mockClient.finalizeIdlenessTxs).toHaveBeenCalledWith({
|
|
82
|
+
txIds: [mockTxId, mockTxId2],
|
|
83
|
+
});
|
|
84
|
+
expect(finalizeAction["succeedSpinner"]).toHaveBeenCalledWith(
|
|
85
|
+
"Idle transactions finalized",
|
|
86
|
+
{count: 2, txIds: [mockTxId, mockTxId2], evmTransactionHash: mockEvmHash},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("finalizeBatch rejects an empty list without calling the client", async () => {
|
|
91
|
+
await finalizeAction.finalizeBatch({txIds: []});
|
|
92
|
+
|
|
93
|
+
expect(mockClient.finalizeIdlenessTxs).not.toHaveBeenCalled();
|
|
94
|
+
expect(finalizeAction["failSpinner"]).toHaveBeenCalledWith(
|
|
95
|
+
"At least one txId is required.",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("finalizeBatch surfaces underlying errors via failSpinner", async () => {
|
|
100
|
+
vi.mocked(mockClient.finalizeIdlenessTxs).mockRejectedValue(new Error("revert"));
|
|
101
|
+
|
|
102
|
+
await finalizeAction.finalizeBatch({txIds: [mockTxId]});
|
|
103
|
+
|
|
104
|
+
expect(finalizeAction["failSpinner"]).toHaveBeenCalledWith(
|
|
105
|
+
"Error finalizing idle transactions",
|
|
106
|
+
expect.any(Error),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {Command} from "commander";
|
|
2
|
+
import {FinalizeAction} from "../../src/commands/transactions/finalize";
|
|
3
|
+
import {vi, describe, beforeEach, afterEach, test, expect} from "vitest";
|
|
4
|
+
import {initializeTransactionsCommands} from "../../src/commands/transactions";
|
|
5
|
+
|
|
6
|
+
vi.mock("../../src/commands/transactions/finalize");
|
|
7
|
+
|
|
8
|
+
describe("finalize command", () => {
|
|
9
|
+
let program: Command;
|
|
10
|
+
const mockTxId = "0x1234567890123456789012345678901234567890123456789012345678901234";
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
program = new Command();
|
|
14
|
+
initializeTransactionsCommands(program);
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
vi.restoreAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("FinalizeAction.finalize is called with txId", async () => {
|
|
23
|
+
program.parse(["node", "test", "finalize", mockTxId]);
|
|
24
|
+
expect(FinalizeAction).toHaveBeenCalledTimes(1);
|
|
25
|
+
expect(FinalizeAction.prototype.finalize).toHaveBeenCalledWith({txId: mockTxId});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("FinalizeAction.finalize is called with custom RPC URL", async () => {
|
|
29
|
+
program.parse(["node", "test", "finalize", mockTxId, "--rpc", "https://custom.com"]);
|
|
30
|
+
expect(FinalizeAction.prototype.finalize).toHaveBeenCalledWith({
|
|
31
|
+
txId: mockTxId,
|
|
32
|
+
rpc: "https://custom.com",
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("throws error for unrecognized options", async () => {
|
|
37
|
+
const finalizeCommand = program.commands.find(cmd => cmd.name() === "finalize");
|
|
38
|
+
finalizeCommand?.exitOverride();
|
|
39
|
+
expect(() =>
|
|
40
|
+
program.parse(["node", "test", "finalize", mockTxId, "--invalid-option"]),
|
|
41
|
+
).toThrowError("error: unknown option '--invalid-option'");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("finalize-batch command", () => {
|
|
46
|
+
let program: Command;
|
|
47
|
+
const mockTxId1 = "0x1111111111111111111111111111111111111111111111111111111111111111";
|
|
48
|
+
const mockTxId2 = "0x2222222222222222222222222222222222222222222222222222222222222222";
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
program = new Command();
|
|
52
|
+
initializeTransactionsCommands(program);
|
|
53
|
+
vi.clearAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.restoreAllMocks();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("FinalizeAction.finalizeBatch is called with a single txId", async () => {
|
|
61
|
+
program.parse(["node", "test", "finalize-batch", mockTxId1]);
|
|
62
|
+
expect(FinalizeAction.prototype.finalizeBatch).toHaveBeenCalledWith({
|
|
63
|
+
txIds: [mockTxId1],
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("FinalizeAction.finalizeBatch is called with multiple txIds", async () => {
|
|
68
|
+
program.parse(["node", "test", "finalize-batch", mockTxId1, mockTxId2]);
|
|
69
|
+
expect(FinalizeAction.prototype.finalizeBatch).toHaveBeenCalledWith({
|
|
70
|
+
txIds: [mockTxId1, mockTxId2],
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("FinalizeAction.finalizeBatch is called with custom RPC", async () => {
|
|
75
|
+
program.parse([
|
|
76
|
+
"node", "test", "finalize-batch", mockTxId1, mockTxId2, "--rpc", "https://custom.com",
|
|
77
|
+
]);
|
|
78
|
+
expect(FinalizeAction.prototype.finalizeBatch).toHaveBeenCalledWith({
|
|
79
|
+
txIds: [mockTxId1, mockTxId2],
|
|
80
|
+
rpc: "https://custom.com",
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|