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,55 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodHandler } from "../../types";
3
+
4
+ export const solforgeAdminCloneProgramAccounts: RpcMethodHandler = async (
5
+ id,
6
+ params,
7
+ context,
8
+ ) => {
9
+ const [programId, options] = params as [
10
+ string,
11
+ { endpoint?: string; limit?: number; filters?: unknown[] }?,
12
+ ];
13
+ if (!programId)
14
+ return context.createErrorResponse(
15
+ id,
16
+ -32602,
17
+ "Invalid params: programId required",
18
+ );
19
+ const endpoint = options?.endpoint || "https://api.mainnet-beta.solana.com";
20
+ const limit = options?.limit
21
+ ? Math.max(1, Math.min(10000, options.limit))
22
+ : undefined;
23
+ try {
24
+ const conn = new Connection(endpoint, "confirmed");
25
+ const pid = new PublicKey(programId);
26
+ const list = await conn.getProgramAccounts(pid, {
27
+ commitment: "confirmed",
28
+ // @ts-expect-error: filters type is loose
29
+ filters: Array.isArray(options?.filters) ? options?.filters : undefined,
30
+ });
31
+ let count = 0;
32
+ for (const { pubkey, account } of list.slice(0, limit ?? list.length)) {
33
+ try {
34
+ context.svm.setAccount(pubkey, {
35
+ data: new Uint8Array(account.data as Buffer),
36
+ executable: account.executable,
37
+ lamports: Number(account.lamports),
38
+ owner: account.owner,
39
+ rentEpoch: 0,
40
+ });
41
+ count++;
42
+ } catch {}
43
+ }
44
+ return context.createSuccessResponse(id, { ok: true, count });
45
+ } catch (e) {
46
+ return context.createErrorResponse(
47
+ id,
48
+ -32603,
49
+ "Clone program accounts failed",
50
+ (e as Error)?.message || String(e),
51
+ );
52
+ }
53
+ };
54
+
55
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,152 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
2
+ import type { RpcMethodHandler } from "../../types";
3
+ import { parseUpgradeableLoader } from "../account/parsers/loader-upgradeable";
4
+
5
+ export const solforgeAdminCloneProgram: RpcMethodHandler = async (
6
+ id,
7
+ params,
8
+ context,
9
+ ) => {
10
+ const [programId, options] = params as [
11
+ string,
12
+ { endpoint?: string; withAccounts?: boolean; accountsLimit?: number }?,
13
+ ];
14
+ if (!programId)
15
+ return context.createErrorResponse(
16
+ id,
17
+ -32602,
18
+ "Invalid params: programId required",
19
+ );
20
+ const endpoint = options?.endpoint || "https://api.mainnet-beta.solana.com";
21
+ try {
22
+ const conn = new Connection(endpoint, "confirmed");
23
+ const pid = new PublicKey(programId);
24
+ const info = await conn.getAccountInfo(pid, "confirmed");
25
+ if (!info)
26
+ return context.createErrorResponse(
27
+ id,
28
+ -32004,
29
+ "Program account not found on endpoint",
30
+ { programId, endpoint },
31
+ );
32
+
33
+ console.log("[admin] clone program start", {
34
+ programId: pid.toBase58(),
35
+ owner: info.owner.toBase58(),
36
+ exec: info.executable,
37
+ dataLen: info.data?.length ?? 0,
38
+ });
39
+ const ownerStr = info.owner.toBase58();
40
+ let addSource: "programData" | "program" | null = null;
41
+
42
+ // If upgradeable loader: fetch program data, extract ELF and addProgram
43
+ const parsed = parseUpgradeableLoader(
44
+ ownerStr,
45
+ new Uint8Array(info.data),
46
+ context,
47
+ );
48
+ if (parsed?.parsed?.type === "program") {
49
+ const programDataAddr = parsed.parsed.info?.programData as
50
+ | string
51
+ | undefined;
52
+ if (programDataAddr) {
53
+ const pda = new PublicKey(programDataAddr);
54
+ const pinfo = await conn.getAccountInfo(pda, "confirmed");
55
+ if (pinfo) {
56
+ const pdataParsed = parseUpgradeableLoader(
57
+ ownerStr,
58
+ new Uint8Array(pinfo.data),
59
+ context,
60
+ );
61
+ const base64 = pdataParsed?.parsed?.info?.data?.[0] as
62
+ | string
63
+ | undefined;
64
+ if (base64) {
65
+ const bytes = Uint8Array.from(Buffer.from(base64, "base64"));
66
+ try {
67
+ context.svm.addProgram(pid, bytes);
68
+ addSource = "programData";
69
+ } catch (e) {
70
+ console.warn("[admin] addProgram failed (programData bytes)", e);
71
+ return context.createErrorResponse(
72
+ id,
73
+ -32603,
74
+ "Clone program failed",
75
+ {
76
+ message: String(e),
77
+ programId,
78
+ endpoint,
79
+ source: "programData",
80
+ },
81
+ );
82
+ }
83
+ } else {
84
+ console.warn("[admin] programData bytes missing");
85
+ return context.createErrorResponse(
86
+ id,
87
+ -32603,
88
+ "Clone program failed",
89
+ {
90
+ message: "ProgramData bytes missing",
91
+ programId,
92
+ endpoint,
93
+ },
94
+ );
95
+ }
96
+ }
97
+ }
98
+ } else {
99
+ // Legacy loaders keep ELF in the program account directly
100
+ try {
101
+ context.svm.addProgram(pid, new Uint8Array(info.data));
102
+ addSource = "program";
103
+ } catch (e) {
104
+ console.warn("[admin] addProgram failed (program account data)", e);
105
+ return context.createErrorResponse(id, -32603, "Clone program failed", {
106
+ message: String(e),
107
+ programId,
108
+ endpoint,
109
+ source: "program",
110
+ });
111
+ }
112
+ }
113
+
114
+ // Optionally clone owned accounts
115
+ if (options?.withAccounts) {
116
+ const { solforgeAdminCloneProgramAccounts } = await import(
117
+ "./clone-program-accounts"
118
+ );
119
+ const res = await solforgeAdminCloneProgramAccounts(
120
+ id,
121
+ [programId, { endpoint, limit: options.accountsLimit }],
122
+ context,
123
+ );
124
+ void res;
125
+ }
126
+
127
+ console.log("[admin] clone program done", {
128
+ programId: pid.toBase58(),
129
+ added: true,
130
+ source: addSource,
131
+ });
132
+ try {
133
+ context.registerProgram?.(pid);
134
+ } catch {}
135
+ return context.createSuccessResponse(id, {
136
+ ok: true,
137
+ programId,
138
+ added: true,
139
+ source: addSource,
140
+ });
141
+ } catch (e) {
142
+ console.error("[admin] clone program error", e);
143
+ return context.createErrorResponse(id, -32603, "Clone program failed", {
144
+ message: (e as Error)?.message || String(e),
145
+ stack: (e as Error)?.stack,
146
+ programId,
147
+ endpoint,
148
+ });
149
+ }
150
+ };
151
+
152
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,117 @@
1
+ import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
2
+ import { Connection, PublicKey } from "@solana/web3.js";
3
+ import type { RpcMethodHandler } from "../../types";
4
+
5
+ export const solforgeAdminCloneTokenAccounts: RpcMethodHandler = async (
6
+ id,
7
+ params,
8
+ context,
9
+ ) => {
10
+ const [mint, options] = params as [
11
+ string,
12
+ { endpoint?: string; holders?: number; allAccounts?: boolean }?,
13
+ ];
14
+ if (!mint)
15
+ return context.createErrorResponse(
16
+ id,
17
+ -32602,
18
+ "Invalid params: mint required",
19
+ );
20
+ const endpoint = options?.endpoint || "https://api.mainnet-beta.solana.com";
21
+ const limit =
22
+ options?.holders && !options?.allAccounts
23
+ ? Math.max(1, Math.min(10000, options.holders))
24
+ : undefined;
25
+ try {
26
+ const conn = new Connection(endpoint, "confirmed");
27
+ const mintPk = new PublicKey(mint);
28
+ let accounts: Array<{
29
+ pubkey: PublicKey;
30
+ data: Buffer;
31
+ lamports: number;
32
+ owner: PublicKey;
33
+ executable: boolean;
34
+ rentEpoch: number;
35
+ }>;
36
+ if (options?.allAccounts) {
37
+ const list = await conn.getProgramAccounts(TOKEN_PROGRAM_ID, {
38
+ commitment: "confirmed",
39
+ filters: [
40
+ { dataSize: 165 },
41
+ { memcmp: { offset: 0, bytes: mintPk.toBase58() } },
42
+ ],
43
+ });
44
+ accounts = list.map(({ pubkey, account }) => ({
45
+ pubkey,
46
+ data: account.data as Buffer,
47
+ lamports: account.lamports,
48
+ owner: account.owner,
49
+ executable: account.executable,
50
+ rentEpoch: account.rentEpoch,
51
+ }));
52
+ } else if (typeof limit === "number") {
53
+ const largest = await conn.getTokenLargestAccounts(mintPk, "confirmed");
54
+ const addrs = largest.value.slice(0, limit).map((x) => x.address);
55
+ const multi = await conn.getMultipleAccountsInfo(addrs, {
56
+ commitment: "confirmed",
57
+ });
58
+ accounts = [];
59
+ for (let i = 0; i < addrs.length; i++) {
60
+ const info = multi[i];
61
+ const pk = addrs[i];
62
+ if (!info || !pk) continue;
63
+ accounts.push({
64
+ pubkey: pk,
65
+ data: info.data as Buffer,
66
+ lamports: info.lamports,
67
+ owner: info.owner,
68
+ executable: info.executable,
69
+ rentEpoch: info.rentEpoch,
70
+ });
71
+ }
72
+ } else {
73
+ const largest = await conn.getTokenLargestAccounts(mintPk, "confirmed");
74
+ const addrs = largest.value.slice(0, 100).map((x) => x.address);
75
+ const multi = await conn.getMultipleAccountsInfo(addrs, {
76
+ commitment: "confirmed",
77
+ });
78
+ accounts = [];
79
+ for (let i = 0; i < addrs.length; i++) {
80
+ const info = multi[i];
81
+ const pk = addrs[i];
82
+ if (!info || !pk) continue;
83
+ accounts.push({
84
+ pubkey: pk,
85
+ data: info.data as Buffer,
86
+ lamports: info.lamports,
87
+ owner: info.owner,
88
+ executable: info.executable,
89
+ rentEpoch: info.rentEpoch,
90
+ });
91
+ }
92
+ }
93
+ let count = 0;
94
+ for (const a of accounts) {
95
+ try {
96
+ context.svm.setAccount(a.pubkey, {
97
+ data: new Uint8Array(a.data),
98
+ executable: a.executable,
99
+ lamports: Number(a.lamports),
100
+ owner: a.owner,
101
+ rentEpoch: 0,
102
+ });
103
+ count++;
104
+ } catch {}
105
+ }
106
+ return context.createSuccessResponse(id, { ok: true, count });
107
+ } catch (e) {
108
+ return context.createErrorResponse(
109
+ id,
110
+ -32603,
111
+ "Clone token accounts failed",
112
+ (e as Error)?.message || String(e),
113
+ );
114
+ }
115
+ };
116
+
117
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,82 @@
1
+ import { MINT_SIZE, MintLayout } from "@solana/spl-token";
2
+ import type { AccountInfo } from "@solana/web3.js";
3
+ import { Connection, PublicKey } from "@solana/web3.js";
4
+ import type { RpcMethodHandler } from "../../types";
5
+ import { cloneMintExtensionAccounts } from "./helpers";
6
+
7
+ export const solforgeAdminCloneTokenMint: RpcMethodHandler = async (
8
+ id,
9
+ params,
10
+ context,
11
+ ) => {
12
+ const [mint, options] = params as [string, { endpoint?: string }?];
13
+ if (!mint)
14
+ return context.createErrorResponse(
15
+ id,
16
+ -32602,
17
+ "Invalid params: mint required",
18
+ );
19
+ const endpoint = options?.endpoint || "https://api.mainnet-beta.solana.com";
20
+ try {
21
+ const conn = new Connection(endpoint, "confirmed");
22
+ const mintPk = new PublicKey(mint);
23
+ console.log(`[admin] clone mint start`, {
24
+ mint: mintPk.toBase58(),
25
+ endpoint,
26
+ });
27
+ const info = await conn.getAccountInfo(mintPk, "confirmed");
28
+ if (!info) {
29
+ console.warn(`[admin] clone mint: account not found`, {
30
+ mint: mintPk.toBase58(),
31
+ endpoint,
32
+ });
33
+ return context.createErrorResponse(
34
+ id,
35
+ -32004,
36
+ "Mint account not found on endpoint",
37
+ { endpoint, mint },
38
+ );
39
+ }
40
+ try {
41
+ const dec = MintLayout.decode(
42
+ (info.data as Buffer).slice(0, MINT_SIZE),
43
+ ).decimals;
44
+ console.log(`[admin] clone mint fetched`, {
45
+ owner: info.owner.toBase58(),
46
+ dataLen: info.data.length,
47
+ decimals: dec,
48
+ lamports: info.lamports,
49
+ });
50
+ } catch {}
51
+ // Write raw account into LiteSVM
52
+ context.svm.setAccount(mintPk, {
53
+ data: new Uint8Array(info.data),
54
+ executable: info.executable,
55
+ lamports: Number(info.lamports),
56
+ owner: info.owner,
57
+ rentEpoch: 0,
58
+ });
59
+
60
+ await cloneMintExtensionAccounts(
61
+ conn,
62
+ context,
63
+ mintPk,
64
+ info as AccountInfo<Buffer>,
65
+ );
66
+ try {
67
+ context.registerMint?.(mintPk);
68
+ } catch {}
69
+ console.log(`[admin] clone mint done`, { mint: mintPk.toBase58() });
70
+ return context.createSuccessResponse(id, { ok: true, address: mint });
71
+ } catch (e) {
72
+ console.error(`[admin] clone mint error`, e);
73
+ return context.createErrorResponse(id, -32603, "Clone mint failed", {
74
+ message: (e as Error)?.message || String(e),
75
+ stack: (e as Error)?.stack,
76
+ endpoint,
77
+ mint,
78
+ });
79
+ }
80
+ };
81
+
82
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,114 @@
1
+ import { MINT_SIZE, MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
2
+ import { PublicKey } from "@solana/web3.js";
3
+ import type { RpcMethodHandler } from "../../types";
4
+
5
+ // Create a new SPL Mint locally with given decimals and mint authority
6
+ export const solforgeCreateMint: RpcMethodHandler = async (
7
+ id,
8
+ params,
9
+ context,
10
+ ) => {
11
+ try {
12
+ const [mintStr, decimals, authorityStr] = params as [
13
+ string | null | undefined,
14
+ number,
15
+ string | null | undefined,
16
+ ];
17
+ if (typeof decimals !== "number" || decimals < 0 || decimals > 18)
18
+ return context.createErrorResponse(
19
+ id,
20
+ -32602,
21
+ "Invalid params: decimals required (0-18)",
22
+ );
23
+ const authority = authorityStr
24
+ ? new PublicKey(authorityStr)
25
+ : context.getFaucet().publicKey;
26
+ const mintPk = mintStr ? new PublicKey(mintStr) : PublicKey.unique();
27
+
28
+ const buf = Buffer.alloc(MINT_SIZE);
29
+ type MintStruct = Parameters<typeof MintLayout.encode>[0];
30
+ const initialMint = {
31
+ mintAuthorityOption: 1,
32
+ mintAuthority: authority,
33
+ supply: 0n,
34
+ decimals,
35
+ isInitialized: true,
36
+ freezeAuthorityOption: 0,
37
+ freezeAuthority: PublicKey.default,
38
+ } satisfies Partial<MintStruct> as MintStruct;
39
+ MintLayout.encode(initialMint, buf);
40
+
41
+ const rentLamports = Number(
42
+ context.svm.minimumBalanceForRentExemption(BigInt(MINT_SIZE)),
43
+ );
44
+ context.svm.setAccount(mintPk, {
45
+ lamports: rentLamports,
46
+ data: new Uint8Array(buf),
47
+ owner: TOKEN_PROGRAM_ID,
48
+ executable: false,
49
+ rentEpoch: 0,
50
+ });
51
+ try {
52
+ context.registerMint?.(mintPk);
53
+ } catch {}
54
+ try {
55
+ await context.store?.upsertAccounts([
56
+ {
57
+ address: mintPk.toBase58(),
58
+ lamports: rentLamports,
59
+ ownerProgram: TOKEN_PROGRAM_ID.toBase58(),
60
+ executable: false,
61
+ rentEpoch: 0,
62
+ dataLen: MINT_SIZE,
63
+ dataBase64: undefined,
64
+ lastSlot: Number(context.slot),
65
+ },
66
+ ]);
67
+ } catch {}
68
+ // Synthetic transaction for explorers
69
+ try {
70
+ const sig = `admin:create-mint:${mintPk.toBase58()}:${Date.now()}`;
71
+ await context.store?.insertTransactionBundle({
72
+ signature: sig,
73
+ slot: Number(context.slot),
74
+ blockTime: Math.floor(Date.now() / 1000),
75
+ version: "legacy",
76
+ fee: 0,
77
+ err: null,
78
+ rawBase64: "",
79
+ preBalances: [],
80
+ postBalances: [],
81
+ logs: ["admin create mint"],
82
+ accounts: [
83
+ {
84
+ address: mintPk.toBase58(),
85
+ index: 0,
86
+ signer: false,
87
+ writable: true,
88
+ },
89
+ {
90
+ address: authority.toBase58(),
91
+ index: 1,
92
+ signer: false,
93
+ writable: false,
94
+ },
95
+ ],
96
+ });
97
+ } catch {}
98
+ return context.createSuccessResponse(id, {
99
+ ok: true,
100
+ mint: mintPk.toBase58(),
101
+ decimals,
102
+ authority: authority.toBase58(),
103
+ });
104
+ } catch (e) {
105
+ return context.createErrorResponse(
106
+ id,
107
+ -32603,
108
+ "Create mint failed",
109
+ (e as Error)?.message || String(e),
110
+ );
111
+ }
112
+ };
113
+
114
+ export type { RpcMethodHandler } from "../../types";
@@ -0,0 +1,137 @@
1
+ import {
2
+ ACCOUNT_SIZE,
3
+ AccountLayout,
4
+ getAssociatedTokenAddressSync,
5
+ TOKEN_PROGRAM_ID,
6
+ } from "@solana/spl-token";
7
+ import { PublicKey } from "@solana/web3.js";
8
+ import type { RpcMethodHandler } from "../../types";
9
+
10
+ // Create or overwrite a token account (ATA) with a specified amount
11
+ export const solforgeCreateTokenAccount: RpcMethodHandler = async (
12
+ id,
13
+ params,
14
+ context,
15
+ ) => {
16
+ try {
17
+ const [mintStr, ownerStr, rawAmount, decimals] = params as [
18
+ string,
19
+ string | null | undefined,
20
+ number | string | bigint,
21
+ number?,
22
+ ];
23
+ if (!mintStr || rawAmount == null)
24
+ return context.createErrorResponse(
25
+ id,
26
+ -32602,
27
+ "Invalid params: mint and amount required",
28
+ );
29
+ const mint = new PublicKey(mintStr);
30
+ const owner = ownerStr
31
+ ? new PublicKey(ownerStr)
32
+ : context.getFaucet().publicKey;
33
+ // Amount is in base units (not UI). The layout stores a u64 bigint
34
+ const amount =
35
+ typeof rawAmount === "bigint" ? rawAmount : BigInt(rawAmount);
36
+
37
+ const buf = Buffer.alloc(ACCOUNT_SIZE);
38
+ type TokenAccountStruct = Parameters<typeof AccountLayout.encode>[0];
39
+ const tokenAccount = {
40
+ mint,
41
+ owner,
42
+ amount,
43
+ delegateOption: 0,
44
+ delegate: PublicKey.default,
45
+ delegatedAmount: 0n,
46
+ state: 1,
47
+ isNativeOption: 0,
48
+ isNative: 0n,
49
+ closeAuthorityOption: 0,
50
+ closeAuthority: PublicKey.default,
51
+ } satisfies Partial<TokenAccountStruct> as TokenAccountStruct;
52
+ AccountLayout.encode(tokenAccount, buf);
53
+
54
+ const rentLamports = Number(
55
+ context.svm.minimumBalanceForRentExemption(BigInt(ACCOUNT_SIZE)),
56
+ );
57
+
58
+ // Compute the canonical associated token account address for mint+owner
59
+ const address = getAssociatedTokenAddressSync(mint, owner, true);
60
+
61
+ context.svm.setAccount(address, {
62
+ lamports: rentLamports,
63
+ data: new Uint8Array(buf),
64
+ owner: TOKEN_PROGRAM_ID,
65
+ executable: false,
66
+ rentEpoch: 0,
67
+ });
68
+ try {
69
+ await context.store?.upsertAccounts([
70
+ {
71
+ address: address.toBase58(),
72
+ lamports: rentLamports,
73
+ ownerProgram: TOKEN_PROGRAM_ID.toBase58(),
74
+ executable: false,
75
+ rentEpoch: 0,
76
+ dataLen: ACCOUNT_SIZE,
77
+ dataBase64: undefined,
78
+ lastSlot: Number(context.slot),
79
+ },
80
+ ]);
81
+ } catch {}
82
+ // Record a synthetic transaction so explorers can show activity
83
+ try {
84
+ const sig = `admin:mint:${address.toBase58()}:${Date.now()}`;
85
+ await context.store?.insertTransactionBundle({
86
+ signature: sig,
87
+ slot: Number(context.slot),
88
+ blockTime: Math.floor(Date.now() / 1000),
89
+ version: "legacy",
90
+ fee: 0,
91
+ err: null,
92
+ rawBase64: "",
93
+ preBalances: [],
94
+ postBalances: [],
95
+ logs: ["admin mint"],
96
+ accounts: [
97
+ {
98
+ address: address.toBase58(),
99
+ index: 0,
100
+ signer: false,
101
+ writable: true,
102
+ },
103
+ {
104
+ address: mint.toBase58(),
105
+ index: 1,
106
+ signer: false,
107
+ writable: false,
108
+ },
109
+ {
110
+ address: owner.toBase58(),
111
+ index: 2,
112
+ signer: false,
113
+ writable: false,
114
+ },
115
+ ],
116
+ });
117
+ } catch {}
118
+
119
+ return context.createSuccessResponse(id, {
120
+ ok: true,
121
+ address: address.toBase58(),
122
+ mint: mintStr,
123
+ owner: owner.toBase58(),
124
+ amount: amount.toString(),
125
+ decimals: decimals ?? null,
126
+ });
127
+ } catch (e) {
128
+ return context.createErrorResponse(
129
+ id,
130
+ -32603,
131
+ "Create token account failed",
132
+ (e as Error)?.message || String(e),
133
+ );
134
+ }
135
+ };
136
+
137
+ export type { RpcMethodHandler } from "../../types";