solforge 0.1.7 → 0.2.1

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 (151) hide show
  1. package/README.md +367 -393
  2. package/docs/API.md +379 -0
  3. package/docs/CONFIGURATION.md +407 -0
  4. package/docs/bun-single-file-executable.md +585 -0
  5. package/docs/cli-plan.md +154 -0
  6. package/docs/data-indexing-plan.md +214 -0
  7. package/docs/gui-roadmap.md +202 -0
  8. package/package.json +38 -51
  9. package/server/index.ts +5 -0
  10. package/server/lib/base58.ts +33 -0
  11. package/server/lib/faucet.ts +110 -0
  12. package/server/lib/spl-token.ts +57 -0
  13. package/server/methods/TEMPLATE.md +117 -0
  14. package/server/methods/account/get-account-info.ts +90 -0
  15. package/server/methods/account/get-balance.ts +27 -0
  16. package/server/methods/account/get-multiple-accounts.ts +83 -0
  17. package/server/methods/account/get-parsed-account-info.ts +21 -0
  18. package/server/methods/account/index.ts +12 -0
  19. package/server/methods/account/parsers/index.ts +52 -0
  20. package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
  21. package/server/methods/account/parsers/spl-token.ts +237 -0
  22. package/server/methods/account/parsers/system.ts +4 -0
  23. package/server/methods/account/request-airdrop.ts +219 -0
  24. package/server/methods/admin/adopt-mint-authority.ts +94 -0
  25. package/server/methods/admin/clone-program-accounts.ts +55 -0
  26. package/server/methods/admin/clone-program.ts +152 -0
  27. package/server/methods/admin/clone-token-accounts.ts +117 -0
  28. package/server/methods/admin/clone-token-mint.ts +82 -0
  29. package/server/methods/admin/create-mint.ts +114 -0
  30. package/server/methods/admin/create-token-account.ts +137 -0
  31. package/server/methods/admin/helpers.ts +70 -0
  32. package/server/methods/admin/index.ts +10 -0
  33. package/server/methods/admin/list-mints.ts +21 -0
  34. package/server/methods/admin/load-program.ts +52 -0
  35. package/server/methods/admin/mint-to.ts +278 -0
  36. package/server/methods/block/get-block-height.ts +5 -0
  37. package/server/methods/block/get-block.ts +35 -0
  38. package/server/methods/block/get-blocks-with-limit.ts +23 -0
  39. package/server/methods/block/get-latest-blockhash.ts +12 -0
  40. package/server/methods/block/get-slot.ts +5 -0
  41. package/server/methods/block/index.ts +6 -0
  42. package/server/methods/block/is-blockhash-valid.ts +23 -0
  43. package/server/methods/epoch/get-cluster-nodes.ts +17 -0
  44. package/server/methods/epoch/get-epoch-info.ts +16 -0
  45. package/server/methods/epoch/get-epoch-schedule.ts +15 -0
  46. package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
  47. package/server/methods/epoch/get-leader-schedule.ts +8 -0
  48. package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
  49. package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
  50. package/server/methods/epoch/get-slot-leader.ts +6 -0
  51. package/server/methods/epoch/get-slot-leaders.ts +9 -0
  52. package/server/methods/epoch/get-stake-activation.ts +9 -0
  53. package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
  54. package/server/methods/epoch/get-vote-accounts.ts +19 -0
  55. package/server/methods/epoch/index.ts +13 -0
  56. package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
  57. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
  58. package/server/methods/fee/get-fee-for-message.ts +8 -0
  59. package/server/methods/fee/get-fee-rate-governor.ts +16 -0
  60. package/server/methods/fee/get-fees.ts +14 -0
  61. package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
  62. package/server/methods/fee/index.ts +5 -0
  63. package/server/methods/get-address-lookup-table.ts +31 -0
  64. package/server/methods/index.ts +265 -0
  65. package/server/methods/performance/get-recent-performance-samples.ts +25 -0
  66. package/server/methods/performance/get-transaction-count.ts +5 -0
  67. package/server/methods/performance/index.ts +2 -0
  68. package/server/methods/program/get-block-commitment.ts +9 -0
  69. package/server/methods/program/get-block-production.ts +14 -0
  70. package/server/methods/program/get-block-time.ts +21 -0
  71. package/server/methods/program/get-blocks.ts +11 -0
  72. package/server/methods/program/get-first-available-block.ts +9 -0
  73. package/server/methods/program/get-genesis-hash.ts +6 -0
  74. package/server/methods/program/get-identity.ts +6 -0
  75. package/server/methods/program/get-inflation-governor.ts +15 -0
  76. package/server/methods/program/get-inflation-rate.ts +10 -0
  77. package/server/methods/program/get-inflation-reward.ts +12 -0
  78. package/server/methods/program/get-largest-accounts.ts +8 -0
  79. package/server/methods/program/get-parsed-program-accounts.ts +12 -0
  80. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
  81. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
  82. package/server/methods/program/get-program-accounts.ts +221 -0
  83. package/server/methods/program/get-supply.ts +13 -0
  84. package/server/methods/program/get-token-account-balance.ts +64 -0
  85. package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
  86. package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
  87. package/server/methods/program/get-token-largest-accounts.ts +80 -0
  88. package/server/methods/program/get-token-supply.ts +38 -0
  89. package/server/methods/program/index.ts +21 -0
  90. package/server/methods/solforge/index.ts +155 -0
  91. package/server/methods/system/get-health.ts +5 -0
  92. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
  93. package/server/methods/system/get-version.ts +9 -0
  94. package/server/methods/system/index.ts +3 -0
  95. package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
  96. package/server/methods/transaction/get-parsed-transaction.ts +21 -0
  97. package/server/methods/transaction/get-signature-statuses.ts +72 -0
  98. package/server/methods/transaction/get-signatures-for-address.ts +45 -0
  99. package/server/methods/transaction/get-transaction.ts +428 -0
  100. package/server/methods/transaction/index.ts +7 -0
  101. package/server/methods/transaction/send-transaction.ts +232 -0
  102. package/server/methods/transaction/simulate-transaction.ts +56 -0
  103. package/server/rpc-server.ts +474 -0
  104. package/server/types.ts +74 -0
  105. package/server/ws-server.ts +171 -0
  106. package/src/cli/bootstrap.ts +67 -0
  107. package/src/cli/commands/airdrop.ts +37 -0
  108. package/src/cli/commands/config.ts +39 -0
  109. package/src/cli/commands/mint.ts +187 -0
  110. package/src/cli/commands/program-clone.ts +124 -0
  111. package/src/cli/commands/program-load.ts +64 -0
  112. package/src/cli/commands/rpc-start.ts +46 -0
  113. package/src/cli/commands/token-adopt-authority.ts +37 -0
  114. package/src/cli/commands/token-clone.ts +113 -0
  115. package/src/cli/commands/token-create.ts +81 -0
  116. package/src/cli/main.ts +130 -0
  117. package/src/cli/run-solforge.ts +98 -0
  118. package/src/cli/setup-utils.ts +54 -0
  119. package/src/cli/setup-wizard.ts +256 -0
  120. package/src/cli/utils/args.ts +15 -0
  121. package/src/config/index.ts +130 -0
  122. package/src/db/index.ts +83 -0
  123. package/src/db/schema/accounts.ts +23 -0
  124. package/src/db/schema/address-signatures.ts +31 -0
  125. package/src/db/schema/index.ts +5 -0
  126. package/src/db/schema/meta-kv.ts +9 -0
  127. package/src/db/schema/transactions.ts +29 -0
  128. package/src/db/schema/tx-accounts.ts +33 -0
  129. package/src/db/tx-store.ts +229 -0
  130. package/src/gui/public/app.css +1 -0
  131. package/src/gui/public/build/main.css +1 -0
  132. package/src/gui/public/build/main.js +303 -0
  133. package/src/gui/public/build/main.js.txt +231 -0
  134. package/src/gui/public/index.html +19 -0
  135. package/src/gui/server.ts +297 -0
  136. package/src/gui/src/api.ts +127 -0
  137. package/src/gui/src/app.tsx +390 -0
  138. package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
  139. package/src/gui/src/components/clone-program-modal.tsx +183 -0
  140. package/src/gui/src/components/clone-token-modal.tsx +211 -0
  141. package/src/gui/src/components/modal.tsx +127 -0
  142. package/src/gui/src/components/programs-panel.tsx +112 -0
  143. package/src/gui/src/components/status-panel.tsx +122 -0
  144. package/src/gui/src/components/tokens-panel.tsx +116 -0
  145. package/src/gui/src/hooks/use-interval.ts +17 -0
  146. package/src/gui/src/index.css +529 -0
  147. package/src/gui/src/main.tsx +17 -0
  148. package/src/migrations-bundled.ts +17 -0
  149. package/src/rpc/start.ts +44 -0
  150. package/scripts/postinstall.cjs +0 -103
  151. package/tsconfig.json +0 -28
