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.
- package/package.json +1 -5
- package/start.cjs +19 -23
- package/docs/API.md +0 -379
- package/docs/CONFIGURATION.md +0 -407
- package/docs/bun-single-file-executable.md +0 -585
- package/docs/cli-plan.md +0 -154
- package/docs/data-indexing-plan.md +0 -214
- package/docs/gui-roadmap.md +0 -202
- package/scripts/decode-b58.ts +0 -10
- package/scripts/install.sh +0 -112
- package/server/index.ts +0 -5
- package/server/lib/base58.ts +0 -33
- package/server/lib/faucet.ts +0 -110
- package/server/lib/instruction-parser.ts +0 -328
- package/server/lib/parsers/spl-associated-token-account.ts +0 -50
- package/server/lib/parsers/spl-token.ts +0 -340
- package/server/lib/spl-token.ts +0 -57
- package/server/methods/TEMPLATE.md +0 -117
- package/server/methods/account/get-account-info.ts +0 -86
- package/server/methods/account/get-balance.ts +0 -23
- package/server/methods/account/get-multiple-accounts.ts +0 -84
- package/server/methods/account/get-parsed-account-info.ts +0 -17
- package/server/methods/account/index.ts +0 -12
- package/server/methods/account/parsers/index.ts +0 -52
- package/server/methods/account/parsers/loader-upgradeable.ts +0 -79
- package/server/methods/account/parsers/spl-token.ts +0 -256
- package/server/methods/account/parsers/system.ts +0 -4
- package/server/methods/account/request-airdrop.ts +0 -271
- package/server/methods/admin/adopt-mint-authority.ts +0 -94
- package/server/methods/admin/clone-program-accounts.ts +0 -55
- package/server/methods/admin/clone-program.ts +0 -152
- package/server/methods/admin/clone-token-accounts.ts +0 -117
- package/server/methods/admin/clone-token-mint.ts +0 -82
- package/server/methods/admin/create-mint.ts +0 -114
- package/server/methods/admin/create-token-account.ts +0 -137
- package/server/methods/admin/helpers.ts +0 -70
- package/server/methods/admin/index.ts +0 -10
- package/server/methods/admin/list-mints.ts +0 -21
- package/server/methods/admin/load-program.ts +0 -52
- package/server/methods/admin/mint-to.ts +0 -266
- package/server/methods/block/get-block-height.ts +0 -5
- package/server/methods/block/get-block.ts +0 -31
- package/server/methods/block/get-blocks-with-limit.ts +0 -19
- package/server/methods/block/get-latest-blockhash.ts +0 -12
- package/server/methods/block/get-slot.ts +0 -5
- package/server/methods/block/index.ts +0 -6
- package/server/methods/block/is-blockhash-valid.ts +0 -19
- package/server/methods/epoch/get-cluster-nodes.ts +0 -17
- package/server/methods/epoch/get-epoch-info.ts +0 -16
- package/server/methods/epoch/get-epoch-schedule.ts +0 -15
- package/server/methods/epoch/get-highest-snapshot-slot.ts +0 -9
- package/server/methods/epoch/get-leader-schedule.ts +0 -8
- package/server/methods/epoch/get-max-retransmit-slot.ts +0 -9
- package/server/methods/epoch/get-max-shred-insert-slot.ts +0 -9
- package/server/methods/epoch/get-slot-leader.ts +0 -6
- package/server/methods/epoch/get-slot-leaders.ts +0 -9
- package/server/methods/epoch/get-stake-activation.ts +0 -9
- package/server/methods/epoch/get-stake-minimum-delegation.ts +0 -9
- package/server/methods/epoch/get-vote-accounts.ts +0 -19
- package/server/methods/epoch/index.ts +0 -13
- package/server/methods/epoch/minimum-ledger-slot.ts +0 -5
- package/server/methods/fee/get-fee-calculator-for-blockhash.ts +0 -12
- package/server/methods/fee/get-fee-for-message.ts +0 -8
- package/server/methods/fee/get-fee-rate-governor.ts +0 -16
- package/server/methods/fee/get-fees.ts +0 -14
- package/server/methods/fee/get-recent-prioritization-fees.ts +0 -22
- package/server/methods/fee/index.ts +0 -5
- package/server/methods/get-address-lookup-table.ts +0 -27
- package/server/methods/index.ts +0 -265
- package/server/methods/performance/get-recent-performance-samples.ts +0 -25
- package/server/methods/performance/get-transaction-count.ts +0 -5
- package/server/methods/performance/index.ts +0 -2
- package/server/methods/program/get-block-commitment.ts +0 -9
- package/server/methods/program/get-block-production.ts +0 -14
- package/server/methods/program/get-block-time.ts +0 -21
- package/server/methods/program/get-blocks.ts +0 -11
- package/server/methods/program/get-first-available-block.ts +0 -9
- package/server/methods/program/get-genesis-hash.ts +0 -6
- package/server/methods/program/get-identity.ts +0 -6
- package/server/methods/program/get-inflation-governor.ts +0 -15
- package/server/methods/program/get-inflation-rate.ts +0 -10
- package/server/methods/program/get-inflation-reward.ts +0 -12
- package/server/methods/program/get-largest-accounts.ts +0 -8
- package/server/methods/program/get-parsed-program-accounts.ts +0 -12
- package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +0 -12
- package/server/methods/program/get-parsed-token-accounts-by-owner.ts +0 -12
- package/server/methods/program/get-program-accounts.ts +0 -221
- package/server/methods/program/get-supply.ts +0 -13
- package/server/methods/program/get-token-account-balance.ts +0 -60
- package/server/methods/program/get-token-accounts-by-delegate.ts +0 -82
- package/server/methods/program/get-token-accounts-by-owner.ts +0 -416
- package/server/methods/program/get-token-largest-accounts.ts +0 -81
- package/server/methods/program/get-token-supply.ts +0 -39
- package/server/methods/program/index.ts +0 -21
- package/server/methods/solforge/index.ts +0 -158
- package/server/methods/system/get-health.ts +0 -5
- package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +0 -13
- package/server/methods/system/get-version.ts +0 -9
- package/server/methods/system/index.ts +0 -3
- package/server/methods/transaction/get-confirmed-transaction.ts +0 -11
- package/server/methods/transaction/get-parsed-transaction.ts +0 -17
- package/server/methods/transaction/get-signature-statuses.ts +0 -79
- package/server/methods/transaction/get-signatures-for-address.ts +0 -41
- package/server/methods/transaction/get-transaction.ts +0 -639
- package/server/methods/transaction/index.ts +0 -7
- package/server/methods/transaction/inner-instructions.test.ts +0 -104
- package/server/methods/transaction/send-transaction.ts +0 -469
- package/server/methods/transaction/simulate-transaction.ts +0 -57
- package/server/rpc-server.ts +0 -521
- package/server/types.ts +0 -109
- package/server/ws-server.ts +0 -178
- package/src/api-server-entry.ts +0 -109
- package/src/cli/bootstrap.ts +0 -67
- package/src/cli/commands/airdrop.ts +0 -37
- package/src/cli/commands/config.ts +0 -39
- package/src/cli/commands/mint.ts +0 -187
- package/src/cli/commands/program-clone.ts +0 -122
- package/src/cli/commands/program-load.ts +0 -64
- package/src/cli/commands/rpc-start.ts +0 -49
- package/src/cli/commands/token-adopt-authority.ts +0 -37
- package/src/cli/commands/token-clone.ts +0 -112
- package/src/cli/commands/token-create.ts +0 -81
- package/src/cli/main.ts +0 -158
- package/src/cli/run-solforge.ts +0 -112
- package/src/cli/setup-utils.ts +0 -54
- package/src/cli/setup-wizard.ts +0 -258
- package/src/cli/utils/args.ts +0 -15
- package/src/commands/add-program.ts +0 -333
- package/src/commands/init.ts +0 -122
- package/src/commands/list.ts +0 -136
- package/src/commands/mint.ts +0 -287
- package/src/commands/start.ts +0 -881
- package/src/commands/status.ts +0 -99
- package/src/commands/stop.ts +0 -405
- package/src/config/index.ts +0 -146
- package/src/config/manager.ts +0 -157
- package/src/db/index.ts +0 -83
- package/src/db/schema/accounts.ts +0 -23
- package/src/db/schema/address-signatures.ts +0 -31
- package/src/db/schema/index.ts +0 -6
- package/src/db/schema/meta-kv.ts +0 -9
- package/src/db/schema/transactions.ts +0 -36
- package/src/db/schema/tx-account-states.ts +0 -23
- package/src/db/schema/tx-accounts.ts +0 -33
- package/src/db/tx-store.ts +0 -264
- package/src/gui/public/app.css +0 -1556
- package/src/gui/public/build/main.css +0 -1569
- package/src/gui/public/build/main.js +0 -303
- package/src/gui/public/build/main.js.txt +0 -231
- package/src/gui/public/index.html +0 -19
- package/src/gui/server.ts +0 -296
- package/src/gui/src/api.ts +0 -127
- package/src/gui/src/app.tsx +0 -441
- package/src/gui/src/components/airdrop-mint-form.tsx +0 -246
- package/src/gui/src/components/clone-program-modal.tsx +0 -202
- package/src/gui/src/components/clone-token-modal.tsx +0 -230
- package/src/gui/src/components/modal.tsx +0 -134
- package/src/gui/src/components/programs-panel.tsx +0 -124
- package/src/gui/src/components/status-panel.tsx +0 -136
- package/src/gui/src/components/tokens-panel.tsx +0 -122
- package/src/gui/src/hooks/use-interval.ts +0 -17
- package/src/gui/src/index.css +0 -557
- package/src/gui/src/main.tsx +0 -17
- package/src/index.ts +0 -216
- package/src/migrations-bundled.ts +0 -23
- package/src/rpc/start.ts +0 -44
- package/src/services/api-server.ts +0 -504
- package/src/services/port-manager.ts +0 -174
- package/src/services/process-registry.ts +0 -153
- package/src/services/program-cloner.ts +0 -317
- package/src/services/token-cloner.ts +0 -811
- package/src/services/validator.ts +0 -293
- package/src/types/config.ts +0 -110
- package/src/utils/shell.ts +0 -110
- package/src/utils/token-loader.ts +0 -115
package/src/gui/src/app.tsx
DELETED
|
@@ -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
|
-
}
|