solforge 0.2.12 → 0.2.14

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 (175) hide show
  1. package/package.json +1 -5
  2. package/start.cjs +19 -23
  3. package/docs/API.md +0 -379
  4. package/docs/CONFIGURATION.md +0 -407
  5. package/docs/bun-single-file-executable.md +0 -585
  6. package/docs/cli-plan.md +0 -154
  7. package/docs/data-indexing-plan.md +0 -214
  8. package/docs/gui-roadmap.md +0 -202
  9. package/scripts/decode-b58.ts +0 -10
  10. package/scripts/install.sh +0 -112
  11. package/server/index.ts +0 -5
  12. package/server/lib/base58.ts +0 -33
  13. package/server/lib/faucet.ts +0 -110
  14. package/server/lib/instruction-parser.ts +0 -328
  15. package/server/lib/parsers/spl-associated-token-account.ts +0 -50
  16. package/server/lib/parsers/spl-token.ts +0 -340
  17. package/server/lib/spl-token.ts +0 -57
  18. package/server/methods/TEMPLATE.md +0 -117
  19. package/server/methods/account/get-account-info.ts +0 -86
  20. package/server/methods/account/get-balance.ts +0 -23
  21. package/server/methods/account/get-multiple-accounts.ts +0 -84
  22. package/server/methods/account/get-parsed-account-info.ts +0 -17
  23. package/server/methods/account/index.ts +0 -12
  24. package/server/methods/account/parsers/index.ts +0 -52
  25. package/server/methods/account/parsers/loader-upgradeable.ts +0 -79
  26. package/server/methods/account/parsers/spl-token.ts +0 -256
  27. package/server/methods/account/parsers/system.ts +0 -4
  28. package/server/methods/account/request-airdrop.ts +0 -271
  29. package/server/methods/admin/adopt-mint-authority.ts +0 -94
  30. package/server/methods/admin/clone-program-accounts.ts +0 -55
  31. package/server/methods/admin/clone-program.ts +0 -152
  32. package/server/methods/admin/clone-token-accounts.ts +0 -117
  33. package/server/methods/admin/clone-token-mint.ts +0 -82
  34. package/server/methods/admin/create-mint.ts +0 -114
  35. package/server/methods/admin/create-token-account.ts +0 -137
  36. package/server/methods/admin/helpers.ts +0 -70
  37. package/server/methods/admin/index.ts +0 -10
  38. package/server/methods/admin/list-mints.ts +0 -21
  39. package/server/methods/admin/load-program.ts +0 -52
  40. package/server/methods/admin/mint-to.ts +0 -266
  41. package/server/methods/block/get-block-height.ts +0 -5
  42. package/server/methods/block/get-block.ts +0 -31
  43. package/server/methods/block/get-blocks-with-limit.ts +0 -19
  44. package/server/methods/block/get-latest-blockhash.ts +0 -12
  45. package/server/methods/block/get-slot.ts +0 -5
  46. package/server/methods/block/index.ts +0 -6
  47. package/server/methods/block/is-blockhash-valid.ts +0 -19
  48. package/server/methods/epoch/get-cluster-nodes.ts +0 -17
  49. package/server/methods/epoch/get-epoch-info.ts +0 -16
  50. package/server/methods/epoch/get-epoch-schedule.ts +0 -15
  51. package/server/methods/epoch/get-highest-snapshot-slot.ts +0 -9
  52. package/server/methods/epoch/get-leader-schedule.ts +0 -8
  53. package/server/methods/epoch/get-max-retransmit-slot.ts +0 -9
  54. package/server/methods/epoch/get-max-shred-insert-slot.ts +0 -9
  55. package/server/methods/epoch/get-slot-leader.ts +0 -6
  56. package/server/methods/epoch/get-slot-leaders.ts +0 -9
  57. package/server/methods/epoch/get-stake-activation.ts +0 -9
  58. package/server/methods/epoch/get-stake-minimum-delegation.ts +0 -9
  59. package/server/methods/epoch/get-vote-accounts.ts +0 -19
  60. package/server/methods/epoch/index.ts +0 -13
  61. package/server/methods/epoch/minimum-ledger-slot.ts +0 -5
  62. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +0 -12
  63. package/server/methods/fee/get-fee-for-message.ts +0 -8
  64. package/server/methods/fee/get-fee-rate-governor.ts +0 -16
  65. package/server/methods/fee/get-fees.ts +0 -14
  66. package/server/methods/fee/get-recent-prioritization-fees.ts +0 -22
  67. package/server/methods/fee/index.ts +0 -5
  68. package/server/methods/get-address-lookup-table.ts +0 -27
  69. package/server/methods/index.ts +0 -265
  70. package/server/methods/performance/get-recent-performance-samples.ts +0 -25
  71. package/server/methods/performance/get-transaction-count.ts +0 -5
  72. package/server/methods/performance/index.ts +0 -2
  73. package/server/methods/program/get-block-commitment.ts +0 -9
  74. package/server/methods/program/get-block-production.ts +0 -14
  75. package/server/methods/program/get-block-time.ts +0 -21
  76. package/server/methods/program/get-blocks.ts +0 -11
  77. package/server/methods/program/get-first-available-block.ts +0 -9
  78. package/server/methods/program/get-genesis-hash.ts +0 -6
  79. package/server/methods/program/get-identity.ts +0 -6
  80. package/server/methods/program/get-inflation-governor.ts +0 -15
  81. package/server/methods/program/get-inflation-rate.ts +0 -10
  82. package/server/methods/program/get-inflation-reward.ts +0 -12
  83. package/server/methods/program/get-largest-accounts.ts +0 -8
  84. package/server/methods/program/get-parsed-program-accounts.ts +0 -12
  85. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +0 -12
  86. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +0 -12
  87. package/server/methods/program/get-program-accounts.ts +0 -221
  88. package/server/methods/program/get-supply.ts +0 -13
  89. package/server/methods/program/get-token-account-balance.ts +0 -60
  90. package/server/methods/program/get-token-accounts-by-delegate.ts +0 -82
  91. package/server/methods/program/get-token-accounts-by-owner.ts +0 -416
  92. package/server/methods/program/get-token-largest-accounts.ts +0 -81
  93. package/server/methods/program/get-token-supply.ts +0 -39
  94. package/server/methods/program/index.ts +0 -21
  95. package/server/methods/solforge/index.ts +0 -158
  96. package/server/methods/system/get-health.ts +0 -5
  97. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +0 -13
  98. package/server/methods/system/get-version.ts +0 -9
  99. package/server/methods/system/index.ts +0 -3
  100. package/server/methods/transaction/get-confirmed-transaction.ts +0 -11
  101. package/server/methods/transaction/get-parsed-transaction.ts +0 -17
  102. package/server/methods/transaction/get-signature-statuses.ts +0 -79
  103. package/server/methods/transaction/get-signatures-for-address.ts +0 -41
  104. package/server/methods/transaction/get-transaction.ts +0 -639
  105. package/server/methods/transaction/index.ts +0 -7
  106. package/server/methods/transaction/inner-instructions.test.ts +0 -104
  107. package/server/methods/transaction/send-transaction.ts +0 -469
  108. package/server/methods/transaction/simulate-transaction.ts +0 -57
  109. package/server/rpc-server.ts +0 -521
  110. package/server/types.ts +0 -109
  111. package/server/ws-server.ts +0 -178
  112. package/src/api-server-entry.ts +0 -109
  113. package/src/cli/bootstrap.ts +0 -67
  114. package/src/cli/commands/airdrop.ts +0 -37
  115. package/src/cli/commands/config.ts +0 -39
  116. package/src/cli/commands/mint.ts +0 -187
  117. package/src/cli/commands/program-clone.ts +0 -122
  118. package/src/cli/commands/program-load.ts +0 -64
  119. package/src/cli/commands/rpc-start.ts +0 -49
  120. package/src/cli/commands/token-adopt-authority.ts +0 -37
  121. package/src/cli/commands/token-clone.ts +0 -112
  122. package/src/cli/commands/token-create.ts +0 -81
  123. package/src/cli/main.ts +0 -158
  124. package/src/cli/run-solforge.ts +0 -112
  125. package/src/cli/setup-utils.ts +0 -54
  126. package/src/cli/setup-wizard.ts +0 -258
  127. package/src/cli/utils/args.ts +0 -15
  128. package/src/commands/add-program.ts +0 -333
  129. package/src/commands/init.ts +0 -122
  130. package/src/commands/list.ts +0 -136
  131. package/src/commands/mint.ts +0 -287
  132. package/src/commands/start.ts +0 -881
  133. package/src/commands/status.ts +0 -99
  134. package/src/commands/stop.ts +0 -405
  135. package/src/config/index.ts +0 -146
  136. package/src/config/manager.ts +0 -157
  137. package/src/db/index.ts +0 -83
  138. package/src/db/schema/accounts.ts +0 -23
  139. package/src/db/schema/address-signatures.ts +0 -31
  140. package/src/db/schema/index.ts +0 -6
  141. package/src/db/schema/meta-kv.ts +0 -9
  142. package/src/db/schema/transactions.ts +0 -36
  143. package/src/db/schema/tx-account-states.ts +0 -23
  144. package/src/db/schema/tx-accounts.ts +0 -33
  145. package/src/db/tx-store.ts +0 -264
  146. package/src/gui/public/app.css +0 -1556
  147. package/src/gui/public/build/main.css +0 -1569
  148. package/src/gui/public/build/main.js +0 -303
  149. package/src/gui/public/build/main.js.txt +0 -231
  150. package/src/gui/public/index.html +0 -19
  151. package/src/gui/server.ts +0 -296
  152. package/src/gui/src/api.ts +0 -127
  153. package/src/gui/src/app.tsx +0 -441
  154. package/src/gui/src/components/airdrop-mint-form.tsx +0 -246
  155. package/src/gui/src/components/clone-program-modal.tsx +0 -202
  156. package/src/gui/src/components/clone-token-modal.tsx +0 -230
  157. package/src/gui/src/components/modal.tsx +0 -134
  158. package/src/gui/src/components/programs-panel.tsx +0 -124
  159. package/src/gui/src/components/status-panel.tsx +0 -136
  160. package/src/gui/src/components/tokens-panel.tsx +0 -122
  161. package/src/gui/src/hooks/use-interval.ts +0 -17
  162. package/src/gui/src/index.css +0 -557
  163. package/src/gui/src/main.tsx +0 -17
  164. package/src/index.ts +0 -216
  165. package/src/migrations-bundled.ts +0 -23
  166. package/src/rpc/start.ts +0 -44
  167. package/src/services/api-server.ts +0 -504
  168. package/src/services/port-manager.ts +0 -174
  169. package/src/services/process-registry.ts +0 -153
  170. package/src/services/program-cloner.ts +0 -317
  171. package/src/services/token-cloner.ts +0 -811
  172. package/src/services/validator.ts +0 -293
  173. package/src/types/config.ts +0 -110
  174. package/src/utils/shell.ts +0 -110
  175. package/src/utils/token-loader.ts +0 -115
