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,127 @@
1
+ export interface ApiConfig {
2
+ rpcUrl: string;
3
+ }
4
+
5
+ export interface ApiStatus {
6
+ slot: number;
7
+ slotBigint: string;
8
+ blockHeight: number;
9
+ blockHeightBigint: string;
10
+ txCount: number;
11
+ txCountBigint: string;
12
+ latestBlockhash: string;
13
+ faucet: {
14
+ address: string;
15
+ lamports: string;
16
+ sol: number;
17
+ };
18
+ }
19
+
20
+ export interface ProgramSummary {
21
+ programId: string;
22
+ owner: string;
23
+ executable: boolean;
24
+ dataLen: number;
25
+ lamports: string;
26
+ }
27
+
28
+ export interface TokenSummary {
29
+ mint: string;
30
+ supply: string;
31
+ decimals: number;
32
+ uiAmount: number;
33
+ uiAmountString: string;
34
+ mintAuthority: string | null;
35
+ isInitialized: boolean;
36
+ }
37
+
38
+ export interface AirdropPayload {
39
+ address: string;
40
+ lamports?: string | number;
41
+ sol?: string | number;
42
+ }
43
+
44
+ export interface MintPayload {
45
+ mint: string;
46
+ owner: string;
47
+ amountRaw: string;
48
+ }
49
+
50
+ export interface CloneProgramPayload {
51
+ programId: string;
52
+ endpoint?: string;
53
+ withAccounts?: boolean;
54
+ accountsLimit?: number;
55
+ }
56
+
57
+ export interface CloneTokenPayload {
58
+ mint: string;
59
+ endpoint?: string;
60
+ cloneAccounts?: boolean;
61
+ holders?: number;
62
+ allAccounts?: boolean;
63
+ }
64
+
65
+ interface ApiError {
66
+ error?: string;
67
+ details?: unknown;
68
+ }
69
+
70
+ async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
71
+ const headers = new Headers(init.headers ?? {});
72
+ if (!headers.has("content-type") && init.body)
73
+ headers.set("content-type", "application/json");
74
+ const response = await fetch(path, { ...init, headers });
75
+ let payload: any = null;
76
+ const text = await response.text();
77
+ if (text) {
78
+ try {
79
+ payload = JSON.parse(text);
80
+ } catch (error) {
81
+ throw new Error(
82
+ `Failed to parse response from ${path}: ${String(error)}`,
83
+ );
84
+ }
85
+ }
86
+ if (!response.ok) {
87
+ const errPayload = payload as ApiError | null;
88
+ const message =
89
+ errPayload?.error || response.statusText || "Request failed";
90
+ throw new Error(message);
91
+ }
92
+ return payload as T;
93
+ }
94
+
95
+ export const fetchConfig = () => request<ApiConfig>("/api/config");
96
+ export const fetchStatus = () => request<ApiStatus>("/api/status");
97
+ export const fetchPrograms = () => request<ProgramSummary[]>("/api/programs");
98
+ export const fetchTokens = () => request<TokenSummary[]>("/api/tokens");
99
+ export const submitAirdrop = (body: AirdropPayload) =>
100
+ request<{ ok: boolean; signature?: string }>("/api/airdrop", {
101
+ method: "POST",
102
+ body: JSON.stringify(body),
103
+ });
104
+ export const submitMint = (body: MintPayload) =>
105
+ request<
106
+ | {
107
+ ok: boolean;
108
+ signature?: string;
109
+ mint: string;
110
+ owner: string;
111
+ amount: string;
112
+ }
113
+ | Record<string, unknown>
114
+ >("/api/mint", {
115
+ method: "POST",
116
+ body: JSON.stringify(body),
117
+ });
118
+ export const cloneProgram = (body: CloneProgramPayload) =>
119
+ request<Record<string, unknown>>("/api/clone/program", {
120
+ method: "POST",
121
+ body: JSON.stringify(body),
122
+ });
123
+ export const cloneToken = (body: CloneTokenPayload) =>
124
+ request<Record<string, unknown>>("/api/clone/token", {
125
+ method: "POST",
126
+ body: JSON.stringify(body),
127
+ });
@@ -0,0 +1,390 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import {
3
+ type ApiConfig,
4
+ type ApiStatus,
5
+ cloneProgram,
6
+ cloneToken,
7
+ fetchConfig,
8
+ fetchPrograms,
9
+ fetchStatus,
10
+ fetchTokens,
11
+ type ProgramSummary,
12
+ submitAirdrop,
13
+ submitMint,
14
+ type TokenSummary,
15
+ } from "./api";
16
+ import { AirdropMintForm } from "./components/airdrop-mint-form";
17
+ import { CloneProgramModal } from "./components/clone-program-modal";
18
+ import { CloneTokenModal } from "./components/clone-token-modal";
19
+ import { ProgramsPanel } from "./components/programs-panel";
20
+ import { StatusPanel } from "./components/status-panel";
21
+ import { TokensPanel } from "./components/tokens-panel";
22
+ import { useInterval } from "./hooks/use-interval";
23
+
24
+ export function App() {
25
+ const [config, setConfig] = useState<ApiConfig | null>(null);
26
+ const [status, setStatus] = useState<ApiStatus | null>(null);
27
+ const [programs, setPrograms] = useState<ProgramSummary[]>([]);
28
+ const [tokens, setTokens] = useState<TokenSummary[]>([]);
29
+ const [loadingStatus, setLoadingStatus] = useState(false);
30
+ const [loadingPrograms, setLoadingPrograms] = useState(false);
31
+ const [loadingTokens, setLoadingTokens] = useState(false);
32
+ const [programModalOpen, setProgramModalOpen] = useState(false);
33
+ const [tokenModalOpen, setTokenModalOpen] = useState(false);
34
+ const [bannerError, setBannerError] = useState<string | null>(null);
35
+ const [sidebarOpen, setSidebarOpen] = useState(false);
36
+ const [activeSection, setActiveSection] = useState("status");
37
+
38
+ const loadConfig = useCallback(async () => {
39
+ try {
40
+ const cfg = await fetchConfig();
41
+ setConfig(cfg);
42
+ setBannerError(null);
43
+ } catch (error: any) {
44
+ setBannerError(error?.message ?? String(error));
45
+ }
46
+ }, []);
47
+
48
+ const loadStatus = useCallback(async () => {
49
+ setLoadingStatus(true);
50
+ try {
51
+ const data = await fetchStatus();
52
+ setStatus(data);
53
+ } catch (error: any) {
54
+ setBannerError(error?.message ?? String(error));
55
+ } finally {
56
+ setLoadingStatus(false);
57
+ }
58
+ }, []);
59
+
60
+ const loadPrograms = useCallback(async () => {
61
+ setLoadingPrograms(true);
62
+ try {
63
+ const data = await fetchPrograms();
64
+ setPrograms(data);
65
+ } catch (error: any) {
66
+ setBannerError(error?.message ?? String(error));
67
+ } finally {
68
+ setLoadingPrograms(false);
69
+ }
70
+ }, []);
71
+
72
+ const loadTokens = useCallback(async () => {
73
+ setLoadingTokens(true);
74
+ try {
75
+ const data = await fetchTokens();
76
+ setTokens(data);
77
+ } catch (error: any) {
78
+ setBannerError(error?.message ?? String(error));
79
+ } finally {
80
+ setLoadingTokens(false);
81
+ }
82
+ }, []);
83
+
84
+ useEffect(() => {
85
+ loadConfig();
86
+ loadStatus();
87
+ loadPrograms();
88
+ loadTokens();
89
+ }, [loadConfig, loadStatus, loadPrograms, loadTokens]);
90
+
91
+ useInterval(loadStatus, 5_000);
92
+
93
+ const onAirdrop = useCallback(
94
+ async (address: string, lamports: string) => {
95
+ const result = await submitAirdrop({ address, lamports });
96
+ await loadStatus();
97
+ return result.signature;
98
+ },
99
+ [loadStatus],
100
+ );
101
+
102
+ const onMint = useCallback(
103
+ async (mint: string, owner: string, amountRaw: string) => {
104
+ const result = await submitMint({ mint, owner, amountRaw });
105
+ await Promise.all([loadStatus(), loadTokens()]);
106
+ if (result && typeof result === "object" && "signature" in result) {
107
+ return (result as { signature?: string }).signature;
108
+ }
109
+ return undefined;
110
+ },
111
+ [loadStatus, loadTokens],
112
+ );
113
+
114
+ const openProgramModal = () => setProgramModalOpen(true);
115
+ const openTokenModal = () => setTokenModalOpen(true);
116
+
117
+ const handleCloneProgram = useCallback(
118
+ async (payload: {
119
+ programId: string;
120
+ endpoint?: string;
121
+ withAccounts: boolean;
122
+ accountsLimit?: number;
123
+ }) => {
124
+ await cloneProgram(payload);
125
+ await loadPrograms();
126
+ },
127
+ [loadPrograms],
128
+ );
129
+
130
+ const handleCloneToken = useCallback(
131
+ async (payload: {
132
+ mint: string;
133
+ endpoint?: string;
134
+ cloneAccounts: boolean;
135
+ holders?: number;
136
+ allAccounts?: boolean;
137
+ }) => {
138
+ await cloneToken(payload);
139
+ await loadTokens();
140
+ },
141
+ [loadTokens],
142
+ );
143
+
144
+ const scrollToSection = (sectionId: string) => {
145
+ setActiveSection(sectionId);
146
+ document.getElementById(sectionId)?.scrollIntoView({ behavior: 'smooth' });
147
+ setSidebarOpen(false);
148
+ };
149
+
150
+ return (
151
+ <div className="min-h-screen relative">
152
+ {/* Mobile Menu Button */}
153
+ <button
154
+ onClick={() => setSidebarOpen(!sidebarOpen)}
155
+ className="lg:hidden fixed top-4 left-4 z-50 btn-icon bg-gradient-to-br from-purple-600 to-violet-600 border-purple-500/30"
156
+ aria-label="Menu"
157
+ >
158
+ <i className={`fas fa-${sidebarOpen ? 'times' : 'bars'} text-white`}></i>
159
+ </button>
160
+
161
+ {/* Sidebar Navigation */}
162
+ <aside className={`fixed top-0 left-0 h-full w-72 glass-panel rounded-none border-r border-white/5 z-40 transition-transform duration-300 ${
163
+ sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
164
+ }`}>
165
+ <div className="p-6 space-y-8">
166
+ {/* Logo */}
167
+ <div className="flex items-center gap-3">
168
+ <div className="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-600 to-violet-600 flex items-center justify-center shadow-lg">
169
+ <i className="fas fa-fire text-white text-xl"></i>
170
+ </div>
171
+ <div>
172
+ <h1 className="text-xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
173
+ SolForge
174
+ </h1>
175
+ <p className="text-xs text-gray-500">Development Suite</p>
176
+ </div>
177
+ </div>
178
+
179
+ {/* Navigation Items */}
180
+ <nav className="space-y-2">
181
+ <button
182
+ onClick={() => scrollToSection('status')}
183
+ className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
184
+ activeSection === 'status'
185
+ ? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
186
+ : 'text-gray-400 hover:bg-white/5'
187
+ }`}
188
+ >
189
+ <i className="fas fa-server w-5"></i>
190
+ <span className="font-medium">Network Status</span>
191
+ </button>
192
+ <button
193
+ onClick={() => scrollToSection('actions')}
194
+ className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
195
+ activeSection === 'actions'
196
+ ? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
197
+ : 'text-gray-400 hover:bg-white/5'
198
+ }`}
199
+ >
200
+ <i className="fas fa-paper-plane w-5"></i>
201
+ <span className="font-medium">Quick Actions</span>
202
+ </button>
203
+ <button
204
+ onClick={() => scrollToSection('programs')}
205
+ className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
206
+ activeSection === 'programs'
207
+ ? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
208
+ : 'text-gray-400 hover:bg-white/5'
209
+ }`}
210
+ >
211
+ <i className="fas fa-code w-5"></i>
212
+ <span className="font-medium">Programs</span>
213
+ </button>
214
+ <button
215
+ onClick={() => scrollToSection('tokens')}
216
+ className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
217
+ activeSection === 'tokens'
218
+ ? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
219
+ : 'text-gray-400 hover:bg-white/5'
220
+ }`}
221
+ >
222
+ <i className="fas fa-coins w-5"></i>
223
+ <span className="font-medium">Tokens</span>
224
+ </button>
225
+ </nav>
226
+
227
+ {/* Quick Stats */}
228
+ {config && (
229
+ <div className="space-y-3">
230
+ <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">Connection</h3>
231
+ <div className="p-3 rounded-xl bg-white/5 border border-white/10">
232
+ <div className="flex items-center gap-2 mb-2">
233
+ <span className="status-dot online"></span>
234
+ <span className="text-xs text-gray-400">Connected</span>
235
+ </div>
236
+ <p className="text-xs text-gray-500 font-mono break-all">{config.rpcUrl}</p>
237
+ </div>
238
+ </div>
239
+ )}
240
+ </div>
241
+ </aside>
242
+
243
+ {/* Overlay for mobile */}
244
+ {sidebarOpen && (
245
+ <div
246
+ className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30 lg:hidden"
247
+ onClick={() => setSidebarOpen(false)}
248
+ />
249
+ )}
250
+
251
+ {/* Main Content */}
252
+ <main className={`transition-all duration-300 lg:ml-72 p-4 md:p-8`}>
253
+ <div className="max-w-7xl mx-auto space-y-6">
254
+ {/* Header - Only show on desktop */}
255
+ <header className="glass-panel p-6 animate-fadeIn hidden md:block">
256
+ <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
257
+ <div>
258
+ <h2 className="text-3xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
259
+ SolForge Dashboard
260
+ </h2>
261
+ <p className="text-gray-400 mt-1">
262
+ Manage your local Solana development environment
263
+ </p>
264
+ </div>
265
+ <button
266
+ onClick={loadStatus}
267
+ className="btn-secondary"
268
+ >
269
+ <i className={`fas fa-sync-alt ${loadingStatus ? 'animate-spin' : ''}`}></i>
270
+ <span>Refresh All</span>
271
+ </button>
272
+ </div>
273
+
274
+ {/* Error Banner */}
275
+ {bannerError && (
276
+ <div className="mt-6 p-4 rounded-xl bg-gradient-to-r from-red-500/10 to-pink-500/10 border border-red-500/30 flex items-start gap-3 animate-slideIn">
277
+ <i className="fas fa-exclamation-circle text-red-400 mt-0.5"></i>
278
+ <div className="flex-1">
279
+ <p className="text-sm text-red-300">{bannerError}</p>
280
+ </div>
281
+ <button
282
+ onClick={() => setBannerError(null)}
283
+ className="text-red-400 hover:text-red-300"
284
+ aria-label="Close error"
285
+ >
286
+ <i className="fas fa-times"></i>
287
+ </button>
288
+ </div>
289
+ )}
290
+ </header>
291
+
292
+ {/* Mobile Header - Simpler version */}
293
+ <div className="md:hidden mb-4">
294
+ <h2 className="text-2xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
295
+ SolForge
296
+ </h2>
297
+ {bannerError && (
298
+ <div className="mt-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
299
+ <p className="text-xs text-red-300">{bannerError}</p>
300
+ </div>
301
+ )}
302
+ </div>
303
+
304
+ {/* Status Panel */}
305
+ <div id="status" className="animate-fadeIn scroll-mt-24">
306
+ <StatusPanel
307
+ status={status}
308
+ loading={loadingStatus}
309
+ onRefresh={loadStatus}
310
+ />
311
+ </div>
312
+
313
+ {/* Quick Actions - Optional */}
314
+ <div id="actions" className="glass-panel p-6 animate-fadeIn scroll-mt-24" style={{animationDelay: '0.1s'}}>
315
+ <AirdropMintForm
316
+ tokens={tokens}
317
+ onAirdrop={onAirdrop}
318
+ onMint={onMint}
319
+ />
320
+ </div>
321
+
322
+ {/* Programs and Tokens Stacked */}
323
+ <div className="space-y-6">
324
+ <div id="programs" className="animate-fadeIn scroll-mt-24" style={{animationDelay: '0.2s'}}>
325
+ <ProgramsPanel
326
+ programs={programs}
327
+ loading={loadingPrograms}
328
+ onRefresh={loadPrograms}
329
+ onAdd={openProgramModal}
330
+ />
331
+ </div>
332
+ <div id="tokens" className="animate-fadeIn scroll-mt-24" style={{animationDelay: '0.3s'}}>
333
+ <TokensPanel
334
+ tokens={tokens}
335
+ loading={loadingTokens}
336
+ onRefresh={loadTokens}
337
+ onAdd={openTokenModal}
338
+ />
339
+ </div>
340
+ </div>
341
+ </div>
342
+ </main>
343
+
344
+ {/* Modals */}
345
+ <CloneProgramModal
346
+ isOpen={programModalOpen}
347
+ onClose={() => setProgramModalOpen(false)}
348
+ onSubmit={handleCloneProgram}
349
+ />
350
+ <CloneTokenModal
351
+ isOpen={tokenModalOpen}
352
+ onClose={() => setTokenModalOpen(false)}
353
+ onSubmit={handleCloneToken}
354
+ />
355
+
356
+ <style jsx>{`
357
+ @keyframes fadeIn {
358
+ from {
359
+ opacity: 0;
360
+ transform: translateY(20px);
361
+ }
362
+ to {
363
+ opacity: 1;
364
+ transform: translateY(0);
365
+ }
366
+ }
367
+
368
+ @keyframes slideIn {
369
+ from {
370
+ opacity: 0;
371
+ transform: translateX(-20px);
372
+ }
373
+ to {
374
+ opacity: 1;
375
+ transform: translateX(0);
376
+ }
377
+ }
378
+
379
+ .animate-fadeIn {
380
+ animation: fadeIn 0.6s ease-out forwards;
381
+ opacity: 0;
382
+ }
383
+
384
+ .animate-slideIn {
385
+ animation: slideIn 0.4s ease-out forwards;
386
+ }
387
+ `}</style>
388
+ </div>
389
+ );
390
+ }