genlayer-js 0.13.0 → 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.
@@ -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
+ };