genlayer-js 0.12.1 → 0.14.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.
@@ -5,20 +5,15 @@ import {
5
5
  GenLayerTransaction,
6
6
  GenLayerRawTransaction,
7
7
  transactionsStatusNameToNumber,
8
- transactionsStatusNumberToName,
9
- transactionResultNumberToName,
10
- VoteType,
11
- voteTypeNumberToName,
12
- DecodedCallData,
13
- DecodedDeployData,
14
8
  } from "../types/transactions";
15
9
  import {transactionsConfig} from "../config/transactions";
16
10
  import {sleep} from "../utils/async";
17
11
  import {GenLayerChain} from "@/types";
18
- import {b64ToArray, calldataToUserFriendlyJson, resultToUserFriendlyJson} from "@/utils/jsonifier";
19
- import {Abi, PublicClient, fromRlp, fromHex, Hex, Address} from "viem";
20
- import * as calldataAbi from "@/abi/calldata";
12
+ import {Abi, PublicClient, Address} from "viem";
21
13
  import {localnet} from "@/chains/localnet";
14
+ import {decodeLocalnetTransaction, decodeTransaction, simplifyTransactionReceipt} from "./decoders";
15
+
16
+
22
17
 
23
18
  export const receiptActions = (client: GenLayerClient<GenLayerChain>, publicClient: PublicClient) => ({
24
19
  waitForTransactionReceipt: async ({
@@ -26,11 +21,13 @@ export const receiptActions = (client: GenLayerClient<GenLayerChain>, publicClie
26
21
  status = TransactionStatus.ACCEPTED,
27
22
  interval = transactionsConfig.waitInterval,
28
23
  retries = transactionsConfig.retries,
24
+ fullTransaction = false,
29
25
  }: {
30
26
  hash: TransactionHash;
31
27
  status: TransactionStatus;
32
28
  interval?: number;
33
29
  retries?: number;
30
+ fullTransaction?: boolean;
34
31
  }): Promise<GenLayerTransaction> => {
35
32
  const transaction = await client.getTransaction({
36
33
  hash,
@@ -46,10 +43,14 @@ export const receiptActions = (client: GenLayerClient<GenLayerChain>, publicClie
46
43
  transactionStatusString === requestedStatus ||
47
44
  (status === TransactionStatus.ACCEPTED && transactionStatusString === transactionStatusFinalized)
48
45
  ) {
46
+ let finalTransaction = transaction;
49
47
  if (client.chain.id === localnet.id) {
50
- return _decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction);
48
+ finalTransaction = decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction);
51
49
  }
52
- return transaction;
50
+ if (!fullTransaction) {
51
+ return simplifyTransactionReceipt(finalTransaction as GenLayerTransaction);
52
+ }
53
+ return finalTransaction;
53
54
  }
54
55
 
55
56
  if (retries === 0) {
@@ -62,6 +63,7 @@ export const receiptActions = (client: GenLayerClient<GenLayerChain>, publicClie
62
63
  status,
63
64
  interval,
64
65
  retries: retries - 1,
66
+ fullTransaction,
65
67
  });
66
68
  },
67
69
  });
@@ -75,7 +77,7 @@ export const transactionActions = (client: GenLayerClient<GenLayerChain>, public
75
77
 
76
78
  transaction.status = Number(transactionsStatusNameToNumber[localnetStatus as TransactionStatus]);
77
79
  transaction.statusName = localnetStatus as TransactionStatus;
78
- return _decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction);
80
+ return decodeLocalnetTransaction(transaction as unknown as GenLayerTransaction);
79
81
  }
80
82
  const transaction = (await publicClient.readContract({
81
83
  address: client.chain.consensusDataContract?.address as Address,
@@ -86,136 +88,8 @@ export const transactionActions = (client: GenLayerClient<GenLayerChain>, public
86
88
  Math.round(new Date().getTime() / 1000), // unix seconds
87
89
  ],
88
90
  })) as unknown as GenLayerRawTransaction;
89
- return _decodeTransaction(transaction);
91
+ return decodeTransaction(transaction);
90
92
  },
91
93
  });
92
94
 
