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,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";
@@ -0,0 +1,70 @@
1
+ import {
2
+ getMetadataPointerState,
3
+ MINT_SIZE,
4
+ TOKEN_2022_PROGRAM_ID,
5
+ TOKEN_PROGRAM_ID,
6
+ unpackMint,
7
+ } from "@solana/spl-token";
8
+ import type { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
9
+
10
+ import type { RpcMethodContext } from "../../types";
11
+
12
+ export async function cloneMintExtensionAccounts(
13
+ conn: Connection,
14
+ context: RpcMethodContext,
15
+ mint: PublicKey,
16
+ info: AccountInfo<Buffer>,
17
+ ): Promise<void> {
18
+ if (!info.data || info.data.length <= MINT_SIZE) return;
19
+ try {
20
+ const programId = info.owner.equals(TOKEN_2022_PROGRAM_ID)
21
+ ? TOKEN_2022_PROGRAM_ID
22
+ : TOKEN_PROGRAM_ID;
23
+ const mintState = unpackMint(mint, info, programId);
24
+ if (!mintState.tlvData || mintState.tlvData.length === 0) return;
25
+
26
+ const metadataPtr = getMetadataPointerState(mintState);
27
+ if (metadataPtr?.metadataAddress) {
28
+ await cloneSingleAccount(
29
+ conn,
30
+ context,
31
+ metadataPtr.metadataAddress,
32
+ "metadata",
33
+ );
34
+ }
35
+ } catch (error) {
36
+ console.warn("[admin] mint extension clone skipped", {
37
+ mint: mint.toBase58(),
38
+ error: String(error),
39
+ });
40
+ }
41
+ }
42
+
43
+ export async function cloneSingleAccount(
44
+ conn: Connection,
45
+ context: RpcMethodContext,
46
+ address: PublicKey,
47
+ label: string,
48
+ ): Promise<void> {
49
+ try {
50
+ const info = await conn.getAccountInfo(address, "confirmed");
51
+ if (!info) {
52
+ console.warn(`[admin] ${label} account not found on endpoint`, {
53
+ address: address.toBase58(),
54
+ });
55
+ return;
56
+ }
57
+ context.svm.setAccount(address, {
58
+ data: new Uint8Array(info.data as Buffer),
59
+ executable: info.executable,
60
+ lamports: Number(info.lamports),
61
+ owner: info.owner,
62
+ rentEpoch: 0,
63
+ });
64
+ } catch (error) {
65
+ console.warn(`[admin] clone ${label} account failed`, {
66
+ address: address.toBase58(),
67
+ error: String(error),
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,10 @@
1
+ export { solforgeAdoptMintAuthority } from "./adopt-mint-authority";
2
+ export { solforgeAdminCloneProgram } from "./clone-program";
3
+ export { solforgeAdminCloneProgramAccounts } from "./clone-program-accounts";
4
+ export { solforgeAdminCloneTokenAccounts } from "./clone-token-accounts";
5
+ export { solforgeAdminCloneTokenMint } from "./clone-token-mint";
6
+ export { solforgeCreateMint } from "./create-mint";
7
+ export { solforgeCreateTokenAccount } from "./create-token-account";
8
+ export { solforgeListMints } from "./list-mints";
9
+ export { solforgeLoadProgram } from "./load-program";
10
+ export { solforgeMintTo } from "./mint-to";
@@ -0,0 +1,21 @@
1
+ import type { RpcMethodHandler } from "../../types";
2
+
3
+ export const solforgeListMints: RpcMethodHandler = async (
4
+ id,
5
+ _params,
6
+ context,
7
+ ) => {
8
+ try {
9
+ const list = context.listMints ? context.listMints() : [];
10
+ return context.createSuccessResponse(id, list);
11
+ } catch (e) {
12
+ return context.createErrorResponse(
13
+ id,
14
+ -32603,
15
+ "List mints failed",
16
+ (e as Error)?.message || String(e),
17
+ );
18
+ }
19
+ };
20
+
21
+ export type { RpcMethodHandler } from "../../types";