solforge 0.1.6 → 0.2.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.
Files changed (194) hide show
  1. package/.agi/agi.sqlite +0 -0
  2. package/.claude/settings.local.json +9 -0
  3. package/.github/workflows/release-binaries.yml +133 -0
  4. package/.tmp/.787ebcdbf7b8fde8-00000000.hm +0 -0
  5. package/.tmp/.bffe6efebdf8aedc-00000000.hm +0 -0
  6. package/AGENTS.md +271 -0
  7. package/CLAUDE.md +106 -0
  8. package/PROJECT_STRUCTURE.md +124 -0
  9. package/README.md +367 -393
  10. package/SOLANA_KIT_GUIDE.md +251 -0
  11. package/SOLFORGE.md +119 -0
  12. package/biome.json +34 -0
  13. package/bun.lock +743 -0
  14. package/docs/bun-single-file-executable.md +585 -0
  15. package/docs/cli-plan.md +154 -0
  16. package/docs/data-indexing-plan.md +214 -0
  17. package/docs/gui-roadmap.md +202 -0
  18. package/drizzle/0000_friendly_millenium_guard.sql +53 -0
  19. package/drizzle/0001_stale_sentinels.sql +2 -0
  20. package/drizzle/meta/0000_snapshot.json +329 -0
  21. package/drizzle/meta/0001_snapshot.json +345 -0
  22. package/drizzle/meta/_journal.json +20 -0
  23. package/drizzle.config.ts +12 -0
  24. package/index.ts +21 -0
  25. package/mint.sh +47 -0
  26. package/package.json +45 -69
  27. package/postcss.config.js +6 -0
  28. package/rpc-server.ts.backup +519 -0
  29. package/server/index.ts +5 -0
  30. package/server/lib/base58.ts +33 -0
  31. package/server/lib/faucet.ts +110 -0
  32. package/server/lib/spl-token.ts +57 -0
  33. package/server/methods/TEMPLATE.md +117 -0
  34. package/server/methods/account/get-account-info.ts +90 -0
  35. package/server/methods/account/get-balance.ts +27 -0
  36. package/server/methods/account/get-multiple-accounts.ts +83 -0
  37. package/server/methods/account/get-parsed-account-info.ts +21 -0
  38. package/server/methods/account/index.ts +12 -0
  39. package/server/methods/account/parsers/index.ts +52 -0
  40. package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
  41. package/server/methods/account/parsers/spl-token.ts +237 -0
  42. package/server/methods/account/parsers/system.ts +4 -0
  43. package/server/methods/account/request-airdrop.ts +219 -0
  44. package/server/methods/admin/adopt-mint-authority.ts +94 -0
  45. package/server/methods/admin/clone-program-accounts.ts +55 -0
  46. package/server/methods/admin/clone-program.ts +152 -0
  47. package/server/methods/admin/clone-token-accounts.ts +117 -0
  48. package/server/methods/admin/clone-token-mint.ts +82 -0
  49. package/server/methods/admin/create-mint.ts +114 -0
  50. package/server/methods/admin/create-token-account.ts +137 -0
  51. package/server/methods/admin/helpers.ts +70 -0
  52. package/server/methods/admin/index.ts +10 -0
  53. package/server/methods/admin/list-mints.ts +21 -0
  54. package/server/methods/admin/load-program.ts +52 -0
  55. package/server/methods/admin/mint-to.ts +278 -0
  56. package/server/methods/block/get-block-height.ts +5 -0
  57. package/server/methods/block/get-block.ts +35 -0
  58. package/server/methods/block/get-blocks-with-limit.ts +23 -0
  59. package/server/methods/block/get-latest-blockhash.ts +12 -0
  60. package/server/methods/block/get-slot.ts +5 -0
  61. package/server/methods/block/index.ts +6 -0
  62. package/server/methods/block/is-blockhash-valid.ts +23 -0
  63. package/server/methods/epoch/get-cluster-nodes.ts +17 -0
  64. package/server/methods/epoch/get-epoch-info.ts +16 -0
  65. package/server/methods/epoch/get-epoch-schedule.ts +15 -0
  66. package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
  67. package/server/methods/epoch/get-leader-schedule.ts +8 -0
  68. package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
  69. package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
  70. package/server/methods/epoch/get-slot-leader.ts +6 -0
  71. package/server/methods/epoch/get-slot-leaders.ts +9 -0
  72. package/server/methods/epoch/get-stake-activation.ts +9 -0
  73. package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
  74. package/server/methods/epoch/get-vote-accounts.ts +19 -0
  75. package/server/methods/epoch/index.ts +13 -0
  76. package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
  77. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
  78. package/server/methods/fee/get-fee-for-message.ts +8 -0
  79. package/server/methods/fee/get-fee-rate-governor.ts +16 -0
  80. package/server/methods/fee/get-fees.ts +14 -0
  81. package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
  82. package/server/methods/fee/index.ts +5 -0
  83. package/server/methods/get-address-lookup-table.ts +31 -0
  84. package/server/methods/index.ts +265 -0
  85. package/server/methods/performance/get-recent-performance-samples.ts +25 -0
  86. package/server/methods/performance/get-transaction-count.ts +5 -0
  87. package/server/methods/performance/index.ts +2 -0
  88. package/server/methods/program/get-block-commitment.ts +9 -0
  89. package/server/methods/program/get-block-production.ts +14 -0
  90. package/server/methods/program/get-block-time.ts +21 -0
  91. package/server/methods/program/get-blocks.ts +11 -0
  92. package/server/methods/program/get-first-available-block.ts +9 -0
  93. package/server/methods/program/get-genesis-hash.ts +6 -0
  94. package/server/methods/program/get-identity.ts +6 -0
  95. package/server/methods/program/get-inflation-governor.ts +15 -0
  96. package/server/methods/program/get-inflation-rate.ts +10 -0
  97. package/server/methods/program/get-inflation-reward.ts +12 -0
  98. package/server/methods/program/get-largest-accounts.ts +8 -0
  99. package/server/methods/program/get-parsed-program-accounts.ts +12 -0
  100. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
  101. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
  102. package/server/methods/program/get-program-accounts.ts +221 -0
  103. package/server/methods/program/get-supply.ts +13 -0
  104. package/server/methods/program/get-token-account-balance.ts +64 -0
  105. package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
  106. package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
  107. package/server/methods/program/get-token-largest-accounts.ts +80 -0
  108. package/server/methods/program/get-token-supply.ts +38 -0
  109. package/server/methods/program/index.ts +21 -0
  110. package/server/methods/solforge/index.ts +155 -0
  111. package/server/methods/system/get-health.ts +5 -0
  112. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
  113. package/server/methods/system/get-version.ts +9 -0
  114. package/server/methods/system/index.ts +3 -0
  115. package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
  116. package/server/methods/transaction/get-parsed-transaction.ts +21 -0
  117. package/server/methods/transaction/get-signature-statuses.ts +72 -0
  118. package/server/methods/transaction/get-signatures-for-address.ts +45 -0
  119. package/server/methods/transaction/get-transaction.ts +428 -0
  120. package/server/methods/transaction/index.ts +7 -0
  121. package/server/methods/transaction/send-transaction.ts +232 -0
  122. package/server/methods/transaction/simulate-transaction.ts +56 -0
  123. package/server/rpc-server.ts +474 -0
  124. package/server/types.ts +74 -0
  125. package/server/ws-server.ts +171 -0
  126. package/sf.config.json +38 -0
  127. package/src/cli/bootstrap.ts +67 -0
  128. package/src/cli/commands/airdrop.ts +37 -0
  129. package/src/cli/commands/config.ts +39 -0
  130. package/src/cli/commands/mint.ts +187 -0
  131. package/src/cli/commands/program-clone.ts +124 -0
  132. package/src/cli/commands/program-load.ts +64 -0
  133. package/src/cli/commands/rpc-start.ts +46 -0
  134. package/src/cli/commands/token-adopt-authority.ts +37 -0
  135. package/src/cli/commands/token-clone.ts +113 -0
  136. package/src/cli/commands/token-create.ts +81 -0
  137. package/src/cli/main.ts +130 -0
  138. package/src/cli/run-solforge.ts +98 -0
  139. package/src/cli/setup-utils.ts +54 -0
  140. package/src/cli/setup-wizard.ts +256 -0
  141. package/src/cli/utils/args.ts +15 -0
  142. package/src/config/index.ts +130 -0
  143. package/src/db/index.ts +83 -0
  144. package/src/db/schema/accounts.ts +23 -0
  145. package/src/db/schema/address-signatures.ts +31 -0
  146. package/src/db/schema/index.ts +5 -0
  147. package/src/db/schema/meta-kv.ts +9 -0
  148. package/src/db/schema/transactions.ts +29 -0
  149. package/src/db/schema/tx-accounts.ts +33 -0
  150. package/src/db/tx-store.ts +229 -0
  151. package/src/gui/public/app.css +1 -0
  152. package/src/gui/public/index.html +19 -0
  153. package/src/gui/server.ts +297 -0
  154. package/src/gui/src/api.ts +127 -0
  155. package/src/gui/src/app.tsx +390 -0
  156. package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
  157. package/src/gui/src/components/clone-program-modal.tsx +183 -0
  158. package/src/gui/src/components/clone-token-modal.tsx +211 -0
  159. package/src/gui/src/components/modal.tsx +127 -0
  160. package/src/gui/src/components/programs-panel.tsx +112 -0
  161. package/src/gui/src/components/status-panel.tsx +122 -0
  162. package/src/gui/src/components/tokens-panel.tsx +116 -0
  163. package/src/gui/src/hooks/use-interval.ts +17 -0
  164. package/src/gui/src/index.css +529 -0
  165. package/src/gui/src/main.tsx +17 -0
  166. package/src/migrations-bundled.ts +17 -0
  167. package/src/rpc/start.ts +44 -0
  168. package/tailwind.config.js +27 -0
  169. package/test-client.ts +120 -0
  170. package/tmp/inspect-html.ts +4 -0
  171. package/tmp/response-test.ts +5 -0
  172. package/tmp/test-html.ts +5 -0
  173. package/tmp/test-server.ts +13 -0
  174. package/tsconfig.json +24 -23
  175. package/LICENSE +0 -21
  176. package/scripts/postinstall.cjs +0 -103
  177. package/src/api-server-entry.ts +0 -109
  178. package/src/commands/add-program.ts +0 -337
  179. package/src/commands/init.ts +0 -122
  180. package/src/commands/list.ts +0 -136
  181. package/src/commands/mint.ts +0 -336
  182. package/src/commands/start.ts +0 -878
  183. package/src/commands/status.ts +0 -99
  184. package/src/commands/stop.ts +0 -406
  185. package/src/config/manager.ts +0 -157
  186. package/src/index.ts +0 -188
  187. package/src/services/api-server.ts +0 -532
  188. package/src/services/port-manager.ts +0 -177
  189. package/src/services/process-registry.ts +0 -154
  190. package/src/services/program-cloner.ts +0 -317
  191. package/src/services/token-cloner.ts +0 -809
  192. package/src/services/validator.ts +0 -295
  193. package/src/types/config.ts +0 -110
  194. package/src/utils/shell.ts +0 -110
