solforge 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agi/agi.sqlite +0 -0
- package/.claude/settings.local.json +9 -0
- package/.github/workflows/release-binaries.yml +133 -0
- package/.tmp/.787ebcdbf7b8fde8-00000000.hm +0 -0
- package/.tmp/.bffe6efebdf8aedc-00000000.hm +0 -0
- package/AGENTS.md +271 -0
- package/CLAUDE.md +106 -0
- package/PROJECT_STRUCTURE.md +124 -0
- package/README.md +367 -393
- package/SOLANA_KIT_GUIDE.md +251 -0
- package/SOLFORGE.md +119 -0
- package/biome.json +34 -0
- package/bun.lock +743 -0
- package/docs/bun-single-file-executable.md +585 -0
- package/docs/cli-plan.md +154 -0
- package/docs/data-indexing-plan.md +214 -0
- package/docs/gui-roadmap.md +202 -0
- package/drizzle/0000_friendly_millenium_guard.sql +53 -0
- package/drizzle/0001_stale_sentinels.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +329 -0
- package/drizzle/meta/0001_snapshot.json +345 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +12 -0
- package/index.ts +21 -0
- package/mint.sh +47 -0
- package/package.json +45 -69
- package/postcss.config.js +6 -0
- package/rpc-server.ts.backup +519 -0
- package/server/index.ts +5 -0
- package/server/lib/base58.ts +33 -0
- package/server/lib/faucet.ts +110 -0
- package/server/lib/spl-token.ts +57 -0
- package/server/methods/TEMPLATE.md +117 -0
- package/server/methods/account/get-account-info.ts +90 -0
- package/server/methods/account/get-balance.ts +27 -0
- package/server/methods/account/get-multiple-accounts.ts +83 -0
- package/server/methods/account/get-parsed-account-info.ts +21 -0
- package/server/methods/account/index.ts +12 -0
- package/server/methods/account/parsers/index.ts +52 -0
- package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
- package/server/methods/account/parsers/spl-token.ts +237 -0
- package/server/methods/account/parsers/system.ts +4 -0
- package/server/methods/account/request-airdrop.ts +219 -0
- package/server/methods/admin/adopt-mint-authority.ts +94 -0
- package/server/methods/admin/clone-program-accounts.ts +55 -0
- package/server/methods/admin/clone-program.ts +152 -0
- package/server/methods/admin/clone-token-accounts.ts +117 -0
- package/server/methods/admin/clone-token-mint.ts +82 -0
- package/server/methods/admin/create-mint.ts +114 -0
- package/server/methods/admin/create-token-account.ts +137 -0
- package/server/methods/admin/helpers.ts +70 -0
- package/server/methods/admin/index.ts +10 -0
- package/server/methods/admin/list-mints.ts +21 -0
- package/server/methods/admin/load-program.ts +52 -0
- package/server/methods/admin/mint-to.ts +278 -0
- package/server/methods/block/get-block-height.ts +5 -0
- package/server/methods/block/get-block.ts +35 -0
- package/server/methods/block/get-blocks-with-limit.ts +23 -0
- package/server/methods/block/get-latest-blockhash.ts +12 -0
- package/server/methods/block/get-slot.ts +5 -0
- package/server/methods/block/index.ts +6 -0
- package/server/methods/block/is-blockhash-valid.ts +23 -0
- package/server/methods/epoch/get-cluster-nodes.ts +17 -0
- package/server/methods/epoch/get-epoch-info.ts +16 -0
- package/server/methods/epoch/get-epoch-schedule.ts +15 -0
- package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
- package/server/methods/epoch/get-leader-schedule.ts +8 -0
- package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
- package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
- package/server/methods/epoch/get-slot-leader.ts +6 -0
- package/server/methods/epoch/get-slot-leaders.ts +9 -0
- package/server/methods/epoch/get-stake-activation.ts +9 -0
- package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
- package/server/methods/epoch/get-vote-accounts.ts +19 -0
- package/server/methods/epoch/index.ts +13 -0
- package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
- package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
- package/server/methods/fee/get-fee-for-message.ts +8 -0
- package/server/methods/fee/get-fee-rate-governor.ts +16 -0
- package/server/methods/fee/get-fees.ts +14 -0
- package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
- package/server/methods/fee/index.ts +5 -0
- package/server/methods/get-address-lookup-table.ts +31 -0
- package/server/methods/index.ts +265 -0
- package/server/methods/performance/get-recent-performance-samples.ts +25 -0
- package/server/methods/performance/get-transaction-count.ts +5 -0
- package/server/methods/performance/index.ts +2 -0
- package/server/methods/program/get-block-commitment.ts +9 -0
- package/server/methods/program/get-block-production.ts +14 -0
- package/server/methods/program/get-block-time.ts +21 -0
- package/server/methods/program/get-blocks.ts +11 -0
- package/server/methods/program/get-first-available-block.ts +9 -0
- package/server/methods/program/get-genesis-hash.ts +6 -0
- package/server/methods/program/get-identity.ts +6 -0
- package/server/methods/program/get-inflation-governor.ts +15 -0
- package/server/methods/program/get-inflation-rate.ts +10 -0
- package/server/methods/program/get-inflation-reward.ts +12 -0
- package/server/methods/program/get-largest-accounts.ts +8 -0
- package/server/methods/program/get-parsed-program-accounts.ts +12 -0
- package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
- package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
- package/server/methods/program/get-program-accounts.ts +221 -0
- package/server/methods/program/get-supply.ts +13 -0
- package/server/methods/program/get-token-account-balance.ts +64 -0
- package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
- package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
- package/server/methods/program/get-token-largest-accounts.ts +80 -0
- package/server/methods/program/get-token-supply.ts +38 -0
- package/server/methods/program/index.ts +21 -0
- package/server/methods/solforge/index.ts +155 -0
- package/server/methods/system/get-health.ts +5 -0
- package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
- package/server/methods/system/get-version.ts +9 -0
- package/server/methods/system/index.ts +3 -0
- package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
- package/server/methods/transaction/get-parsed-transaction.ts +21 -0
- package/server/methods/transaction/get-signature-statuses.ts +72 -0
- package/server/methods/transaction/get-signatures-for-address.ts +45 -0
- package/server/methods/transaction/get-transaction.ts +428 -0
- package/server/methods/transaction/index.ts +7 -0
- package/server/methods/transaction/send-transaction.ts +232 -0
- package/server/methods/transaction/simulate-transaction.ts +56 -0
- package/server/rpc-server.ts +474 -0
- package/server/types.ts +74 -0
- package/server/ws-server.ts +171 -0
- package/sf.config.json +38 -0
- package/src/cli/bootstrap.ts +67 -0
- package/src/cli/commands/airdrop.ts +37 -0
- package/src/cli/commands/config.ts +39 -0
- package/src/cli/commands/mint.ts +187 -0
- package/src/cli/commands/program-clone.ts +124 -0
- package/src/cli/commands/program-load.ts +64 -0
- package/src/cli/commands/rpc-start.ts +46 -0
- package/src/cli/commands/token-adopt-authority.ts +37 -0
- package/src/cli/commands/token-clone.ts +113 -0
- package/src/cli/commands/token-create.ts +81 -0
- package/src/cli/main.ts +130 -0
- package/src/cli/run-solforge.ts +98 -0
- package/src/cli/setup-utils.ts +54 -0
- package/src/cli/setup-wizard.ts +256 -0
- package/src/cli/utils/args.ts +15 -0
- package/src/config/index.ts +130 -0
- package/src/db/index.ts +83 -0
- package/src/db/schema/accounts.ts +23 -0
- package/src/db/schema/address-signatures.ts +31 -0
- package/src/db/schema/index.ts +5 -0
- package/src/db/schema/meta-kv.ts +9 -0
- package/src/db/schema/transactions.ts +29 -0
- package/src/db/schema/tx-accounts.ts +33 -0
- package/src/db/tx-store.ts +229 -0
- package/src/gui/public/app.css +1 -0
- package/src/gui/public/index.html +19 -0
- package/src/gui/server.ts +297 -0
- package/src/gui/src/api.ts +127 -0
- package/src/gui/src/app.tsx +390 -0
- package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
- package/src/gui/src/components/clone-program-modal.tsx +183 -0
- package/src/gui/src/components/clone-token-modal.tsx +211 -0
- package/src/gui/src/components/modal.tsx +127 -0
- package/src/gui/src/components/programs-panel.tsx +112 -0
- package/src/gui/src/components/status-panel.tsx +122 -0
- package/src/gui/src/components/tokens-panel.tsx +116 -0
- package/src/gui/src/hooks/use-interval.ts +17 -0
- package/src/gui/src/index.css +529 -0
- package/src/gui/src/main.tsx +17 -0
- package/src/migrations-bundled.ts +17 -0
- package/src/rpc/start.ts +44 -0
- package/tailwind.config.js +27 -0
- package/test-client.ts +120 -0
- package/tmp/inspect-html.ts +4 -0
- package/tmp/response-test.ts +5 -0
- package/tmp/test-html.ts +5 -0
- package/tmp/test-server.ts +13 -0
- package/tsconfig.json +24 -23
- package/LICENSE +0 -21
- package/scripts/postinstall.cjs +0 -103
- package/src/api-server-entry.ts +0 -109
- package/src/commands/add-program.ts +0 -337
- package/src/commands/init.ts +0 -122
- package/src/commands/list.ts +0 -136
- package/src/commands/mint.ts +0 -288
- package/src/commands/start.ts +0 -877
- package/src/commands/status.ts +0 -99
- package/src/commands/stop.ts +0 -406
- package/src/config/manager.ts +0 -157
- package/src/index.ts +0 -188
- package/src/services/api-server.ts +0 -485
- package/src/services/port-manager.ts +0 -177
- package/src/services/process-registry.ts +0 -154
- package/src/services/program-cloner.ts +0 -317
- package/src/services/token-cloner.ts +0 -809
- package/src/services/validator.ts +0 -295
- package/src/types/config.ts +0 -110
- package/src/utils/shell.ts +0 -110
- package/src/utils/token-loader.ts +0 -115
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export interface ApiConfig {
|
|
2
|
+
rpcUrl: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface ApiStatus {
|
|
6
|
+
slot: number;
|
|
7
|
+
slotBigint: string;
|
|
8
|
+
blockHeight: number;
|
|
9
|
+
blockHeightBigint: string;
|
|
10
|
+
txCount: number;
|
|
11
|
+
txCountBigint: string;
|
|
12
|
+
latestBlockhash: string;
|
|
13
|
+
faucet: {
|
|
14
|
+
address: string;
|
|
15
|
+
lamports: string;
|
|
16
|
+
sol: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ProgramSummary {
|
|
21
|
+
programId: string;
|
|
22
|
+
owner: string;
|
|
23
|
+
executable: boolean;
|
|
24
|
+
dataLen: number;
|
|
25
|
+
lamports: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TokenSummary {
|
|
29
|
+
mint: string;
|
|
30
|
+
supply: string;
|
|
31
|
+
decimals: number;
|
|
32
|
+
uiAmount: number;
|
|
33
|
+
uiAmountString: string;
|
|
34
|
+
mintAuthority: string | null;
|
|
35
|
+
isInitialized: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AirdropPayload {
|
|
39
|
+
address: string;
|
|
40
|
+
lamports?: string | number;
|
|
41
|
+
sol?: string | number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MintPayload {
|
|
45
|
+
mint: string;
|
|
46
|
+
owner: string;
|
|
47
|
+
amountRaw: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CloneProgramPayload {
|
|
51
|
+
programId: string;
|
|
52
|
+
endpoint?: string;
|
|
53
|
+
withAccounts?: boolean;
|
|
54
|
+
accountsLimit?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CloneTokenPayload {
|
|
58
|
+
mint: string;
|
|
59
|
+
endpoint?: string;
|
|
60
|
+
cloneAccounts?: boolean;
|
|
61
|
+
holders?: number;
|
|
62
|
+
allAccounts?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ApiError {
|
|
66
|
+
error?: string;
|
|
67
|
+
details?: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
71
|
+
const headers = new Headers(init.headers ?? {});
|
|
72
|
+
if (!headers.has("content-type") && init.body)
|
|
73
|
+
headers.set("content-type", "application/json");
|
|
74
|
+
const response = await fetch(path, { ...init, headers });
|
|
75
|
+
let payload: any = null;
|
|
76
|
+
const text = await response.text();
|
|
77
|
+
if (text) {
|
|
78
|
+
try {
|
|
79
|
+
payload = JSON.parse(text);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Failed to parse response from ${path}: ${String(error)}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const errPayload = payload as ApiError | null;
|
|
88
|
+
const message =
|
|
89
|
+
errPayload?.error || response.statusText || "Request failed";
|
|
90
|
+
throw new Error(message);
|
|
91
|
+
}
|
|
92
|
+
return payload as T;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const fetchConfig = () => request<ApiConfig>("/api/config");
|
|
96
|
+
export const fetchStatus = () => request<ApiStatus>("/api/status");
|
|
97
|
+
export const fetchPrograms = () => request<ProgramSummary[]>("/api/programs");
|
|
98
|
+
export const fetchTokens = () => request<TokenSummary[]>("/api/tokens");
|
|
99
|
+
export const submitAirdrop = (body: AirdropPayload) =>
|
|
100
|
+
request<{ ok: boolean; signature?: string }>("/api/airdrop", {
|
|
101
|
+
method: "POST",
|
|
102
|
+
body: JSON.stringify(body),
|
|
103
|
+
});
|
|
104
|
+
export const submitMint = (body: MintPayload) =>
|
|
105
|
+
request<
|
|
106
|
+
| {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
signature?: string;
|
|
109
|
+
mint: string;
|
|
110
|
+
owner: string;
|
|
111
|
+
amount: string;
|
|
112
|
+
}
|
|
113
|
+
| Record<string, unknown>
|
|
114
|
+
>("/api/mint", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: JSON.stringify(body),
|
|
117
|
+
});
|
|
118
|
+
export const cloneProgram = (body: CloneProgramPayload) =>
|
|
119
|
+
request<Record<string, unknown>>("/api/clone/program", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
});
|
|
123
|
+
export const cloneToken = (body: CloneTokenPayload) =>
|
|
124
|
+
request<Record<string, unknown>>("/api/clone/token", {
|
|
125
|
+
method: "POST",
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
});
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
type ApiConfig,
|
|
4
|
+
type ApiStatus,
|
|
5
|
+
cloneProgram,
|
|
6
|
+
cloneToken,
|
|
7
|
+
fetchConfig,
|
|
8
|
+
fetchPrograms,
|
|
9
|
+
fetchStatus,
|
|
10
|
+
fetchTokens,
|
|
11
|
+
type ProgramSummary,
|
|
12
|
+
submitAirdrop,
|
|
13
|
+
submitMint,
|
|
14
|
+
type TokenSummary,
|
|
15
|
+
} from "./api";
|
|
16
|
+
import { AirdropMintForm } from "./components/airdrop-mint-form";
|
|
17
|
+
import { CloneProgramModal } from "./components/clone-program-modal";
|
|
18
|
+
import { CloneTokenModal } from "./components/clone-token-modal";
|
|
19
|
+
import { ProgramsPanel } from "./components/programs-panel";
|
|
20
|
+
import { StatusPanel } from "./components/status-panel";
|
|
21
|
+
import { TokensPanel } from "./components/tokens-panel";
|
|
22
|
+
import { useInterval } from "./hooks/use-interval";
|
|
23
|
+
|
|
24
|
+
export function App() {
|
|
25
|
+
const [config, setConfig] = useState<ApiConfig | null>(null);
|
|
26
|
+
const [status, setStatus] = useState<ApiStatus | null>(null);
|
|
27
|
+
const [programs, setPrograms] = useState<ProgramSummary[]>([]);
|
|
28
|
+
const [tokens, setTokens] = useState<TokenSummary[]>([]);
|
|
29
|
+
const [loadingStatus, setLoadingStatus] = useState(false);
|
|
30
|
+
const [loadingPrograms, setLoadingPrograms] = useState(false);
|
|
31
|
+
const [loadingTokens, setLoadingTokens] = useState(false);
|
|
32
|
+
const [programModalOpen, setProgramModalOpen] = useState(false);
|
|
33
|
+
const [tokenModalOpen, setTokenModalOpen] = useState(false);
|
|
34
|
+
const [bannerError, setBannerError] = useState<string | null>(null);
|
|
35
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
36
|
+
const [activeSection, setActiveSection] = useState("status");
|
|
37
|
+
|
|
38
|
+
const loadConfig = useCallback(async () => {
|
|
39
|
+
try {
|
|
40
|
+
const cfg = await fetchConfig();
|
|
41
|
+
setConfig(cfg);
|
|
42
|
+
setBannerError(null);
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
setBannerError(error?.message ?? String(error));
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const loadStatus = useCallback(async () => {
|
|
49
|
+
setLoadingStatus(true);
|
|
50
|
+
try {
|
|
51
|
+
const data = await fetchStatus();
|
|
52
|
+
setStatus(data);
|
|
53
|
+
} catch (error: any) {
|
|
54
|
+
setBannerError(error?.message ?? String(error));
|
|
55
|
+
} finally {
|
|
56
|
+
setLoadingStatus(false);
|
|
57
|
+
}
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const loadPrograms = useCallback(async () => {
|
|
61
|
+
setLoadingPrograms(true);
|
|
62
|
+
try {
|
|
63
|
+
const data = await fetchPrograms();
|
|
64
|
+
setPrograms(data);
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
setBannerError(error?.message ?? String(error));
|
|
67
|
+
} finally {
|
|
68
|
+
setLoadingPrograms(false);
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const loadTokens = useCallback(async () => {
|
|
73
|
+
setLoadingTokens(true);
|
|
74
|
+
try {
|
|
75
|
+
const data = await fetchTokens();
|
|
76
|
+
setTokens(data);
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
setBannerError(error?.message ?? String(error));
|
|
79
|
+
} finally {
|
|
80
|
+
setLoadingTokens(false);
|
|
81
|
+
}
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
loadConfig();
|
|
86
|
+
loadStatus();
|
|
87
|
+
loadPrograms();
|
|
88
|
+
loadTokens();
|
|
89
|
+
}, [loadConfig, loadStatus, loadPrograms, loadTokens]);
|
|
90
|
+
|
|
91
|
+
useInterval(loadStatus, 5_000);
|
|
92
|
+
|
|
93
|
+
const onAirdrop = useCallback(
|
|
94
|
+
async (address: string, lamports: string) => {
|
|
95
|
+
const result = await submitAirdrop({ address, lamports });
|
|
96
|
+
await loadStatus();
|
|
97
|
+
return result.signature;
|
|
98
|
+
},
|
|
99
|
+
[loadStatus],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const onMint = useCallback(
|
|
103
|
+
async (mint: string, owner: string, amountRaw: string) => {
|
|
104
|
+
const result = await submitMint({ mint, owner, amountRaw });
|
|
105
|
+
await Promise.all([loadStatus(), loadTokens()]);
|
|
106
|
+
if (result && typeof result === "object" && "signature" in result) {
|
|
107
|
+
return (result as { signature?: string }).signature;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
},
|
|
111
|
+
[loadStatus, loadTokens],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const openProgramModal = () => setProgramModalOpen(true);
|
|
115
|
+
const openTokenModal = () => setTokenModalOpen(true);
|
|
116
|
+
|
|
117
|
+
const handleCloneProgram = useCallback(
|
|
118
|
+
async (payload: {
|
|
119
|
+
programId: string;
|
|
120
|
+
endpoint?: string;
|
|
121
|
+
withAccounts: boolean;
|
|
122
|
+
accountsLimit?: number;
|
|
123
|
+
}) => {
|
|
124
|
+
await cloneProgram(payload);
|
|
125
|
+
await loadPrograms();
|
|
126
|
+
},
|
|
127
|
+
[loadPrograms],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const handleCloneToken = useCallback(
|
|
131
|
+
async (payload: {
|
|
132
|
+
mint: string;
|
|
133
|
+
endpoint?: string;
|
|
134
|
+
cloneAccounts: boolean;
|
|
135
|
+
holders?: number;
|
|
136
|
+
allAccounts?: boolean;
|
|
137
|
+
}) => {
|
|
138
|
+
await cloneToken(payload);
|
|
139
|
+
await loadTokens();
|
|
140
|
+
},
|
|
141
|
+
[loadTokens],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const scrollToSection = (sectionId: string) => {
|
|
145
|
+
setActiveSection(sectionId);
|
|
146
|
+
document.getElementById(sectionId)?.scrollIntoView({ behavior: 'smooth' });
|
|
147
|
+
setSidebarOpen(false);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="min-h-screen relative">
|
|
152
|
+
{/* Mobile Menu Button */}
|
|
153
|
+
<button
|
|
154
|
+
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
155
|
+
className="lg:hidden fixed top-4 left-4 z-50 btn-icon bg-gradient-to-br from-purple-600 to-violet-600 border-purple-500/30"
|
|
156
|
+
aria-label="Menu"
|
|
157
|
+
>
|
|
158
|
+
<i className={`fas fa-${sidebarOpen ? 'times' : 'bars'} text-white`}></i>
|
|
159
|
+
</button>
|
|
160
|
+
|
|
161
|
+
{/* Sidebar Navigation */}
|
|
162
|
+
<aside className={`fixed top-0 left-0 h-full w-72 glass-panel rounded-none border-r border-white/5 z-40 transition-transform duration-300 ${
|
|
163
|
+
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
|
|
164
|
+
}`}>
|
|
165
|
+
<div className="p-6 space-y-8">
|
|
166
|
+
{/* Logo */}
|
|
167
|
+
<div className="flex items-center gap-3">
|
|
168
|
+
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-600 to-violet-600 flex items-center justify-center shadow-lg">
|
|
169
|
+
<i className="fas fa-fire text-white text-xl"></i>
|
|
170
|
+
</div>
|
|
171
|
+
<div>
|
|
172
|
+
<h1 className="text-xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
|
|
173
|
+
SolForge
|
|
174
|
+
</h1>
|
|
175
|
+
<p className="text-xs text-gray-500">Development Suite</p>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
{/* Navigation Items */}
|
|
180
|
+
<nav className="space-y-2">
|
|
181
|
+
<button
|
|
182
|
+
onClick={() => scrollToSection('status')}
|
|
183
|
+
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
184
|
+
activeSection === 'status'
|
|
185
|
+
? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
|
|
186
|
+
: 'text-gray-400 hover:bg-white/5'
|
|
187
|
+
}`}
|
|
188
|
+
>
|
|
189
|
+
<i className="fas fa-server w-5"></i>
|
|
190
|
+
<span className="font-medium">Network Status</span>
|
|
191
|
+
</button>
|
|
192
|
+
<button
|
|
193
|
+
onClick={() => scrollToSection('actions')}
|
|
194
|
+
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
195
|
+
activeSection === 'actions'
|
|
196
|
+
? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
|
|
197
|
+
: 'text-gray-400 hover:bg-white/5'
|
|
198
|
+
}`}
|
|
199
|
+
>
|
|
200
|
+
<i className="fas fa-paper-plane w-5"></i>
|
|
201
|
+
<span className="font-medium">Quick Actions</span>
|
|
202
|
+
</button>
|
|
203
|
+
<button
|
|
204
|
+
onClick={() => scrollToSection('programs')}
|
|
205
|
+
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
206
|
+
activeSection === 'programs'
|
|
207
|
+
? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
|
|
208
|
+
: 'text-gray-400 hover:bg-white/5'
|
|
209
|
+
}`}
|
|
210
|
+
>
|
|
211
|
+
<i className="fas fa-code w-5"></i>
|
|
212
|
+
<span className="font-medium">Programs</span>
|
|
213
|
+
</button>
|
|
214
|
+
<button
|
|
215
|
+
onClick={() => scrollToSection('tokens')}
|
|
216
|
+
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
217
|
+
activeSection === 'tokens'
|
|
218
|
+
? 'bg-gradient-to-r from-purple-600/20 to-violet-600/20 border border-purple-500/30 text-purple-300'
|
|
219
|
+
: 'text-gray-400 hover:bg-white/5'
|
|
220
|
+
}`}
|
|
221
|
+
>
|
|
222
|
+
<i className="fas fa-coins w-5"></i>
|
|
223
|
+
<span className="font-medium">Tokens</span>
|
|
224
|
+
</button>
|
|
225
|
+
</nav>
|
|
226
|
+
|
|
227
|
+
{/* Quick Stats */}
|
|
228
|
+
{config && (
|
|
229
|
+
<div className="space-y-3">
|
|
230
|
+
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">Connection</h3>
|
|
231
|
+
<div className="p-3 rounded-xl bg-white/5 border border-white/10">
|
|
232
|
+
<div className="flex items-center gap-2 mb-2">
|
|
233
|
+
<span className="status-dot online"></span>
|
|
234
|
+
<span className="text-xs text-gray-400">Connected</span>
|
|
235
|
+
</div>
|
|
236
|
+
<p className="text-xs text-gray-500 font-mono break-all">{config.rpcUrl}</p>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
</aside>
|
|
242
|
+
|
|
243
|
+
{/* Overlay for mobile */}
|
|
244
|
+
{sidebarOpen && (
|
|
245
|
+
<div
|
|
246
|
+
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30 lg:hidden"
|
|
247
|
+
onClick={() => setSidebarOpen(false)}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
|
|
251
|
+
{/* Main Content */}
|
|
252
|
+
<main className={`transition-all duration-300 lg:ml-72 p-4 md:p-8`}>
|
|
253
|
+
<div className="max-w-7xl mx-auto space-y-6">
|
|
254
|
+
{/* Header - Only show on desktop */}
|
|
255
|
+
<header className="glass-panel p-6 animate-fadeIn hidden md:block">
|
|
256
|
+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
257
|
+
<div>
|
|
258
|
+
<h2 className="text-3xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
|
|
259
|
+
SolForge Dashboard
|
|
260
|
+
</h2>
|
|
261
|
+
<p className="text-gray-400 mt-1">
|
|
262
|
+
Manage your local Solana development environment
|
|
263
|
+
</p>
|
|
264
|
+
</div>
|
|
265
|
+
<button
|
|
266
|
+
onClick={loadStatus}
|
|
267
|
+
className="btn-secondary"
|
|
268
|
+
>
|
|
269
|
+
<i className={`fas fa-sync-alt ${loadingStatus ? 'animate-spin' : ''}`}></i>
|
|
270
|
+
<span>Refresh All</span>
|
|
271
|
+
</button>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
{/* Error Banner */}
|
|
275
|
+
{bannerError && (
|
|
276
|
+
<div className="mt-6 p-4 rounded-xl bg-gradient-to-r from-red-500/10 to-pink-500/10 border border-red-500/30 flex items-start gap-3 animate-slideIn">
|
|
277
|
+
<i className="fas fa-exclamation-circle text-red-400 mt-0.5"></i>
|
|
278
|
+
<div className="flex-1">
|
|
279
|
+
<p className="text-sm text-red-300">{bannerError}</p>
|
|
280
|
+
</div>
|
|
281
|
+
<button
|
|
282
|
+
onClick={() => setBannerError(null)}
|
|
283
|
+
className="text-red-400 hover:text-red-300"
|
|
284
|
+
aria-label="Close error"
|
|
285
|
+
>
|
|
286
|
+
<i className="fas fa-times"></i>
|
|
287
|
+
</button>
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
</header>
|
|
291
|
+
|
|
292
|
+
{/* Mobile Header - Simpler version */}
|
|
293
|
+
<div className="md:hidden mb-4">
|
|
294
|
+
<h2 className="text-2xl font-bold bg-gradient-to-r from-purple-400 to-violet-400 bg-clip-text text-transparent">
|
|
295
|
+
SolForge
|
|
296
|
+
</h2>
|
|
297
|
+
{bannerError && (
|
|
298
|
+
<div className="mt-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
299
|
+
<p className="text-xs text-red-300">{bannerError}</p>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{/* Status Panel */}
|
|
305
|
+
<div id="status" className="animate-fadeIn scroll-mt-24">
|
|
306
|
+
<StatusPanel
|
|
307
|
+
status={status}
|
|
308
|
+
loading={loadingStatus}
|
|
309
|
+
onRefresh={loadStatus}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
{/* Quick Actions - Optional */}
|
|
314
|
+
<div id="actions" className="glass-panel p-6 animate-fadeIn scroll-mt-24" style={{animationDelay: '0.1s'}}>
|
|
315
|
+
<AirdropMintForm
|
|
316
|
+
tokens={tokens}
|
|
317
|
+
onAirdrop={onAirdrop}
|
|
318
|
+
onMint={onMint}
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
{/* Programs and Tokens Stacked */}
|
|
323
|
+
<div className="space-y-6">
|
|
324
|
+
<div id="programs" className="animate-fadeIn scroll-mt-24" style={{animationDelay: '0.2s'}}>
|
|
325
|
+
<ProgramsPanel
|
|
326
|
+
programs={programs}
|
|
327
|
+
loading={loadingPrograms}
|
|
328
|
+
onRefresh={loadPrograms}
|
|
329
|
+
onAdd={openProgramModal}
|
|
330
|
+
/>
|
|
331
|
+
</div>
|
|
332
|
+
<div id="tokens" className="animate-fadeIn scroll-mt-24" style={{animationDelay: '0.3s'}}>
|
|
333
|
+
<TokensPanel
|
|
334
|
+
tokens={tokens}
|
|
335
|
+
loading={loadingTokens}
|
|
336
|
+
onRefresh={loadTokens}
|
|
337
|
+
onAdd={openTokenModal}
|
|
338
|
+
/>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</main>
|
|
343
|
+
|
|
344
|
+
{/* Modals */}
|
|
345
|
+
<CloneProgramModal
|
|
346
|
+
isOpen={programModalOpen}
|
|
347
|
+
onClose={() => setProgramModalOpen(false)}
|
|
348
|
+
onSubmit={handleCloneProgram}
|
|
349
|
+
/>
|
|
350
|
+
<CloneTokenModal
|
|
351
|
+
isOpen={tokenModalOpen}
|
|
352
|
+
onClose={() => setTokenModalOpen(false)}
|
|
353
|
+
onSubmit={handleCloneToken}
|
|
354
|
+
/>
|
|
355
|
+
|
|
356
|
+
<style jsx>{`
|
|
357
|
+
@keyframes fadeIn {
|
|
358
|
+
from {
|
|
359
|
+
opacity: 0;
|
|
360
|
+
transform: translateY(20px);
|
|
361
|
+
}
|
|
362
|
+
to {
|
|
363
|
+
opacity: 1;
|
|
364
|
+
transform: translateY(0);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@keyframes slideIn {
|
|
369
|
+
from {
|
|
370
|
+
opacity: 0;
|
|
371
|
+
transform: translateX(-20px);
|
|
372
|
+
}
|
|
373
|
+
to {
|
|
374
|
+
opacity: 1;
|
|
375
|
+
transform: translateX(0);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.animate-fadeIn {
|
|
380
|
+
animation: fadeIn 0.6s ease-out forwards;
|
|
381
|
+
opacity: 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.animate-slideIn {
|
|
385
|
+
animation: slideIn 0.4s ease-out forwards;
|
|
386
|
+
}
|
|
387
|
+
`}</style>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
}
|