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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -4,9 +4,9 @@
4
4
  - Skips if SOLFORGE_SKIP_DOWNLOAD=true
5
5
  - Falls back silently on errors (CLI will still work via Bun if installed)
6
6
  */
7
- const fs = require("fs");
8
- const path = require("path");
9
- const https = require("https");
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+ const https = require("node:https");
10
10
 
11
11
  function log(msg) {
12
12
  console.log(`[solforge] ${msg}`);
@@ -12,7 +12,7 @@ export function encodeBase58(bytes: Uint8Array): string {
12
12
  encoded = ALPHABET[Number(remainder)] + encoded;
13
13
  }
14
14
  for (let i = 0; i < bytes.length && bytes[i] === 0; i++)
15
- encoded = "1" + encoded;
15
+ encoded = `1${encoded}`;
16
16
  return encoded || "1";
17
17
  }
18
18
 
@@ -0,0 +1,242 @@
1
+ import {
2
+ AddressLookupTableInstruction,
3
+ ComputeBudgetInstruction,
4
+ PublicKey,
5
+ StakeInstruction,
6
+ SystemInstruction,
7
+ SystemProgram,
8
+ TransactionInstruction,
9
+ VoteInstruction,
10
+ } from "@solana/web3.js";
11
+ import { decodeBase58 as _decodeBase58 } from "./base58";
12
+
13
+ export type ParsedInstruction =
14
+ | { program: string; programId: string; parsed: { type: string; info: any } }
15
+ | { programId: string; accounts: string[]; data: string };
16
+
17
+ function makeIx(
18
+ programId: string,
19
+ accountKeys: string[],
20
+ accounts: number[],
21
+ dataBase58: string,
22
+ ): TransactionInstruction {
23
+ const keys = accounts.map((i) => ({
24
+ pubkey: new PublicKey(accountKeys[i] || SystemProgram.programId),
25
+ isSigner: false,
26
+ isWritable: false,
27
+ }));
28
+ const data = Buffer.from(_decodeBase58(dataBase58));
29
+ return new TransactionInstruction({ programId: new PublicKey(programId), keys, data });
30
+ }
31
+
32
+ function ok(program: string, programId: string, type: string, info: any): ParsedInstruction {
33
+ return { program, programId, parsed: { type, info } };
34
+ }
35
+
36
+ export function parseInstruction(
37
+ programId: string,
38
+ accounts: number[],
39
+ dataBase58: string,
40
+ accountKeys: string[],
41
+ ): ParsedInstruction {
42
+ try {
43
+ const pid = new PublicKey(programId);
44
+ const ix = makeIx(programId, accountKeys, accounts, dataBase58);
45
+
46
+ // System Program
47
+ if (pid.equals(SystemProgram.programId)) {
48
+ const t = SystemInstruction.decodeInstructionType(ix);
49
+ switch (t) {
50
+ case "Create": {
51
+ const p = SystemInstruction.decodeCreateAccount(ix);
52
+ return ok("system", programId, "createAccount", {
53
+ fromPubkey: p.fromPubkey.toBase58(),
54
+ newAccountPubkey: p.newAccountPubkey.toBase58(),
55
+ lamports: Number(p.lamports),
56
+ space: Number(p.space),
57
+ programId: p.programId.toBase58(),
58
+ });
59
+ }
60
+ case "Transfer": {
61
+ const p = SystemInstruction.decodeTransfer(ix);
62
+ return ok("system", programId, "transfer", {
63
+ source: p.fromPubkey.toBase58(),
64
+ destination: p.toPubkey.toBase58(),
65
+ lamports: Number(p.lamports),
66
+ });
67
+ }
68
+ case "TransferWithSeed": {
69
+ const p = SystemInstruction.decodeTransferWithSeed(ix);
70
+ return ok("system", programId, "transferWithSeed", {
71
+ fromPubkey: p.fromPubkey.toBase58(),
72
+ basePubkey: p.basePubkey.toBase58(),
73
+ toPubkey: p.toPubkey.toBase58(),
74
+ lamports: Number(p.lamports),
75
+ seed: p.seed,
76
+ programId: p.programId.toBase58(),
77
+ });
78
+ }
79
+ case "Allocate": {
80
+ const p = SystemInstruction.decodeAllocate(ix);
81
+ return ok("system", programId, "allocate", {
82
+ accountPubkey: p.accountPubkey.toBase58(),
83
+ space: Number(p.space),
84
+ });
85
+ }
86
+ case "AllocateWithSeed": {
87
+ const p = SystemInstruction.decodeAllocateWithSeed(ix);
88
+ return ok("system", programId, "allocateWithSeed", {
89
+ accountPubkey: p.accountPubkey.toBase58(),
90
+ basePubkey: p.basePubkey.toBase58(),
91
+ seed: p.seed,
92
+ space: Number(p.space),
93
+ programId: p.programId.toBase58(),
94
+ });
95
+ }
96
+ case "Assign": {
97
+ const p = SystemInstruction.decodeAssign(ix);
98
+ return ok("system", programId, "assign", {
99
+ accountPubkey: p.accountPubkey.toBase58(),
100
+ programId: p.programId.toBase58(),
101
+ });
102
+ }
103
+ case "AssignWithSeed": {
104
+ const p = SystemInstruction.decodeAssignWithSeed(ix);
105
+ return ok("system", programId, "assignWithSeed", {
106
+ accountPubkey: p.accountPubkey.toBase58(),
107
+ basePubkey: p.basePubkey.toBase58(),
108
+ seed: p.seed,
109
+ programId: p.programId.toBase58(),
110
+ });
111
+ }
112
+ case "InitializeNonceAccount":
113
+ case "AdvanceNonceAccount":
114
+ case "WithdrawNonceAccount":
115
+ case "AuthorizeNonceAccount":
116
+ case "CreateWithSeed":
117
+ case "UpgradeNonceAccount": {
118
+ // For brevity: rely on type only; details can be added if needed
119
+ return ok("system", programId, t, {});
120
+ }
121
+ default:
122
+ break;
123
+ }
124
+ }
125
+
126
+ // Compute Budget
127
+ try {
128
+ const t = ComputeBudgetInstruction.decodeInstructionType(ix);
129
+ switch (t) {
130
+ case "SetComputeUnitLimit": {
131
+ const p = ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix);
132
+ return ok("computeBudget", programId, "setComputeUnitLimit", { units: Number(p.units) });
133
+ }
134
+ case "SetComputeUnitPrice": {
135
+ const p = ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix);
136
+ return ok("computeBudget", programId, "setComputeUnitPrice", { microLamports: Number(p.microLamports) });
137
+ }
138
+ case "RequestHeapFrame": {
139
+ const p = ComputeBudgetInstruction.decodeRequestHeapFrame(ix);
140
+ return ok("computeBudget", programId, "requestHeapFrame", { bytes: Number(p.bytes) });
141
+ }
142
+ case "RequestUnits": {
143
+ const p = ComputeBudgetInstruction.decodeRequestUnits(ix);
144
+ return ok("computeBudget", programId, "requestUnits", { units: Number(p.units), additionalFee: Number(p.additionalFee) });
145
+ }
146
+ }
147
+ } catch {}
148
+
149
+ // Stake
150
+ try {
151
+ const t = StakeInstruction.decodeInstructionType(ix);
152
+ switch (t) {
153
+ case "Initialize":
154
+ return ok("stake", programId, "initialize", {});
155
+ case "Delegate": {
156
+ const p = StakeInstruction.decodeDelegate(ix);
157
+ return ok("stake", programId, "delegate", {
158
+ stakePubkey: p.stakePubkey.toBase58(),
159
+ votePubkey: p.votePubkey.toBase58(),
160
+ authorizedPubkey: p.authorizedPubkey.toBase58(),
161
+ });
162
+ }
163
+ case "Authorize": {
164
+ const p = StakeInstruction.decodeAuthorize(ix);
165
+ return ok("stake", programId, "authorize", {
166
+ stakePubkey: p.stakePubkey.toBase58(),
167
+ authorizedPubkey: p.authorizedPubkey.toBase58(),
168
+ newAuthorizedPubkey: p.newAuthorizedPubkey.toBase58(),
169
+ stakeAuthorizationType: p.stakeAuthorizationType.index,
170
+ });
171
+ }
172
+ case "AuthorizeWithSeed":
173
+ return ok("stake", programId, "authorizeWithSeed", {});
174
+ case "Split":
175
+ return ok("stake", programId, "split", {});
176
+ case "Withdraw":
177
+ return ok("stake", programId, "withdraw", {});
178
+ case "Deactivate":
179
+ return ok("stake", programId, "deactivate", {});
180
+ case "Merge":
181
+ return ok("stake", programId, "merge", {});
182
+ }
183
+ } catch {}
184
+
185
+ // Vote
186
+ try {
187
+ const t = VoteInstruction.decodeInstructionType(ix);
188
+ switch (t) {
189
+ case "InitializeAccount":
190
+ return ok("vote", programId, "initialize", {});
191
+ case "Authorize":
192
+ return ok("vote", programId, "authorize", {});
193
+ case "AuthorizeWithSeed":
194
+ return ok("vote", programId, "authorizeWithSeed", {});
195
+ case "Withdraw":
196
+ return ok("vote", programId, "withdraw", {});
197
+ default:
198
+ return ok("vote", programId, t, {});
199
+ }
200
+ } catch {}
201
+
202
+ // Address Lookup Table
203
+ try {
204
+ const t = AddressLookupTableInstruction.decodeInstructionType(ix);
205
+ switch (t) {
206
+ case "CreateLookupTable":
207
+ case "ExtendLookupTable":
208
+ case "CloseLookupTable":
209
+ case "FreezeLookupTable":
210
+ case "DeactivateLookupTable":
211
+ return ok("address-lookup-table", programId, t, {});
212
+ }
213
+ } catch {}
214
+
215
+ // Memo program: parse utf8 memo if possible
216
+ if (
217
+ programId === "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" ||
218
+ programId === "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
219
+ ) {
220
+ try {
221
+ const bytes = _decodeBase58(dataBase58);
222
+ const memo = new TextDecoder().decode(bytes);
223
+ return ok("spl-memo", programId, "memo", { memo });
224
+ } catch {}
225
+ return ok("spl-memo", programId, "memo", {});
226
+ }
227
+
228
+ // Fallback: unknown program, return raw
229
+ return {
230
+ programId,
231
+ accounts: accounts.map((i) => accountKeys[i] || ""),
232
+ data: dataBase58,
233
+ };
234
+ } catch {
235
+ return {
236
+ programId,
237
+ accounts: accounts.map((i) => accountKeys[i] || ""),
238
+ data: dataBase58,
239
+ };
240
+ }
241
+ }
242
+
@@ -79,12 +79,8 @@ export const getAccountInfo: RpcMethodHandler = async (id, params, context) => {
79
79
  context: { slot: Number(context.slot) },
80
80
  value: accountInfo,
81
81
  });