@@ -0,0 +1,57 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+
3
+ export const TOKEN_PROGRAM_ID = new PublicKey(
4
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
5
+ );
6
+
7
+ // Minimal SPL Token v1 layouts
8
+ export type DecodedMint = {
9
+ supply: bigint;
10
+ decimals: number;
11
+ isInitialized: boolean;
12
+ };
13
+
14
+ export type DecodedTokenAccount = {
15
+ mint: string;
16
+ owner: string;
17
+ amount: bigint;
18
+ state: number;
19
+ delegatedAmount: bigint;
20
+ };
21
+
22
+ export function decodeMint(data: Uint8Array): DecodedMint | null {
23
+ if (!data || data.length < 82) return null;
24
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
25
+ // supply at offset 36? Common layout: 4 (opt) + 32 (auth) + 8 (supply) + 1 (dec) + 1 (init)
26
+ const supply = view.getBigUint64(36, true);
27
+ const decimals = view.getUint8(44);
28
+ const isInitialized = view.getUint8(45) !== 0;
29
+ return { supply, decimals, isInitialized };
30
+ }
31
+
32
+ export function decodeTokenAccount(
33
+ data: Uint8Array,
34
+ ): DecodedTokenAccount | null {
35
+ if (!data || data.length < 165) return null;
36
+ const mint = new PublicKey(data.slice(0, 32)).toBase58();
37
+ const owner = new PublicKey(data.slice(32, 64)).toBase58();
38
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
39
+ const amount = view.getBigUint64(64, true);
40
+ const state = data[108];
41
+ const delegatedAmount = view.getBigUint64(124, true);
42
+ return { mint, owner, amount, state, delegatedAmount };
43
+ }
44
+
45
+ export function formatUiAmount(amount: bigint, decimals: number) {
46
+ const base = BigInt(10) ** BigInt(decimals);
47
+ const whole = amount / base;
48
+ const frac = amount % base;
49
+ const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
50
+ const ui = fracStr.length > 0 ? `${whole}.${fracStr}` : `${whole}`;
51
+ return {
52
+ amount: amount.toString(),
53
+ decimals,
54
+ uiAmount: Number(ui),
55
+ uiAmountString: ui,
56
+ };
57
+ }
@@ -0,0 +1,117 @@
1
+ # RPC Method Template
2
+
3
+ Use this template when adding new RPC methods to SolForge.
4
+
5
+ ## File Template
6
+
7
+ ```typescript
8
+ import { PublicKey } from "@solana/web3.js"; // Only if needed
9
+ import type { RpcMethodHandler } from "../types"; // Adjust path based on location
10
+
11
+ /**
12
+ * Implements the methodName RPC method
13
+ * @see https://docs.solana.com/api/http#methodname
14
+ */
15
+ export const methodName: RpcMethodHandler = (id, params, context) => {
16
+ // 1. Parse parameters
17
+ const [param1, param2, config] = params;
18
+
19
+ // 2. Validate parameters (if needed)
20
+ if (!param1) {
21
+ return context.createErrorResponse(
22
+ id,
23
+ -32602,
24
+ "Missing required parameter: param1"
25
+ );
26
+ }
27
+
28
+ try {
29
+ // 3. Execute the RPC logic
30
+ const result = context.svm.someOperation();
31
+
32
+ // 4. Format and return the response
33
+ return context.createSuccessResponse(id, {
34
+ context: {
35
+ slot: Number(context.slot),
36
+ apiVersion: "1.18.0" // If needed
37
+ },
38
+ value: result
39
+ });
40
+ } catch (error: any) {
41
+ // 5. Handle errors appropriately
42
+ return context.createErrorResponse(
43
+ id,
44
+ -32603, // Use appropriate error code
45
+ "Operation failed",
46
+ error.message
47
+ );
48
+ }
49
+ };
50
+ ```
51
+
52
+ ## Common Patterns
53
+
54
+ ### Methods that modify state
55
+ ```typescript
56
+ // After successful operation, increment slot/blockHeight
57
+ if (success) {
58
+ // This is handled in the main server now
59
+ // Just return success response
60
+ return context.createSuccessResponse(id, signature);
61
+ }
62
+ ```
63
+
64
+ ### Methods with optional config
65
+ ```typescript
66
+ const [address, config] = params;
67
+ const encoding = config?.encoding || "base64";
68
+ const commitment = config?.commitment || "confirmed";
69
+ ```
70
+
71
+ ### Batch operations
72
+ ```typescript
73
+ const results = addresses.map(addr => {
74
+ try {
75
+ // Process each item
76
+ return processItem(addr);
77
+ } catch {
78
+ // Return null for failed items
79
+ return null;
80
+ }
81
+ });
82
+ ```
83
+
84
+ ## Error Codes Reference
85
+
86
+ - `-32700`: Parse error (JSON parsing failed)
87
+ - `-32600`: Invalid request (not a valid JSON-RPC request)
88
+ - `-32601`: Method not found
89
+ - `-32602`: Invalid params
90
+ - `-32603`: Internal error
91
+ - `-32000`: Generic server error
92
+ - `-32001`: Resource not found
93
+ - `-32002`: Resource unavailable
94
+ - `-32003`: Transaction rejected
95
+ - `-32004`: Method not supported
96
+ - `-32005`: Limit exceeded
97
+
98
+ ## Checklist
99
+
100
+ Before committing a new RPC method:
101
+
102
+ - [ ] Method follows naming convention (camelCase)
103
+ - [ ] File uses kebab-case naming
104
+ - [ ] Proper TypeScript types used
105
+ - [ ] Error handling implemented
106
+ - [ ] Success case tested
107
+ - [ ] Error cases tested
108
+ - [ ] Added to methods/index.ts
109
+ - [ ] Documentation updated in README.md
110
+ - [ ] Follows SolForge patterns
111
+
112
+ ## Examples in Codebase
113
+
114
+ - Simple query: See `get-balance.ts`
115
+ - Complex response: See `get-account-info.ts`
116
+ - Transaction handling: See `send-transaction.ts`
117
+ - Batch operation: See `get-multiple-accounts.ts`
@@ -0,0 +1,90 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodHandler } from "../../types";
3
+ import { parseAccountJson } from "./parsers";
4
+
5
+ /**
6
+ * Implements the getAccountInfo RPC method
7
+ * @see https://docs.solana.com/api/http#getaccountinfo
8
+ */
9
+ export const getAccountInfo: RpcMethodHandler = async (id, params, context) => {
10
+ const [pubkeyStr, config] = params;
11
+ const encoding = config?.encoding || "base64";
12
+
13
+ try {
14
+ const pubkey = new PublicKey(pubkeyStr);
15
+ const account = context.svm.getAccount(pubkey);
16
+
17
+ if (!account) {
18
+ return context.createSuccessResponse(id, {
19
+ context: { slot: Number(context.slot) },
20
+ value: null,
21
+ });
22
+ }
23
+
24
+ const owner = new PublicKey(account.owner).toBase58();
25
+ // Opportunistic index update
26
+ try {
27
+ await context.store?.upsertAccounts([
28
+ {
29
+ address: pubkey.toBase58(),
30
+ lamports: Number(account.lamports || 0n),
31
+ ownerProgram: owner,
32
+ executable: !!account.executable,
33
+ rentEpoch: Number(account.rentEpoch || 0),
34
+ dataLen: account.data?.length ?? 0,
35
+ dataBase64: undefined,
36
+ lastSlot: Number(context.slot),
37
+ },
38
+ ]);
39
+ } catch {}
40
+
41
+ if (encoding === "jsonParsed") {
42
+ const parsed = parseAccountJson(
43
+ pubkey,
44
+ {
45
+ owner: new PublicKey(account.owner),
46
+ data: account.data ? new Uint8Array(account.data) : new Uint8Array(),
47
+ lamports: account.lamports,
48
+ executable: account.executable,
49
+ rentEpoch: account.rentEpoch,
50
+ },
51
+ context,
52
+ );
53
+
54
+ return context.createSuccessResponse(id, {
55
+ context: { slot: Number(context.slot) },
56
+ value: {
57
+ lamports: Number(account.lamports),
58
+ owner,
59
+ executable: account.executable,
60
+ rentEpoch: Number(account.rentEpoch || 0),
61
+ data: parsed || {
62
+ program: "unknown",
63
+ parsed: null,
64
+ space: account.data?.length ?? 0,
65
+ },
66
+ },
67
+ });
68
+ }
69
+
70
+ const accountInfo = {
71
+ lamports: Number(account.lamports),
72
+ owner,
73
+ data: [Buffer.from(account.data).toString("base64"), "base64"] as const,
74
+ executable: account.executable,
75
+ rentEpoch: Number(account.rentEpoch || 0),
76
+ };
77
+
78
+ return context.createSuccessResponse(id, {
79
+ context: { slot: Number(context.slot) },
80
+ value: accountInfo,
81
+ });
82
+ } catch (error: any) {
83
+ return context.createErrorResponse(
84
+ id,
85
+ -32602,
86
+ "Invalid params",
87
+ error.message,
88
+ );
89
+ }
90
+ };
@@ -0,0 +1,27 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodHandler } from "../../types";
3
+
4
+ /**
5
+ * Implements the getBalance RPC method
6
+ * @see https://docs.solana.com/api/http#getbalance
7
+ */
8
+ export const getBalance: RpcMethodHandler = (id, params, context) => {
9
+ const [pubkeyStr] = params;
10
+
11
+ try {
12
+ const pubkey = new PublicKey(pubkeyStr);
13
+ const balance = context.svm.getBalance(pubkey);
14
+
15
+ return context.createSuccessResponse(id, {
16
+ context: { slot: Number(context.slot) },
17
+ value: Number(balance || 0n),
18
+ });
19
+ } catch (error: any) {
20
+ return context.createErrorResponse(
21
+ id,
22
+ -32602,
23
+ "Invalid params",
24
+ error.message,
25
+ );
26
+ }
27
+ };
@@ -0,0 +1,83 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodHandler } from "../../types";
3
+
4
+ /**
5
+ * Implements the getMultipleAccounts RPC method
6
+ * @see https://docs.solana.com/api/http#getmultipleaccounts
7
+ */
8
+ export const getMultipleAccounts: RpcMethodHandler = async (
9
+ id,
10
+ params,
11
+ context,
12
+ ) => {
13
+ const [pubkeys, config] = params;
14
+ const encoding = config?.encoding || "base64";
15
+
16
+ const accounts = pubkeys.map((pubkeyStr: string) => {
17
+ try {
18
+ const pubkey = new PublicKey(pubkeyStr);
19
+ const account = context.svm.getAccount(pubkey);
20
+
21
+ if (!account) {
22
+ return null;
23
+ }
24
+
25
+ const owner = new PublicKey(account.owner).toBase58();
26
+ if (encoding === "jsonParsed") {
27
+ const program =
28
+ owner === "11111111111111111111111111111111" ? "system" : "unknown";
29
+ const space = account.data?.length ?? 0;
30
+ return {
31
+ lamports: Number(account.lamports),
32
+ owner,
33
+ executable: account.executable,
34
+ rentEpoch: Number(account.rentEpoch || 0),
35
+ data: {
36
+ program,
37
+ parsed: program === "system" ? { type: "account", info: {} } : null,
38
+ space,
39
+ },
40
+ };
41
+ }
42
+
43
+ return {
44
+ lamports: Number(account.lamports),
45
+ owner,
46
+ data: [Buffer.from(account.data).toString("base64"), "base64"] as const,
47
+ executable: account.executable,
48
+ rentEpoch: Number(account.rentEpoch || 0),
49
+ };
50
+ } catch {
51
+ return null;
52
+ }
53
+ });
54
+
55
+ // Opportunistic index update
56
+ try {
57
+ const snaps: any[] = [];
58
+ for (const pubkeyStr of pubkeys) {
59
+ try {
60
+ const pubkey = new PublicKey(pubkeyStr);
61
+ const acc = context.svm.getAccount(pubkey);
62
+ if (!acc) continue;
63
+ const owner = new PublicKey(acc.owner).toBase58();
64
+ snaps.push({
65
+ address: pubkey.toBase58(),
66
+ lamports: Number(acc.lamports || 0n),
67
+ ownerProgram: owner,
68
+ executable: !!acc.executable,
69
+ rentEpoch: Number(acc.rentEpoch || 0),
70
+ dataLen: acc.data?.length ?? 0,
71
+ dataBase64: undefined,
72
+ lastSlot: Number(context.slot),
73
+ });
74
+ } catch {}
75
+ }
76
+ if (snaps.length) await context.store?.upsertAccounts(snaps);
77
+ } catch {}
78
+
79
+ return context.createSuccessResponse(id, {
80
+ context: { slot: Number(context.slot) },
81
+ value: accounts,
82
+ });
83
+ };
@@ -0,0 +1,21 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+ import { getAccountInfo } from "./get-account-info";
3
+
4
+ export const getParsedAccountInfo: RpcMethodHandler = async (
5
+ id,
6
+ params,
7
+ context,
8
+ ) => {
9
+ const [pubkey, config] = params || [];
10
+ const cfg = { ...(config || {}), encoding: "jsonParsed" };
11
+ try {
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
+ );
20
+ }
21
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Account-related RPC methods
3
+ *
4
+ * This module contains all account-related RPC methods.
5
+ * Each method is in its own file to maintain modularity.
6
+ */
7
+
8
+ export { getAccountInfo } from "./get-account-info";
9
+ export { getBalance } from "./get-balance";
10
+ export { getMultipleAccounts } from "./get-multiple-accounts";
11
+ export { getParsedAccountInfo } from "./get-parsed-account-info";
12
+ export { requestAirdrop } from "./request-airdrop";
@@ -0,0 +1,52 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodContext } from "../../../server/types";
3
+ import { parseUpgradeableLoader } from "./loader-upgradeable";
4
+ import { parseSplTokenAccountOrMint } from "./spl-token";
5
+ import { parseSystemAccount } from "./system";
6
+
7
+ export type ParsedAccountData = {
8
+ program: string;
9
+ parsed: any; // match Solana RPC jsonParsed payloads
10
+ space: number;
11
+ } | null;
12
+
13
+ export function parseAccountJson(
14
+ pubkey: PublicKey,
15
+ account: {
16
+ owner: PublicKey | string;
17
+ data: Uint8Array | Buffer | number[];
18
+ lamports: bigint | number;
19
+ executable?: boolean;
20
+ rentEpoch?: bigint | number;
21
+ },
22
+ context: RpcMethodContext,
23
+ ): ParsedAccountData {
24
+ const ownerStr =
25
+ typeof account.owner === "string"
26
+ ? account.owner
27
+ : new PublicKey(account.owner).toBase58();
28
+ const ownerPk =
29
+ typeof account.owner === "string"
30
+ ? new PublicKey(account.owner)
31
+ : (account.owner as PublicKey);
32
+ const dataBytes =
33
+ account.data instanceof Uint8Array
34
+ ? account.data
35
+ : Buffer.from(account.data as any);
36
+ const space = dataBytes.length;
37
+
38
+ // 1) System program
39
+ const sys = parseSystemAccount(ownerStr, space);
40
+ if (sys) return sys;
41
+
42
+ // 2) SPL Token (v1) & Token-2022
43
+ const token = parseSplTokenAccountOrMint(pubkey, ownerPk, dataBytes, context);
44
+ if (token) return token;
45
+
46
+ // 3) BPF Upgradeable Loader
47
+ const loader = parseUpgradeableLoader(ownerStr, dataBytes, context);
48
+ if (loader) return loader;
49
+
50
+ // 4) Unknown
51
+ return { program: "unknown", parsed: null, space };
52
+ }
@@ -0,0 +1,66 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodContext } from "../../../server/types";
3
+
4
+ const LOADER_UPGRADEABLE = "BPFLoaderUpgradeab1e11111111111111111111111";
5
+
6
+ export function parseUpgradeableLoader(
7
+ owner: string,
8
+ data: Uint8Array,
9
+ context: RpcMethodContext,
10
+ ) {
11
+ if (owner !== LOADER_UPGRADEABLE) return null;
12
+ const bytes = data;
13
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
14
+ const space = bytes.length;
15
+ let parsed: any = null;
16
+ try {
17
+ if (bytes.length >= 4) {
18
+ const tag = dv.getUint32(0, true);
19
+ if (tag === 2 && bytes.length >= 36) {
20
+ // Program: [u32 tag][32 programDataPubkey]
21
+ const pd = new PublicKey(bytes.slice(4, 36)).toBase58();
22
+ parsed = { type: "program", info: { programData: pd } };
23
+ } else if (tag === 3 && bytes.length >= 4 + 8 + 1) {
24
+ // ProgramData: [u32 tag][u64 slot][Option<Pubkey> upgradeAuthority]
25
+ const slot = Number(dv.getBigUint64(4, true));
26
+ let upgradeAuthority: string | null = null;
27
+ const opt = dv.getUint8(12);
28
+ let hdr = 13; // after u8 option
29
+ if (opt === 1 && bytes.length >= 13 + 32) {
30
+ upgradeAuthority = new PublicKey(bytes.slice(13, 45)).toBase58();
31
+ hdr = 45;
32
+ } else if (opt === 0) {
33
+ hdr = 13;
34
+ } else if (bytes.length >= 12 + 4) {
35
+ // Fallback u32 option at offset 12
36
+ const opt32 = dv.getUint32(12, true);
37
+ hdr = 16;
38
+ if (opt32 === 1 && bytes.length >= 16 + 32) {
39
+ upgradeAuthority = new PublicKey(bytes.slice(16, 48)).toBase58();
40
+ hdr = 48;
41
+ }
42
+ }
43
+ const programBytes = bytes.slice(hdr);
44
+ parsed = {
45
+ type: "programData",
46
+ info: {
47
+ slot: slot === 0 ? Number(context.slot) : slot,
48
+ upgradeAuthority,
49
+ authority: upgradeAuthority,
50
+ data: [Buffer.from(programBytes).toString("base64"), "base64"],
51
+ },
52
+ };
53
+ } else if (tag === 1) {
54
+ // Buffer: [u32 tag][Option<Pubkey> authority]
55
+ let authority: string | null = null;
56
+ if (bytes.length >= 5) {
57
+ const hasAuth = dv.getUint8(4);
58
+ if (hasAuth === 1 && bytes.length >= 5 + 32)
59
+ authority = new PublicKey(bytes.slice(5, 37)).toBase58();
60
+ }
61
+ parsed = { type: "buffer", info: { authority } };
62
+ }
63
+ }
64
+ } catch {}
65
+ return { program: "bpf-upgradeable-loader", parsed, space };
66
+ }