solforge 0.2.4 → 0.2.6
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/README.md +471 -79
- package/cli.cjs +106 -78
- package/package.json +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.cjs +69 -61
- package/server/lib/base58.ts +1 -1
- package/server/methods/account/get-account-info.ts +3 -7
- package/server/methods/account/get-balance.ts +3 -7
- package/server/methods/account/get-multiple-accounts.ts +2 -1
- package/server/methods/account/get-parsed-account-info.ts +3 -7
- package/server/methods/account/parsers/index.ts +2 -2
- package/server/methods/account/parsers/loader-upgradeable.ts +14 -1
- package/server/methods/account/parsers/spl-token.ts +29 -10
- package/server/methods/account/request-airdrop.ts +44 -31
- package/server/methods/block/get-block.ts +3 -7
- package/server/methods/block/get-blocks-with-limit.ts +3 -7
- package/server/methods/block/is-blockhash-valid.ts +3 -7
- package/server/methods/get-address-lookup-table.ts +3 -7
- package/server/methods/program/get-program-accounts.ts +9 -9
- package/server/methods/program/get-token-account-balance.ts +3 -7
- package/server/methods/program/get-token-accounts-by-delegate.ts +4 -3
- package/server/methods/program/get-token-accounts-by-owner.ts +61 -35
- package/server/methods/program/get-token-largest-accounts.ts +3 -2
- package/server/methods/program/get-token-supply.ts +3 -2
- package/server/methods/solforge/index.ts +9 -6
- package/server/methods/transaction/get-parsed-transaction.ts +3 -7
- package/server/methods/transaction/get-signature-statuses.ts +14 -7
- package/server/methods/transaction/get-signatures-for-address.ts +3 -7
- package/server/methods/transaction/get-transaction.ts +167 -81
- package/server/methods/transaction/send-transaction.ts +29 -16
- package/server/methods/transaction/simulate-transaction.ts +3 -2
- package/server/rpc-server.ts +47 -34
- package/server/types.ts +9 -6
- package/server/ws-server.ts +15 -8
- package/src/api-server-entry.ts +91 -91
- package/src/cli/commands/airdrop.ts +2 -2
- package/src/cli/commands/config.ts +2 -2
- package/src/cli/commands/mint.ts +3 -3
- package/src/cli/commands/program-clone.ts +9 -11
- package/src/cli/commands/program-load.ts +3 -3
- package/src/cli/commands/rpc-start.ts +8 -5
- package/src/cli/commands/token-adopt-authority.ts +1 -1
- package/src/cli/commands/token-clone.ts +5 -6
- package/src/cli/commands/token-create.ts +5 -5
- package/src/cli/main.ts +38 -37
- package/src/cli/run-solforge.ts +20 -6
- package/src/cli/setup-wizard.ts +8 -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 +247 -248
- package/src/commands/start.ts +837 -833
- package/src/commands/status.ts +80 -80
- package/src/commands/stop.ts +381 -382
- package/src/config/index.ts +33 -17
- package/src/config/manager.ts +150 -150
- package/src/db/index.ts +2 -2
- package/src/db/tx-store.ts +12 -8
- package/src/gui/public/app.css +1556 -1
- package/src/gui/public/build/main.css +1569 -1
- package/src/gui/server.ts +21 -22
- package/src/gui/src/api.ts +1 -1
- package/src/gui/src/app.tsx +96 -45
- package/src/gui/src/components/airdrop-mint-form.tsx +49 -19
- package/src/gui/src/components/clone-program-modal.tsx +31 -12
- package/src/gui/src/components/clone-token-modal.tsx +32 -13
- package/src/gui/src/components/modal.tsx +18 -11
- package/src/gui/src/components/programs-panel.tsx +27 -15
- package/src/gui/src/components/status-panel.tsx +32 -18
- package/src/gui/src/components/tokens-panel.tsx +25 -19
- package/src/gui/src/index.css +491 -463
- package/src/index.ts +177 -149
- package/src/rpc/start.ts +1 -1
- package/src/services/api-server.ts +494 -475
- package/src/services/port-manager.ts +164 -167
- package/src/services/process-registry.ts +144 -145
- package/src/services/program-cloner.ts +312 -312
- package/src/services/token-cloner.ts +799 -797
- package/src/services/validator.ts +288 -290
- package/src/types/config.ts +72 -72
- package/src/utils/shell.ts +75 -75
- package/src/utils/token-loader.ts +78 -78
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChangeEvent, useState } from "react";
|
|
1
|
+
import { type ChangeEvent, useId, useState } from "react";
|
|
2
2
|
import { Modal } from "./modal";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
@@ -14,6 +14,9 @@ interface Props {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
17
|
+
const mintId = useId();
|
|
18
|
+
const endpointId = useId();
|
|
19
|
+
const holdersId = useId();
|
|
17
20
|
const [mint, setMint] = useState("");
|
|
18
21
|
const [endpoint, setEndpoint] = useState("");
|
|
19
22
|
// Default OFF to avoid hitting public RPC rate limits by cloning holders.
|
|
@@ -42,8 +45,12 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
42
45
|
setEndpoint("");
|
|
43
46
|
setHolders("20");
|
|
44
47
|
setAllAccounts(false);
|
|
45
|
-
} catch (err:
|
|
46
|
-
|
|
48
|
+
} catch (err: unknown) {
|
|
49
|
+
const message =
|
|
50
|
+
err && typeof err === "object" && "message" in err
|
|
51
|
+
? String((err as { message?: unknown }).message)
|
|
52
|
+
: String(err);
|
|
53
|
+
setError(message);
|
|
47
54
|
} finally {
|
|
48
55
|
setPending(false);
|
|
49
56
|
}
|
|
@@ -77,7 +84,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
77
84
|
type="button"
|
|
78
85
|
onClick={handleSubmit}
|
|
79
86
|
disabled={pending || mint.trim().length === 0}
|
|
80
|
-
className={`btn-primary ${
|
|
87
|
+
className={`btn-primary ${pending || mint.trim().length === 0 ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
81
88
|
>
|
|
82
89
|
{pending ? (
|
|
83
90
|
<>
|
|
@@ -97,11 +104,15 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
97
104
|
>
|
|
98
105
|
<div className="space-y-5">
|
|
99
106
|
<div className="space-y-2">
|
|
100
|
-
<label
|
|
107
|
+
<label
|
|
108
|
+
htmlFor={mintId}
|
|
109
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
110
|
+
>
|
|
101
111
|
Mint Address *
|
|
102
112
|
</label>
|
|
103
113
|
<div className="relative">
|
|
104
114
|
<input
|
|
115
|
+
id={mintId}
|
|
105
116
|
value={mint}
|
|
106
117
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
107
118
|
setMint(event.target.value)
|
|
@@ -112,13 +123,17 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
112
123
|
<i className="fas fa-coin absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
113
124
|
</div>
|
|
114
125
|
</div>
|
|
115
|
-
|
|
126
|
+
|
|
116
127
|
<div className="space-y-2">
|
|
117
|
-
<label
|
|
128
|
+
<label
|
|
129
|
+
htmlFor={endpointId}
|
|
130
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
131
|
+
>
|
|
118
132
|
RPC Endpoint (Optional)
|
|
119
133
|
</label>
|
|
120
134
|
<div className="relative">
|
|
121
135
|
<input
|
|
136
|
+
id={endpointId}
|
|
122
137
|
value={endpoint}
|
|
123
138
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
124
139
|
setEndpoint(event.target.value)
|
|
@@ -129,7 +144,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
129
144
|
<i className="fas fa-globe absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
|
130
145
|
</div>
|
|
131
146
|
</div>
|
|
132
|
-
|
|
147
|
+
|
|
133
148
|
<div className="p-4 rounded-xl bg-white/5 border border-white/10 space-y-3">
|
|
134
149
|
<label className="flex items-center gap-3 cursor-pointer group">
|
|
135
150
|
<input
|
|
@@ -149,7 +164,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
149
164
|
</p>
|
|
150
165
|
</div>
|
|
151
166
|
</label>
|
|
152
|
-
|
|
167
|
+
|
|
153
168
|
{cloneAccounts && (
|
|
154
169
|
<div className="ml-8 space-y-4 pt-3 border-t border-white/5">
|
|
155
170
|
<label className="flex items-center gap-3 cursor-pointer group">
|
|
@@ -170,14 +185,18 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
170
185
|
</p>
|
|
171
186
|
</div>
|
|
172
187
|
</label>
|
|
173
|
-
|
|
188
|
+
|
|
174
189
|
{!allAccounts && (
|
|
175
190
|
<div className="space-y-2">
|
|
176
|
-
<label
|
|
191
|
+
<label
|
|
192
|
+
htmlFor={holdersId}
|
|
193
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
194
|
+
>
|
|
177
195
|
Top Holders Limit
|
|
178
196
|
</label>
|
|
179
197
|
<div className="relative">
|
|
180
198
|
<input
|
|
199
|
+
id={holdersId}
|
|
181
200
|
value={holders}
|
|
182
201
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
183
202
|
setHolders(event.target.value)
|
|
@@ -198,7 +217,7 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
198
217
|
</div>
|
|
199
218
|
)}
|
|
200
219
|
</div>
|
|
201
|
-
|
|
220
|
+
|
|
202
221
|
{error && (
|
|
203
222
|
<div className="flex items-start gap-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30">
|
|
204
223
|
<i className="fas fa-exclamation-circle text-red-400 mt-0.5"></i>
|
|
@@ -208,4 +227,4 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
208
227
|
</div>
|
|
209
228
|
</Modal>
|
|
210
229
|
);
|
|
211
|
-
}
|
|
230
|
+
}
|
|
@@ -31,27 +31,34 @@ 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
|
-
<
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
44
45
|
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
|
46
|
+
aria-label="Close modal"
|
|
45
47
|
onClick={onClose}
|
|
48
|
+
onKeyDown={(e) => {
|
|
49
|
+
if (e.key === "Enter" || e.key === " ") onClose();
|
|
50
|
+
}}
|
|
46
51
|
/>
|
|
47
|
-
|
|
52
|
+
|
|
48
53
|
{/* Modal */}
|
|
49
54
|
<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
55
|
{/* Header */}
|
|
51
56
|
<div className="p-6 border-b border-white/10 bg-gradient-to-r from-white/5 to-transparent">
|
|
52
57
|
<div className="flex items-center justify-between">
|
|
53
58
|
<div className="flex items-center gap-3">
|
|
54
|
-
<div
|
|
59
|
+
<div
|
|
60
|
+
className={`w-10 h-10 rounded-xl bg-gradient-to-br ${colorClasses[iconColor as keyof typeof colorClasses]} flex items-center justify-center`}
|
|
61
|
+
>
|
|
55
62
|
<i className={`fas ${icon}`}></i>
|
|
56
63
|
</div>
|
|
57
64
|
<h3 className="text-xl font-bold text-white">{title}</h3>
|
|
@@ -66,12 +73,12 @@ export function Modal({
|
|
|
66
73
|
</button>
|
|
67
74
|
</div>
|
|
68
75
|
</div>
|
|
69
|
-
|
|
76
|
+
|
|
70
77
|
{/* Content */}
|
|
71
78
|
<div className="p-6 max-h-[60vh] overflow-y-auto custom-scrollbar">
|
|
72
79
|
{children}
|
|
73
80
|
</div>
|
|
74
|
-
|
|
81
|
+
|
|
75
82
|
{/* Footer */}
|
|
76
83
|
{footer && (
|
|
77
84
|
<div className="p-6 border-t border-white/10 bg-gradient-to-r from-white/5 to-transparent">
|
|
@@ -79,7 +86,7 @@ export function Modal({
|
|
|
79
86
|
</div>
|
|
80
87
|
)}
|
|
81
88
|
</div>
|
|
82
|
-
|
|
89
|
+
|
|
83
90
|
<style jsx>{`
|
|
84
91
|
@keyframes fadeIn {
|
|
85
92
|
from {
|
|
@@ -124,4 +131,4 @@ export function Modal({
|
|
|
124
131
|
`}</style>
|
|
125
132
|
</div>
|
|
126
133
|
);
|
|
127
|
-
}
|
|
134
|
+
}
|
|
@@ -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>
|
|
@@ -18,20 +18,22 @@ export function StatusPanel({ status, loading, onRefresh }: Props) {
|
|
|
18
18
|
</div>
|
|
19
19
|
<div>
|
|
20
20
|
<h2 className="text-xl font-bold text-white">Network Status</h2>
|
|
21
|
-
<p className="text-xs text-gray-500">
|
|
21
|
+
<p className="text-xs text-gray-500">
|
|
22
|
+
Real-time blockchain metrics
|
|
23
|
+
</p>
|
|
22
24
|
</div>
|
|
23
25
|
</div>
|
|
24
26
|
<button
|
|
25
27
|
type="button"
|
|
26
28
|
onClick={onRefresh}
|
|
27
29
|
disabled={loading}
|
|
28
|
-
className={`btn-secondary ${loading ?
|
|
30
|
+
className={`btn-secondary ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
29
31
|
>
|
|
30
|
-
<i className={`fas fa-sync-alt ${loading ?
|
|
32
|
+
<i className={`fas fa-sync-alt ${loading ? "animate-spin" : ""}`}></i>
|
|
31
33
|
<span>{loading ? "Refreshing" : "Refresh"}</span>
|
|
32
34
|
</button>
|
|
33
35
|
</div>
|
|
34
|
-
|
|
36
|
+
|
|
35
37
|
{status ? (
|
|
36
38
|
<>
|
|
37
39
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
@@ -59,19 +61,23 @@ export function StatusPanel({ status, loading, onRefresh }: Props) {
|
|
|
59
61
|
<StatusCard
|
|
60
62
|
title="Faucet Balance"
|
|
61
63
|
value={`${status.faucet.sol.toFixed(3)} SOL`}
|
|
62
|
-
subtitle={status.faucet.address.slice(0, 10)
|
|
64
|
+
subtitle={`${status.faucet.address.slice(0, 10)}…`}
|
|
63
65
|
icon="fa-wallet"
|
|
64
66
|
color="green"
|
|
65
67
|
/>
|
|
66
68
|
</div>
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
{status.latestBlockhash && (
|
|
69
71
|
<div className="mt-6 p-4 rounded-xl bg-white/5 border border-white/10">
|
|
70
72
|
<div className="flex items-center gap-2 mb-2">
|
|
71
73
|
<i className="fas fa-fingerprint text-violet-400 text-xs"></i>
|
|
72
|
-
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
74
|
+
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wider">
|
|
75
|
+
Latest Blockhash
|
|
76
|
+
</span>
|
|
73
77
|
</div>
|
|
74
|
-
<p className="text-sm font-mono text-violet-300 break-all">
|
|
78
|
+
<p className="text-sm font-mono text-violet-300 break-all">
|
|
79
|
+
{status.latestBlockhash}
|
|
80
|
+
</p>
|
|
75
81
|
</div>
|
|
76
82
|
)}
|
|
77
83
|
</>
|
|
@@ -81,7 +87,9 @@ export function StatusPanel({ status, loading, onRefresh }: Props) {
|
|
|
81
87
|
<i className="fas fa-server text-gray-500 text-2xl"></i>
|
|
82
88
|
</div>
|
|
83
89
|
<p className="text-gray-400 mb-2">No connection to RPC</p>
|
|
84
|
-
<p className="text-sm text-gray-500">
|
|
90
|
+
<p className="text-sm text-gray-500">
|
|
91
|
+
Start the RPC server to see network status
|
|
92
|
+
</p>
|
|
85
93
|
</div>
|
|
86
94
|
)}
|
|
87
95
|
</section>
|
|
@@ -93,30 +101,36 @@ interface StatusCardProps {
|
|
|
93
101
|
value: string;
|
|
94
102
|
subtitle?: string;
|
|
95
103
|
icon: string;
|
|
96
|
-
color:
|
|
104
|
+
color: "purple" | "blue" | "amber" | "green";
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
const colorClasses = {
|
|
100
|
-
purple:
|
|
101
|
-
blue:
|
|
102
|
-
amber:
|
|
103
|
-
green:
|
|
108
|
+
purple: "from-purple-500/20 to-violet-500/20 text-purple-400",
|
|
109
|
+
blue: "from-blue-500/20 to-cyan-500/20 text-blue-400",
|
|
110
|
+
amber: "from-amber-500/20 to-orange-500/20 text-amber-400",
|
|
111
|
+
green: "from-green-500/20 to-emerald-500/20 text-green-400",
|
|
104
112
|
};
|
|
105
113
|
|
|
106
114
|
function StatusCard({ title, value, subtitle, icon, color }: StatusCardProps) {
|
|
107
115
|
return (
|
|
108
116
|
<div className="card group hover:scale-[1.02] transition-all duration-200">
|
|
109
117
|
<div className="flex items-start justify-between mb-3">
|
|
110
|
-
<div
|
|
118
|
+
<div
|
|
119
|
+
className={`w-10 h-10 rounded-xl bg-gradient-to-br ${colorClasses[color]} flex items-center justify-center group-hover:scale-110 transition-transform`}
|
|
120
|
+
>
|
|
111
121
|
<i className={`fas ${icon} text-sm`}></i>
|
|
112
122
|
</div>
|
|
113
123
|
<span className="status-dot online"></span>
|
|
114
124
|
</div>
|
|
115
|
-
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">
|
|
125
|
+
<p className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">
|
|
126
|
+
{title}
|
|
127
|
+
</p>
|
|
116
128
|
<p className="text-2xl font-bold text-white">{value}</p>
|
|
117
129
|
{subtitle && (
|
|
118
|
-
<p className="mt-2 text-xs text-gray-500 font-mono truncate">
|
|
130
|
+
<p className="mt-2 text-xs text-gray-500 font-mono truncate">
|
|
131
|
+
{subtitle}
|
|
132
|
+
</p>
|
|
119
133
|
)}
|
|
120
134
|
</div>
|
|
121
135
|
);
|
|
122
|
-
}
|
|
136
|
+
}
|
|
@@ -17,9 +17,7 @@ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
|
|
|
17
17
|
</div>
|
|
18
18
|
<div>
|
|
19
19
|
<h2 className="text-xl font-bold text-white">Tokens</h2>
|
|
20
|
-
<p className="text-xs text-gray-500">
|
|
21
|
-
{tokens.length} SPL tokens
|
|
22
|
-
</p>
|
|
20
|
+
<p className="text-xs text-gray-500">{tokens.length} SPL tokens</p>
|
|
23
21
|
</div>
|
|
24
22
|
</div>
|
|
25
23
|
<div className="flex items-center gap-2">
|
|
@@ -27,22 +25,20 @@ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
|
|
|
27
25
|
type="button"
|
|
28
26
|
onClick={onRefresh}
|
|
29
27
|
disabled={loading}
|
|
30
|
-
className={`btn-secondary text-sm ${loading ?
|
|
28
|
+
className={`btn-secondary text-sm ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
|
|
31
29
|
>
|
|
32
|
-
<i
|
|
30
|
+
<i
|
|
31
|
+
className={`fas fa-sync-alt ${loading ? "animate-spin" : ""}`}
|
|
32
|
+
></i>
|
|
33
33
|
<span>{loading ? "Refreshing" : "Refresh"}</span>
|
|
34
34
|
</button>
|
|
35
|
-
<button
|
|
36
|
-
type="button"
|
|
37
|
-
onClick={onAdd}
|
|
38
|
-
className="btn-primary text-sm"
|
|
39
|
-
>
|
|
35
|
+
<button type="button" onClick={onAdd} className="btn-primary text-sm">
|
|
40
36
|
<i className="fas fa-plus"></i>
|
|
41
37
|
<span>Add Token</span>
|
|
42
38
|
</button>
|
|
43
39
|
</div>
|
|
44
40
|
</header>
|
|
45
|
-
|
|
41
|
+
|
|
46
42
|
<div className="overflow-x-auto rounded-xl">
|
|
47
43
|
{tokens.length === 0 ? (
|
|
48
44
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
@@ -50,7 +46,9 @@ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
|
|
|
50
46
|
<i className="fas fa-coins text-amber-500 text-2xl"></i>
|
|
51
47
|
</div>
|
|
52
48
|
<p className="text-gray-400 mb-2">No tokens created</p>
|
|
53
|
-
<p className="text-sm text-gray-500">
|
|
49
|
+
<p className="text-sm text-gray-500">
|
|
50
|
+
Click "Add Token" to clone from mainnet
|
|
51
|
+
</p>
|
|
54
52
|
</div>
|
|
55
53
|
) : (
|
|
56
54
|
<table className="table-modern">
|
|
@@ -65,7 +63,11 @@ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
|
|
|
65
63
|
</thead>
|
|
66
64
|
<tbody>
|
|
67
65
|
{tokens.map((token, index) => (
|
|
68
|
-
<tr
|
|
66
|
+
<tr
|
|
67
|
+
key={token.mint}
|
|
68
|
+
style={{ animationDelay: `${index * 50}ms` }}
|
|
69
|
+
className="animate-fadeIn"
|
|
70
|
+
>
|
|
69
71
|
<td>
|
|
70
72
|
<div className="flex items-center gap-2">
|
|
71
73
|
<i className="fas fa-coin text-amber-400 text-xs"></i>
|
|
@@ -83,9 +85,7 @@ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
|
|
|
83
85
|
</div>
|
|
84
86
|
</td>
|
|
85
87
|
<td>
|
|
86
|
-
<span className="badge">
|
|
87
|
-
{token.decimals}
|
|
88
|
-
</span>
|
|
88
|
+
<span className="badge">{token.decimals}</span>
|
|
89
89
|
</td>
|
|
90
90
|
<td>
|
|
91
91
|
{token.mintAuthority ? (
|
|
@@ -96,12 +96,18 @@ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
|
|
|
96
96
|
</span>
|
|
97
97
|
</div>
|
|
98
98
|
) : (
|
|
99
|
-
<span className="text-gray-500 text-sm">
|
|
99
|
+
<span className="text-gray-500 text-sm">
|
|
100
|
+
No authority
|
|
101
|
+
</span>
|
|
100
102
|
)}
|
|
101
103
|
</td>
|
|
102
104
|
<td>
|
|
103
|
-
<span
|
|
104
|
-
|
|
105
|
+
<span
|
|
106
|
+
className={`badge ${token.isInitialized ? "badge-success" : "badge-warning"}`}
|
|
107
|
+
>
|
|
108
|
+
<i
|
|
109
|
+
className={`fas fa-${token.isInitialized ? "check-circle" : "clock"} text-xs`}
|
|
110
|
+
></i>
|
|
105
111
|
<span>{token.isInitialized ? "Active" : "Pending"}</span>
|
|
106
112
|
</span>
|
|
107
113
|
</td>
|