genlayer 0.22.1 → 0.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.23.0 (2025-07-15)
4
+
5
+ ### Features
6
+
7
+ * fetch transaction receipt command ([#234](https://github.com/yeagerai/genlayer-cli/issues/234)) ([5cf868c](https://github.com/yeagerai/genlayer-cli/commit/5cf868c28ce036e41b7b40ed6cdde222cdc5dcf0))
8
+
3
9
  ## 0.22.1 (2025-07-14)
4
10
 
5
11
  ## 0.22.0 (2025-07-14)
package/dist/index.js CHANGED
@@ -17856,7 +17856,7 @@ var require_semver2 = __commonJS({
17856
17856
  import { program } from "commander";
17857
17857
 
17858
17858
  // package.json
17859
- var version = "0.22.1";
17859
+ var version = "0.23.0";
17860
17860
  var package_default = {
17861
17861
  name: "genlayer",
17862
17862
  version,
@@ -42909,6 +42909,51 @@ function initializeNetworkCommands(program2) {
42909
42909
  return program2;
42910
42910
  }
42911
42911
 
42912
+ // src/commands/transactions/receipt.ts
42913
+ var ReceiptAction = class extends BaseAction {
42914
+ constructor() {
42915
+ super();
42916
+ }
42917
+ validateTransactionStatus(status) {
42918
+ const upperStatus = status.toUpperCase();
42919
+ if (!(upperStatus in TransactionStatus)) {
42920
+ const validStatuses = Object.values(TransactionStatus);
42921
+ this.failSpinner(
42922
+ "Invalid transaction status",
42923
+ `Invalid status: ${status}. Valid values are: ${validStatuses.join(", ")}`
42924
+ );
42925
+ return;
42926
+ }
42927
+ return TransactionStatus[upperStatus];
42928
+ }
42929
+ async receipt({
42930
+ txId,
42931
+ status = TransactionStatus.FINALIZED,
42932
+ retries,
42933
+ interval,
42934
+ rpc
42935
+ }) {
42936
+ const client = await this.getClient(rpc);
42937
+ await client.initializeConsensusSmartContract();
42938
+ this.startSpinner(`Waiting for transaction receipt ${txId} (status: ${status})...`);
42939
+ try {
42940
+ let validatedStatus = this.validateTransactionStatus(status);
42941
+ if (!validatedStatus) {
42942
+ return;
42943
+ }
42944
+ const result = await client.waitForTransactionReceipt({
42945
+ hash: txId,
42946
+ status: validatedStatus,
42947
+ retries,
42948
+ interval
42949
+ });
42950
+ this.succeedSpinner("Transaction receipt retrieved successfully", result);
42951
+ } catch (error) {
42952
+ this.failSpinner("Error retrieving transaction receipt", error);
42953
+ }
42954
+ }
42955
+ };
42956
+
42912
42957
  // src/commands/transactions/appeal.ts
42913
42958
  var AppealAction = class extends BaseAction {
42914
42959
  constructor() {
@@ -42938,7 +42983,16 @@ var AppealAction = class extends BaseAction {
42938
42983
  };
42939
42984
 
42940
42985
  // src/commands/transactions/index.ts
42986
+ function parseIntOption(value, fallback2) {
42987
+ const parsed = parseInt(value, 10);
42988
+ return isNaN(parsed) ? fallback2 : parsed;
42989
+ }
42941
42990
  function initializeTransactionsCommands(program2) {
42991
+ const validStatuses = Object.values(TransactionStatus).join(", ");
42992
+ program2.command("receipt <txId>").description("Get transaction receipt by hash").option("--status <status>", `Transaction status to wait for (${validStatuses})`, TransactionStatus.FINALIZED).option("--retries <retries>", "Number of retries", (value) => parseIntOption(value, 100), 100).option("--interval <interval>", "Interval between retries in milliseconds", (value) => parseIntOption(value, 5e3), 5e3).option("--rpc <rpcUrl>", "RPC URL for the network").action(async (txId, options) => {
42993
+ const receiptAction = new ReceiptAction();
42994
+ await receiptAction.receipt({ txId, ...options });
42995
+ });
42942
42996
  program2.command("appeal <txId>").description("Appeal a transaction by its hash").option("--rpc <rpcUrl>", "RPC URL for the network").action(async (txId, options) => {
42943
42997
  const appealAction = new AppealAction();
42944
42998
  await appealAction.appeal({ txId, ...options });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -1,8 +1,29 @@
1
1
  import {Command} from "commander";
2
- import {TransactionHash} from "genlayer-js/types";
2
+ import {TransactionStatus, TransactionHash} from "genlayer-js/types";
3
+ import {ReceiptAction, ReceiptOptions} from "./receipt";
3
4
  import {AppealAction, AppealOptions} from "./appeal";
4
5
 
6
+ function parseIntOption(value: string, fallback: number): number {
7
+ const parsed = parseInt(value, 10);
8
+ return isNaN(parsed) ? fallback : parsed;
9
+ }
10
+
5
11
  export function initializeTransactionsCommands(program: Command) {
12
+ const validStatuses = Object.values(TransactionStatus).join(", ");
13
+
14
+ program
15
+ .command("receipt <txId>")
16
+ .description("Get transaction receipt by hash")
17
+ .option("--status <status>", `Transaction status to wait for (${validStatuses})`, TransactionStatus.FINALIZED)
18
+ .option("--retries <retries>", "Number of retries", (value) => parseIntOption(value, 100), 100)
19
+ .option("--interval <interval>", "Interval between retries in milliseconds", (value) => parseIntOption(value, 5000), 5000)
20
+ .option("--rpc <rpcUrl>", "RPC URL for the network")
21
+ .action(async (txId: TransactionHash, options: ReceiptOptions) => {
22
+ const receiptAction = new ReceiptAction();
23
+
24
+ await receiptAction.receipt({txId, ...options});
25
+ })
26
+
6
27
  program
7
28
  .command("appeal <txId>")
8
29
  .description("Appeal a transaction by its hash")
@@ -0,0 +1,64 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+ import {TransactionHash, TransactionStatus} from "genlayer-js/types";
3
+
4
+ export interface ReceiptParams {
5
+ txId: TransactionHash;
6
+ status?: string | TransactionStatus;
7
+ retries?: number;
8
+ interval?: number;
9
+ rpc?: string;
10
+ }
11
+
12
+ export interface ReceiptOptions extends Omit<ReceiptParams, 'txId'> {}
13
+
14
+ export class ReceiptAction extends BaseAction {
15
+ constructor() {
16
+ super();
17
+ }
18
+
19
+ private validateTransactionStatus(status: string): TransactionStatus | undefined {
20
+ const upperStatus = status.toUpperCase() as keyof typeof TransactionStatus;
21
+
22
+ if (!(upperStatus in TransactionStatus)) {
23
+ const validStatuses = Object.values(TransactionStatus);
24
+ this.failSpinner(
25
+ "Invalid transaction status",
26
+ `Invalid status: ${status}. Valid values are: ${validStatuses.join(", ")}`
27
+ );
28
+ return
29
+ }
30
+
31
+ return TransactionStatus[upperStatus];
32
+ }
33
+
34
+ async receipt({
35
+ txId,
36
+ status = TransactionStatus.FINALIZED,
37
+ retries,
38
+ interval,
39
+ rpc,
40
+ }: ReceiptParams): Promise<void> {
41
+ const client = await this.getClient(rpc);
42
+ await client.initializeConsensusSmartContract();
43
+ this.startSpinner(`Waiting for transaction receipt ${txId} (status: ${status})...`);
44
+
45
+ try {
46
+ let validatedStatus = this.validateTransactionStatus(status);
47
+
48
+ if (!validatedStatus) {
49
+ return;
50
+ }
51
+
52
+ const result = await client.waitForTransactionReceipt({
53
+ hash: txId,
54
+ status: validatedStatus,
55
+ retries,
56
+ interval,
57
+ });
58
+
59
+ this.succeedSpinner("Transaction receipt retrieved successfully", result);
60
+ } catch (error) {
61
+ this.failSpinner("Error retrieving transaction receipt", error);
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,173 @@
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 {TransactionStatus} from "genlayer-js/types";
5
+ import {ReceiptAction, type ReceiptParams} from "../../src/commands/transactions/receipt";
6
+
7
+ vi.mock("genlayer-js");
8
+
9
+ describe("ReceiptAction", () => {
10
+ let receiptAction: ReceiptAction;
11
+ const mockClient = {
12
+ waitForTransactionReceipt: vi.fn(),
13
+ initializeConsensusSmartContract: vi.fn(),
14
+ };
15
+
16
+ const mockPrivateKey = "mocked_private_key";
17
+ const mockTxId = "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash;
18
+ const defaultRetries = 100;
19
+ const defaultInterval = 5000;
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ vi.mocked(createClient).mockReturnValue(mockClient as any);
24
+ vi.mocked(createAccount).mockReturnValue({privateKey: mockPrivateKey} as any);
25
+ receiptAction = new ReceiptAction();
26
+ vi.spyOn(receiptAction as any, "getPrivateKey").mockResolvedValue(mockPrivateKey);
27
+
28
+ vi.spyOn(receiptAction as any, "startSpinner").mockImplementation(() => {});
29
+ vi.spyOn(receiptAction as any, "succeedSpinner").mockImplementation(() => {});
30
+ vi.spyOn(receiptAction as any, "failSpinner").mockImplementation(() => {});
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.restoreAllMocks();
35
+ });
36
+
37
+ test("retrieves transaction receipt successfully with default options", async () => {
38
+ const mockReceipt = {status: "FINALIZED", data: {hash: mockTxId}};
39
+
40
+ vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
41
+
42
+ await receiptAction.receipt({
43
+ txId: mockTxId,
44
+ retries: defaultRetries,
45
+ interval: defaultInterval,
46
+ });
47
+
48
+ expect(mockClient.waitForTransactionReceipt).toHaveBeenCalledWith({
49
+ hash: mockTxId,
50
+ status: TransactionStatus.FINALIZED,
51
+ retries: defaultRetries,
52
+ interval: defaultInterval,
53
+ });
54
+ expect(receiptAction["succeedSpinner"]).toHaveBeenCalledWith(
55
+ "Transaction receipt retrieved successfully",
56
+ mockReceipt,
57
+ );
58
+ });
59
+
60
+ test("retrieves transaction receipt with custom options", async () => {
61
+ const mockReceipt = {status: "ACCEPTED", data: {hash: mockTxId}};
62
+
63
+ vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
64
+
65
+ await receiptAction.receipt({
66
+ txId: mockTxId,
67
+ status: "ACCEPTED",
68
+ retries: 50,
69
+ interval: 3000,
70
+ });
71
+
72
+ expect(mockClient.waitForTransactionReceipt).toHaveBeenCalledWith({
73
+ hash: mockTxId,
74
+ status: TransactionStatus.ACCEPTED,
75
+ retries: 50,
76
+ interval: 3000,
77
+ });
78
+ expect(receiptAction["succeedSpinner"]).toHaveBeenCalledWith(
79
+ "Transaction receipt retrieved successfully",
80
+ mockReceipt,
81
+ );
82
+ });
83
+
84
+ test("handles waitForTransactionReceipt errors", async () => {
85
+ vi.mocked(mockClient.waitForTransactionReceipt).mockRejectedValue(new Error("Mocked receipt error"));
86
+
87
+ await receiptAction.receipt({
88
+ txId: mockTxId,
89
+ retries: defaultRetries,
90
+ interval: defaultInterval,
91
+ });
92
+
93
+ expect(receiptAction["failSpinner"]).toHaveBeenCalledWith(
94
+ "Error retrieving transaction receipt",
95
+ expect.any(Error),
96
+ );
97
+ });
98
+
99
+ test("uses custom RPC URL for receipt operations", async () => {
100
+ const rpcUrl = "https://custom-rpc-url.com";
101
+ const mockReceipt = {status: "FINALIZED", data: {hash: mockTxId}};
102
+
103
+ vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
104
+
105
+ await receiptAction.receipt({
106
+ txId: mockTxId,
107
+ retries: defaultRetries,
108
+ interval: defaultInterval,
109
+ rpc: rpcUrl,
110
+ });
111
+
112
+ expect(createClient).toHaveBeenCalledWith(
113
+ expect.objectContaining({
114
+ endpoint: rpcUrl,
115
+ }),
116
+ );
117
+ expect(mockClient.waitForTransactionReceipt).toHaveBeenCalledWith({
118
+ hash: mockTxId,
119
+ status: TransactionStatus.FINALIZED,
120
+ retries: defaultRetries,
121
+ interval: defaultInterval,
122
+ });
123
+ expect(receiptAction["succeedSpinner"]).toHaveBeenCalledWith(
124
+ "Transaction receipt retrieved successfully",
125
+ mockReceipt,
126
+ );
127
+ });
128
+
129
+ test("validates transaction status and shows error for invalid status", async () => {
130
+ await receiptAction.receipt({
131
+ txId: mockTxId,
132
+ status: "INVALID_STATUS",
133
+ retries: defaultRetries,
134
+ interval: defaultInterval,
135
+ });
136
+
137
+ expect(receiptAction["failSpinner"]).toHaveBeenCalledWith(
138
+ "Invalid transaction status",
139
+ expect.stringContaining("Invalid status: INVALID_STATUS")
140
+ );
141
+
142
+ expect(mockClient.waitForTransactionReceipt).not.toHaveBeenCalled();
143
+ });
144
+
145
+ test("accepts valid transaction statuses", async () => {
146
+ const mockReceipt = {status: "PENDING", data: {hash: mockTxId}};
147
+ vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
148
+
149
+ const testStatuses = [
150
+ {input: "accepted", expected: TransactionStatus.ACCEPTED},
151
+ {input: "FINALIZED", expected: TransactionStatus.FINALIZED},
152
+ {input: "pending", expected: TransactionStatus.PENDING},
153
+ {input: "COMMITTING", expected: TransactionStatus.COMMITTING},
154
+ ];
155
+
156
+ for (const {input, expected} of testStatuses) {
157
+ await receiptAction.receipt({
158
+ txId: mockTxId,
159
+ status: input,
160
+ retries: defaultRetries,
161
+ interval: defaultInterval,
162
+ });
163
+
164
+ expect(mockClient.waitForTransactionReceipt).toHaveBeenCalledWith({
165
+ hash: mockTxId,
166
+ status: expected,
167
+ retries: defaultRetries,
168
+ interval: defaultInterval,
169
+ });
170
+ }
171
+ });
172
+
173
+ });
@@ -0,0 +1,108 @@
1
+ import {Command} from "commander";
2
+ import {ReceiptAction} from "../../src/commands/transactions/receipt";
3
+ import {vi, describe, beforeEach, afterEach, test, expect} from "vitest";
4
+ import {initializeTransactionsCommands} from "../../src/commands/transactions";
5
+
6
+ vi.mock("../../src/commands/transactions/receipt");
7
+
8
+ describe("receipt 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("ReceiptAction.receipt is called with default options", async () => {
23
+ program.parse(["node", "test", "receipt", mockTxId]);
24
+ expect(ReceiptAction).toHaveBeenCalledTimes(1);
25
+ expect(ReceiptAction.prototype.receipt).toHaveBeenCalledWith({
26
+ txId: mockTxId,
27
+ status: "FINALIZED",
28
+ retries: 100,
29
+ interval: 5000,
30
+ });
31
+ });
32
+
33
+ test("ReceiptAction.receipt is called with custom options", async () => {
34
+ program.parse([
35
+ "node",
36
+ "test",
37
+ "receipt",
38
+ mockTxId,
39
+ "--status",
40
+ "ACCEPTED",
41
+ "--retries",
42
+ "50",
43
+ "--interval",
44
+ "3000",
45
+ "--rpc",
46
+ "https://custom-rpc-url-for-receipt.com",
47
+ ]);
48
+ expect(ReceiptAction).toHaveBeenCalledTimes(1);
49
+ expect(ReceiptAction.prototype.receipt).toHaveBeenCalledWith({
50
+ txId: mockTxId,
51
+ status: "ACCEPTED",
52
+ retries: 50,
53
+ interval: 3000,
54
+ rpc: "https://custom-rpc-url-for-receipt.com",
55
+ });
56
+ });
57
+
58
+ test("ReceiptAction is instantiated when the receipt command is executed", async () => {
59
+ program.parse(["node", "test", "receipt", mockTxId]);
60
+ expect(ReceiptAction).toHaveBeenCalledTimes(1);
61
+ });
62
+
63
+ test("throws error for unrecognized options", async () => {
64
+ const receiptCommand = program.commands.find(cmd => cmd.name() === "receipt");
65
+ receiptCommand?.exitOverride();
66
+ expect(() =>
67
+ program.parse(["node", "test", "receipt", mockTxId, "--invalid-option"]),
68
+ ).toThrowError("error: unknown option '--invalid-option'");
69
+ });
70
+
71
+ test("parses numeric options correctly", async () => {
72
+ program.parse([
73
+ "node",
74
+ "test",
75
+ "receipt",
76
+ mockTxId,
77
+ "--retries",
78
+ "25",
79
+ "--interval",
80
+ "1000",
81
+ ]);
82
+ expect(ReceiptAction.prototype.receipt).toHaveBeenCalledWith({
83
+ txId: mockTxId,
84
+ status: "FINALIZED",
85
+ retries: 25,
86
+ interval: 1000,
87
+ });
88
+ });
89
+
90
+ test("uses fallback value for invalid numeric options", async () => {
91
+ program.parse([
92
+ "node",
93
+ "test",
94
+ "receipt",
95
+ mockTxId,
96
+ "--retries",
97
+ "invalid",
98
+ "--interval",
99
+ "notanumber",
100
+ ]);
101
+ expect(ReceiptAction.prototype.receipt).toHaveBeenCalledWith({
102
+ txId: mockTxId,
103
+ status: "FINALIZED",
104
+ retries: 100,
105
+ interval: 5000,
106
+ });
107
+ });
108
+ });