82
- } catch (error: any) {
83
- return context.createErrorResponse(
84
- id,
85
- -32602,
86
- "Invalid params",
87
- error.message,
88
- );
82
+ } catch (error: unknown) {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ return context.createErrorResponse(id, -32602, "Invalid params", message);
89
85
  }
90
86
  };
@@ -16,12 +16,8 @@ export const getBalance: RpcMethodHandler = (id, params, context) => {
16
16
  context: { slot: Number(context.slot) },
17
17
  value: Number(balance || 0n),
18
18
  });
19
- } catch (error: any) {
20
- return context.createErrorResponse(
21
- id,
22
- -32602,
23
- "Invalid params",
24
- error.message,
25
- );
19
+ } catch (error: unknown) {
20
+ const message = error instanceof Error ? error.message : String(error);
21
+ return context.createErrorResponse(id, -32602, "Invalid params", message);
26
22
  }
27
23
  };
@@ -1,5 +1,6 @@
1
1
  import { PublicKey } from "@solana/web3.js";
2
2
  import type { RpcMethodHandler } from "../../types";
3
+ import type { AccountSnapshot } from "../../../src/db/tx-store";
3
4
 
4
5
  /**
5
6
  * Implements the getMultipleAccounts RPC method
@@ -54,7 +55,7 @@ export const getMultipleAccounts: RpcMethodHandler = async (
54
55
 
55
56
  // Opportunistic index update
56
57
  try {
57
- const snaps: any[] = [];
58
+ const snaps: AccountSnapshot[] = [];
58
59
  for (const pubkeyStr of pubkeys) {
59
60
  try {
60
61
  const pubkey = new PublicKey(pubkeyStr);
@@ -10,12 +10,8 @@ export const getParsedAccountInfo: RpcMethodHandler = async (
10
10
  const cfg = { ...(config || {}), encoding: "jsonParsed" };
11
11
  try {
12
12
  return await getAccountInfo(id, [pubkey, cfg], context);
13
- } catch (error: any) {
14
- return context.createErrorResponse(
15
- id,
16
- -32603,
17
- "Internal error",
18
- error.message,
19
- );
13
+ } catch (error: unknown) {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ return context.createErrorResponse(id, -32603, "Internal error", message);
20
16
  }
21
17
  };
@@ -6,7 +6,7 @@ import { parseSystemAccount } from "./system";
6
6
 
7
7
  export type ParsedAccountData = {
8
8
  program: string;
9
- parsed: any; // match Solana RPC jsonParsed payloads
9
+ parsed: unknown; // match Solana RPC jsonParsed payloads
10
10
  space: number;
11
11
  } | null;
12
12
 
@@ -32,7 +32,7 @@ export function parseAccountJson(
32
32
  const dataBytes =
33
33
  account.data instanceof Uint8Array
34
34
  ? account.data
35
- : Buffer.from(account.data as any);
35
+ : Buffer.from(account.data as ReadonlyArray<number>);
36
36
  const space = dataBytes.length;
37
37
 
38
38
  // 1) System program
@@ -12,7 +12,20 @@ export function parseUpgradeableLoader(
12
12
  const bytes = data;
13
13
  const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
14
14
  const space = bytes.length;
15
- let parsed: any = null;
15
+ type UpgradeableParsed =
16
+ | { type: "program"; info: { programData: string } }
17
+ | {
18
+ type: "programData";
19
+ info: {
20
+ slot: number;
21
+ upgradeAuthority: string | null;
22
+ authority: string | null;
23
+ data: [string, "base64"];
24
+ };
25
+ }
26
+ | { type: "buffer"; info: { authority: string | null } }
27
+ | null;
28
+ let parsed: UpgradeableParsed = null;
16
29
  try {
17
30
  if (bytes.length >= 4) {
18
31
  const tag = dv.getUint32(0, true);
@@ -51,10 +51,17 @@ export function parseSplTokenAccountOrMint(
51
51
  try {
52
52
  const mintAcc = context.svm.getAccount(dec.mint);
53
53
  if (mintAcc) {
54
- const mintOwner =
55
- typeof (mintAcc as any).owner?.toBase58 === "function"
56
- ? ((mintAcc as any).owner as PublicKey)
57
- : new PublicKey(mintAcc.owner);
54
+ const rawOwner = (mintAcc as { owner?: unknown }).owner;
55
+ const mintOwner = ((): PublicKey => {
56
+ if (
57
+ rawOwner &&
58
+ typeof (rawOwner as { toBase58?: unknown }).toBase58 ===
59
+ "function"
60
+ ) {
61
+ return rawOwner as PublicKey;
62
+ }
63
+ return new PublicKey(String(rawOwner));
64
+ })();
58
65
  const mintProg = mintOwner.equals(TOKEN_2022_PROGRAM_ID)
59
66
  ? TOKEN_2022_PROGRAM_ID
60
67
  : TOKEN_PROGRAM_ID;
@@ -176,18 +183,18 @@ function buildAccountExtensions(account: {
176
183
 
177
184
  function buildMintExtensions(mint: {
178
185
  tlvData: Buffer;
179
- }): Array<Record<string, any>> | undefined {
186
+ }): Array<Record<string, unknown>> | undefined {
180
187
  if (!mint.tlvData || mint.tlvData.length === 0) return undefined;
181
188
  const types = getExtensionTypes(mint.tlvData);
182
189
  if (!types.length) return undefined;
183
- const out: Array<Record<string, any>> = [];
190
+ const out: Array<Record<string, unknown>> = [];
184
191
  for (const ext of types) {
185
- const entry: Record<string, any> = {
192
+ const entry: Record<string, unknown> = {
186
193
  type: ExtensionType[ext] ?? String(ext),
187
194
  };
188
195
  try {
189
196
  if (ext === ExtensionType.MetadataPointer) {
190
- const state = getMetadataPointerState(mint as any);
197
+ const state = getMetadataPointerState(mint as { tlvData: Buffer });
191
198
  if (state) {
192
199
  entry.info = {
193
200
  authority: state.authority ? state.authority.toBase58() : null,
@@ -222,9 +229,21 @@ function buildMintExtensions(mint: {
222
229
  return out.length ? out : undefined;
223
230
  }
224
231
 
225
- function toAccountInfo(raw: any, owner: PublicKey): AccountInfo<Buffer> {
232
+ function toAccountInfo(
233
+ raw: {
234
+ data?: Buffer | Uint8Array | number[];
235
+ lamports?: number | bigint;
236
+ executable?: boolean;
237
+ rentEpoch?: number | bigint;
238
+ },
239
+ owner: PublicKey,
240
+ ): AccountInfo<Buffer> {
226
241
  const data =
227
- raw.data instanceof Buffer ? raw.data : Buffer.from(raw.data ?? []);
242
+ raw.data instanceof Buffer
243
+ ? raw.data
244
+ : raw.data instanceof Uint8Array
245
+ ? Buffer.from(raw.data)
246
+ : Buffer.from((raw.data ?? []) as number[]);
228
247
  return {
229
248
  data,
230
249
  executable: !!raw.executable,