93
- const _decodeInputData = (
94
- rlpEncodedAppData: Hex | undefined | null,
95
- recipient: Address,
96
- ): DecodedDeployData | DecodedCallData | null => {
97
- if (!rlpEncodedAppData || rlpEncodedAppData === "0x" || rlpEncodedAppData.length <= 2) {
98
- return null;
99
- }
100
- try {
101
- const rlpDecodedArray = fromRlp(rlpEncodedAppData) as Hex[];
102
-
103
- if (rlpDecodedArray.length === 3) {
104
- return {
105
- code: fromHex(rlpDecodedArray[0], "string") as `0x${string}`,
106
- constructorArgs:
107
- rlpDecodedArray[1] && rlpDecodedArray[1] !== "0x"
108
- ? calldataAbi.decode(fromHex(rlpDecodedArray[1], "bytes"))
109
- : null,
110
- leaderOnly: rlpDecodedArray[2] === "0x01",
111
- type: "deploy",
112
- contractAddress: recipient,
113
- };
114
- } else if (rlpDecodedArray.length === 2) {
115
- return {
116
- callData:
117
- rlpDecodedArray[0] && rlpDecodedArray[0] !== "0x"
118
- ? calldataAbi.decode(fromHex(rlpDecodedArray[0], "bytes"))
119
- : null,
120
- leaderOnly: rlpDecodedArray[1] === "0x01",
121
- type: "call",
122
- };
123
- } else {
124
- console.warn(
125
- "[decodeInputData] WRITE: Unexpected RLP array length:",
126
- rlpDecodedArray.length,
127
- rlpDecodedArray,
128
- );
129
- return null;
130
- }
131
- } catch (e) {
132
- console.error(
133
- "[decodeInputData] Error during comprehensive decoding:",
134
- e,
135
- "Raw RLP App Data:",
136
- rlpEncodedAppData,
137
- );
138
- return null;
139
- }
140
- };
141
-
142
- const _decodeTransaction = (tx: GenLayerRawTransaction): GenLayerTransaction => {
143
- const txDataDecoded = _decodeInputData(tx.txData, tx.recipient);
144
-
145
- const decodedTx = {
146
- ...tx,
147
- txData: tx.txData,
148
- txDataDecoded: txDataDecoded,
149
95
 
150
- currentTimestamp: tx.currentTimestamp.toString(),
151
- numOfInitialValidators: tx.numOfInitialValidators.toString(),
152
- txSlot: tx.txSlot.toString(),
153
- createdTimestamp: tx.createdTimestamp.toString(),
154
- lastVoteTimestamp: tx.lastVoteTimestamp.toString(),
155
- queuePosition: tx.queuePosition.toString(),
156
- numOfRounds: tx.numOfRounds.toString(),
157
-
158
- readStateBlockRange: {
159
- ...tx.readStateBlockRange,
160
- activationBlock: tx.readStateBlockRange.activationBlock.toString(),
161
- processingBlock: tx.readStateBlockRange.processingBlock.toString(),
162
- proposalBlock: tx.readStateBlockRange.proposalBlock.toString(),
163
- },
164
-
165
- statusName:
166
- transactionsStatusNumberToName[String(tx.status) as keyof typeof transactionsStatusNumberToName],
167
- resultName:
168
- transactionResultNumberToName[String(tx.result) as keyof typeof transactionResultNumberToName],
169
-
170
- lastRound: {
171
- ...tx.lastRound,
172
- round: tx.lastRound.round.toString(),
173
- leaderIndex: tx.lastRound.leaderIndex.toString(),
174
- votesCommitted: tx.lastRound.votesCommitted.toString(),
175
- votesRevealed: tx.lastRound.votesRevealed.toString(),
176
- appealBond: tx.lastRound.appealBond.toString(),
177
- rotationsLeft: tx.lastRound.rotationsLeft.toString(),
178
- validatorVotesName: tx.lastRound.validatorVotes.map(
179
- vote => voteTypeNumberToName[String(vote) as keyof typeof voteTypeNumberToName],
180
- ) as VoteType[],
181
- },
182
- };
183
- return decodedTx as GenLayerTransaction;
184
- };
185
-
186
- const _decodeLocalnetTransaction = (tx: GenLayerTransaction): GenLayerTransaction => {
187
- if (!tx.data) return tx;
188
- try {
189
- const leaderReceipt = tx.consensus_data?.leader_receipt;
190
- if (leaderReceipt) {
191
- const receipts = Array.isArray(leaderReceipt) ? leaderReceipt : [leaderReceipt];
192
- receipts.forEach((receipt) => {
193
- if (receipt.result && typeof receipt.result === "string") {
194
- receipt.result = resultToUserFriendlyJson(receipt.result);
195
- }
196
- if (receipt.calldata && typeof receipt.calldata === "string") {
197
- receipt.calldata = {
198
- base64: receipt.calldata as string,
199
- ...calldataToUserFriendlyJson(b64ToArray(receipt.calldata as string)),
200
- };
201
- }
202
- if (receipt.eq_outputs) {
203
- receipt.eq_outputs = Object.fromEntries(
204
- Object.entries(receipt.eq_outputs).map(([key, value]) => {
205
- return [key, resultToUserFriendlyJson(String(value))];
206
- }),
207
- );
208
- }
209
- });
210
- }
211
- if (tx.data?.calldata && typeof tx.data.calldata === "string") {
212
- tx.data.calldata = {
213
- base64: tx.data.calldata as string,
214
- ...calldataToUserFriendlyJson(b64ToArray(tx.data.calldata as string)),
215
- };
216
- }
217
- } catch (e) {
218
- console.error("Error in _decodeLocalnetTransaction:", e);
219
- }
220
- return tx;
221
- };
@@ -0,0 +1,276 @@
1
+ import {GenLayerTransaction, GenLayerRawTransaction, DecodedCallData, DecodedDeployData} from "../types/transactions";
2
+ import {transactionsStatusNumberToName, transactionResultNumberToName, voteTypeNumberToName, VoteType} from "../types/transactions";
3
+ import {b64ToArray, calldataToUserFriendlyJson, resultToUserFriendlyJson} from "../utils/jsonifier";
4
+ import {fromRlp, fromHex, Hex, Address} from "viem";
5
+ import * as calldataAbi from "../abi/calldata";
6
+
7
+ // Fields to remove from simplified transaction receipts
8
+ const FIELDS_TO_REMOVE = [
9
+ "raw", "contract_state", "base64", "consensus_history", "tx_data",
10
+ "eq_blocks_outputs", "r", "s", "v", "created_timestamp",
11
+ "current_timestamp", "tx_execution_hash", "random_seed", "states",
12
+ "contract_code", "appeal_failed", "appeal_leader_timeout",
13
+ "appeal_processing_time", "appeal_undetermined", "appealed",
14
+ "timestamp_appeal", "config_rotation_rounds", "rotation_count",
15
+ "queue_position", "queue_type", "leader_timeout_validators",
16
+ "triggered_by", "num_of_initial_validators",
17
+ "timestamp_awaiting_finalization", "last_vote_timestamp",
18
+ "read_state_block_range", "tx_slot", "blockHash", "blockNumber",
19
+ "to", "transactionIndex"
20
+ ];
21
+
22
+ // Field name mappings for cross-language compatibility with genlayer-py
23
+ const FIELD_NAME_MAPPINGS: Record<string, string> = {
24
+ statusName: "status_name",
25
+ typeHex: "type"
26
+ };
27
+
28
+ export const decodeInputData = (
29
+ rlpEncodedAppData: Hex | undefined | null,
30
+ recipient: Address,
31
+ ): DecodedDeployData | DecodedCallData | null => {
32
+ if (!rlpEncodedAppData || rlpEncodedAppData === "0x" || rlpEncodedAppData.length <= 2) {
33
+ return null;
34
+ }
35
+ try {
36
+ const rlpDecodedArray = fromRlp(rlpEncodedAppData) as Hex[];
37
+
38
+ if (rlpDecodedArray.length === 3) {
39
+ return {
40
+ code: fromHex(rlpDecodedArray[0], "string") as `0x${string}`,
41
+ constructorArgs:
42
+ rlpDecodedArray[1] && rlpDecodedArray[1] !== "0x"
43
+ ? calldataAbi.decode(fromHex(rlpDecodedArray[1], "bytes"))
44
+ : null,
45
+ leaderOnly: rlpDecodedArray[2] === "0x01",
46
+ type: "deploy",
47
+ contractAddress: recipient,
48
+ };
49
+ } else if (rlpDecodedArray.length === 2) {
50
+ return {
51
+ callData:
52
+ rlpDecodedArray[0] && rlpDecodedArray[0] !== "0x"
53
+ ? calldataAbi.decode(fromHex(rlpDecodedArray[0], "bytes"))
54
+ : null,
55
+ leaderOnly: rlpDecodedArray[1] === "0x01",
56
+ type: "call",
57
+ };
58
+ } else {
59
+ console.warn(
60
+ "[decodeInputData] WRITE: Unexpected RLP array length:",
61
+ rlpDecodedArray.length,
62
+ rlpDecodedArray,
63
+ );
64
+ return null;
65
+ }
66
+ } catch (e) {
67
+ console.error(
68
+ "[decodeInputData] Error during comprehensive decoding:",
69
+ e,
70
+ "Raw RLP App Data:",
71
+ rlpEncodedAppData,
72
+ );
73
+ return null;
74
+ }
75
+ };
76
+
77
+ export const decodeTransaction = (tx: GenLayerRawTransaction): GenLayerTransaction => {
78
+ const txDataDecoded = decodeInputData(tx.txData, tx.recipient);
79
+
80
+ const decodedTx = {
81
+ ...tx,
82
+ txData: tx.txData,
83
+ txDataDecoded: txDataDecoded,
84
+
85
+ currentTimestamp: tx.currentTimestamp.toString(),
86
+ numOfInitialValidators: tx.numOfInitialValidators.toString(),
87
+ txSlot: tx.txSlot.toString(),
88
+ createdTimestamp: tx.createdTimestamp.toString(),
89
+ lastVoteTimestamp: tx.lastVoteTimestamp.toString(),
90
+ queuePosition: tx.queuePosition.toString(),
91
+ numOfRounds: tx.numOfRounds.toString(),
92
+
93
+ readStateBlockRange: {
94
+ ...tx.readStateBlockRange,
95
+ activationBlock: tx.readStateBlockRange.activationBlock.toString(),
96
+ processingBlock: tx.readStateBlockRange.processingBlock.toString(),
97
+ proposalBlock: tx.readStateBlockRange.proposalBlock.toString(),
98
+ },
99
+
100
+ statusName:
101
+ transactionsStatusNumberToName[String(tx.status) as keyof typeof transactionsStatusNumberToName],
102
+ resultName:
103
+ transactionResultNumberToName[String(tx.result) as keyof typeof transactionResultNumberToName],
104
+
105
+ lastRound: {
106
+ ...tx.lastRound,
107
+ round: tx.lastRound.round.toString(),
108
+ leaderIndex: tx.lastRound.leaderIndex.toString(),
109
+ votesCommitted: tx.lastRound.votesCommitted.toString(),
110
+ votesRevealed: tx.lastRound.votesRevealed.toString(),
111
+ appealBond: tx.lastRound.appealBond.toString(),
112
+ rotationsLeft: tx.lastRound.rotationsLeft.toString(),
113
+ validatorVotesName: tx.lastRound.validatorVotes.map(
114
+ vote => voteTypeNumberToName[String(vote) as keyof typeof voteTypeNumberToName],
115
+ ) as VoteType[],
116
+ },
117
+ };
118
+ return decodedTx as GenLayerTransaction;
119
+ };
120
+
121
+ export const simplifyTransactionReceipt = (tx: GenLayerTransaction): GenLayerTransaction => {
122
+ /**
123
+ * Simplify transaction receipt by removing non-essential fields while preserving functionality.
124
+ *
125
+ * Removes: Binary data, internal timestamps, appeal fields, processing details, historical data
126
+ * Preserves: Transaction IDs, status, execution results, node configs, readable data
127
+ */
128
+ const simplifyObject = (obj: any, path = ""): any => {
129
+ if (obj === null || obj === undefined) return obj;
130
+
131
+ if (Array.isArray(obj)) {
132
+ return obj.map(item => simplifyObject(item, path)).filter(item => item !== undefined);
133
+ }
134
+
135
+ if (typeof obj === "object") {
136
+ const result: any = {};
137
+
138
+ for (const [key, value] of Object.entries(obj)) {
139
+ const currentPath = path ? `${path}.${key}` : key;
140
+
141
+ // Always remove these fields
142
+ if (FIELDS_TO_REMOVE.includes(key)) {
143
+ continue;
144
+ }
145
+
146
+ // Remove node_config only from top level (keep it in consensus_data)
147
+ if (key === "node_config" && !path.includes("consensus_data")) {
148
+ continue;
149
+ }
150
+
151
+ // Special handling for consensus_data - keep execution results and votes
152
+ if (key === "consensus_data" && typeof value === "object" && value !== null) {
153
+ const simplifiedConsensus: any = {};
154
+
155
+ // Keep votes
156
+ if ("votes" in value) {
157
+ simplifiedConsensus.votes = value.votes;
158
+ }
159
+
160
+ // Process leader_receipt to keep only essential fields
161
+ if ("leader_receipt" in value && Array.isArray(value.leader_receipt)) {
162
+ simplifiedConsensus.leader_receipt = value.leader_receipt.map((receipt: any) => {
163
+ const simplifiedReceipt: any = {};
164
+
165
+ // Keep essential execution info
166
+ ["execution_result", "genvm_result", "mode", "vote", "node_config"].forEach(field => {
167
+ if (field in receipt) {
168
+ simplifiedReceipt[field] = receipt[field];
169
+ }
170
+ });
171
+
172
+ // Keep readable calldata
173
+ if (receipt.calldata && typeof receipt.calldata === "object" && "readable" in receipt.calldata) {
174
+ simplifiedReceipt.calldata = { readable: receipt.calldata.readable };
175
+ }
176
+
177
+ // Keep readable outputs
178
+ if (receipt.eq_outputs) {
179
+ simplifiedReceipt.eq_outputs = simplifyObject(receipt.eq_outputs, currentPath);
180
+ }
181
+ if (receipt.result) {
182
+ simplifiedReceipt.result = simplifyObject(receipt.result, currentPath);
183
+ }
184
+
185
+ return simplifiedReceipt;
186
+ });
187
+ }
188
+
189
+ // Process validators to keep execution results
190
+ if ("validators" in value && Array.isArray(value.validators)) {
191
+ const simplifiedValidators = value.validators.map((validator: any) => {
192
+ const simplifiedValidator: any = {};
193
+ ["execution_result", "genvm_result", "mode", "vote", "node_config"].forEach(field => {
194
+ if (field in validator) {
195
+ simplifiedValidator[field] = validator[field];
196
+ }
197
+ });
198
+ return simplifiedValidator;
199
+ }).filter((validator: any) => Object.keys(validator).length > 0);
200
+
201
+ if (simplifiedValidators.length > 0) {
202
+ simplifiedConsensus.validators = simplifiedValidators;
203
+ }
204
+ }
205
+
206
+ result[key] = simplifiedConsensus;
207
+ continue;
208
+ }
209
+
210
+ const simplifiedValue = simplifyObject(value, currentPath);
211
+ // Include the value if it's not undefined and not an empty object
212
+ // Special case: include numeric 0 values (like value: 0)
213
+ const shouldInclude = simplifiedValue !== undefined &&
214
+ !(typeof simplifiedValue === "object" && simplifiedValue !== null && Object.keys(simplifiedValue).length === 0);
215
+
216
+ if (shouldInclude || simplifiedValue === 0) {
217
+ // Map field names for cross-language compatibility
218
+ const mappedKey = FIELD_NAME_MAPPINGS[key] || key;
219
+ result[mappedKey] = simplifiedValue;
220
+ }
221
+ }
222
+
223
+ return result;
224
+ }
225
+
226
+ return obj;
227
+ };
228
+
229
+ return simplifyObject({...tx});
230
+ };
231
+
232
+ export const decodeLocalnetTransaction = (tx: GenLayerTransaction): GenLayerTransaction => {
233
+ if (!tx.data) return tx;
234
+ try {
235
+ const leaderReceipt = tx.consensus_data?.leader_receipt;
236
+ if (leaderReceipt) {
237
+ const receipts = Array.isArray(leaderReceipt) ? leaderReceipt : [leaderReceipt];
238
+ receipts.forEach((receipt) => {
239
+ if (receipt.result && typeof receipt.result === "string") {
240
+ receipt.result = resultToUserFriendlyJson(receipt.result);
241
+ }
242
+ if (receipt.calldata && typeof receipt.calldata === "string") {
243
+ receipt.calldata = {
244
+ base64: receipt.calldata as string,
245
+ ...calldataToUserFriendlyJson(b64ToArray(receipt.calldata as string)),
246
+ };
247
+ }
248
+ if (receipt.eq_outputs) {
249
+ const decodedOutputs: any = {};
250
+ for (const [key, value] of Object.entries(receipt.eq_outputs)) {
251
+ if (typeof value === "object" && value !== null) {
252
+ decodedOutputs[key] = value;
253
+ } else {
254
+ try {
255
+ decodedOutputs[key] = resultToUserFriendlyJson(value as string);
256
+ } catch (e) {
257
+ console.warn(`Error decoding eq_output ${key}: ${e}`);
258
+ decodedOutputs[key] = value;
259
+ }
260
+ }
261
+ }
262
+ receipt.eq_outputs = decodedOutputs;
263
+ }
264
+ });
265
+ }
266
+ if (tx.data?.calldata && typeof tx.data.calldata === "string") {
267
+ tx.data.calldata = {
268
+ base64: tx.data.calldata as string,
269
+ ...calldataToUserFriendlyJson(b64ToArray(tx.data.calldata as string)),
270
+ };
271
+ }
272
+ } catch (e) {
273
+ console.error("Error in decodeLocalnetTransaction:", e);
274
+ }
275
+ return tx;
276
+ };