solforge 0.2.5 → 0.2.7

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.
Files changed (80) hide show
  1. package/package.json +1 -1
  2. package/scripts/postinstall.cjs +3 -3
  3. package/server/lib/base58.ts +1 -1
  4. package/server/lib/instruction-parser.ts +242 -0
  5. package/server/methods/account/get-account-info.ts +3 -7
  6. package/server/methods/account/get-balance.ts +3 -7
  7. package/server/methods/account/get-multiple-accounts.ts +2 -1
  8. package/server/methods/account/get-parsed-account-info.ts +3 -7
  9. package/server/methods/account/parsers/index.ts +2 -2
  10. package/server/methods/account/parsers/loader-upgradeable.ts +14 -1
  11. package/server/methods/account/parsers/spl-token.ts +29 -10
  12. package/server/methods/account/request-airdrop.ts +122 -86
  13. package/server/methods/admin/mint-to.ts +11 -38
  14. package/server/methods/block/get-block.ts +3 -7
  15. package/server/methods/block/get-blocks-with-limit.ts +3 -7
  16. package/server/methods/block/is-blockhash-valid.ts +3 -7
  17. package/server/methods/get-address-lookup-table.ts +3 -7
  18. package/server/methods/program/get-program-accounts.ts +9 -9
  19. package/server/methods/program/get-token-account-balance.ts +3 -7
  20. package/server/methods/program/get-token-accounts-by-delegate.ts +4 -3
  21. package/server/methods/program/get-token-accounts-by-owner.ts +54 -33
  22. package/server/methods/program/get-token-largest-accounts.ts +3 -2
  23. package/server/methods/program/get-token-supply.ts +3 -2
  24. package/server/methods/solforge/index.ts +9 -6
  25. package/server/methods/transaction/get-parsed-transaction.ts +3 -7
  26. package/server/methods/transaction/get-signature-statuses.ts +14 -7
  27. package/server/methods/transaction/get-signatures-for-address.ts +3 -7
  28. package/server/methods/transaction/get-transaction.ts +434 -287
  29. package/server/methods/transaction/inner-instructions.test.ts +63 -0
  30. package/server/methods/transaction/send-transaction.ts +248 -56
  31. package/server/methods/transaction/simulate-transaction.ts +3 -2
  32. package/server/rpc-server.ts +98 -61
  33. package/server/types.ts +65 -30
  34. package/server/ws-server.ts +11 -7
  35. package/src/api-server-entry.ts +5 -5
  36. package/src/cli/commands/airdrop.ts +2 -2
  37. package/src/cli/commands/config.ts +2 -2
  38. package/src/cli/commands/mint.ts +3 -3
  39. package/src/cli/commands/program-clone.ts +9 -11
  40. package/src/cli/commands/program-load.ts +3 -3
  41. package/src/cli/commands/rpc-start.ts +7 -7
  42. package/src/cli/commands/token-adopt-authority.ts +1 -1
  43. package/src/cli/commands/token-clone.ts +5 -6
  44. package/src/cli/commands/token-create.ts +5 -5
  45. package/src/cli/main.ts +33 -36
  46. package/src/cli/run-solforge.ts +3 -3
  47. package/src/cli/setup-wizard.ts +8 -6
  48. package/src/commands/add-program.ts +1 -1
  49. package/src/commands/init.ts +2 -2
  50. package/src/commands/mint.ts +5 -6
  51. package/src/commands/start.ts +10 -9
  52. package/src/commands/status.ts +1 -1
  53. package/src/commands/stop.ts +1 -1
  54. package/src/config/index.ts +33 -17
  55. package/src/config/manager.ts +3 -3
  56. package/src/db/index.ts +2 -2
  57. package/src/db/schema/index.ts +1 -0
  58. package/src/db/schema/transactions.ts +29 -22
  59. package/src/db/schema/tx-account-states.ts +21 -0
  60. package/src/db/tx-store.ts +113 -76
  61. package/src/gui/public/app.css +13 -13
  62. package/src/gui/server.ts +1 -1
  63. package/src/gui/src/api.ts +1 -1
  64. package/src/gui/src/app.tsx +49 -17
  65. package/src/gui/src/components/airdrop-mint-form.tsx +32 -8
  66. package/src/gui/src/components/clone-program-modal.tsx +25 -6
  67. package/src/gui/src/components/clone-token-modal.tsx +25 -6
  68. package/src/gui/src/components/modal.tsx +6 -1
  69. package/src/gui/src/components/status-panel.tsx +1 -1
  70. package/src/index.ts +19 -6
  71. package/src/migrations-bundled.ts +8 -2
  72. package/src/services/api-server.ts +41 -19
  73. package/src/services/port-manager.ts +7 -10
  74. package/src/services/process-registry.ts +4 -5
  75. package/src/services/program-cloner.ts +4 -4
  76. package/src/services/token-cloner.ts +4 -4
  77. package/src/services/validator.ts +2 -4
  78. package/src/types/config.ts +2 -2
  79. package/src/utils/shell.ts +1 -1
  80. package/src/utils/token-loader.ts +2 -2