@@ -1,441 +0,0 @@
1
- import { useCallback, useEffect, useId, 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) {
44
- const message = error instanceof Error ? error.message : String(error);
45
- setBannerError(message);
46
- }
47
- }, []);
48
-
49
- const loadStatus = useCallback(async () => {
50
- setLoadingStatus(true);
51
- try {
52
- const data = await fetchStatus();
53
- setStatus(data);
54
- } catch (error) {
55
- const message = error instanceof Error ? error.message : String(error);
56
- setBannerError(message);
57
- } finally {
58
- setLoadingStatus(false);
59
- }
60
- }, []);
61
-
62
- const loadPrograms = useCallback(async () => {
63
- setLoadingPrograms(true);
64
- try {
65
- const data = await fetchPrograms();
66
- setPrograms(data);
67
- } catch (error) {
68
- const message = error instanceof Error ? error.message : String(error);
69
- setBannerError(message);
70
- } finally {
71
- setLoadingPrograms(false);
72
- }
73
- }, []);
74
-
75
- const loadTokens = useCallback(async () => {
76
- setLoadingTokens(true);
77
- try {
78
- const data = await fetchTokens();
79
- setTokens(data);
80
- } catch (error) {
81
- const message = error instanceof Error ? error.message : String(error);
82
- setBannerError(message);
83
- } finally {
84
- setLoadingTokens(false);
85
- }
86
- }, []);
87
-
88
- useEffect(() => {
89
- loadConfig();
90
- loadStatus();
91
- loadPrograms();
92
- loadTokens();
93
- }, [loadConfig, loadStatus, loadPrograms, loadTokens]);
94
-
95
- useInterval(loadStatus, 5_000);
96
-
97
- const onAirdrop = useCallback(
98
- async (address: string, lamports: string) => {
99
- const result = await submitAirdrop({ address, lamports });
100
- await loadStatus();
101
- return result.signature;
102
- },
103
- [loadStatus],
104
- );
105
-
106
- const onMint = useCallback(
107
- async (mint: string, owner: string, amountRaw: string) => {
108
- const result = await submitMint({ mint, owner, amountRaw });
109
- await Promise.all([loadStatus(), loadTokens()]);
110
- if (result && typeof result === "object" && "signature" in result) {
111
- return (result as { signature?: string }).signature;
112
- }
113
- return undefined;
114
- },
115
- [loadStatus, loadTokens],
116
- );
117
-
118
- const openProgramModal = () => setProgramModalOpen(true);
119
- const openTokenModal = () => setTokenModalOpen(true);
120
-
121
- const handleCloneProgram = useCallback(
122
- async (payload: {
123
- programId: string;
124
- endpoint?: string;
125
- withAccounts: boolean;
126
- accountsLimit?: number;
127
- }) => {
128
- await cloneProgram(payload);
129
- await loadPrograms();
130
- },
131
- [loadPrograms],
132
- );
133
-
134
- const handleCloneToken = useCallback(
135
- async (payload: {
136
- mint: string;
137
- endpoint?: string;
138
- cloneAccounts: boolean;
139
- holders?: number;
140
- allAccounts?: boolean;
141
- }) => {
142
- await cloneToken(payload);
143
- await loadTokens();
144
- },
145
- [loadTokens],
146
- );
147
-
148
- type SectionKey = "status" | "actions" | "programs" | "tokens";
149
- const uid = useId();
150
- const sectionIds: Record<SectionKey, string> = {
151
- status: `${uid}-status`,
152
- actions: `${uid}-actions`,
153
- programs: `${uid}-programs`,
154
- tokens: `${uid}-tokens`,
155
- };
156
-
157
- const scrollToSection = (sectionId: SectionKey) => {
158
- setActiveSection(sectionId);
159
- document
160
- .getElementById(sectionIds[sectionId])
161
- ?.scrollIntoView({ behavior: "smooth" });
162
- setSidebarOpen(false);
163
- };
164
-
165
- return (
166
- <div className="min-h-screen relative">
167
- {/* Mobile Menu Button */}
168
- <button
169
- type="button"
170
- onClick={() => setSidebarOpen(!sidebarOpen)}
171
- 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"
172
- aria-label="Menu"
173
- >
174
- <i
175
- className={`fas fa-${sidebarOpen ? "times" : "bars"} text-white`}
176
- ></i>
177
- </button>
178
-
179
- {/* Sidebar Navigation */}
180
- <aside
181
- 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 ${
182
- sidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
183
- }`}
184
- >
185
- <div className="p-6 space-y-8">
186
- {/* Logo */}
187
- <div className="flex items-center gap-3">
188
- <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">
189
- <i className="fas fa-fire text-white text-xl"></i>
190
- </div>
191
- <div>
192
- <h1 className="text-xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
193
- SolForge
194
- </h1>
195
- <p className="text-xs text-gray-500">Development Suite</p>
196
- </div>
197
- </div>
198
-
199
- {/* Navigation Items */}
200
- <nav className="space-y-2">
201
- <button
202
- type="button"
203
- onClick={() => scrollToSection("status")}
204
- className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
205
- activeSection === "status"
206
- ? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
207
- : "text-gray-400 hover:bg-white/5"
208
- }`}
209
- >
210
- <i className="fas fa-server w-5"></i>
211
- <span className="font-medium">Network Status</span>
212
- </button>
213
- <button
214
- type="button"
215
- onClick={() => scrollToSection("actions")}
216
- className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
217
- activeSection === "actions"
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-paper-plane w-5"></i>
223
- <span className="font-medium">Quick Actions</span>
224
- </button>
225
- <button
226
- type="button"
227
- onClick={() => scrollToSection("programs")}
228
- className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
229
- activeSection === "programs"
230
- ? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
231
- : "text-gray-400 hover:bg-white/5"
232
- }`}
233
- >
234
- <i className="fas fa-code w-5"></i>
235
- <span className="font-medium">Programs</span>
236
- </button>
237
- <button
238
- type="button"
239
- onClick={() => scrollToSection("tokens")}
240
- className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
241
- activeSection === "tokens"
242
- ? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
243
- : "text-gray-400 hover:bg-white/5"
244
- }`}
245
- >
246
- <i className="fas fa-coins w-5"></i>
247
- <span className="font-medium">Tokens</span>
248
- </button>
249
- </nav>
250
-
251
- {/* Quick Stats */}
252
- {config && (
253
- <div className="space-y-3">
254
- <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
255
- Connection
256
- </h3>
257
- <div className="p-3 rounded-xl bg-white/5 border border-white/10">
258
- <div className="flex items-center gap-2 mb-2">
259
- <span className="status-dot online"></span>
260
- <span className="text-xs text-gray-400">Connected</span>
261
- </div>
262
- <p className="text-xs text-gray-500 font-mono break-all">
263
- {config.rpcUrl}
264
- </p>
265
- </div>
266
- </div>
267
- )}
268
- </div>
269
- </aside>
270
-
271
- {/* Overlay for mobile */}
272
- {sidebarOpen && (
273
- <button
274
- type="button"
275
- className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30 lg:hidden"
276
- aria-label="Close sidebar overlay"
277
- onClick={() => setSidebarOpen(false)}
278
- onKeyDown={(e) => {
279
- if (e.key === "Escape" || e.key === "Enter" || e.key === " ") {
280
- setSidebarOpen(false);
281
- }
282
- }}
283
- />
284
- )}
285
-
286
- {/* Main Content */}
287
- <main className={`transition-all duration-300 lg:ml-72 p-4 md:p-8`}>
288
- <div className="max-w-7xl mx-auto space-y-6">
289
- {/* Header - Only show on desktop */}
290
- <header className="glass-panel p-6 animate-fadeIn hidden md:block">
291
- <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
292
- <div>
293
- <h2 className="text-3xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
294
- SolForge Dashboard
295
- </h2>
296
- <p className="text-gray-400 mt-1">
297
- Manage your local Solana development environment
298
- </p>
299
- </div>
300
- <button
301
- type="button"
302
- onClick={loadStatus}
303
- className="btn-secondary"
304
- >
305
- <i
306
- className={`fas fa-sync-alt ${loadingStatus ? "animate-spin" : ""}`}
307
- ></i>
308
- <span>Refresh All</span>
309
- </button>
310
- </div>
311
-
312
- {/* Error Banner */}
313
- {bannerError && (
314
- <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">
315
- <i className="fas fa-exclamation-circle text-red-400 mt-0.5"></i>
316
- <div className="flex-1">
317
- <p className="text-sm text-red-300">{bannerError}</p>
318
- </div>
319
- <button
320
- type="button"
321
- onClick={() => setBannerError(null)}
322
- className="text-red-400 hover:text-red-300"
323
- aria-label="Close error"
324
- >
325
- <i className="fas fa-times"></i>
326
- </button>
327
- </div>
328
- )}
329
- </header>
330
-
331
- {/* Mobile Header - Simpler version */}
332
- <div className="md:hidden mb-4">
333
- <h2 className="text-2xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
334
- SolForge
335
- </h2>
336
- {bannerError && (
337
- <div className="mt-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
338
- <p className="text-xs text-red-300">{bannerError}</p>
339
- </div>
340
- )}
341
- </div>
342
-
343
- {/* Status Panel */}
344
- <div id={sectionIds.status} className="animate-fadeIn scroll-mt-24">
345
- <StatusPanel
346
- status={status}
347
- loading={loadingStatus}
348
- onRefresh={loadStatus}
349
- />
350
- </div>
351
-
352
- {/* Quick Actions - Optional */}
353
- <div
354
- id={sectionIds.actions}
355
- className="glass-panel p-6 animate-fadeIn scroll-mt-24"
356
- style={{ animationDelay: "0.1s" }}
357
- >
358
- <AirdropMintForm
359
- tokens={tokens}
360
- onAirdrop={onAirdrop}
361
- onMint={onMint}
362
- />
363
- </div>
364
-
365
- {/* Programs and Tokens Stacked */}
366
- <div className="space-y-6">
367
- <div
368
- id={sectionIds.programs}
369
- className="animate-fadeIn scroll-mt-24"
370
- style={{ animationDelay: "0.2s" }}
371
- >
372
- <ProgramsPanel
373
- programs={programs}
374
- loading={loadingPrograms}
375
- onRefresh={loadPrograms}
376
- onAdd={openProgramModal}
377
- />
378
- </div>
379
- <div
380
- id={sectionIds.tokens}
381
- className="animate-fadeIn scroll-mt-24"
382
- style={{ animationDelay: "0.3s" }}
383
- >
384
- <TokensPanel
385
- tokens={tokens}
386
- loading={loadingTokens}
387
- onRefresh={loadTokens}
388
- onAdd={openTokenModal}
389
- />
390
- </div>
391
- </div>
392
- </div>
393
- </main>
394
-
395
- {/* Modals */}
396
- <CloneProgramModal
397
- isOpen={programModalOpen}
398
- onClose={() => setProgramModalOpen(false)}
399
- onSubmit={handleCloneProgram}
400
- />
401
- <CloneTokenModal
402
- isOpen={tokenModalOpen}
403
- onClose={() => setTokenModalOpen(false)}
404
- onSubmit={handleCloneToken}
405
- />
406
-
407
- <style jsx>{`
408
- @keyframes fadeIn {
409
- from {
410
- opacity: 0;
411
- transform: translateY(20px);
412
- }
413
- to {
414
- opacity: 1;
415
- transform: translateY(0);
416
- }
417
- }
418
-
419
- @keyframes slideIn {
420
- from {
421
- opacity: 0;
422
- transform: translateX(-20px);
423
- }
424
- to {
425
- opacity: 1;
426
- transform: translateX(0);
427
- }
428
- }
429
-
430
- .animate-fadeIn {
431
- animation: fadeIn 0.6s ease-out forwards;
432
- opacity: 0;
433
- }
434
-
435
- .animate-slideIn {
436
- animation: slideIn 0.4s ease-out forwards;
437
- }
438
- `}</style>
439
- </div>
440
- );
441
- }
@@ -1,246 +0,0 @@
1
- import {
2
- type ChangeEvent,
3
- type FormEvent,
4
- useId,
5
- useMemo,
6
- useState,
7
- } from "react";
8
- import type { TokenSummary } from "../api";
9
-
10
- interface Props {
11
- tokens: TokenSummary[];
12
- onAirdrop: (address: string, lamports: string) => Promise<string | undefined>;
13
- onMint: (
14
- mint: string,
15
- owner: string,
16
- amountRaw: string,
17
- ) => Promise<string | undefined>;
18
- }
19
-
20
- const SOL_OPTION = {
21
- value: "SOL",
22
- label: "SOL (Lamports)",
23
- decimals: 9,
24
- } as const;
25
-
26
- const BIGINT_TEN = 10n;
27
-
28
- function toBaseUnits(rawInput: string, decimals: number) {
29
- const input = rawInput.trim();
30
- if (!input) throw new Error("Amount is required");
31
- const negative = input.startsWith("-");
32
- if (negative) throw new Error("Amount must be positive");
33
- const [wholeRaw = "0", fracRaw = ""] = input.split(".");
34
- const whole = wholeRaw.replace(/[^0-9]/g, "") || "0";
35
- const fracClean = fracRaw.replace(/[^0-9]/g, "");
36
- if (fracClean.length > decimals)
37
- throw new Error(`Too many decimal places (max ${decimals})`);
38
- const scale = BIGINT_TEN ** BigInt(decimals);
39
- const wholeValue = BigInt(whole);
40
- const fracPadded = decimals === 0 ? "0" : fracClean.padEnd(decimals, "0");
41
- const fracValue = BigInt(fracPadded || "0");
42
- const total = wholeValue * scale + fracValue;
43
- if (total <= 0n) throw new Error("Amount must be greater than zero");
44
- return total.toString();
45
- }
46
-
47
- function formatTokenLabel(token: TokenSummary) {
48
- const suffix = token.mintAuthority
49
- ? `Authority ${token.mintAuthority.slice(0, 6)}…`
50
- : "No authority";
51
- return `${token.mint.slice(0, 6)}…${token.mint.slice(-4)} · ${token.decimals} dec · ${suffix}`;
52
- }
53
-
54
- export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
55
- const [asset, setAsset] = useState<string>(SOL_OPTION.value);
56
- const [recipient, setRecipient] = useState<string>("");
57
- const [amount, setAmount] = useState<string>("1");
58
- const [pending, setPending] = useState(false);
59
- const [error, setError] = useState<string | null>(null);
60
- const [message, setMessage] = useState<string | null>(null);
61
-
62
- const uid = useId();
63
- const recipientId = `${uid}-recipient`;
64
- const assetId = `${uid}-asset`;
65
- const amountId = `${uid}-amount`;
66
-
67
- const options = useMemo(() => {
68
- const tokenOpts = tokens.map((token) => ({
69
- value: token.mint,
70
- label: formatTokenLabel(token),
71
- decimals: token.decimals,
72
- }));
73
- return [SOL_OPTION, ...tokenOpts];
74
- }, [tokens]);
75
-
76
- const selected = options.find((opt) => opt.value === asset) ?? SOL_OPTION;
77
-
78
- const submit = async () => {
79
- if (!recipient.trim()) throw new Error("Recipient address is required");
80
- const canonicalRecipient = recipient.trim();
81
- if (asset === SOL_OPTION.value) {
82
- const lamports = toBaseUnits(amount, SOL_OPTION.decimals);
83
- const signature = await onAirdrop(canonicalRecipient, lamports);
84
- return signature
85
- ? `Airdrop signature: ${signature}`
86
- : "Airdrop submitted";
87
- }
88
- const raw = toBaseUnits(amount, selected.decimals);
89
- const signature = await onMint(asset, canonicalRecipient, raw);
90
- return signature ? `Mint signature: ${signature}` : "Mint submitted";
91
- };
92
-
93
- const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
94
- event.preventDefault();
95
- setPending(true);
96
- setError(null);
97
- setMessage(null);
98
- try {
99
- const note = await submit();
100
- setMessage(note);
101
- } catch (err) {
102
- const message = err instanceof Error ? err.message : String(err);
103
- setError(message);
104
- } finally {
105
- setPending(false);
106
- }
107
- };
108
-
109
- return (
110
- <form onSubmit={handleSubmit}>
111
- <div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4 mb-6">
112
- <div className="flex items-center gap-3">
113
- <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-500/20 to-purple-500/20 flex items-center justify-center">
114
- <i className="fas fa-paper-plane text-violet-400"></i>
115
- </div>
116
- <div>
117
- <h2 className="text-xl font-bold text-white">Quick Actions</h2>
118
- <p className="text-xs text-gray-500">Airdrop SOL or mint tokens</p>
119
- </div>
120
- </div>
121
- <div className="flex items-center gap-2">
122
- <span className="badge badge-info">
123
- <i className="fas fa-bolt text-xs"></i>
124
- <span>Faucet Powered</span>
125
- </span>
126
- </div>
127
- </div>
128
-
129
- <div className="grid gap-4 lg:grid-cols-3">
130
- <div className="space-y-2">
131
- <label
132
- htmlFor={recipientId}
133
- className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
134
- >
135
- Recipient Address
136
- </label>
137
- <div className="relative">
138
- <input
139
- id={recipientId}
140
- value={recipient}
141
- onChange={(event: ChangeEvent<HTMLInputElement>) =>
142
- setRecipient(event.target.value)
143
- }
144
- placeholder="Enter Solana public key"
145
- className="input pl-10"
146
- />
147
- <i className="fas fa-user absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
148
- </div>
149
- </div>
150
-
151
- <div className="space-y-2">
152
- <label
153
- htmlFor={assetId}
154
- className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
155
- >
156
- Asset
157
- </label>
158
- <div className="relative">
159
- <select
160
- id={assetId}
161
- value={asset}
162
- onChange={(event: ChangeEvent<HTMLSelectElement>) =>
163
- setAsset(event.target.value)
164
- }
165
- className="select pl-10 appearance-none"
166
- >
167
- {options.map((opt) => (
168
- <option key={opt.value} value={opt.value}>
169
- {opt.label}
170
- </option>
171
- ))}
172
- </select>
173
- <i className="fas fa-coins absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
174
- </div>
175
- </div>
176
-
177
- <div className="space-y-2">
178
- <label
179
- htmlFor={amountId}
180
- className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
181
- >
182
- Amount
183
- </label>
184
- <div className="relative">
185
- <input
186
- id={amountId}
187
- value={amount}
188
- onChange={(event: ChangeEvent<HTMLInputElement>) =>
189
- setAmount(event.target.value)
190
- }
191
- placeholder="1.0"
192
- inputMode="decimal"
193
- className="input pl-10"
194
- />
195
- <i className="fas fa-calculator absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
196
- </div>
197
- <p className="text-xs text-gray-500">
198
- {asset === SOL_OPTION.value
199
- ? "In SOL (9 decimals)"
200
- : `In tokens (${selected.decimals} decimals)`}
201
- </p>
202
- </div>
203
- </div>
204
-
205
- <div className="mt-6 flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
206
- <button
207
- type="submit"
208
- disabled={pending}
209
- className={`btn-primary flex-1 sm:flex-initial ${pending ? "opacity-50 cursor-not-allowed" : ""}`}
210
- >
211
- {pending ? (
212
- <>
213
- <div className="spinner"></div>
214
- <span>Processing</span>
215
- </>
216
- ) : (
217
- <>
218
- <i
219
- className={`fas fa-${asset === SOL_OPTION.value ? "parachute-box" : "coins"}`}
220
- ></i>
221
- <span>
222
- {asset === SOL_OPTION.value ? "Airdrop SOL" : "Mint Tokens"}
223
- </span>
224
- </>
225
- )}
226
- </button>
227
-
228
- {error && (
229
- <div className="flex-1 flex items-center gap-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
230
- <i className="fas fa-exclamation-circle text-red-400"></i>
231
- <p className="text-sm text-red-300">{error}</p>
232
- </div>
233
- )}
234
-
235
- {message && (
236
- <div className="flex-1 flex items-center gap-2 p-3 rounded-lg bg-green-500/10 border border-green-500/30">
237
- <i className="fas fa-check-circle text-green-400"></i>
238
- <p className="text-sm text-green-300 font-mono text-xs break-all">
239
- {message}
240
- </p>
241
- </div>
242
- )}
243
- </div>
244
- </form>
245
- );
246
- }