solforge 0.1.7 → 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 (195) 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 -288
  182. package/src/commands/start.ts +0 -877
  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 -485
  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
  195. package/src/utils/token-loader.ts +0 -115
@@ -0,0 +1,52 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodHandler } from "../../types";
3
+
4
+ // Load a program ELF into LiteSVM for a given programId
5
+ export const solforgeLoadProgram: RpcMethodHandler = async (
6
+ id,
7
+ params,
8
+ context,
9
+ ) => {
10
+ try {
11
+ const [programIdStr, elfBase64] = params as [string, string];
12
+ if (!programIdStr || !elfBase64)
13
+ return context.createErrorResponse(
14
+ id,
15
+ -32602,
16
+ "Invalid params: programId, base64 required",
17
+ );
18
+ const pid = new PublicKey(programIdStr);
19
+ const bytes = Uint8Array.from(Buffer.from(elfBase64, "base64"));
20
+ try {
21
+ context.svm.addProgram(pid, bytes);
22
+ } catch {}
23
+ // Mirror program account metadata as executable; owner = upgradeable loader for realism
24
+ const LOADER_UPGRADEABLE = new PublicKey(
25
+ "BPFLoaderUpgradeab1e11111111111111111111111",
26
+ );
27
+ context.svm.setAccount(pid, {
28
+ lamports: 1_000_000_000,
29
+ data: new Uint8Array(bytes.length), // minimal stub data for the program account itself
30
+ owner: LOADER_UPGRADEABLE,
31
+ executable: true,
32
+ rentEpoch: 0,
33
+ });
34
+ try {
35
+ context.registerProgram?.(pid);
36
+ } catch {}
37
+ return context.createSuccessResponse(id, {
38
+ ok: true,
39
+ programId: programIdStr,
40
+ size: bytes.length,
41
+ });
42
+ } catch (e) {
43
+ return context.createErrorResponse(
44
+ id,
45
+ -32603,
46
+ "Load program failed",
47
+ (e as Error)?.message || String(e),
48
+ );
49
+ }
50
+ };
51
+
52
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,278 @@
1
+ import {
2
+ ACCOUNT_SIZE,
3
+ AccountLayout,
4
+ createAssociatedTokenAccountInstruction,
5
+ createMintToCheckedInstruction,
6
+ getAssociatedTokenAddressSync,
7
+ MINT_SIZE,
8
+ MintLayout,
9
+ TOKEN_2022_PROGRAM_ID,
10
+ TOKEN_PROGRAM_ID,
11
+ } from "@solana/spl-token";
12
+ import type { TransactionInstruction } from "@solana/web3.js";
13
+ import {
14
+ PublicKey,
15
+ TransactionMessage,
16
+ VersionedTransaction,
17
+ } from "@solana/web3.js";
18
+ import type { RpcMethodHandler } from "../../types";
19
+
20
+ // Mint via a real SPL Token transaction signed by faucet (must be mint authority)
21
+ export const solforgeMintTo: RpcMethodHandler = async (id, params, context) => {
22
+ try {
23
+ const [mintStr, ownerStr, rawAmount] = params as [
24
+ string,
25
+ string,
26
+ number | string | bigint,
27
+ ];
28
+ if (!mintStr || !ownerStr || rawAmount == null)
29
+ return context.createErrorResponse(
30
+ id,
31
+ -32602,
32
+ "Invalid params: mint, owner, amount required",
33
+ );
34
+ const mint = new PublicKey(mintStr);
35
+ const owner = new PublicKey(ownerStr);
36
+ const faucet = context.getFaucet();
37
+
38
+ // Read mint to get decimals and authority
39
+ const mintAcc = context.svm.getAccount(mint);
40
+ if (!mintAcc)
41
+ return context.createErrorResponse(
42
+ id,
43
+ -32004,
44
+ "Mint not found in LiteSVM",
45
+ );
46
+ const mintInfo = MintLayout.decode(
47
+ Buffer.from(mintAcc.data).slice(0, MINT_SIZE),
48
+ );
49
+ const decimals = mintInfo.decimals;
50
+ const hasAuth = mintInfo.mintAuthorityOption === 1;
51
+ const authPk = hasAuth ? new PublicKey(mintInfo.mintAuthority) : null;
52
+ if (!hasAuth || !authPk || !authPk.equals(faucet.publicKey)) {
53
+ return context.createErrorResponse(
54
+ id,
55
+ -32000,
56
+ "Mint has no faucet authority; cannot mint real tokens",
57
+ );
58
+ }
59
+
60
+ const ixs: TransactionInstruction[] = [];
61
+ // Detect which token program the mint belongs to (SPL v1 vs Token-2022)
62
+ const mintOwnerStr = (() => {
63
+ try {
64
+ return (mintAcc.owner as PublicKey).toBase58();
65
+ } catch {
66
+ return String(mintAcc.owner);
67
+ }
68
+ })();
69
+ const tokenProgramId =
70
+ mintOwnerStr === TOKEN_2022_PROGRAM_ID.toBase58()
71
+ ? TOKEN_2022_PROGRAM_ID
72
+ : TOKEN_PROGRAM_ID;
73
+
74
+ // Derive ATA using the correct token program
75
+ const ata = getAssociatedTokenAddressSync(
76
+ mint,
77
+ owner,
78
+ true,
79
+ tokenProgramId,
80
+ );
81
+ const ataAcc = context.svm.getAccount(ata);
82
+
83
+ // Ensure ATA exists under the correct token program
84
+ if (!ataAcc || (ataAcc.data?.length ?? 0) < ACCOUNT_SIZE) {
85
+ ixs.push(
86
+ createAssociatedTokenAccountInstruction(
87
+ faucet.publicKey,
88
+ ata,
89
+ owner,
90
+ mint,
91
+ tokenProgramId,
92
+ ),
93
+ );
94
+ }
95
+
96
+ const amount =
97
+ typeof rawAmount === "bigint" ? rawAmount : BigInt(rawAmount);
98
+ ixs.push(
99
+ createMintToCheckedInstruction(
100
+ mint,
101
+ ata,
102
+ faucet.publicKey,
103
+ amount,
104
+ decimals,
105
+ [],
106
+ tokenProgramId,
107
+ ),
108
+ );
109
+
110
+ // Build a VersionedTransaction (legacy message) to ensure consistent encoding/decoding downstream
111
+ let rb = context.svm.latestBlockhash();
112
+ if (!rb || rb.length === 0) {
113
+ const bh = new Uint8Array(32);
114
+ crypto.getRandomValues(bh);
115
+ rb = context.encodeBase58(bh);
116
+ }
117
+ const msg = new TransactionMessage({
118
+ payerKey: faucet.publicKey,
119
+ recentBlockhash: rb,
120
+ instructions: ixs,
121
+ });
122
+ const legacy = msg.compileToLegacyMessage();
123
+ const vtx = new VersionedTransaction(legacy);
124
+ vtx.sign([faucet]);
125
+
126
+ // Capture preBalances for primary accounts referenced and token pre amount
127
+ const trackedKeys = [faucet.publicKey, ata, mint, owner];
128
+ const preBalances = trackedKeys.map((pk) => {
129
+ try {
130
+ return Number(context.svm.getBalance(pk) || 0n);
131
+ } catch {
132
+ return 0;
133
+ }
134
+ });
135
+ // Token mint decimals and pre amount
136
+ let decsForMint = 0;
137
+ let preTokenAmt: bigint = 0n;
138
+ try {
139
+ const mintAcc0 = context.svm.getAccount(mint);
140
+ const mintInfo0 = mintAcc0
141
+ ? MintLayout.decode(Buffer.from(mintAcc0.data).slice(0, MINT_SIZE))
142
+ : undefined;
143
+ decsForMint = Number(mintInfo0?.decimals ?? decimals ?? 0);
144
+ const ataAcc0 = context.svm.getAccount(ata);
145
+ if (ataAcc0 && (ataAcc0.data?.length ?? 0) >= ACCOUNT_SIZE) {
146
+ const dec0 = AccountLayout.decode(Buffer.from(ataAcc0.data));
147
+ preTokenAmt = BigInt(dec0.amount.toString());
148
+ }
149
+ } catch {}
150
+
151
+ // Send transaction via svm
152
+ const _res = context.svm.sendTransaction(vtx);
153
+ // Compute signature (base58) from the signed transaction
154
+ let signatureStr = "";
155
+ try {
156
+ const sigBytes = vtx.signatures?.[0];
157
+ if (sigBytes)
158
+ signatureStr = context.encodeBase58(new Uint8Array(sigBytes));
159
+ } catch {}
160
+ if (!signatureStr) signatureStr = `mint:${ata.toBase58()}:${Date.now()}`;
161
+
162
+ // Token balance deltas (pre/post) for ATA
163
+ type UiTokenAmount = {
164
+ amount: string;
165
+ decimals: number;
166
+ uiAmount: number;
167
+ uiAmountString: string;
168
+ };
169
+ type TokenBalance = {
170
+ accountIndex: number;
171
+ mint: string;
172
+ owner: string;
173
+ uiTokenAmount: UiTokenAmount;
174
+ };
175
+ let preTokenBalances: TokenBalance[] = [];
176
+ let postTokenBalances: TokenBalance[] = [];
177
+ try {
178
+ const decs = decsForMint;
179
+ const ui = (n: bigint) => ({
180
+ amount: n.toString(),
181
+ decimals: decs,
182
+ uiAmount: Number(n) / 10 ** decs,
183
+ uiAmountString: (Number(n) / 10 ** decs).toString(),
184
+ });
185
+ const preAmt = preTokenAmt;
186
+ const ataPostAcc = context.svm.getAccount(ata); // after send
187
+ const postAmt =
188
+ ataPostAcc && (ataPostAcc.data?.length ?? 0) >= ACCOUNT_SIZE
189
+ ? BigInt(
190
+ AccountLayout.decode(
191
+ Buffer.from(ataPostAcc.data),
192
+ ).amount.toString(),
193
+ )
194
+ : preAmt;
195
+ const msgAny = vtx.message as unknown as {
196
+ staticAccountKeys?: Array<string | PublicKey>;
197
+ accountKeys?: Array<string | PublicKey>;
198
+ };
199
+ const rawKeys: Array<string | PublicKey> = Array.isArray(
200
+ msgAny.staticAccountKeys,
201
+ )
202
+ ? msgAny.staticAccountKeys
203
+ : Array.isArray(msgAny.accountKeys)
204
+ ? msgAny.accountKeys
205
+ : [];
206
+ const keys = rawKeys.map((k: string | PublicKey) => {
207
+ try {
208
+ return typeof k === "string" ? k : new PublicKey(k).toBase58();
209
+ } catch {
210
+ return String(k);
211
+ }
212
+ });
213
+ const ataIndex = keys.indexOf(ata.toBase58());
214
+ preTokenBalances = [
215
+ {
216
+ accountIndex: ataIndex >= 0 ? ataIndex : 0,
217
+ mint: mint.toBase58(),
218
+ owner: owner.toBase58(),
219
+ uiTokenAmount: ui(preAmt),
220
+ },
221
+ ];
222
+ postTokenBalances = [
223
+ {
224
+ accountIndex: ataIndex >= 0 ? ataIndex : 0,
225
+ mint: mint.toBase58(),
226
+ owner: owner.toBase58(),
227
+ uiTokenAmount: ui(postAmt),
228
+ },
229
+ ];
230
+ } catch {}
231
+
232
+ // Insert into DB for explorer via context.recordTransaction for richer details
233
+ try {
234
+ const rawBase64 = Buffer.from(vtx.serialize()).toString("base64");
235
+ const postBalances = trackedKeys.map((pk) => {
236
+ try {
237
+ return Number(context.svm.getBalance(pk) || 0n);
238
+ } catch {
239
+ return 0;
240
+ }
241
+ });
242
+ const logs: string[] = ["spl-token mintToChecked"];
243
+ try {
244
+ (vtx as unknown as { serialize: () => Uint8Array }).serialize = () =>
245
+ Buffer.from(rawBase64, "base64");
246
+ } catch {}
247
+ context.recordTransaction(signatureStr, vtx, {
248
+ logs,
249
+ fee: 0,
250
+ blockTime: Math.floor(Date.now() / 1000),
251
+ preBalances,
252
+ postBalances,
253
+ preTokenBalances,
254
+ postTokenBalances,
255
+ });
256
+ } catch {}
257
+ try {
258
+ context.notifySignature(signatureStr);
259
+ } catch {}
260
+
261
+ return context.createSuccessResponse(id, {
262
+ ok: true,
263
+ signature: signatureStr,
264
+ mint: mintStr,
265
+ owner: ownerStr,
266
+ amount: amount.toString(),
267
+ });
268
+ } catch (e) {
269
+ return context.createErrorResponse(
270
+ id,
271
+ -32603,
272
+ "MintTo failed",
273
+ (e as Error)?.message || String(e),
274
+ );
275
+ }
276
+ };
277
+
278
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,5 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getBlockHeight: RpcMethodHandler = (id, _params, context) => {
4
+ return context.createSuccessResponse(id, Number(context.blockHeight));
5
+ };
@@ -0,0 +1,35 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getBlock: RpcMethodHandler = (id, params, context) => {
4
+ try {
5
+ const [slotRaw] = params || [];
6
+ const slot = Number(slotRaw);
7
+ if (!Number.isFinite(slot) || slot < 0) throw new Error("Invalid slot");
8
+ const parentSlot = Math.max(0, slot - 1);
9
+ const blockhash = context.svm.latestBlockhash();
10
+ const previousBlockhash = context.svm.latestBlockhash();
11
+ const SLOT_TIME_MS = 400;
12
+ const currentTime = Math.floor(Date.now() / 1000);
13
+ const slotDiff = Number(context.slot) - slot;
14
+ const blockTime =
15
+ slot > Number(context.slot)
16
+ ? null
17
+ : currentTime -
18
+ Math.floor((Math.max(0, slotDiff) * SLOT_TIME_MS) / 1000);
19
+ return context.createSuccessResponse(id, {
20
+ blockhash,
21
+ previousBlockhash,
22
+ parentSlot,
23
+ transactions: [],
24
+ rewards: [],
25
+ blockTime,
26
+ });
27
+ } catch (error: any) {
28
+ return context.createErrorResponse(
29
+ id,
30
+ -32602,
31
+ "Invalid params",
32
+ error.message,
33
+ );
34
+ }
35
+ };
@@ -0,0 +1,23 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getBlocksWithLimit: RpcMethodHandler = (id, params, context) => {
4
+ try {
5
+ const [startSlotRaw, limitRaw] = params || [];
6
+ const start = Number(startSlotRaw);
7
+ const limit = Number(limitRaw);
8
+ if (!Number.isFinite(start) || start < 0)
9
+ throw new Error("Invalid start slot");
10
+ if (!Number.isFinite(limit) || limit <= 0) throw new Error("Invalid limit");
11
+ const end = Math.min(Number(context.slot), start + limit - 1);
12
+ const blocks: number[] = [];
13
+ for (let s = start; s <= end; s++) blocks.push(s);
14
+ return context.createSuccessResponse(id, blocks);
15
+ } catch (error: any) {
16
+ return context.createErrorResponse(
17
+ id,
18
+ -32602,
19
+ "Invalid params",
20
+ error.message,
21
+ );
22
+ }
23
+ };
@@ -0,0 +1,12 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getLatestBlockhash: RpcMethodHandler = (id, _params, context) => {
4
+ const blockhash = context.svm.latestBlockhash();
5
+ return context.createSuccessResponse(id, {
6
+ context: { slot: Number(context.slot) },
7
+ value: {
8
+ blockhash,
9
+ lastValidBlockHeight: Number(context.blockHeight + 150n),
10
+ },
11
+ });
12
+ };
@@ -0,0 +1,5 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getSlot: RpcMethodHandler = (id, _params, context) => {
4
+ return context.createSuccessResponse(id, Number(context.slot));
5
+ };
@@ -0,0 +1,6 @@
1
+ export { getBlock } from "./get-block";
2
+ export { getBlockHeight } from "./get-block-height";
3
+ export { getBlocksWithLimit } from "./get-blocks-with-limit";
4
+ export { getLatestBlockhash } from "./get-latest-blockhash";
5
+ export { getSlot } from "./get-slot";
6
+ export { isBlockhashValid } from "./is-blockhash-valid";
@@ -0,0 +1,23 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const isBlockhashValid: RpcMethodHandler = (id, params, context) => {
4
+ try {
5
+ const [blockhash] = params || [];
6
+ const current = context.svm.latestBlockhash();
7
+ const isValid =
8
+ typeof blockhash === "string" &&
9
+ blockhash.length > 0 &&
10
+ blockhash === current;
11
+ return context.createSuccessResponse(id, {
12
+ context: { slot: Number(context.slot) },
13
+ value: isValid,
14
+ });
15
+ } catch (error: any) {
16
+ return context.createErrorResponse(
17
+ id,
18
+ -32602,
19
+ "Invalid params",
20
+ error.message,
21
+ );
22
+ }
23
+ };
@@ -0,0 +1,17 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getClusterNodes: RpcMethodHandler = (id, _params, _context) => {
4
+ return {
5
+ jsonrpc: "2.0",
6
+ id,
7
+ result: [
8
+ {
9
+ pubkey: "11111111111111111111111111111111",
10
+ gossip: "127.0.0.1:8001",
11
+ tpu: "127.0.0.1:8003",
12
+ rpc: "127.0.0.1:8899",
13
+ version: "1.17.0",
14
+ },
15
+ ],
16
+ };
17
+ };
@@ -0,0 +1,16 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getEpochInfo: RpcMethodHandler = (id, _params, context) => {
4
+ const slotsPerEpoch = 432000;
5
+ const currentSlot = Number(context.slot);
6
+ const epoch = Math.floor(currentSlot / slotsPerEpoch);
7
+ const slotIndex = currentSlot % slotsPerEpoch;
8
+ return context.createSuccessResponse(id, {
9
+ absoluteSlot: currentSlot,
10
+ blockHeight: currentSlot,
11
+ epoch,
12
+ slotIndex,
13
+ slotsInEpoch: slotsPerEpoch,
14
+ transactionCount: currentSlot * 100,
15
+ });
16
+ };
@@ -0,0 +1,15 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getEpochSchedule: RpcMethodHandler = (id, _params, _context) => {
4
+ return {
5
+ jsonrpc: "2.0",
6
+ id,
7
+ result: {
8
+ slotsPerEpoch: 432000,
9
+ leaderScheduleSlotOffset: 432000,
10
+ warmup: false,
11
+ firstNormalEpoch: 0,
12
+ firstNormalSlot: 0,
13
+ },
14
+ };
15
+ };
@@ -0,0 +1,9 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getHighestSnapshotSlot: RpcMethodHandler = (
4
+ id,
5
+ _params,
6
+ _context,
7
+ ) => {
8
+ return { jsonrpc: "2.0", id, result: { full: 0, incremental: null } };
9
+ };
@@ -0,0 +1,8 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getLeaderSchedule: RpcMethodHandler = (id, _params, _context) => {
4
+ const identity = "11111111111111111111111111111111";
5
+ const schedule: Record<string, number[]> = {};
6
+ schedule[identity] = Array.from({ length: 100 }, (_, i) => i);
7
+ return { jsonrpc: "2.0", id, result: schedule };
8
+ };
@@ -0,0 +1,9 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getMaxRetransmitSlot: RpcMethodHandler = (
4
+ id,
5
+ _params,
6
+ context,
7
+ ) => {
8
+ return context.createSuccessResponse(id, Number(context.slot));
9
+ };
@@ -0,0 +1,9 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getMaxShredInsertSlot: RpcMethodHandler = (
4
+ id,
5
+ _params,
6
+ context,
7
+ ) => {
8
+ return context.createSuccessResponse(id, Number(context.slot));
9
+ };
@@ -0,0 +1,6 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getSlotLeader: RpcMethodHandler = (id, _params, _context) => {
4
+ const LEADER_PUBKEY = "11111111111111111111111111111111";
5
+ return { jsonrpc: "2.0", id, result: LEADER_PUBKEY };
6
+ };
@@ -0,0 +1,9 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getSlotLeaders: RpcMethodHandler = (id, params, _context) => {
4
+ const [_startSlot, limit] = params || [];
5
+ const LEADER_PUBKEY = "11111111111111111111111111111111";
6
+ const count = Math.min(Number(limit || 100), 5000);
7
+ const leaders = Array(count).fill(LEADER_PUBKEY);
8
+ return { jsonrpc: "2.0", id, result: leaders };
9
+ };
@@ -0,0 +1,9 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getStakeActivation: RpcMethodHandler = (id, _params, _context) => {
4
+ return {
5
+ jsonrpc: "2.0",
6
+ id,
7
+ result: { state: "active", active: 1000000000, inactive: 0 },
8
+ };
9
+ };
@@ -0,0 +1,9 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getStakeMinimumDelegation: RpcMethodHandler = (
4
+ id,
5
+ _params,
6
+ context,
7
+ ) => {
8
+ return context.createSuccessResponse(id, 1_000_000_000);
9
+ };
@@ -0,0 +1,19 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getVoteAccounts: RpcMethodHandler = (id, _params, context) => {
4
+ return context.createSuccessResponse(id, {
5
+ current: [
6
+ {
7
+ votePubkey: "11111111111111111111111111111111",
8
+ nodePubkey: "11111111111111111111111111111111",
9
+ activatedStake: 1000000000,
10
+ epochVoteAccount: true,
11
+ commission: 0,
12
+ lastVote: Number(context.slot),
13
+ epochCredits: [[0, 1000, 0]],
14
+ rootSlot: Number(context.slot) - 1,
15
+ },
16
+ ],
17
+ delinquent: [],
18
+ });
19
+ };
@@ -0,0 +1,13 @@
1
+ export { getClusterNodes } from "./get-cluster-nodes";
2
+ export { getEpochInfo } from "./get-epoch-info";
3
+ export { getEpochSchedule } from "./get-epoch-schedule";
4
+ export { getHighestSnapshotSlot } from "./get-highest-snapshot-slot";
5
+ export { getLeaderSchedule } from "./get-leader-schedule";
6
+ export { getMaxRetransmitSlot } from "./get-max-retransmit-slot";
7
+ export { getMaxShredInsertSlot } from "./get-max-shred-insert-slot";
8
+ export { getSlotLeader } from "./get-slot-leader";
9
+ export { getSlotLeaders } from "./get-slot-leaders";
10
+ export { getStakeActivation } from "./get-stake-activation";
11
+ export { getStakeMinimumDelegation } from "./get-stake-minimum-delegation";
12
+ export { getVoteAccounts } from "./get-vote-accounts";
13
+ export { minimumLedgerSlot } from "./minimum-ledger-slot";
@@ -0,0 +1,5 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const minimumLedgerSlot: RpcMethodHandler = (id, _params, _context) => {
4
+ return { jsonrpc: "2.0", id, result: 0 };
5
+ };
@@ -0,0 +1,12 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getFeeCalculatorForBlockhash: RpcMethodHandler = (
4
+ id,
5
+ _params,
6
+ context,
7
+ ) => {
8
+ return context.createSuccessResponse(id, {
9
+ context: { slot: Number(context.slot) },
10
+ value: { feeCalculator: { lamportsPerSignature: 5000 } },
11
+ });
12
+ };
@@ -0,0 +1,8 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getFeeForMessage: RpcMethodHandler = (id, _params, context) => {
4
+ return context.createSuccessResponse(id, {
5
+ context: { slot: Number(context.slot), apiVersion: "1.17.9" },
6
+ value: 5000,
7
+ });
8
+ };
@@ -0,0 +1,16 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getFeeRateGovernor: RpcMethodHandler = (id, _params, context) => {
4
+ return context.createSuccessResponse(id, {
5
+ context: { slot: Number(context.slot) },
6
+ value: {
7
+ feeRateGovernor: {
8
+ burnPercent: 50,
9
+ maxLamportsPerSignature: 100000,
10
+ minLamportsPerSignature: 5000,
11
+ targetLamportsPerSignature: 10000,
12
+ targetSignaturesPerSlot: 20000,
13
+ },
14
+ },
15
+ });
16
+ };
@@ -0,0 +1,14 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const getFees: RpcMethodHandler = (id, _params, context) => {
4
+ const blockhash = context.svm.latestBlockhash();
5
+ return context.createSuccessResponse(id, {
6
+ context: { slot: Number(context.slot) },
7
+ value: {
8
+ blockhash,
9
+ feeCalculator: { lamportsPerSignature: 5000 },
10
+ lastValidSlot: Number(context.slot) + 150,
11
+ lastValidBlockHeight: Number(context.slot) + 150,
12
+ },
13
+ });
14
+ };