@@ -1,85 +1,141 @@
1
1
  import { VersionedTransaction } from "@solana/web3.js";
2
2
  import type { RpcMethodHandler } from "../../types";
3
+ import { parseInstruction } from "../../lib/instruction-parser";
3
4
 
4
5
  export const getTransaction: RpcMethodHandler = async (id, params, context) => {
5
- const [signature, config] = params || [];
6
- const encoding = config?.encoding ?? "json";
6
+ const [signature, config] = params || [];
7
+ const encoding = config?.encoding ?? "json";
8
+ const DBG = process.env.DEBUG_TX_CAPTURE === "1";
9
+ try {
10
+ if (DBG) console.debug(`[tx-capture] getTransaction request: sig=${signature} enc=${encoding}`);
11
+ } catch {}
7
12
 
8
13
  try {
9
14
  const rec = context.getRecordedTransaction(signature);
10
- if (rec) {
11
- const tx = rec.tx as any;
12
- if (encoding === "base64") {
13
- const raw = Buffer.from(tx.serialize()).toString("base64");
14
- // Top-level version is required by some clients
15
- const isV0 =
16
- typeof (tx.message as any)?.version === "number"
17
- ? (tx.message as any).version === 0
18
- : true;
19
- return context.createSuccessResponse(id, {
20
- slot: rec.slot,
21
- transaction: [raw, "base64"],
22
- version: isV0 ? 0 : "legacy",
23
- meta: {
24
- status: rec.err ? { Err: rec.err } : { Ok: null },
25
- err: rec.err ?? null,
26
- fee: rec.fee,
27
- loadedAddresses: { writable: [], readonly: [] },
28
- preBalances: Array.isArray(rec.preBalances) ? rec.preBalances : [],
29
- postBalances: Array.isArray(rec.postBalances)
30
- ? rec.postBalances
31
- : [],
32
- innerInstructions: [],
33
- logMessages: rec.logs || [],
34
- preTokenBalances: Array.isArray((rec as any).preTokenBalances)
35
- ? (rec as any).preTokenBalances
36
- : [],
37
- postTokenBalances: Array.isArray((rec as any).postTokenBalances)
38
- ? (rec as any).postTokenBalances
39
- : [],
40
- rewards: [],
41
- },
42
- blockTime: rec.blockTime,
43
- });
44
- }
15
+ if (rec) {
16
+ try {
17
+ if (DBG)
18
+ console.debug(
19
+ `[tx-capture] getTransaction hit memory: logs=${rec.logs?.length || 0} inner=${Array.isArray((rec as any).innerInstructions) ? (rec as any).innerInstructions.length : 0}`,
20
+ );
21
+ } catch {}
22
+ const tx = rec.tx;
23
+ if (encoding === "base64") {
24
+ const raw = Buffer.from(tx.serialize()).toString("base64");
25
+ // Top-level version is required by some clients
26
+ const isV0 = (() => {
27
+ const m = tx.message as unknown as { version?: number };
28
+ return typeof m?.version === "number" ? m.version === 0 : true;
29
+ })();
30
+ return context.createSuccessResponse(id, {
31
+ slot: rec.slot,
32
+ transaction: [raw, "base64"],
33
+ version: isV0 ? 0 : "legacy",
34
+ meta: {
35
+ status: rec.err ? { Err: rec.err } : { Ok: null },
36
+ err: rec.err ?? null,
37
+ fee: rec.fee,
38
+ loadedAddresses: { writable: [], readonly: [] },
39
+ preBalances: Array.isArray(rec.preBalances) ? rec.preBalances : [],
40
+ postBalances: Array.isArray(rec.postBalances)
41
+ ? rec.postBalances
42
+ : [],
43
+ innerInstructions: Array.isArray((rec as any).innerInstructions)
44
+ ? (rec as any).innerInstructions
45
+ : [],
46
+ logMessages: rec.logs || [],
47
+ preTokenBalances: (() => {
48
+ const arr = (rec as unknown as { preTokenBalances?: unknown[] })
49
+ .preTokenBalances;
50
+ return Array.isArray(arr) ? arr : [];
51
+ })(),
52
+ postTokenBalances: (() => {
53
+ const arr = (rec as unknown as { postTokenBalances?: unknown[] })
54
+ .postTokenBalances;
55
+ return Array.isArray(arr) ? arr : [];
56
+ })(),
57
+ computeUnitsConsumed:
58
+ typeof (rec as any).computeUnits === "number"
59
+ ? (rec as any).computeUnits
60
+ : null,
61
+ returnData: (() => {
62
+ const rd = (rec as any).returnData as
63
+ | { programId: string; dataBase64: string }
64
+ | null
65
+ | undefined;
66
+ if (!rd) return null;
67
+ return { programId: rd.programId, data: [rd.dataBase64, "base64"] };
68
+ })(),
69
+ rewards: [],
70
+ },
71
+ blockTime: rec.blockTime,
72
+ });
73
+ }
45
74
 
46
- const msg: any = tx.message as any;
47
- const rawKeys1: any[] = Array.isArray(msg.staticAccountKeys)
75
+ const msg = tx.message as unknown as {
76
+ staticAccountKeys?: unknown[];
77
+ accountKeys?: unknown[];
78
+ compiledInstructions?: unknown[];
79
+ instructions?: unknown[];
80
+ header?: unknown;
81
+ recentBlockhash?: string;
82
+ version?: number;
83
+ addressTableLookups?: unknown[];
84
+ isAccountSigner?: (i: number) => boolean;
85
+ isAccountWritable?: (i: number) => boolean;
86
+ };
87
+ const rawKeys1: unknown[] = Array.isArray(msg.staticAccountKeys)
48
88
  ? msg.staticAccountKeys
49
89
  : Array.isArray(msg.accountKeys)
50
90
  ? msg.accountKeys
51
91
  : [];
52
- const accountKeys = rawKeys1.map((k: any) => {
92
+ const accountKeys = rawKeys1.map((k) => {
53
93
  try {
54
- return typeof k === "string" ? k : k.toBase58();
94
+ return typeof k === "string"
95
+ ? k
96
+ : (k as { toBase58: () => string }).toBase58();
55
97
  } catch {
56
98
  return String(k);
57
99
  }
58
100
  });
59
- const compiled = Array.isArray(msg.compiledInstructions)
101
+ const compiled: unknown[] = Array.isArray(msg.compiledInstructions)
60
102
  ? msg.compiledInstructions
61
103
  : Array.isArray(msg.instructions)
62
104
  ? msg.instructions
63
105
  : [];
64
- const instructions = compiled.map((ci: any) => {
106
+ const instructions = compiled.map((ci) => {
107
+ const c = ci as {
108
+ programIdIndex: number;
109
+ accountKeyIndexes?: number[];
110
+ accounts?: number[];
111
+ data: Uint8Array | number[];
112
+ };
65
113
  const dataBytes: Uint8Array =
66
- ci.data instanceof Uint8Array ? ci.data : Buffer.from(ci.data);
114
+ c.data instanceof Uint8Array ? c.data : Buffer.from(c.data);
67
115
  return {
68
- programIdIndex: ci.programIdIndex,
69
- accounts: Array.from(ci.accountKeyIndexes || ci.accounts || []),
116
+ programIdIndex: c.programIdIndex,
117
+ accounts: Array.from(c.accountKeyIndexes || c.accounts || []),
70
118
  data: context.encodeBase58(dataBytes),
71
119
  };
72
120
  });
73
- const addressTableLookups = (msg.addressTableLookups || []).map(
74
- (l: any) => ({
121
+ const addressTableLookups = (
122
+ Array.isArray(msg.addressTableLookups) ? msg.addressTableLookups : []
123
+ ).map((l) => {
124
+ const a = l as {
125
+ accountKey?: { toBase58?: () => string } | string;
126
+ writableIndexes?: number[];
127
+ readonlyIndexes?: number[];
128
+ };
129
+ return {
75
130
  accountKey:
76
- typeof l.accountKey?.toBase58 === "function"
77
- ? l.accountKey.toBase58()
78
- : String(l.accountKey),
79
- writableIndexes: Array.from(l.writableIndexes || []),
80
- readonlyIndexes: Array.from(l.readonlyIndexes || []),
81
- }),
82
- );
131
+ typeof (a.accountKey as { toBase58?: unknown })?.toBase58 ===
132
+ "function"
133
+ ? (a.accountKey as { toBase58: () => string }).toBase58()
134
+ : String(a.accountKey),
135
+ writableIndexes: Array.from(a.writableIndexes || []),
136
+ readonlyIndexes: Array.from(a.readonlyIndexes || []),
137
+ };
138
+ });
83
139
  const header = msg.header || {
84
140
  numRequiredSignatures: tx.signatures.length,
85
141
  numReadonlySignedAccounts: 0,
@@ -88,7 +144,7 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
88
144
  const recentBlockhash = msg.recentBlockhash || "";
89
145
 
90
146
  const isV0 = typeof msg.version === "number" ? msg.version === 0 : true;
91
- const result: any = {
147
+ const result = {
92
148
  slot: rec.slot,
93
149
  transaction: {
94
150
  signatures: [signature],
@@ -101,80 +157,102 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
101
157
  },
102
158
  },
103
159
  version: isV0 ? 0 : "legacy",
104
- meta: {
105
- status: rec.err ? { Err: rec.err } : { Ok: null },
106
- err: rec.err ?? null,
107
- fee: rec.fee,
108
- loadedAddresses: { writable: [], readonly: [] },
109
- preBalances: Array.isArray(rec.preBalances) ? rec.preBalances : [],
110
- postBalances: Array.isArray(rec.postBalances) ? rec.postBalances : [],
111
- innerInstructions: [],
112
- logMessages: rec.logs || [],
113
- preTokenBalances: Array.isArray((rec as any).preTokenBalances)
114
- ? (rec as any).preTokenBalances
115
- : [],
116
- postTokenBalances: Array.isArray((rec as any).postTokenBalances)
117
- ? (rec as any).postTokenBalances
118
- : [],
119
- rewards: [],
120
- },
121
- blockTime: rec.blockTime,
122
- };
160
+ meta: {
161
+ status: rec.err ? { Err: rec.err } : { Ok: null },
162
+ err: rec.err ?? null,
163
+ fee: rec.fee,
164
+ loadedAddresses: { writable: [], readonly: [] },
165
+ preBalances: Array.isArray(rec.preBalances) ? rec.preBalances : [],
166
+ postBalances: Array.isArray(rec.postBalances) ? rec.postBalances : [],
167
+ innerInstructions: Array.isArray((rec as any).innerInstructions)
168
+ ? (rec as any).innerInstructions
169
+ : [],
170
+ logMessages: rec.logs || [],
171
+ preTokenBalances: (() => {
172
+ const arr = (rec as unknown as { preTokenBalances?: unknown[] })
173
+ .preTokenBalances;
174
+ return Array.isArray(arr) ? arr : [];
175
+ })(),
176
+ postTokenBalances: (() => {
177
+ const arr = (rec as unknown as { postTokenBalances?: unknown[] })
178
+ .postTokenBalances;
179
+ return Array.isArray(arr) ? arr : [];
180
+ })(),
181
+ computeUnitsConsumed:
182
+ typeof (rec as any).computeUnits === "number"
183
+ ? (rec as any).computeUnits
184
+ : null,
185
+ returnData: (() => {
186
+ const rd = (rec as any).returnData as
187
+ | { programId: string; dataBase64: string }
188
+ | null
189
+ | undefined;
190
+ if (!rd) return null;
191
+ return { programId: rd.programId, data: [rd.dataBase64, "base64"] };
192
+ })(),
193
+ rewards: [],
194
+ },
195
+ blockTime: rec.blockTime,
196
+ };
123
197
 
124
- if (encoding === "jsonParsed") {
125
- const SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
126
- const accountKeysParsed = accountKeys.map((pk: string, i: number) => ({
127
- pubkey: pk,
128
- signer:
129
- typeof msg.isAccountSigner === "function"
130
- ? !!msg.isAccountSigner(i)
131
- : i < (header?.numRequiredSignatures ?? 0),
132
- writable:
133
- typeof msg.isAccountWritable === "function"
134
- ? !!msg.isAccountWritable(i)
135
- : i < (header?.numRequiredSignatures ?? 0),
136
- }));
137
- const parsedInstructions = compiled.map((ci: any) => {
138
- const programId = accountKeys[ci.programIdIndex];
139
- let parsed: any;
140
- try {
141
- const data: Uint8Array =
142
- ci.data instanceof Uint8Array ? ci.data : Buffer.from(ci.data);
143
- if (programId === SYSTEM_PROGRAM_ID && data.length >= 12) {
144
- const dv = new DataView(
145
- data.buffer,
146
- data.byteOffset,
147
- data.byteLength,
148
- );
149
- const discriminator = dv.getUint32(0, true);
150
- if (
151
- discriminator === 2 &&
152
- (ci.accountKeyIndexes?.length ?? 0) >= 2
153
- ) {
154
- const lamports = Number(dv.getBigUint64(4, true));
155
- const source = accountKeys[ci.accountKeyIndexes[0]];
156
- const destination = accountKeys[ci.accountKeyIndexes[1]];
157
- parsed = {
158
- type: "transfer",
159
- info: { source, destination, lamports },
160
- };
161
- }
162
- }
163
- } catch {}
164
- if (parsed) return { program: "system", programId, parsed };
165
- return {
166
- programId,
167
- accounts: (ci.accountKeyIndexes || []).map(
168
- (ix: number) => accountKeys[ix],
169
- ),
170
- data: context.encodeBase58(
171
- ci.data instanceof Uint8Array ? ci.data : Buffer.from(ci.data),
172
- ),
173
- };
174
- });
175
- result.transaction.message.accountKeys = accountKeysParsed;
176
- result.transaction.message.instructions = parsedInstructions;
177
- }
198
+ if (encoding === "jsonParsed") {
199
+ const accountKeysParsed = accountKeys.map((pk: string, i: number) => ({
200
+ pubkey: pk,
201
+ signer:
202
+ typeof msg.isAccountSigner === "function"
203
+ ? !!msg.isAccountSigner(i)
204
+ : i < (header?.numRequiredSignatures ?? 0),
205
+ writable:
206
+ typeof msg.isAccountWritable === "function"
207
+ ? !!msg.isAccountWritable(i)
208
+ : i < (header?.numRequiredSignatures ?? 0),
209
+ }));
210
+ const parsedInstructions = compiled.map((ci) => {
211
+ const c = ci as {
212
+ programIdIndex: number;
213
+ accountKeyIndexes?: number[];
214
+ accounts?: number[];
215
+ data: Uint8Array | number[];
216
+ };
217
+ const dataBytes: Uint8Array =
218
+ c.data instanceof Uint8Array ? c.data : Buffer.from(c.data);
219
+ const accountsIdx = Array.from(
220
+ c.accountKeyIndexes || c.accounts || [],
221
+ );
222
+ const programId = accountKeys[c.programIdIndex];
223
+ return parseInstruction(
224
+ programId,
225
+ accountsIdx,
226
+ context.encodeBase58(dataBytes),
227
+ accountKeys,
228
+ );
229
+ });
230
+ (result.transaction.message as { accountKeys: unknown[] }).accountKeys =
231
+ accountKeysParsed;
232
+ (
233
+ result.transaction.message as { instructions: unknown[] }
234
+ ).instructions = parsedInstructions as unknown[];
235
+ // Parse inner instructions using the same parser
236
+ try {
237
+ const inner = (result.meta as any)?.innerInstructions as
238
+ | Array<{ index: number; instructions: any[] }>
239
+ | undefined;
240
+ if (Array.isArray(inner)) {
241
+ const parsedInner = inner.map((group) => ({
242
+ index: group.index,
243
+ instructions: (group.instructions || []).map((ii) => {
244
+ const accountsIdx = Array.isArray(ii.accounts)
245
+ ? ii.accounts
246
+ : [];
247
+ const dataB58 = typeof ii.data === "string" ? ii.data : String(ii.data ?? "");
248
+ const pid = accountKeys[ii.programIdIndex ?? 0] || accountKeys[0];
249
+ return parseInstruction(pid, accountsIdx, dataB58, accountKeys);
250
+ }),
251
+ }));
252
+ (result.meta as any).innerInstructions = parsedInner;
253
+ }
254
+ } catch {}
255
+ }
178
256
 
179
257
  return context.createSuccessResponse(id, result);
180
258
  }
@@ -182,46 +260,75 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
182
260
  // Fallback: persistent store
183
261
  try {
184
262
  const row = await context.store?.getTransaction(signature);
185
- if (row) {
186
- const errVal = row.errJson ? JSON.parse(row.errJson) : null;
187
- const preBalances = JSON.parse(row.preBalancesJson || "[]");
188
- const postBalances = JSON.parse(row.postBalancesJson || "[]");
189
- const logs = JSON.parse(row.logsJson || "[]");
190
- const versionVal =
191
- row.version === "0" || row.version === 0 ? 0 : row.version;
192
- if (encoding === "base64") {
193
- return context.createSuccessResponse(id, {
194
- slot: Number(row.slot),
195
- transaction: [row.rawBase64, "base64"],
196
- version: versionVal,
197
- meta: {
198
- status: errVal ? { Err: errVal } : { Ok: null },
199
- err: errVal,
200
- fee: Number(row.fee),
201
- loadedAddresses: { writable: [], readonly: [] },
202
- preBalances,
203
- postBalances,
204
- innerInstructions: [],
205
- logMessages: logs,
206
- preTokenBalances: JSON.parse(row.preTokenBalancesJson || "[]"),
207
- postTokenBalances: JSON.parse(row.postTokenBalancesJson || "[]"),
208
- rewards: [],
209
- },
210
- blockTime: row.blockTime ? Number(row.blockTime) : null,
211
- });
212
- } else if (encoding === "jsonParsed") {
213
- // Build jsonParsed similar to in-memory path
214
- const raw = Buffer.from(row.rawBase64, "base64");
215
- const tx = VersionedTransaction.deserialize(raw);
216
- const msg: any = tx.message as any;
217
- const rawKeys2: any[] = Array.isArray(msg.staticAccountKeys)
263
+ if (row) {
264
+ try {
265
+ if (DBG)
266
+ console.debug(
267
+ `[tx-capture] getTransaction hit sqlite: slot=${row.slot} logs=${JSON.parse(row.logsJson || '[]').length} inner=${JSON.parse(row.innerInstructionsJson || '[]').length}`,
268
+ );
269
+ } catch {}
270
+ const errVal = row.errJson ? JSON.parse(row.errJson) : null;
271
+ const preBalances = JSON.parse(row.preBalancesJson || "[]");
272
+ const postBalances = JSON.parse(row.postBalancesJson || "[]");
273
+ const logs = JSON.parse(row.logsJson || "[]");
274
+ const inner = JSON.parse(row.innerInstructionsJson || "[]");
275
+ const versionVal =
276
+ row.version === "0" || row.version === 0 ? 0 : row.version;
277
+ if (encoding === "base64") {
278
+ return context.createSuccessResponse(id, {
279
+ slot: Number(row.slot),
280
+ transaction: [row.rawBase64, "base64"],
281
+ version: versionVal,
282
+ meta: {
283
+ status: errVal ? { Err: errVal } : { Ok: null },
284
+ err: errVal,
285
+ fee: Number(row.fee),
286
+ loadedAddresses: { writable: [], readonly: [] },
287
+ preBalances,
288
+ postBalances,
289
+ innerInstructions: Array.isArray(inner) ? inner : [],
290
+ logMessages: logs,
291
+ preTokenBalances: JSON.parse(row.preTokenBalancesJson || "[]"),
292
+ postTokenBalances: JSON.parse(row.postTokenBalancesJson || "[]"),
293
+ computeUnitsConsumed:
294
+ row.computeUnits != null ? Number(row.computeUnits) : null,
295
+ returnData: (() => {
296
+ if (row.returnDataProgramId && row.returnDataBase64)
297
+ return {
298
+ programId: row.returnDataProgramId,
299
+ data: [row.returnDataBase64, "base64"],
300
+ };
301
+ return null;
302
+ })(),
303
+ rewards: [],
304
+ },
305
+ blockTime: row.blockTime ? Number(row.blockTime) : null,
306
+ });
307
+ } else if (encoding === "jsonParsed") {
308
+ // Build jsonParsed similar to in-memory path
309
+ const raw = Buffer.from(row.rawBase64, "base64");
310
+ const tx = VersionedTransaction.deserialize(raw);
311
+ const msg = tx.message as unknown as {
312
+ staticAccountKeys?: unknown[];
313
+ accountKeys?: unknown[];
314
+ compiledInstructions?: unknown[];
315
+ instructions?: unknown[];
316
+ header?: unknown;
317
+ recentBlockhash?: string;
318
+ addressTableLookups?: unknown[];
319
+ isAccountSigner?: (i: number) => boolean;
320
+ isAccountWritable?: (i: number) => boolean;
321
+ };
322
+ const rawKeys2: unknown[] = Array.isArray(msg.staticAccountKeys)
218
323
  ? msg.staticAccountKeys
219
324
  : Array.isArray(msg.accountKeys)
220
325
  ? msg.accountKeys
221
326
  : [];
222
- const accountKeys = rawKeys2.map((k: any) => {
327
+ const accountKeys = rawKeys2.map((k) => {
223
328
  try {
224
- return typeof k === "string" ? k : k.toBase58();
329
+ return typeof k === "string"
330
+ ? k
331
+ : (k as { toBase58: () => string }).toBase58();
225
332
  } catch {
226
333
  return String(k);
227
334
  }
@@ -231,53 +338,31 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
231
338
  numReadonlySignedAccounts: 0,
232
339
  numReadonlyUnsignedAccounts: 0,
233
340
  };
234
- const compiled = Array.isArray(msg.compiledInstructions)
341
+ const compiled: unknown[] = Array.isArray(msg.compiledInstructions)
235
342
  ? msg.compiledInstructions
236
343
  : Array.isArray(msg.instructions)
237
344
  ? msg.instructions
238
345
  : [];
239
- const parsedInstructions = compiled.map((ci: any) => {
240
- const programId = accountKeys[ci.programIdIndex];
241
- let parsed: any;
242
- try {
243
- const data: Uint8Array =
244
- ci.data instanceof Uint8Array ? ci.data : Buffer.from(ci.data);
245
- // Minimal system transfer parser
246
- if (
247
- programId === "11111111111111111111111111111111" &&
248
- data.length >= 12
249
- ) {
250
- const dv = new DataView(
251
- data.buffer,
252
- data.byteOffset,
253
- data.byteLength,
254
- );
255
- const discriminator = dv.getUint32(0, true);
256
- if (
257
- discriminator === 2 &&
258
- (ci.accountKeyIndexes?.length ?? 0) >= 2
259
- ) {
260
- const lamports = Number(dv.getBigUint64(4, true));
261
- const source = accountKeys[ci.accountKeyIndexes[0]];
262
- const destination = accountKeys[ci.accountKeyIndexes[1]];
263
- parsed = {
264
- type: "transfer",
265
- info: { source, destination, lamports },
266
- };
267
- }
268
- }
269
- } catch {}
270
- if (parsed) return { program: "system", programId, parsed };
271
- return {
272
- programId,
273
- accounts: (ci.accountKeyIndexes || []).map(
274
- (ix: number) => accountKeys[ix],
275
- ),
276
- data: context.encodeBase58(
277
- ci.data instanceof Uint8Array ? ci.data : Buffer.from(ci.data),
278
- ),
279
- };
280
- });
346
+ const parsedInstructions = compiled.map((ci) => {
347
+ const c = ci as {
348
+ programIdIndex: number;
349
+ accountKeyIndexes?: number[];
350
+ accounts?: number[];
351
+ data: Uint8Array | number[];
352
+ };
353
+ const dataBytes: Uint8Array =
354
+ c.data instanceof Uint8Array ? c.data : Buffer.from(c.data);
355
+ const accountsIdx = Array.from(
356
+ c.accountKeyIndexes || c.accounts || [],
357
+ );
358
+ const programId = accountKeys[c.programIdIndex];
359
+ return parseInstruction(
360
+ programId,
361
+ accountsIdx,
362
+ context.encodeBase58(dataBytes),
363
+ accountKeys,
364
+ );
365
+ });
281
366
  const accountKeysParsed = accountKeys.map(
282
367
  (pk: string, i: number) => ({
283
368
  pubkey: pk,
@@ -291,47 +376,86 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
291
376
  : i < (header?.numRequiredSignatures ?? 0),
292
377
  }),
293
378
  );
294
- const result: any = {
295
- slot: Number(row.slot),
296
- transaction: {
297
- signatures: [signature],
298
- message: {
299
- accountKeys: accountKeysParsed,
300
- header,
301
- recentBlockhash: msg.recentBlockhash || "",
302
- instructions: parsedInstructions,
303
- addressTableLookups: msg.addressTableLookups || [],
304
- },
305
- },
306
- version: row.version === "0" || row.version === 0 ? 0 : row.version,
307
- meta: {
308
- status: errVal ? { Err: errVal } : { Ok: null },
309
- err: errVal,
310
- fee: Number(row.fee),
311
- loadedAddresses: { writable: [], readonly: [] },
312
- preBalances,
313
- postBalances,
314
- innerInstructions: [],
315
- logMessages: logs,
316
- preTokenBalances: JSON.parse(row.preTokenBalancesJson || "[]"),
317
- postTokenBalances: JSON.parse(row.postTokenBalancesJson || "[]"),
318
- rewards: [],
319
- },
320
- blockTime: row.blockTime ? Number(row.blockTime) : null,
321
- };
322
- return context.createSuccessResponse(id, result);
323
- } else {
379
+ const result = {
380
+ slot: Number(row.slot),
381
+ transaction: {
382
+ signatures: [signature],
383
+ message: {
384
+ accountKeys: accountKeysParsed,
385
+ header,
386
+ recentBlockhash: msg.recentBlockhash || "",
387
+ instructions: parsedInstructions,
388
+ addressTableLookups: msg.addressTableLookups || [],
389
+ },
390
+ },
391
+ version: row.version === "0" || row.version === 0 ? 0 : row.version,
392
+ meta: {
393
+ status: errVal ? { Err: errVal } : { Ok: null },
394
+ err: errVal,
395
+ fee: Number(row.fee),
396
+ loadedAddresses: { writable: [], readonly: [] },
397
+ preBalances,
398
+ postBalances,
399
+ innerInstructions: Array.isArray(inner) ? inner : [],
400
+ logMessages: logs,
401
+ preTokenBalances: JSON.parse(row.preTokenBalancesJson || "[]"),
402
+ postTokenBalances: JSON.parse(row.postTokenBalancesJson || "[]"),
403
+ computeUnitsConsumed:
404
+ row.computeUnits != null ? Number(row.computeUnits) : null,
405
+ returnData: (() => {
406
+ if (row.returnDataProgramId && row.returnDataBase64)
407
+ return {
408
+ programId: row.returnDataProgramId,
409
+ data: [row.returnDataBase64, "base64"],
410
+ };
411
+ return null;
412
+ })(),
413
+ rewards: [],
414
+ },
415
+ blockTime: row.blockTime ? Number(row.blockTime) : null,
416
+ };
417
+ // Also parse inner instructions from DB row
418
+ try {
419
+ const innerSrc = JSON.parse(row.innerInstructionsJson || "[]");
420
+ const innerParsed = (innerSrc as any[]).map((group) => ({
421
+ index: Number(group.index || 0),
422
+ instructions: Array.isArray(group.instructions)
423
+ ? group.instructions.map((ii: any) =>
424
+ parseInstruction(
425
+ accountKeys[ii.programIdIndex ?? 0] || accountKeys[0],
426
+ Array.isArray(ii.accounts) ? ii.accounts : [],
427
+ typeof ii.data === "string" ? ii.data : String(ii.data ?? ""),
428
+ accountKeys,
429
+ ),
430
+ )
431
+ : [],
432
+ }));
433
+ (result as any).meta.innerInstructions = innerParsed;
434
+ } catch {}
435
+
436
+ return context.createSuccessResponse(id, result);
437
+ } else {
324
438
  const raw = Buffer.from(row.rawBase64, "base64");
325
439
  const tx = VersionedTransaction.deserialize(raw);
326
- const msg: any = tx.message as any;
327
- const rawKeys3: any[] = Array.isArray(msg.staticAccountKeys)
440
+ const msg = tx.message as unknown as {
441
+ staticAccountKeys?: unknown[];
442
+ accountKeys?: unknown[];
443
+ compiledInstructions?: unknown[];
444
+ instructions?: unknown[];
445
+ header?: unknown;
446
+ recentBlockhash?: string;
447
+ addressTableLookups?: unknown[];
448
+ };
449
+ const rawKeys3: unknown[] = Array.isArray(msg.staticAccountKeys)
328
450
  ? msg.staticAccountKeys
329
451
  : Array.isArray(msg.accountKeys)
330
452
  ? msg.accountKeys
331
453
  : [];
332
- const accountKeys = rawKeys3.map((k: any) => {
454
+ const accountKeys = rawKeys3.map((k) => {
333
455
  try {
334
- return typeof k === "string" ? k : k.toBase58();
456
+ return typeof k === "string"
457
+ ? k
458
+ : (k as { toBase58: () => string }).toBase58();
335
459
  } catch {
336
460
  return String(k);
337
461
  }
@@ -341,54 +465,81 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
341
465
  numReadonlySignedAccounts: 0,
342
466
  numReadonlyUnsignedAccounts: 0,
343
467
  };
344
- const compiled = Array.isArray(msg.compiledInstructions)
468
+ const compiled: unknown[] = Array.isArray(msg.compiledInstructions)
345
469
  ? msg.compiledInstructions
346
470
  : Array.isArray(msg.instructions)
347
471
  ? msg.instructions
348
472
  : [];
349
- const instructions = compiled.map((ci: any) => ({
350
- programIdIndex: ci.programIdIndex,
351
- accounts: Array.from(ci.accountKeyIndexes || ci.accounts || []),
352
- data: context.encodeBase58(
353
- ci.data instanceof Uint8Array ? ci.data : Buffer.from(ci.data),
354
- ),
355
- }));
356
- const result: any = {
357
- slot: Number(row.slot),
358
- transaction: {
359
- signatures: [signature],
360
- message: {
361
- accountKeys,
362
- header,
363
- recentBlockhash: msg.recentBlockhash || "",
364
- instructions,
365
- addressTableLookups: msg.addressTableLookups || [],
366
- },
367
- },
368
- version: versionVal,
369
- meta: {
370
- status: errVal ? { Err: errVal } : { Ok: null },
371
- err: errVal,
372
- fee: Number(row.fee),
373
- loadedAddresses: { writable: [], readonly: [] },
374
- preBalances,
375
- postBalances,
376
- innerInstructions: [],
377
- logMessages: logs,
378
- preTokenBalances: JSON.parse(row.preTokenBalancesJson || "[]"),
379
- postTokenBalances: JSON.parse(row.postTokenBalancesJson || "[]"),
380
- rewards: [],
381
- },
382
- blockTime: row.blockTime ? Number(row.blockTime) : null,
383
- };
384
- return context.createSuccessResponse(id, result);
385
- }
473
+ const instructions = compiled.map((ci) => {
474
+ const c = ci as {
475
+ programIdIndex: number;
476
+ accountKeyIndexes?: number[];
477
+ accounts?: number[];
478
+ data: Uint8Array | number[];
479
+ };
480
+ return {
481
+ programIdIndex: c.programIdIndex,
482
+ accounts: Array.from(c.accountKeyIndexes || c.accounts || []),
483
+ data: context.encodeBase58(
484
+ c.data instanceof Uint8Array ? c.data : Buffer.from(c.data),
485
+ ),
486
+ };
487
+ });
488
+ const result = {
489
+ slot: Number(row.slot),
490
+ transaction: {
491
+ signatures: [signature],
492
+ message: {
493
+ accountKeys,
494
+ header,
495
+ recentBlockhash: msg.recentBlockhash || "",
496
+ instructions,
497
+ addressTableLookups: msg.addressTableLookups || [],
498
+ },
499
+ },
500
+ version: versionVal,
501
+ meta: {
502
+ status: errVal ? { Err: errVal } : { Ok: null },
503
+ err: errVal,
504
+ fee: Number(row.fee),
505
+ loadedAddresses: { writable: [], readonly: [] },
506
+ preBalances,
507
+ postBalances,
508
+ innerInstructions: Array.isArray(inner) ? inner : [],
509
+ logMessages: logs,
510
+ preTokenBalances: JSON.parse(row.preTokenBalancesJson || "[]"),
511
+ postTokenBalances: JSON.parse(row.postTokenBalancesJson || "[]"),
512
+ computeUnitsConsumed:
513
+ row.computeUnits != null ? Number(row.computeUnits) : null,
514
+ returnData: (() => {
515
+ if (row.returnDataProgramId && row.returnDataBase64)
516
+ return {
517
+ programId: row.returnDataProgramId,
518
+ data: [row.returnDataBase64, "base64"],
519
+ };
520
+ return null;
521
+ })(),
522
+ rewards: [],
523
+ },
524
+ blockTime: row.blockTime ? Number(row.blockTime) : null,
525
+ };
526
+ return context.createSuccessResponse(id, result);
527
+ }
386
528
  }
387
529
  } catch {}
388
530
 
389
531
  // Fallback to LiteSVM history when no local record exists
390
532
  const sigBytes = context.decodeBase58(signature);
391
- const txh = (context.svm as any).getTransaction(sigBytes);
533
+ const getTx = (
534
+ context.svm as unknown as {
535
+ getTransaction?: (
536
+ sig: Uint8Array,
537
+ ) =>
538
+ | { logs: () => string[]; err: () => unknown }
539
+ | { meta: () => { logs: () => string[] }; err: () => unknown };
540
+ }
541
+ ).getTransaction;
542
+ const txh = typeof getTx === "function" ? getTx(sigBytes) : undefined;
392
543
  if (!txh) return context.createSuccessResponse(id, null);
393
544
 
394
545
  const isError = "err" in txh;
@@ -417,12 +568,8 @@ export const getTransaction: RpcMethodHandler = async (id, params, context) => {
417
568
  },
418
569
  blockTime: Math.floor(Date.now() / 1000),
419
570
  });
420
- } catch (error: any) {
421
- return context.createErrorResponse(
422
- id,
423
- -32602,
424
- "Invalid params",
425
- error.message,
426
- );
571
+ } catch (error: unknown) {
572
+ const message = error instanceof Error ? error.message : String(error);
573
+ return context.createErrorResponse(id, -32602, "Invalid params", message);
427
574
  }
428
575
  };