@@ -0,0 +1,390 @@
1
+ import {
2
+ ACCOUNT_SIZE,
3
+ getAssociatedTokenAddressSync,
4
+ TOKEN_2022_PROGRAM_ID,
5
+ TOKEN_PROGRAM_ID,
6
+ unpackAccount,
7
+ unpackMint,
8
+ } from "@solana/spl-token";
9
+ import { type AccountInfo, PublicKey } from "@solana/web3.js";
10
+ import type { RpcMethodHandler } from "../../types";
11
+
12
+ export const getTokenAccountsByOwner: RpcMethodHandler = async (
13
+ id,
14
+ params,
15
+ context,
16
+ ) => {
17
+ try {
18
+ const [ownerStr, filter, config] = params || [];
19
+ if (!ownerStr) throw new Error("owner public key required");
20
+ const owner = new PublicKey(ownerStr);
21
+ const wantMint: string | null = filter?.mint ? String(filter.mint) : null;
22
+ const requestedProgramId: string | null = filter?.programId
23
+ ? String(filter.programId)
24
+ : null;
25
+ const encoding: string = config?.encoding || "jsonParsed";
26
+ const classicId = TOKEN_PROGRAM_ID.toBase58();
27
+ const token2022Id = TOKEN_2022_PROGRAM_ID.toBase58();
28
+ const programIds = requestedProgramId
29
+ ? [requestedProgramId]
30
+ : [classicId, token2022Id];
31
+
32
+ // Query DB for accounts owned by both SPL Token programs (classic + 2022)
33
+ const rows: Array<{ address: string; lastSlot: number } & any> = [];
34
+ for (const programId of programIds) {
35
+ try {
36
+ const found =
37
+ (await context.store?.getAccountsByOwner(programId, 50_000)) || [];
38
+ rows.push(...found);
39
+ } catch (dbErr) {
40
+ try {
41
+ console.warn("[rpc] getTokenAccountsByOwner: db read failed", dbErr);
42
+ } catch {}
43
+ }
44
+ }
45
+
46
+ const out: any[] = [];
47
+ const seen = new Set<string>();
48
+ for (const r of rows) {
49
+ if (seen.has(r.address)) continue;
50
+ try {
51
+ const pk = new PublicKey(r.address);
52
+ const acc = context.svm.getAccount(pk);
53
+ if (!acc) continue;
54
+ if ((acc.data?.length ?? 0) < 165) continue;
55
+ let ownerPk: PublicKey;
56
+ try {
57
+ // acc.owner may already be a PublicKey in LiteSVM
58
+ const anyOwner: any = (acc as any).owner;
59
+ ownerPk =
60
+ typeof anyOwner?.toBase58 === "function"
61
+ ? (anyOwner as PublicKey)
62
+ : new PublicKey(anyOwner);
63
+ } catch {
64
+ ownerPk = TOKEN_PROGRAM_ID; // fallback avoids throw; unpackAccount will fail if wrong and be skipped
65
+ }
66
+ const programPk = ownerPk.equals(TOKEN_2022_PROGRAM_ID)
67
+ ? TOKEN_2022_PROGRAM_ID
68
+ : TOKEN_PROGRAM_ID;
69
+ const dec = unpackAccount(
70
+ pk,
71
+ { data: Buffer.from(acc.data), owner: ownerPk },
72
+ programPk,
73
+ );
74
+ const decMint = dec.mint.toBase58();
75
+ const decOwner = dec.owner.toBase58();
76
+ if (decOwner !== owner.toBase58()) continue;
77
+ if (wantMint && decMint !== wantMint) continue;
78
+ if (encoding === "jsonParsed") {
79
+ let decimals = 0;
80
+ try {
81
+ const mintAcc = context.svm.getAccount(dec.mint);
82
+ const mintOwnerPk = mintAcc
83
+ ? typeof (mintAcc as any).owner?.toBase58 === "function"
84
+ ? (mintAcc as any).owner
85
+ : new PublicKey(mintAcc.owner)
86
+ : programPk;
87
+ const info = mintAcc
88
+ ? unpackMint(
89
+ dec.mint,
90
+ {
91
+ data: Buffer.from(mintAcc.data),
92
+ owner: mintOwnerPk,
93
+ } as AccountInfo<Buffer>,
94
+ mintOwnerPk,
95
+ )
96
+ : null;
97
+ decimals = info?.decimals ?? 0;
98
+ } catch {}
99
+ const amount = BigInt(dec.amount?.toString?.() ?? dec.amount ?? 0);
100
+ const ui = decimals >= 0 ? Number(amount) / 10 ** decimals : null;
101
+ const state = dec.isFrozen
102
+ ? "frozen"
103
+ : dec.isInitialized
104
+ ? "initialized"
105
+ : "uninitialized";
106
+ const amountUi = {
107
+ amount: amount.toString(),
108
+ decimals,
109
+ uiAmount: ui,
110
+ uiAmountString: (ui ?? 0).toString(),
111
+ };
112
+ // delegatedAmount as UiTokenAmount per RPC schema
113
+ const delegated = BigInt(
114
+ dec.delegatedAmount?.toString?.() ?? dec.delegatedAmount ?? 0n,
115
+ );
116
+ const delegatedUiAmount =
117
+ decimals >= 0 ? Number(delegated) / 10 ** decimals : null;
118
+ const delegatedAmount = {
119
+ amount: delegated.toString(),
120
+ decimals,
121
+ uiAmount: delegatedUiAmount,
122
+ uiAmountString: (delegatedUiAmount ?? 0).toString(),
123
+ };
124
+ // rentExemptReserve only for native (wrapped SOL) accounts; value in lamports (9 decimals)
125
+ let rentExemptReserve = null as any;
126
+ if (dec.isNative) {
127
+ const lamports = BigInt(
128
+ dec.rentExemptReserve?.toString?.() ??
129
+ dec.rentExemptReserve ??
130
+ 0n,
131
+ );
132
+ const lamportsUi = Number(lamports) / 1_000_000_000;
133
+ rentExemptReserve = {
134
+ amount: lamports.toString(),
135
+ decimals: 9,
136
+ uiAmount: lamportsUi,
137
+ uiAmountString: lamportsUi.toString(),
138
+ };
139
+ }
140
+ const programLabel = programPk.equals(TOKEN_2022_PROGRAM_ID)
141
+ ? "spl-token-2022"
142
+ : "spl-token";
143
+ out.push({
144
+ pubkey: r.address,
145
+ account: {
146
+ lamports: Number(acc.lamports || 0n),
147
+ owner: ownerPk.toBase58(),
148
+ executable: !!acc.executable,
149
+ rentEpoch: Number(acc.rentEpoch || 0),
150
+ data: {
151
+ program: programLabel,
152
+ parsed: {
153
+ type: "account",
154
+ info: {
155
+ mint: decMint,
156
+ owner: decOwner,
157
+ tokenAmount: amountUi,
158
+ state,
159
+ isNative: !!dec.isNative,
160
+ delegatedAmount: delegatedAmount,
161
+ delegate: dec.delegate ? dec.delegate.toBase58() : null,
162
+ rentExemptReserve,
163
+ closeAuthority: dec.closeAuthority
164
+ ? dec.closeAuthority.toBase58()
165
+ : null,
166
+ },
167
+ },
168
+ space: acc.data?.length ?? 0,
169
+ },
170
+ },
171
+ });
172
+ seen.add(r.address);
173
+ } else {
174
+ out.push({
175
+ pubkey: r.address,
176
+ account: {
177
+ lamports: Number(acc.lamports || 0n),
178
+ owner: ownerPk.toBase58(),
179
+ executable: !!acc.executable,
180
+ rentEpoch: Number(acc.rentEpoch || 0),
181
+ data: [
182
+ Buffer.from(acc.data).toString("base64"),
183
+ "base64",
184
+ ] as const,
185
+ },
186
+ });
187
+ seen.add(r.address);
188
+ }
189
+ } catch {}
190
+ }
191
+ // Fallback: probe known mints' ATAs for this owner
192
+ try {
193
+ const mints = context.listMints ? context.listMints() : [];
194
+ for (const m of mints) {
195
+ try {
196
+ const mintPk = new PublicKey(m);
197
+ const mintAcc = context.svm.getAccount(mintPk);
198
+ const mintOwnerPk = mintAcc
199
+ ? typeof (mintAcc as any).owner?.toBase58 === "function"
200
+ ? (mintAcc as any).owner
201
+ : new PublicKey(mintAcc.owner)
202
+ : TOKEN_PROGRAM_ID;
203
+ // Determine which token program this mint belongs to
204
+ const programForMint = mintOwnerPk.equals(TOKEN_2022_PROGRAM_ID)
205
+ ? TOKEN_2022_PROGRAM_ID
206
+ : TOKEN_PROGRAM_ID;
207
+ // If a specific programId filter was requested, honor it strictly
208
+ if (
209
+ requestedProgramId &&
210
+ programForMint.toBase58() !== requestedProgramId
211
+ ) {
212
+ continue;
213
+ }
214
+
215
+ const ata = getAssociatedTokenAddressSync(
216
+ mintPk,
217
+ owner,
218
+ true,
219
+ programForMint,
220
+ );
221
+ const acc = context.svm.getAccount(ata);
222
+ if (!acc || (acc.data?.length ?? 0) < ACCOUNT_SIZE) continue;
223
+ if (seen.has(ata.toBase58())) continue;
224
+ let ownerPk: PublicKey;
225
+ const anyOwner: any = (acc as any).owner;
226
+ ownerPk =
227
+ typeof anyOwner?.toBase58 === "function"
228
+ ? (anyOwner as PublicKey)
229
+ : new PublicKey(anyOwner);
230
+ const programPk = ownerPk.equals(TOKEN_2022_PROGRAM_ID)
231
+ ? TOKEN_2022_PROGRAM_ID
232
+ : TOKEN_PROGRAM_ID;
233
+ // If a programId filter was provided, skip non-matching accounts
234
+ if (
235
+ requestedProgramId &&
236
+ programPk.toBase58() !== requestedProgramId
237
+ ) {
238
+ continue;
239
+ }
240
+ const dec = unpackAccount(
241
+ ata,
242
+ { data: Buffer.from(acc.data), owner: ownerPk },
243
+ programPk,
244
+ );
245
+ const decMint = dec.mint.toBase58();
246
+ const decOwner = dec.owner.toBase58();
247
+ if (decOwner !== owner.toBase58()) continue;
248
+ let decimals = 0;
249
+ try {
250
+ const mintAcc = context.svm.getAccount(dec.mint);
251
+ const mintOwnerPk = mintAcc
252
+ ? typeof (mintAcc as any).owner?.toBase58 === "function"
253
+ ? (mintAcc as any).owner
254
+ : new PublicKey(mintAcc.owner)
255
+ : programPk;
256
+ const info = mintAcc
257
+ ? unpackMint(
258
+ dec.mint,
259
+ {
260
+ data: Buffer.from(mintAcc.data),
261
+ owner: mintOwnerPk,
262
+ } as AccountInfo<Buffer>,
263
+ mintOwnerPk,
264
+ )
265
+ : null;
266
+ decimals = info?.decimals ?? 0;
267
+ } catch {}
268
+ const amount = BigInt(dec.amount?.toString?.() ?? dec.amount ?? 0);
269
+ const ui = decimals >= 0 ? Number(amount) / 10 ** decimals : null;
270
+ const state = dec.isFrozen
271
+ ? "frozen"
272
+ : dec.isInitialized
273
+ ? "initialized"
274
+ : "uninitialized";
275
+ const amountUi = {
276
+ amount: amount.toString(),
277
+ decimals,
278
+ uiAmount: ui,
279
+ uiAmountString: (ui ?? 0).toString(),
280
+ };
281
+ const programLabel = programPk.equals(TOKEN_2022_PROGRAM_ID)
282
+ ? "spl-token-2022"
283
+ : "spl-token";
284
+ out.push({
285
+ pubkey: ata.toBase58(),
286
+ account: {
287
+ lamports: Number(acc.lamports || 0n),
288
+ owner: ownerPk.toBase58(),
289
+ executable: !!acc.executable,
290
+ rentEpoch: Number(acc.rentEpoch || 0),
291
+ data: {
292
+ program: programLabel,
293
+ parsed: {
294
+ type: "account",
295
+ info: {
296
+ mint: decMint,
297
+ owner: decOwner,
298
+ tokenAmount: amountUi,
299
+ state,
300
+ isNative: !!dec.isNative,
301
+ },
302
+ },
303
+ space: acc.data?.length ?? 0,
304
+ },
305
+ },
306
+ });
307
+ seen.add(ata.toBase58());
308
+ } catch {}
309
+ }
310
+ } catch {}
311
+
312
+ // Deduplicate: prefer the canonical ATA for each (owner,mint) under the mint's token program
313
+ if (encoding === "jsonParsed") {
314
+ try {
315
+ const pick = new Map<string, number>();
316
+ for (let i = 0; i < out.length; i++) {
317
+ const entry = out[i];
318
+ const pubkey: string = entry.pubkey;
319
+ const info = entry.account?.data?.parsed?.info;
320
+ const mintStr: string | undefined = info?.mint;
321
+ const ownerStr: string | undefined = info?.owner;
322
+ if (!mintStr || !ownerStr) continue;
323
+ let canonical = pubkey;
324
+ try {
325
+ const mintPk = new PublicKey(mintStr);
326
+ const ownerPk = new PublicKey(ownerStr);
327
+ const mintAcc = context.svm.getAccount(mintPk);
328
+ const mintOwnerPk = mintAcc
329
+ ? typeof (mintAcc as any).owner?.toBase58 === "function"
330
+ ? (mintAcc as any).owner
331
+ : new PublicKey(mintAcc.owner)
332
+ : TOKEN_PROGRAM_ID;
333
+ const programForMint = mintOwnerPk.equals(TOKEN_2022_PROGRAM_ID)
334
+ ? TOKEN_2022_PROGRAM_ID
335
+ : TOKEN_PROGRAM_ID;
336
+ const ata = getAssociatedTokenAddressSync(
337
+ mintPk,
338
+ ownerPk,
339
+ true,
340
+ programForMint,
341
+ );
342
+ canonical = ata.toBase58();
343
+ } catch {}
344
+ const key = `${ownerStr}:${mintStr}`;
345
+ const prev = pick.get(key);
346
+ if (prev === undefined) {
347
+ // First time; keep if canonical, otherwise tentatively keep
348
+ pick.set(key, i);
349
+ // Prefer canonical if current isn't canonical but later we may find it
350
+ if (pubkey !== canonical) pick.set(`${key}:prefer`, -1);
351
+ } else {
352
+ // If previous wasn't canonical and this one is, replace
353
+ const prefer = pick.get(`${key}:prefer`) === -1;
354
+ if (prefer && pubkey === canonical) {
355
+ pick.set(key, i);
356
+ pick.delete(`${key}:prefer`);
357
+ }
358
+ }
359
+ }
360
+
361
+ // Build filtered list keeping only chosen indices
362
+ const keep = new Set<number>(Array.from(pick.values()).filter((v) => typeof v === "number" && v >= 0));
363
+ const filtered: any[] = [];
364
+ for (let i = 0; i < out.length; i++) {
365
+ const e = out[i];
366
+ const info = e.account?.data?.parsed?.info;
367
+ const key = info?.owner && info?.mint ? `${info.owner}:${info.mint}` : null;
368
+ if (key && keep.has(i)) filtered.push(e);
369
+ else if (!key) filtered.push(e); // non-parsed or unexpected shape
370
+ }
371
+ out.length = 0;
372
+ out.push(...filtered);
373
+ } catch {}
374
+ }
375
+ return context.createSuccessResponse(id, {
376
+ context: { slot: Number(context.slot) },
377
+ value: out,
378
+ });
379
+ } catch (e: any) {
380
+ try {
381
+ console.error("[rpc] getTokenAccountsByOwner error", e);
382
+ } catch {}
383
+ return context.createErrorResponse(
384
+ id,
385
+ -32603,
386
+ "Internal error",
387
+ e?.message || String(e),
388
+ );
389
+ }
390
+ };
@@ -0,0 +1,80 @@
1
+ import {
2
+ TOKEN_2022_PROGRAM_ID,
3
+ TOKEN_PROGRAM_ID,
4
+ unpackAccount,
5
+ unpackMint,
6
+ } from "@solana/spl-token";
7
+ import { PublicKey } from "@solana/web3.js";
8
+ import type { RpcMethodHandler } from "../../types";
9
+
10
+ export const getTokenLargestAccounts: RpcMethodHandler = async (
11
+ id,
12
+ params,
13
+ context,
14
+ ) => {
15
+ const [mintStr] = params || [];
16
+ try {
17
+ const mint = new PublicKey(mintStr);
18
+ const mintAcc = context.svm.getAccount(mint);
19
+ const programPk =
20
+ mintAcc && new PublicKey(mintAcc.owner).equals(TOKEN_2022_PROGRAM_ID)
21
+ ? TOKEN_2022_PROGRAM_ID
22
+ : TOKEN_PROGRAM_ID;
23
+ const mintInfo = mintAcc
24
+ ? unpackMint(
25
+ mint,
26
+ {
27
+ data: Buffer.from(mintAcc.data),
28
+ owner: new PublicKey(mintAcc.owner),
29
+ },
30
+ programPk,
31
+ )
32
+ : null;
33
+ const decimals = mintInfo?.decimals ?? 0;
34
+ // Scan DB list of SPL token accounts and pick those with this mint
35
+ const rows =
36
+ (await context.store?.getAccountsByOwner(
37
+ TOKEN_PROGRAM_ID.toBase58(),
38
+ 50_000,
39
+ )) || [];
40
+ const list: Array<{ address: string; amount: bigint }> = [];
41
+ for (const r of rows) {
42
+ try {
43
+ const acc = context.svm.getAccount(new PublicKey(r.address));
44
+ if (!acc) continue;
45
+ if ((acc.data?.length ?? 0) < 165) continue;
46
+ const accOwnerPk = new PublicKey(acc.owner);
47
+ const accProgramPk = accOwnerPk.equals(TOKEN_2022_PROGRAM_ID)
48
+ ? TOKEN_2022_PROGRAM_ID
49
+ : TOKEN_PROGRAM_ID;
50
+ const dec = unpackAccount(
51
+ new PublicKey(r.address),
52
+ { data: Buffer.from(acc.data), owner: accOwnerPk },
53
+ accProgramPk,
54
+ );
55
+ if (!dec.mint.equals(mint)) continue;
56
+ const amount = BigInt(dec.amount?.toString?.() ?? dec.amount ?? 0);
57
+ list.push({ address: r.address, amount });
58
+ } catch {}
59
+ }
60
+ list.sort((a, b) =>
61
+ a.amount < b.amount ? 1 : a.amount > b.amount ? -1 : 0,
62
+ );
63
+ const top = list.slice(0, 20).map((e) => {
64
+ const ui = Number(e.amount) / 10 ** decimals;
65
+ return {
66
+ address: e.address,
67
+ amount: e.amount.toString(),
68
+ decimals,
69
+ uiAmount: ui,
70
+ uiAmountString: ui.toString(),
71
+ };
72
+ });
73
+ return context.createSuccessResponse(id, {
74
+ context: { slot: Number(context.slot) },
75
+ value: top,
76
+ });
77
+ } catch (e: any) {
78
+ return context.createErrorResponse(id, -32602, "Invalid params", e.message);
79
+ }
80
+ };
@@ -0,0 +1,38 @@
1
+ import {
2
+ TOKEN_2022_PROGRAM_ID,
3
+ TOKEN_PROGRAM_ID,
4
+ unpackMint,
5
+ } from "@solana/spl-token";
6
+ import { PublicKey } from "@solana/web3.js";
7
+ import type { RpcMethodHandler } from "../../types";
8
+
9
+ export const getTokenSupply: RpcMethodHandler = (id, params, context) => {
10
+ const [mintStr] = params || [];
11
+ try {
12
+ const mint = new PublicKey(mintStr);
13
+ const acc = context.svm.getAccount(mint);
14
+ if (!acc) return context.createErrorResponse(id, -32602, "Mint not found");
15
+ const ownerPk = new PublicKey(acc.owner);
16
+ const programPk = ownerPk.equals(TOKEN_2022_PROGRAM_ID)
17
+ ? TOKEN_2022_PROGRAM_ID
18
+ : TOKEN_PROGRAM_ID;
19
+ const info = unpackMint(
20
+ mint,
21
+ { data: Buffer.from(acc.data), owner: ownerPk },
22
+ programPk,
23
+ );
24
+ const supply = BigInt(info.supply?.toString?.() ?? info.supply ?? 0);
25
+ const ui = Number(supply) / 10 ** info.decimals;
26
+ return context.createSuccessResponse(id, {
27
+ context: { slot: Number(context.slot) },
28
+ value: {
29
+ amount: supply.toString(),
30
+ decimals: info.decimals,
31
+ uiAmount: ui,
32
+ uiAmountString: ui.toString(),
33
+ },
34
+ });
35
+ } catch (e: any) {
36
+ return context.createErrorResponse(id, -32602, "Invalid params", e.message);
37
+ }
38
+ };
@@ -0,0 +1,21 @@
1
+ export { getBlockCommitment } from "./get-block-commitment";
2
+ export { getBlockProduction } from "./get-block-production";
3
+ export { getBlockTime } from "./get-block-time";
4
+ export { getBlocks } from "./get-blocks";
5
+ export { getFirstAvailableBlock } from "./get-first-available-block";
6
+ export { getGenesisHash } from "./get-genesis-hash";
7
+ export { getIdentity } from "./get-identity";
8
+ export { getInflationGovernor } from "./get-inflation-governor";
9
+ export { getInflationRate } from "./get-inflation-rate";
10
+ export { getInflationReward } from "./get-inflation-reward";
11
+ export { getLargestAccounts } from "./get-largest-accounts";
12
+ export { getParsedProgramAccounts } from "./get-parsed-program-accounts";
13
+ export { getParsedTokenAccountsByDelegate } from "./get-parsed-token-accounts-by-delegate";
14
+ export { getParsedTokenAccountsByOwner } from "./get-parsed-token-accounts-by-owner";
15
+ export { getProgramAccounts } from "./get-program-accounts";
16
+ export { getSupply } from "./get-supply";
17
+ export { getTokenAccountBalance } from "./get-token-account-balance";
18
+ export { getTokenAccountsByDelegate } from "./get-token-accounts-by-delegate";
19
+ export { getTokenAccountsByOwner } from "./get-token-accounts-by-owner";
20
+ export { getTokenLargestAccounts } from "./get-token-largest-accounts";
21
+ export { getTokenSupply } from "./get-token-supply";
@@ -0,0 +1,155 @@
1
+ import { MINT_SIZE, MintLayout } from "@solana/spl-token";
2
+ import { PublicKey } from "@solana/web3.js";
3
+ import { formatUiAmount } from "../../lib/spl-token";
4
+ import type { RpcMethodHandler } from "../../types";
5
+
6
+ const LAMPORTS_PER_SOL = 1_000_000_000n;
7
+
8
+ function formatLamports(lamports: bigint) {
9
+ const sol = Number(lamports) / Number(LAMPORTS_PER_SOL);
10
+ return {
11
+ lamports: lamports.toString(),
12
+ sol,
13
+ };
14
+ }
15
+
16
+ export const solforgeGetStatus: RpcMethodHandler = async (
17
+ id,
18
+ _params,
19
+ context,
20
+ ) => {
21
+ try {
22
+ const latestBlockhash = (() => {
23
+ try {
24
+ return context.svm.latestBlockhash();
25
+ } catch {
26
+ return "";
27
+ }
28
+ })();
29
+ const faucet = context.getFaucet();
30
+ let faucetLamports = 0n;
31
+ try {
32
+ faucetLamports =
33
+ context.svm.getBalance(faucet.publicKey as PublicKey) ?? 0n;
34
+ } catch {}
35
+
36
+ return context.createSuccessResponse(id, {
37
+ slot: Number(context.slot),
38
+ slotBigint: context.slot.toString(),
39
+ blockHeight: Number(context.blockHeight),
40
+ blockHeightBigint: context.blockHeight.toString(),
41
+ txCount: Number(context.getTxCount()),
42
+ txCountBigint: context.getTxCount().toString(),
43
+ latestBlockhash,
44
+ faucet: {
45
+ address: faucet.publicKey.toBase58(),
46
+ ...formatLamports(faucetLamports),
47
+ },
48
+ });
49
+ } catch (error: any) {
50
+ return context.createErrorResponse(
51
+ id,
52
+ -32603,
53
+ "Status unavailable",
54
+ error?.message || String(error),
55
+ );
56
+ }
57
+ };
58
+
59
+ export const solforgeListPrograms: RpcMethodHandler = async (
60
+ id,
61
+ _params,
62
+ context,
63
+ ) => {
64
+ try {
65
+ const programs = context.listPrograms ? context.listPrograms() : [];
66
+ const detailed = programs
67
+ .map((programId) => {
68
+ try {
69
+ const info = context.svm.getAccount(new PublicKey(programId));
70
+ if (!info) return null;
71
+ const owner = (() => {
72
+ try {
73
+ return (info.owner as PublicKey).toBase58();
74
+ } catch {
75
+ return String(info.owner);
76
+ }
77
+ })();
78
+ const lamports = BigInt(info.lamports ?? 0);
79
+ return {
80
+ programId,
81
+ owner,
82
+ executable: !!info.executable,
83
+ dataLen: info.data?.length ?? 0,
84
+ lamports: lamports.toString(),
85
+ };
86
+ } catch {
87
+ return null;
88
+ }
89
+ })
90
+ .filter(Boolean);
91
+
92
+ return context.createSuccessResponse(id, detailed);
93
+ } catch (error: any) {
94
+ return context.createErrorResponse(
95
+ id,
96
+ -32603,
97
+ "List programs failed",
98
+ error?.message || String(error),
99
+ );
100
+ }
101
+ };
102
+
103
+ export const solforgeListTokensDetailed: RpcMethodHandler = async (
104
+ id,
105
+ _params,
106
+ context,
107
+ ) => {
108
+ try {
109
+ const mints = context.listMints ? context.listMints() : [];
110
+ const detailed = mints
111
+ .map((mint) => {
112
+ try {
113
+ const info = context.svm.getAccount(new PublicKey(mint));
114
+ if (!info) return null;
115
+ const data = Buffer.from(info.data?.slice(0, MINT_SIZE) ?? []);
116
+ if (data.length < MINT_SIZE) return null;
117
+ const decoded = MintLayout.decode(data);
118
+ const supply = BigInt(decoded.supply.toString());
119
+ const decimals = Number(decoded.decimals ?? 0);
120
+ const ui = formatUiAmount(supply, decimals);
121
+ const hasAuthority = decoded.mintAuthorityOption === 1;
122
+ const authority = hasAuthority
123
+ ? (() => {
124
+ try {
125
+ return new PublicKey(decoded.mintAuthority).toBase58();
126
+ } catch {
127
+ return null;
128
+ }
129
+ })()
130
+ : null;
131
+ return {
132
+ mint,
133
+ supply: supply.toString(),
134
+ decimals,
135
+ uiAmount: ui.uiAmount,
136
+ uiAmountString: ui.uiAmountString,
137
+ mintAuthority: authority,
138
+ isInitialized: decoded.isInitialized ?? false,
139
+ };
140
+ } catch {
141
+ return null;
142
+ }
143
+ })
144
+ .filter(Boolean);
145
+
146
+ return context.createSuccessResponse(id, detailed);
147
+ } catch (error: any) {
148
+ return context.createErrorResponse(
149
+ id,
150
+ -32603,
151
+ "List tokens failed",
152
+ error?.message || String(error),
153
+ );
154
+ }
155
+ };
@@ -0,0 +1,5 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getHealth: RpcMethodHandler = (id, _params, _context) => {
4
+ return { jsonrpc: "2.0", id, result: "ok" };
5
+ };
@@ -0,0 +1,13 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getMinimumBalanceForRentExemption: RpcMethodHandler = (
4
+ id,
5
+ params,
6
+ context,
7
+ ) => {
8
+ const [dataLength] = params || [];
9
+ const minBalance = context.svm.minimumBalanceForRentExemption(
10
+ BigInt(dataLength),
11
+ );
12
+ return context.createSuccessResponse(id, Number(minBalance));
13
+ };