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,519 @@
1
+ import { LiteSVM } from "litesvm";
2
+ import {
3
+ PublicKey,
4
+ VersionedTransaction
5
+ } from "@solana/web3.js";
6
+
7
+ interface JsonRpcRequest {
8
+ jsonrpc: "2.0";
9
+ id: string | number;
10
+ method: string;
11
+ params?: any;
12
+ }
13
+
14
+ interface JsonRpcResponse {
15
+ jsonrpc: "2.0";
16
+ id: string | number;
17
+ result?: any;
18
+ error?: {
19
+ code: number;
20
+ message: string;
21
+ data?: any;
22
+ };
23
+ }
24
+
25
+ class LiteSVMRpcServer {
26
+ private svm: LiteSVM;
27
+ private slot: bigint = 1n;
28
+ private blockHeight: bigint = 1n;
29
+
30
+ constructor() {
31
+ this.svm = new LiteSVM()
32
+ .withSysvars()
33
+ .withBuiltins()
34
+ .withDefaultPrograms()
35
+ .withLamports(1000000000000n)
36
+ .withBlockhashCheck(false)
37
+ .withTransactionHistory(0n)
38
+ .withSigverify(false);
39
+ }
40
+
41
+ private encodeBase58(bytes: Uint8Array): string {
42
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
43
+ const base = BigInt(ALPHABET.length);
44
+
45
+ let num = 0n;
46
+ for (let i = 0; i < bytes.length; i++) {
47
+ num = num * 256n + BigInt(bytes[i] || 0);
48
+ }
49
+
50
+ let encoded = "";
51
+ while (num > 0n) {
52
+ const remainder = num % base;
53
+ num = num / base;
54
+ encoded = ALPHABET[Number(remainder)] + encoded;
55
+ }
56
+
57
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
58
+ encoded = "1" + encoded;
59
+ }
60
+
61
+ return encoded || "1";
62
+ }
63
+
64
+ private decodeBase58(str: string): Uint8Array {
65
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
66
+ const base = BigInt(ALPHABET.length);
67
+
68
+ let num = 0n;
69
+ for (const char of str) {
70
+ const index = ALPHABET.indexOf(char);
71
+ if (index === -1) throw new Error("Invalid base58 character");
72
+ num = num * base + BigInt(index);
73
+ }
74
+
75
+ const bytes = [];
76
+ while (num > 0n) {
77
+ bytes.unshift(Number(num % 256n));
78
+ num = num / 256n;
79
+ }
80
+
81
+ for (let i = 0; i < str.length && str[i] === "1"; i++) {
82
+ bytes.unshift(0);
83
+ }
84
+
85
+ return new Uint8Array(bytes.length > 0 ? bytes : [0]);
86
+ }
87
+
88
+ private createSuccessResponse(id: string | number, result: any): JsonRpcResponse {
89
+ return {
90
+ jsonrpc: "2.0",
91
+ id,
92
+ result
93
+ };
94
+ }
95
+
96
+ private createErrorResponse(
97
+ id: string | number,
98
+ code: number,
99
+ message: string,
100
+ data?: any
101
+ ): JsonRpcResponse {
102
+ return {
103
+ jsonrpc: "2.0",
104
+ id,
105
+ error: { code, message, data }
106
+ };
107
+ }
108
+
109
+ async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
110
+ const { method, params, id } = request;
111
+
112
+ try {
113
+ switch (method) {
114
+ case "getAccountInfo":
115
+ return this.handleGetAccountInfo(id, params);
116
+
117
+ case "getBalance":
118
+ return this.handleGetBalance(id, params);
119
+
120
+ case "getLatestBlockhash":
121
+ return this.handleGetLatestBlockhash(id, params);
122
+
123
+ case "sendTransaction":
124
+ return this.handleSendTransaction(id, params);
125
+
126
+ case "simulateTransaction":
127
+ return this.handleSimulateTransaction(id, params);
128
+
129
+ case "requestAirdrop":
130
+ return this.handleRequestAirdrop(id, params);
131
+
132
+ case "getSlot":
133
+ return this.handleGetSlot(id, params);
134
+
135
+ case "getBlockHeight":
136
+ return this.handleGetBlockHeight(id, params);
137
+
138
+ case "getTransaction":
139
+ return this.handleGetTransaction(id, params);
140
+
141
+ case "getSignatureStatuses":
142
+ return this.handleGetSignatureStatuses(id, params);
143
+
144
+ case "getMinimumBalanceForRentExemption":
145
+ return this.handleGetMinimumBalanceForRentExemption(id, params);
146
+
147
+ case "getMultipleAccounts":
148
+ return this.handleGetMultipleAccounts(id, params);
149
+
150
+ case "getHealth":
151
+ return this.createSuccessResponse(id, "ok");
152
+
153
+ case "getVersion":
154
+ return this.createSuccessResponse(id, {
155
+ "solana-core": "1.18.0",
156
+ "feature-set": 1
157
+ });
158
+
159
+ default:
160
+ return this.createErrorResponse(
161
+ id,
162
+ -32601,
163
+ `Method not found: ${method}`
164
+ );
165
+ }
166
+ } catch (error: any) {
167
+ return this.createErrorResponse(
168
+ id,
169
+ -32603,
170
+ "Internal error",
171
+ error.message
172
+ );
173
+ }
174
+ }
175
+
176
+ private handleGetAccountInfo(id: string | number, params: any): JsonRpcResponse {
177
+ const [pubkeyStr, config] = params;
178
+ const encoding = config?.encoding || "base64";
179
+
180
+ try {
181
+ const pubkey = new PublicKey(pubkeyStr);
182
+ const account = this.svm.getAccount(pubkey);
183
+
184
+ if (!account) {
185
+ return this.createSuccessResponse(id, {
186
+ context: { slot: Number(this.slot) },
187
+ value: null
188
+ });
189
+ }
190
+
191
+ const accountInfo = {
192
+ lamports: Number(account.lamports),
193
+ owner: new PublicKey(account.owner).toBase58(),
194
+ data: encoding === "base64"
195
+ ? [Buffer.from(account.data).toString("base64"), encoding]
196
+ : Array.from(account.data),
197
+ executable: account.executable,
198
+ rentEpoch: Number(account.rentEpoch || 0)
199
+ };
200
+
201
+ return this.createSuccessResponse(id, {
202
+ context: { slot: Number(this.slot) },
203
+ value: accountInfo
204
+ });
205
+ } catch (error: any) {
206
+ return this.createErrorResponse(id, -32602, "Invalid params", error.message);
207
+ }
208
+ }
209
+
210
+ private handleGetBalance(id: string | number, params: any): JsonRpcResponse {
211
+ const [pubkeyStr] = params;
212
+
213
+ try {
214
+ const pubkey = new PublicKey(pubkeyStr);
215
+ const balance = this.svm.getBalance(pubkey);
216
+
217
+ return this.createSuccessResponse(id, {
218
+ context: { slot: Number(this.slot) },
219
+ value: Number(balance || 0n)
220
+ });
221
+ } catch (error: any) {
222
+ return this.createErrorResponse(id, -32602, "Invalid params", error.message);
223
+ }
224
+ }
225
+
226
+ private handleGetLatestBlockhash(id: string | number, params: any): JsonRpcResponse {
227
+ const blockhash = this.svm.latestBlockhash();
228
+
229
+ return this.createSuccessResponse(id, {
230
+ context: { slot: Number(this.slot) },
231
+ value: {
232
+ blockhash,
233
+ lastValidBlockHeight: Number(this.blockHeight + 150n)
234
+ }
235
+ });
236
+ }
237
+
238
+ private handleSendTransaction(id: string | number, params: any): JsonRpcResponse {
239
+ const [encodedTx, config] = params;
240
+
241
+ try {
242
+ const txData = Buffer.from(encodedTx, "base64");
243
+ const tx = VersionedTransaction.deserialize(txData);
244
+
245
+ const result = this.svm.sendTransaction(tx);
246
+
247
+ if ("err" in result) {
248
+ return this.createErrorResponse(
249
+ id,
250
+ -32002,
251
+ "Transaction simulation failed",
252
+ result.err
253
+ );
254
+ }
255
+
256
+ const signature = tx.signatures[0] ? this.encodeBase58(tx.signatures[0]) : this.encodeBase58(new Uint8Array(64).fill(0));
257
+
258
+ this.slot += 1n;
259
+ this.blockHeight += 1n;
260
+
261
+ return this.createSuccessResponse(id, signature);
262
+ } catch (error: any) {
263
+ return this.createErrorResponse(
264
+ id,
265
+ -32003,
266
+ "Transaction failed",
267
+ error.message
268
+ );
269
+ }
270
+ }
271
+
272
+ private handleSimulateTransaction(id: string | number, params: any): JsonRpcResponse {
273
+ const [encodedTx, config] = params;
274
+
275
+ try {
276
+ const txData = Buffer.from(encodedTx, "base64");
277
+ const tx = VersionedTransaction.deserialize(txData);
278
+
279
+ const result = this.svm.simulateTransaction(tx);
280
+
281
+ if ("err" in result) {
282
+ const errorMeta = result.meta();
283
+ return this.createSuccessResponse(id, {
284
+ context: { slot: Number(this.slot) },
285
+ value: {
286
+ err: result.err(),
287
+ logs: errorMeta.logs(),
288
+ accounts: null,
289
+ unitsConsumed: Number(errorMeta.computeUnitsConsumed()),
290
+ returnData: null
291
+ }
292
+ });
293
+ }
294
+
295
+ const meta = result.meta();
296
+ const returnData = meta.returnData();
297
+
298
+ return this.createSuccessResponse(id, {
299
+ context: { slot: Number(this.slot) },
300
+ value: {
301
+ err: null,
302
+ logs: meta.logs(),
303
+ accounts: null,
304
+ unitsConsumed: Number(meta.computeUnitsConsumed()),
305
+ returnData: returnData ? {
306
+ programId: this.encodeBase58(returnData.programId()),
307
+ data: [Buffer.from(returnData.data()).toString("base64"), "base64"]
308
+ } : null
309
+ }
310
+ });
311
+ } catch (error: any) {
312
+ return this.createErrorResponse(
313
+ id,
314
+ -32003,
315
+ "Simulation failed",
316
+ error.message
317
+ );
318
+ }
319
+ }
320
+
321
+ private handleRequestAirdrop(id: string | number, params: any): JsonRpcResponse {
322
+ const [pubkeyStr, lamports] = params;
323
+
324
+ try {
325
+ const pubkey = new PublicKey(pubkeyStr);
326
+ const result = this.svm.airdrop(pubkey, BigInt(lamports));
327
+
328
+ if (!result || "err" in result) {
329
+ return this.createErrorResponse(
330
+ id,
331
+ -32003,
332
+ "Airdrop failed",
333
+ result?.err || "Unknown error"
334
+ );
335
+ }
336
+
337
+ const signature = this.encodeBase58(new Uint8Array(64).fill(1));
338
+
339
+ this.slot += 1n;
340
+ this.blockHeight += 1n;
341
+
342
+ return this.createSuccessResponse(id, signature);
343
+ } catch (error: any) {
344
+ return this.createErrorResponse(id, -32602, "Invalid params", error.message);
345
+ }
346
+ }
347
+
348
+ private handleGetSlot(id: string | number, params: any): JsonRpcResponse {
349
+ return this.createSuccessResponse(id, Number(this.slot));
350
+ }
351
+
352
+ private handleGetBlockHeight(id: string | number, params: any): JsonRpcResponse {
353
+ return this.createSuccessResponse(id, Number(this.blockHeight));
354
+ }
355
+
356
+ private handleGetTransaction(id: string | number, params: any): JsonRpcResponse {
357
+ const [signature] = params;
358
+
359
+ try {
360
+ const sigBytes = this.decodeBase58(signature);
361
+ const tx = this.svm.getTransaction(sigBytes);
362
+
363
+ if (!tx) {
364
+ return this.createSuccessResponse(id, null);
365
+ }
366
+
367
+ const isError = "err" in tx;
368
+ const logs = isError ? tx.meta().logs() : tx.logs();
369
+
370
+ return this.createSuccessResponse(id, {
371
+ slot: Number(this.slot),
372
+ transaction: {
373
+ signatures: [signature]
374
+ },
375
+ meta: {
376
+ err: isError ? tx.err() : null,
377
+ fee: 5000,
378
+ preBalances: [],
379
+ postBalances: [],
380
+ innerInstructions: [],
381
+ logMessages: logs,
382
+ preTokenBalances: [],
383
+ postTokenBalances: [],
384
+ rewards: []
385
+ }
386
+ });
387
+ } catch (error: any) {
388
+ return this.createErrorResponse(id, -32602, "Invalid params", error.message);
389
+ }
390
+ }
391
+
392
+ private handleGetSignatureStatuses(id: string | number, params: any): JsonRpcResponse {
393
+ const [signatures, config] = params;
394
+
395
+ const statuses = signatures.map((sig: string) => {
396
+ try {
397
+ const sigBytes = this.decodeBase58(sig);
398
+ const tx = this.svm.getTransaction(sigBytes);
399
+
400
+ if (!tx) {
401
+ return null;
402
+ }
403
+
404
+ return {
405
+ slot: Number(this.slot),
406
+ confirmations: 0,
407
+ err: "err" in tx ? tx.err : null,
408
+ confirmationStatus: "finalized"
409
+ };
410
+ } catch {
411
+ return null;
412
+ }
413
+ });
414
+
415
+ return this.createSuccessResponse(id, {
416
+ context: { slot: Number(this.slot) },
417
+ value: statuses
418
+ });
419
+ }
420
+
421
+ private handleGetMinimumBalanceForRentExemption(
422
+ id: string | number,
423
+ params: any
424
+ ): JsonRpcResponse {
425
+ const [dataLength] = params;
426
+ const minBalance = this.svm.minimumBalanceForRentExemption(BigInt(dataLength));
427
+
428
+ return this.createSuccessResponse(id, Number(minBalance));
429
+ }
430
+
431
+ private handleGetMultipleAccounts(id: string | number, params: any): JsonRpcResponse {
432
+ const [pubkeys, config] = params;
433
+ const encoding = config?.encoding || "base64";
434
+
435
+ const accounts = pubkeys.map((pubkeyStr: string) => {
436
+ try {
437
+ const pubkey = new PublicKey(pubkeyStr);
438
+ const account = this.svm.getAccount(pubkey);
439
+
440
+ if (!account) {
441
+ return null;
442
+ }
443
+
444
+ return {
445
+ lamports: Number(account.lamports),
446
+ owner: new PublicKey(account.owner).toBase58(),
447
+ data: encoding === "base64"
448
+ ? [Buffer.from(account.data).toString("base64"), encoding]
449
+ : Array.from(account.data),
450
+ executable: account.executable,
451
+ rentEpoch: Number(account.rentEpoch || 0)
452
+ };
453
+ } catch {
454
+ return null;
455
+ }
456
+ });
457
+
458
+ return this.createSuccessResponse(id, {
459
+ context: { slot: Number(this.slot) },
460
+ value: accounts
461
+ });
462
+ }
463
+ }
464
+
465
+ export function createLiteSVMRpcServer(port: number = 8899) {
466
+ const server = new LiteSVMRpcServer();
467
+
468
+ const bunServer = Bun.serve({
469
+ port,
470
+ async fetch(req) {
471
+ if (req.method === "POST") {
472
+ try {
473
+ const body = await req.json();
474
+
475
+ if (Array.isArray(body)) {
476
+ const responses = await Promise.all(
477
+ body.map(request => server.handleRequest(request))
478
+ );
479
+ return Response.json(responses);
480
+ } else {
481
+ const response = await server.handleRequest(body as JsonRpcRequest);
482
+ return Response.json(response);
483
+ }
484
+ } catch (error) {
485
+ return Response.json({
486
+ jsonrpc: "2.0",
487
+ id: null,
488
+ error: {
489
+ code: -32700,
490
+ message: "Parse error"
491
+ }
492
+ });
493
+ }
494
+ }
495
+
496
+ if (req.method === "OPTIONS") {
497
+ return new Response(null, {
498
+ headers: {
499
+ "Access-Control-Allow-Origin": "*",
500
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
501
+ "Access-Control-Allow-Headers": "Content-Type"
502
+ }
503
+ });
504
+ }
505
+
506
+ return new Response("Method not allowed", { status: 405 });
507
+ },
508
+ error(error) {
509
+ console.error("Server error:", error);
510
+ return new Response("Internal Server Error", { status: 500 });
511
+ }
512
+ });
513
+
514
+ console.log(`🚀 LiteSVM RPC Server running on http://localhost:${port}`);
515
+ console.log(` Compatible with Solana RPC API`);
516
+ console.log(` Use with: solana config set -u http://localhost:${port}`);
517
+
518
+ return bunServer;
519
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./methods";
2
+ export { rpcMethods } from "./methods";
3
+ export { createLiteSVMRpcServer, LiteSVMRpcServer } from "./rpc-server";
4
+ export * from "./types";
5
+ export { createLiteSVMWebSocketServer } from "./ws-server";
@@ -0,0 +1,33 @@
1
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2
+ const BASE = BigInt(ALPHABET.length);
3
+
4
+ export function encodeBase58(bytes: Uint8Array): string {
5
+ let num = 0n;
6
+ for (let i = 0; i < bytes.length; i++)
7
+ num = num * 256n + BigInt(bytes[i] || 0);
8
+ let encoded = "";
9
+ while (num > 0n) {
10
+ const remainder = num % BASE;
11
+ num = num / BASE;
12
+ encoded = ALPHABET[Number(remainder)] + encoded;
13
+ }
14
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++)
15
+ encoded = "1" + encoded;
16
+ return encoded || "1";
17
+ }
18
+
19
+ export function decodeBase58(str: string): Uint8Array {
20
+ let num = 0n;
21
+ for (const char of str) {
22
+ const index = ALPHABET.indexOf(char);
23
+ if (index === -1) throw new Error("Invalid base58 character");
24
+ num = num * BASE + BigInt(index);
25
+ }
26
+ const bytes: number[] = [];
27
+ while (num > 0n) {
28
+ bytes.unshift(Number(num % 256n));
29
+ num = num / 256n;
30
+ }
31
+ for (let i = 0; i < str.length && str[i] === "1"; i++) bytes.unshift(0);
32
+ return new Uint8Array(bytes.length > 0 ? bytes : [0]);
33
+ }
@@ -0,0 +1,110 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import {
4
+ Keypair,
5
+ type PublicKey,
6
+ SystemProgram,
7
+ Transaction,
8
+ } from "@solana/web3.js";
9
+ import type { LiteSVM } from "litesvm";
10
+
11
+ const DEFAULT_PATH =
12
+ process.env.SOLFORGE_FAUCET_PATH || ".solforge/faucet.json";
13
+ // Default to 1,000,000 SOL so we never run out in local dev.
14
+ // Override with SOLFORGE_FAUCET_LAMPORTS if desired.
15
+ const DEFAULT_INITIAL_LAMPORTS = BigInt(
16
+ process.env.SOLFORGE_FAUCET_LAMPORTS || "1000000000000000",
17
+ ); // 1e15 lamports = 1,000,000 SOL
18
+
19
+ type FaucetFile = { secretKey: string; publicKey: string; createdAt: string };
20
+
21
+ function ensureDir(path: string) {
22
+ try {
23
+ mkdirSync(dirname(path), { recursive: true });
24
+ } catch {}
25
+ }
26
+
27
+ export function loadOrCreateFaucet(path: string = DEFAULT_PATH): Keypair {
28
+ try {
29
+ if (existsSync(path)) {
30
+ const raw = readFileSync(path, "utf8");
31
+ const data = JSON.parse(raw) as FaucetFile;
32
+ const secret = Buffer.from(data.secretKey, "base64");
33
+ return Keypair.fromSecretKey(new Uint8Array(secret));
34
+ }
35
+ } catch {}
36
+
37
+ const kp = Keypair.generate();
38
+ try {
39
+ ensureDir(path);
40
+ const payload: FaucetFile = {
41
+ secretKey: Buffer.from(kp.secretKey).toString("base64"),
42
+ publicKey: kp.publicKey.toBase58(),
43
+ createdAt: new Date().toISOString(),
44
+ };
45
+ writeFileSync(path, JSON.stringify(payload, null, 2), "utf8");
46
+ } catch {}
47
+ return kp;
48
+ }
49
+
50
+ export function fundFaucetIfNeeded(
51
+ svm: LiteSVM,
52
+ faucet: Keypair,
53
+ targetLamports: bigint = DEFAULT_INITIAL_LAMPORTS,
54
+ ): bigint {
55
+ let bal = 0n;
56
+ try {
57
+ bal = svm.getBalance(faucet.publicKey as PublicKey) || 0n;
58
+ } catch {
59
+ bal = 0n;
60
+ }
61
+ if (bal >= targetLamports) return bal;
62
+
63
+ // Observed per-account airdrop cap ~10k SOL. Work around by creating feeder accounts,
64
+ // airdropping to each, then transferring to the faucet until the target is reached.
65
+ const LAMPORTS_PER_SOL = 1_000_000_000n;
66
+ const PER_ACCOUNT_CAP = 10_000n * LAMPORTS_PER_SOL; // 10k SOL per feeder
67
+ const FEE = 5_000n; // rough fee for legacy transfer
68
+
69
+ let remaining = targetLamports - bal;
70
+ let safety = 0;
71
+ while (remaining > 0n && safety < 1000) {
72
+ safety++;
73
+ const feeder = Keypair.generate();
74
+ const mint = remaining > PER_ACCOUNT_CAP ? PER_ACCOUNT_CAP : remaining;
75
+ // Airdrop enough to cover transfer + fee
76
+ try {
77
+ svm.airdrop(feeder.publicKey as PublicKey, mint + FEE);
78
+ } catch {
79
+ // If airdrop fails, try smaller amount; if still fails, stop
80
+ try {
81
+ svm.airdrop(feeder.publicKey as PublicKey, 1_000_000_000n);
82
+ } catch {
83
+ break;
84
+ }
85
+ }
86
+
87
+ // Transfer from feeder -> faucet
88
+ try {
89
+ const tx = new Transaction();
90
+ try {
91
+ tx.recentBlockhash = svm.latestBlockhash();
92
+ } catch {}
93
+ tx.add(
94
+ SystemProgram.transfer({
95
+ fromPubkey: feeder.publicKey,
96
+ toPubkey: faucet.publicKey as PublicKey,
97
+ lamports: Number(mint),
98
+ }),
99
+ );
100
+ tx.sign(feeder);
101
+ svm.sendTransaction(tx);
102
+ remaining -= mint;
103
+ } catch {}
104
+ }
105
+
106
+ try {
107
+ bal = svm.getBalance(faucet.publicKey as PublicKey) || 0n;
108
+ } catch {}
109
+ return bal;
110
+ }