solforge 0.2.3 → 0.2.5
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.
- package/LICENSE +2 -2
- package/README.md +323 -364
- package/cli.cjs +126 -69
- package/package.json +1 -1
- package/scripts/install.sh +112 -0
- package/scripts/postinstall.cjs +66 -58
- package/server/methods/program/get-token-accounts-by-owner.ts +7 -2
- package/server/ws-server.ts +4 -1
- package/src/api-server-entry.ts +91 -91
- package/src/cli/commands/rpc-start.ts +4 -1
- package/src/cli/main.ts +39 -14
- package/src/cli/run-solforge.ts +20 -6
- package/src/commands/add-program.ts +324 -328
- package/src/commands/init.ts +106 -106
- package/src/commands/list.ts +125 -125
- package/src/commands/mint.ts +246 -246
- package/src/commands/start.ts +834 -831
- package/src/commands/status.ts +80 -80
- package/src/commands/stop.ts +381 -382
- package/src/config/manager.ts +149 -149
- package/src/gui/public/app.css +1556 -1
- package/src/gui/public/build/main.css +1569 -1
- package/src/gui/server.ts +20 -21
- package/src/gui/src/app.tsx +56 -37
- package/src/gui/src/components/airdrop-mint-form.tsx +17 -11
- package/src/gui/src/components/clone-program-modal.tsx +6 -6
- package/src/gui/src/components/clone-token-modal.tsx +7 -7
- package/src/gui/src/components/modal.tsx +13 -11
- package/src/gui/src/components/programs-panel.tsx +27 -15
- package/src/gui/src/components/status-panel.tsx +31 -17
- package/src/gui/src/components/tokens-panel.tsx +25 -19
- package/src/gui/src/index.css +491 -463
- package/src/index.ts +161 -146
- package/src/rpc/start.ts +1 -1
- package/src/services/api-server.ts +470 -473
- package/src/services/port-manager.ts +167 -167
- package/src/services/process-registry.ts +143 -143
- package/src/services/program-cloner.ts +312 -312
- package/src/services/token-cloner.ts +799 -797
- package/src/services/validator.ts +288 -288
- package/src/types/config.ts +71 -71
- package/src/utils/shell.ts +75 -75
- package/src/utils/token-loader.ts +77 -77
package/src/gui/server.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { serve } from "bun";
|
|
1
|
+
import { file, serve } from "bun";
|
|
2
2
|
import type { LiteSVMRpcServer } from "../../server/rpc-server";
|
|
3
3
|
import type { JsonRpcResponse } from "../../server/types";
|
|
4
4
|
import { readConfig, writeConfig } from "../config";
|
|
5
|
-
import indexHtml from "./public/index.html";
|
|
6
|
-
import { file } from "bun";
|
|
7
5
|
// Embed built GUI assets as files so the compiled binary can stream them
|
|
8
6
|
import appCssFile from "./public/app.css" with { type: "file" };
|
|
9
|
-
import mainJsFile from "./public/build/main.js" with { type: "file" };
|
|
10
7
|
import bundledCssFile from "./public/build/main.css" with { type: "file" };
|
|
8
|
+
import mainJsFile from "./public/build/main.js" with { type: "file" };
|
|
9
|
+
import indexHtml from "./public/index.html";
|
|
11
10
|
|
|
12
11
|
type GuiStartOptions = {
|
|
13
12
|
port?: number;
|
|
@@ -47,13 +46,13 @@ const text = (value: string, status = 200) =>
|
|
|
47
46
|
|
|
48
47
|
const okOptions = () => new Response(null, { status: 204, headers: CORS });
|
|
49
48
|
const css = (fpath: string) =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
new Response(file(fpath), {
|
|
50
|
+
headers: { ...CORS, "Content-Type": "text/css" },
|
|
51
|
+
});
|
|
53
52
|
const js = (fpath: string) =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
new Response(file(fpath), {
|
|
54
|
+
headers: { ...CORS, "Content-Type": "application/javascript" },
|
|
55
|
+
});
|
|
57
56
|
|
|
58
57
|
const handleError = (error: unknown) => {
|
|
59
58
|
if (error instanceof HttpError)
|
|
@@ -153,11 +152,11 @@ export function startGuiServer(opts: GuiStartOptions = {}) {
|
|
|
153
152
|
}
|
|
154
153
|
}
|
|
155
154
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
155
|
+
const routes = {
|
|
156
|
+
"/": indexHtml,
|
|
157
|
+
"/app.css": { GET: () => css(appCssFile) },
|
|
158
|
+
"/build/main.css": { GET: () => css(bundledCssFile) },
|
|
159
|
+
"/build/main.js": { GET: () => js(mainJsFile) },
|
|
161
160
|
"/health": { GET: () => text("ok") },
|
|
162
161
|
"/api/config": { GET: () => json({ rpcUrl }), OPTIONS: okOptions },
|
|
163
162
|
"/api/status": {
|
|
@@ -284,12 +283,12 @@ export function startGuiServer(opts: GuiStartOptions = {}) {
|
|
|
284
283
|
},
|
|
285
284
|
} as const;
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
286
|
+
const server = serve({
|
|
287
|
+
port,
|
|
288
|
+
hostname: host,
|
|
289
|
+
routes,
|
|
290
|
+
development: false,
|
|
291
|
+
});
|
|
293
292
|
console.log(`🖥️ Solforge GUI available at http://${host}:${server.port}`);
|
|
294
293
|
return { server, port: server.port };
|
|
295
294
|
}
|
package/src/gui/src/app.tsx
CHANGED
|
@@ -143,7 +143,7 @@ export function App() {
|
|
|
143
143
|
|
|
144
144
|
const scrollToSection = (sectionId: string) => {
|
|
145
145
|
setActiveSection(sectionId);
|
|
146
|
-
document.getElementById(sectionId)?.scrollIntoView({ behavior:
|
|
146
|
+
document.getElementById(sectionId)?.scrollIntoView({ behavior: "smooth" });
|
|
147
147
|
setSidebarOpen(false);
|
|
148
148
|
};
|
|
149
149
|
|
|
@@ -155,13 +155,17 @@ export function App() {
|
|
|
155
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
156
|
aria-label="Menu"
|
|
157
157
|
>
|
|
158
|
-
<i
|
|
158
|
+
<i
|
|
159
|
+
className={`fas fa-${sidebarOpen ? "times" : "bars"} text-white`}
|
|
160
|
+
></i>
|
|
159
161
|
</button>
|
|
160
162
|
|
|
161
163
|
{/* Sidebar Navigation */}
|
|
162
|
-
<aside
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
<aside
|
|
165
|
+
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 ${
|
|
166
|
+
sidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
|
|
167
|
+
}`}
|
|
168
|
+
>
|
|
165
169
|
<div className="p-6 space-y-8">
|
|
166
170
|
{/* Logo */}
|
|
167
171
|
<div className="flex items-center gap-3">
|
|
@@ -178,45 +182,45 @@ export function App() {
|
|
|
178
182
|
|
|
179
183
|
{/* Navigation Items */}
|
|
180
184
|
<nav className="space-y-2">
|
|
181
|
-
<button
|
|
182
|
-
onClick={() => scrollToSection(
|
|
185
|
+
<button
|
|
186
|
+
onClick={() => scrollToSection("status")}
|
|
183
187
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
184
|
-
activeSection ===
|
|
185
|
-
?
|
|
186
|
-
:
|
|
188
|
+
activeSection === "status"
|
|
189
|
+
? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
|
|
190
|
+
: "text-gray-400 hover:bg-white/5"
|
|
187
191
|
}`}
|
|
188
192
|
>
|
|
189
193
|
<i className="fas fa-server w-5"></i>
|
|
190
194
|
<span className="font-medium">Network Status</span>
|
|
191
195
|
</button>
|
|
192
|
-
<button
|
|
193
|
-
onClick={() => scrollToSection(
|
|
196
|
+
<button
|
|
197
|
+
onClick={() => scrollToSection("actions")}
|
|
194
198
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
195
|
-
activeSection ===
|
|
196
|
-
?
|
|
197
|
-
:
|
|
199
|
+
activeSection === "actions"
|
|
200
|
+
? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
|
|
201
|
+
: "text-gray-400 hover:bg-white/5"
|
|
198
202
|
}`}
|
|
199
203
|
>
|
|
200
204
|
<i className="fas fa-paper-plane w-5"></i>
|
|
201
205
|
<span className="font-medium">Quick Actions</span>
|
|
202
206
|
</button>
|
|
203
|
-
<button
|
|
204
|
-
onClick={() => scrollToSection(
|
|
207
|
+
<button
|
|
208
|
+
onClick={() => scrollToSection("programs")}
|
|
205
209
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
206
|
-
activeSection ===
|
|
207
|
-
?
|
|
208
|
-
:
|
|
210
|
+
activeSection === "programs"
|
|
211
|
+
? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
|
|
212
|
+
: "text-gray-400 hover:bg-white/5"
|
|
209
213
|
}`}
|
|
210
214
|
>
|
|
211
215
|
<i className="fas fa-code w-5"></i>
|
|
212
216
|
<span className="font-medium">Programs</span>
|
|
213
217
|
</button>
|
|
214
|
-
<button
|
|
215
|
-
onClick={() => scrollToSection(
|
|
218
|
+
<button
|
|
219
|
+
onClick={() => scrollToSection("tokens")}
|
|
216
220
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
217
|
-
activeSection ===
|
|
218
|
-
?
|
|
219
|
-
:
|
|
221
|
+
activeSection === "tokens"
|
|
222
|
+
? "bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300"
|
|
223
|
+
: "text-gray-400 hover:bg-white/5"
|
|
220
224
|
}`}
|
|
221
225
|
>
|
|
222
226
|
<i className="fas fa-coins w-5"></i>
|
|
@@ -227,13 +231,17 @@ export function App() {
|
|
|
227
231
|
{/* Quick Stats */}
|
|
228
232
|
{config && (
|
|
229
233
|
<div className="space-y-3">
|
|
230
|
-
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
234
|
+
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
235
|
+
Connection
|
|
236
|
+
</h3>
|
|
231
237
|
<div className="p-3 rounded-xl bg-white/5 border border-white/10">
|
|
232
238
|
<div className="flex items-center gap-2 mb-2">
|
|
233
239
|
<span className="status-dot online"></span>
|
|
234
240
|
<span className="text-xs text-gray-400">Connected</span>
|
|
235
241
|
</div>
|
|
236
|
-
<p className="text-xs text-gray-500 font-mono break-all">
|
|
242
|
+
<p className="text-xs text-gray-500 font-mono break-all">
|
|
243
|
+
{config.rpcUrl}
|
|
244
|
+
</p>
|
|
237
245
|
</div>
|
|
238
246
|
</div>
|
|
239
247
|
)}
|
|
@@ -242,7 +250,7 @@ export function App() {
|
|
|
242
250
|
|
|
243
251
|
{/* Overlay for mobile */}
|
|
244
252
|
{sidebarOpen && (
|
|
245
|
-
<div
|
|
253
|
+
<div
|
|
246
254
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30 lg:hidden"
|
|
247
255
|
onClick={() => setSidebarOpen(false)}
|
|
248
256
|
/>
|
|
@@ -262,11 +270,10 @@ export function App() {
|
|
|
262
270
|
Manage your local Solana development environment
|
|
263
271
|
</p>
|
|
264
272
|
</div>
|
|
265
|
-
<button
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
<i className={`fas fa-sync-alt ${loadingStatus ? 'animate-spin' : ''}`}></i>
|
|
273
|
+
<button onClick={loadStatus} className="btn-secondary">
|
|
274
|
+
<i
|
|
275
|
+
className={`fas fa-sync-alt ${loadingStatus ? "animate-spin" : ""}`}
|
|
276
|
+
></i>
|
|
270
277
|
<span>Refresh All</span>
|
|
271
278
|
</button>
|
|
272
279
|
</div>
|
|
@@ -311,7 +318,11 @@ export function App() {
|
|
|
311
318
|
</div>
|
|
312
319
|
|
|
313
320
|
{/* Quick Actions - Optional */}
|
|
314
|
-
<div
|
|
321
|
+
<div
|
|
322
|
+
id="actions"
|
|
323
|
+
className="glass-panel p-6 animate-fadeIn scroll-mt-24"
|
|
324
|
+
style={{ animationDelay: "0.1s" }}
|
|
325
|
+
>
|
|
315
326
|
<AirdropMintForm
|
|
316
327
|
tokens={tokens}
|
|
317
328
|
onAirdrop={onAirdrop}
|
|
@@ -321,7 +332,11 @@ export function App() {
|
|
|
321
332
|
|
|
322
333
|
{/* Programs and Tokens Stacked */}
|
|
323
334
|
<div className="space-y-6">
|
|
324
|
-
<div
|
|
335
|
+
<div
|
|
336
|
+
id="programs"
|
|
337
|
+
className="animate-fadeIn scroll-mt-24"
|
|
338
|
+
style={{ animationDelay: "0.2s" }}
|
|
339
|
+
>
|
|
325
340
|
<ProgramsPanel
|
|
326
341
|
programs={programs}
|
|
327
342
|
loading={loadingPrograms}
|
|
@@ -329,7 +344,11 @@ export function App() {
|
|
|
329
344
|
onAdd={openProgramModal}
|
|
330
345
|
/>
|
|
331
346
|
</div>
|
|
332
|
-
<div
|
|
347
|
+
<div
|
|
348
|
+
id="tokens"
|
|
349
|
+
className="animate-fadeIn scroll-mt-24"
|
|
350
|
+
style={{ animationDelay: "0.3s" }}
|
|
351
|
+
>
|
|
333
352
|
<TokensPanel
|
|
334
353
|
tokens={tokens}
|
|
335
354
|
loading={loadingTokens}
|
|
@@ -387,4 +406,4 @@ export function App() {
|
|
|
387
406
|
`}</style>
|
|
388
407
|
</div>
|
|
389
408
|
);
|
|
390
|
-
}
|
|
409
|
+
}
|
|
@@ -113,7 +113,7 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
113
113
|
</span>
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
118
118
|
<div className="space-y-2">
|
|
119
119
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
@@ -131,7 +131,7 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
131
131
|
<i className="fas fa-user absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
132
132
|
</div>
|
|
133
133
|
</div>
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
<div className="space-y-2">
|
|
136
136
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
137
137
|
Asset
|
|
@@ -153,7 +153,7 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
153
153
|
<i className="fas fa-coins absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
154
154
|
</div>
|
|
155
155
|
</div>
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
<div className="space-y-2">
|
|
158
158
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
159
159
|
Amount
|
|
@@ -177,12 +177,12 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
177
177
|
</p>
|
|
178
178
|
</div>
|
|
179
179
|
</div>
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
<div className="mt-6 flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
|
|
182
182
|
<button
|
|
183
183
|
type="submit"
|
|
184
184
|
disabled={pending}
|
|
185
|
-
className={`btn-primary flex-1 sm:flex-initial ${pending ?
|
|
185
|
+
className={`btn-primary flex-1 sm:flex-initial ${pending ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
186
186
|
>
|
|
187
187
|
{pending ? (
|
|
188
188
|
<>
|
|
@@ -191,26 +191,32 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
191
191
|
</>
|
|
192
192
|
) : (
|
|
193
193
|
<>
|
|
194
|
-
<i
|
|
195
|
-
|
|
194
|
+
<i
|
|
195
|
+
className={`fas fa-${asset === SOL_OPTION.value ? "parachute-box" : "coins"}`}
|
|
196
|
+
></i>
|
|
197
|
+
<span>
|
|
198
|
+
{asset === SOL_OPTION.value ? "Airdrop SOL" : "Mint Tokens"}
|
|
199
|
+
</span>
|
|
196
200
|
</>
|
|
197
201
|
)}
|
|
198
202
|
</button>
|
|
199
|
-
|
|
203
|
+
|
|
200
204
|
{error && (
|
|
201
205
|
<div className="flex-1 flex items-center gap-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
202
206
|
<i className="fas fa-exclamation-circle text-red-400"></i>
|
|
203
207
|
<p className="text-sm text-red-300">{error}</p>
|
|
204
208
|
</div>
|
|
205
209
|
)}
|
|
206
|
-
|
|
210
|
+
|
|
207
211
|
{message && (
|
|
208
212
|
<div className="flex-1 flex items-center gap-2 p-3 rounded-lg bg-green-500/10 border border-green-500/30">
|
|
209
213
|
<i className="fas fa-check-circle text-green-400"></i>
|
|
210
|
-
<p className="text-sm text-green-300 font-mono text-xs break-all">
|
|
214
|
+
<p className="text-sm text-green-300 font-mono text-xs break-all">
|
|
215
|
+
{message}
|
|
216
|
+
</p>
|
|
211
217
|
</div>
|
|
212
218
|
)}
|
|
213
219
|
</div>
|
|
214
220
|
</form>
|
|
215
221
|
);
|
|
216
|
-
}
|
|
222
|
+
}
|
|
@@ -72,7 +72,7 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
72
72
|
type="button"
|
|
73
73
|
onClick={handleSubmit}
|
|
74
74
|
disabled={pending || programId.trim().length === 0}
|
|
75
|
-
className={`btn-primary ${
|
|
75
|
+
className={`btn-primary ${pending || programId.trim().length === 0 ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
76
76
|
>
|
|
77
77
|
{pending ? (
|
|
78
78
|
<>
|
|
@@ -107,7 +107,7 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
107
107
|
<i className="fas fa-fingerprint absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
108
108
|
</div>
|
|
109
109
|
</div>
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
<div className="space-y-2">
|
|
112
112
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
113
113
|
RPC Endpoint (Optional)
|
|
@@ -124,7 +124,7 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
124
124
|
<i className="fas fa-globe absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
<div className="p-4 rounded-xl bg-white/5 border border-white/10 space-y-3">
|
|
129
129
|
<label className="flex items-center gap-3 cursor-pointer group">
|
|
130
130
|
<input
|
|
@@ -144,7 +144,7 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
144
144
|
</p>
|
|
145
145
|
</div>
|
|
146
146
|
</label>
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
{withAccounts && (
|
|
149
149
|
<div className="ml-8 space-y-2 pt-2 border-t border-white/5">
|
|
150
150
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
@@ -170,7 +170,7 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
170
170
|
</div>
|
|
171
171
|
)}
|
|
172
172
|
</div>
|
|
173
|
-
|
|
173
|
+
|
|
174
174
|
{error && (
|
|
175
175
|
<div className="flex items-start gap-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
176
176
|
<i className="fas fa-exclamation-circle text-red-400 mt-0.5"></i>
|
|
@@ -180,4 +180,4 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
180
180
|
</div>
|
|
181
181
|
</Modal>
|
|
182
182
|
);
|
|
183
|
-
}
|
|
183
|
+
}
|
|
@@ -77,7 +77,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
77
77
|
type="button"
|
|
78
78
|
onClick={handleSubmit}
|
|
79
79
|
disabled={pending || mint.trim().length === 0}
|
|
80
|
-
className={`btn-primary ${
|
|
80
|
+
className={`btn-primary ${pending || mint.trim().length === 0 ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
81
81
|
>
|
|
82
82
|
{pending ? (
|
|
83
83
|
<>
|
|
@@ -112,7 +112,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
112
112
|
<i className="fas fa-coin absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
113
113
|
</div>
|
|
114
114
|
</div>
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
<div className="space-y-2">
|
|
117
117
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
118
118
|
RPC Endpoint (Optional)
|
|
@@ -129,7 +129,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
129
129
|
<i className="fas fa-globe absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
130
130
|
</div>
|
|
131
131
|
</div>
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
<div className="p-4 rounded-xl bg-white/5 border border-white/10 space-y-3">
|
|
134
134
|
<label className="flex items-center gap-3 cursor-pointer group">
|
|
135
135
|
<input
|
|
@@ -149,7 +149,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
149
149
|
</p>
|
|
150
150
|
</div>
|
|
151
151
|
</label>
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
{cloneAccounts && (
|
|
154
154
|
<div className="ml-8 space-y-4 pt-3 border-t border-white/5">
|
|
155
155
|
<label className="flex items-center gap-3 cursor-pointer group">
|
|
@@ -170,7 +170,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
170
170
|
</p>
|
|
171
171
|
</div>
|
|
172
172
|
</label>
|
|
173
|
-
|
|
173
|
+
|
|
174
174
|
{!allAccounts && (
|
|
175
175
|
<div className="space-y-2">
|
|
176
176
|
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
@@ -198,7 +198,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
198
198
|
</div>
|
|
199
199
|
)}
|
|
200
200
|
</div>
|
|
201
|
-
|
|
201
|
+
|
|
202
202
|
{error && (
|
|
203
203
|
<div className="flex items-start gap-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
204
204
|
<i className="fas fa-exclamation-circle text-red-400 mt-0.5"></i>
|
|
@@ -208,4 +208,4 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
208
208
|
</div>
|
|
209
209
|
</Modal>
|
|
210
210
|
);
|
|
211
|
-
}
|
|
211
|
+
}
|
|
@@ -31,27 +31,29 @@ export function Modal({
|
|
|
31
31
|
if (!isOpen) return null;
|
|
32
32
|
|
|
33
33
|
const colorClasses = {
|
|
34
|
-
purple:
|
|
35
|
-
blue:
|
|
36
|
-
amber:
|
|
37
|
-
green:
|
|
34
|
+
purple: "from-purple-500/20 to-violet-500/20 text-purple-400",
|
|
35
|
+
blue: "from-blue-500/20 to-cyan-500/20 text-blue-400",
|
|
36
|
+
amber: "from-amber-500/20 to-orange-500/20 text-amber-400",
|
|
37
|
+
green: "from-green-500/20 to-emerald-500/20 text-green-400",
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-fadeIn">
|
|
42
42
|
{/* Backdrop */}
|
|
43
|
-
<div
|
|
43
|
+
<div
|
|
44
44
|
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
|
45
45
|
onClick={onClose}
|
|
46
46
|
/>
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
{/* Modal */}
|
|
49
49
|
<div className="w-full max-w-lg p-0 relative animate-modalSlideIn overflow-hidden rounded-2xl border border-white/20 bg-gray-900/95 backdrop-blur-xl shadow-2xl">
|
|
50
50
|
{/* Header */}
|
|
51
51
|
<div className="p-6 border-b border-white/10 bg-gradient-to-r from-white/5 to-transparent">
|
|
52
52
|
<div className="flex items-center justify-between">
|
|
53
53
|
<div className="flex items-center gap-3">
|
|
54
|
-
<div
|
|
54
|
+
<div
|
|
55
|
+
className={`w-10 h-10 rounded-xl bg-gradient-to-br ${colorClasses[iconColor as keyof typeof colorClasses]} flex items-center justify-center`}
|
|
56
|
+
>
|
|
55
57
|
<i className={`fas ${icon}`}></i>
|
|
56
58
|
</div>
|
|
57
59
|
<h3 className="text-xl font-bold text-white">{title}</h3>
|
|
@@ -66,12 +68,12 @@ export function Modal({
|
|
|
66
68
|
</button>
|
|
67
69
|
</div>
|
|
68
70
|
</div>
|
|
69
|
-
|
|
71
|
+
|
|
70
72
|
{/* Content */}
|
|
71
73
|
<div className="p-6 max-h-[60vh] overflow-y-auto custom-scrollbar">
|
|
72
74
|
{children}
|
|
73
75
|
</div>
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
{/* Footer */}
|
|
76
78
|
{footer && (
|
|
77
79
|
<div className="p-6 border-t border-white/10 bg-gradient-to-r from-white/5 to-transparent">
|
|
@@ -79,7 +81,7 @@ export function Modal({
|
|
|
79
81
|
</div>
|
|
80
82
|
)}
|
|
81
83
|
</div>
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
<style jsx>{`
|
|
84
86
|
@keyframes fadeIn {
|
|
85
87
|
from {
|
|
@@ -124,4 +126,4 @@ export function Modal({
|
|
|
124
126
|
`}</style>
|
|
125
127
|
</div>
|
|
126
128
|
);
|
|
127
|
-
}
|
|
129
|
+
}
|
|
@@ -27,22 +27,20 @@ export function ProgramsPanel({ programs, loading, onRefresh, onAdd }: Props) {
|
|
|
27
27
|
type="button"
|
|
28
28
|
onClick={onRefresh}
|
|
29
29
|
disabled={loading}
|
|
30
|
-
className={`btn-secondary text-sm ${loading ?
|
|
30
|
+
className={`btn-secondary text-sm ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
31
31
|
>
|
|
32
|
-
<i
|
|
32
|
+
<i
|
|
33
|
+
className={`fas fa-sync-alt ${loading ? "animate-spin" : ""}`}
|
|
34
|
+
></i>
|
|
33
35
|
<span>{loading ? "Refreshing" : "Refresh"}</span>
|
|
34
36
|
</button>
|
|
35
|
-
<button
|
|
36
|
-
type="button"
|
|
37
|
-
onClick={onAdd}
|
|
38
|
-
className="btn-primary text-sm"
|
|
39
|
-
>
|
|
37
|
+
<button type="button" onClick={onAdd} className="btn-primary text-sm">
|
|
40
38
|
<i className="fas fa-plus"></i>
|
|
41
39
|
<span>Add Program</span>
|
|
42
40
|
</button>
|
|
43
41
|
</div>
|
|
44
42
|
</header>
|
|
45
|
-
|
|
43
|
+
|
|
46
44
|
<div className="overflow-x-auto rounded-xl">
|
|
47
45
|
{programs.length === 0 ? (
|
|
48
46
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
@@ -50,7 +48,9 @@ export function ProgramsPanel({ programs, loading, onRefresh, onAdd }: Props) {
|
|
|
50
48
|
<i className="fas fa-code text-blue-500 text-2xl"></i>
|
|
51
49
|
</div>
|
|
52
50
|
<p className="text-gray-400 mb-2">No programs deployed</p>
|
|
53
|
-
<p className="text-sm text-gray-500">
|
|
51
|
+
<p className="text-sm text-gray-500">
|
|
52
|
+
Click "Add Program" to clone from mainnet
|
|
53
|
+
</p>
|
|
54
54
|
</div>
|
|
55
55
|
) : (
|
|
56
56
|
<table className="table-modern">
|
|
@@ -65,12 +65,17 @@ export function ProgramsPanel({ programs, loading, onRefresh, onAdd }: Props) {
|
|
|
65
65
|
</thead>
|
|
66
66
|
<tbody>
|
|
67
67
|
{programs.map((program, index) => (
|
|
68
|
-
<tr
|
|
68
|
+
<tr
|
|
69
|
+
key={program.programId}
|
|
70
|
+
style={{ animationDelay: `${index * 50}ms` }}
|
|
71
|
+
className="animate-fadeIn"
|
|
72
|
+
>
|
|
69
73
|
<td>
|
|
70
74
|
<div className="flex items-center gap-2">
|
|
71
75
|
<i className="fas fa-cube text-blue-400 text-xs"></i>
|
|
72
76
|
<span className="font-mono text-xs text-blue-300">
|
|
73
|
-
{program.programId.slice(0, 8)}...
|
|
77
|
+
{program.programId.slice(0, 8)}...
|
|
78
|
+
{program.programId.slice(-6)}
|
|
74
79
|
</span>
|
|
75
80
|
</div>
|
|
76
81
|
</td>
|
|
@@ -80,9 +85,15 @@ export function ProgramsPanel({ programs, loading, onRefresh, onAdd }: Props) {
|
|
|
80
85
|
</span>
|
|
81
86
|
</td>
|
|
82
87
|
<td>
|
|
83
|
-
<span
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
<span
|
|
89
|
+
className={`badge ${program.executable ? "badge-success" : "badge-warning"}`}
|
|
90
|
+
>
|
|
91
|
+
<i
|
|
92
|
+
className={`fas fa-${program.executable ? "check" : "pause"} text-xs`}
|
|
93
|
+
></i>
|
|
94
|
+
<span>
|
|
95
|
+
{program.executable ? "Executable" : "Data Only"}
|
|
96
|
+
</span>
|
|
86
97
|
</span>
|
|
87
98
|
</td>
|
|
88
99
|
<td>
|
|
@@ -97,7 +108,8 @@ export function ProgramsPanel({ programs, loading, onRefresh, onAdd }: Props) {
|
|
|
97
108
|
<div className="flex items-center gap-2">
|
|
98
109
|
<i className="fas fa-wallet text-gray-400 text-xs"></i>
|
|
99
110
|
<span className="text-gray-300">
|
|
100
|
-
{(Number(BigInt(program.lamports)) / 1e9).toFixed(4)}
|
|
111
|
+
{(Number(BigInt(program.lamports)) / 1e9).toFixed(4)}{" "}
|
|
112
|
+
SOL
|
|
101
113
|
</span>
|
|
102
114
|
</div>
|
|
103
115
|
</td>
|