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.
Files changed (82) hide show
  1. package/README.md +471 -79
  2. package/cli.cjs +106 -78
  3. package/package.json +1 -1
  4. package/scripts/install.sh +1 -1
  5. package/scripts/postinstall.cjs +69 -61
  6. package/server/lib/base58.ts +1 -1
  7. package/server/methods/account/get-account-info.ts +3 -7
  8. package/server/methods/account/get-balance.ts +3 -7
  9. package/server/methods/account/get-multiple-accounts.ts +2 -1
  10. package/server/methods/account/get-parsed-account-info.ts +3 -7
  11. package/server/methods/account/parsers/index.ts +2 -2
  12. package/server/methods/account/parsers/loader-upgradeable.ts +14 -1
  13. package/server/methods/account/parsers/spl-token.ts +29 -10
  14. package/server/methods/account/request-airdrop.ts +44 -31
  15. package/server/methods/block/get-block.ts +3 -7
  16. package/server/methods/block/get-blocks-with-limit.ts +3 -7
  17. package/server/methods/block/is-blockhash-valid.ts +3 -7
  18. package/server/methods/get-address-lookup-table.ts +3 -7
  19. package/server/methods/program/get-program-accounts.ts +9 -9
  20. package/server/methods/program/get-token-account-balance.ts +3 -7
  21. package/server/methods/program/get-token-accounts-by-delegate.ts +4 -3
  22. package/server/methods/program/get-token-accounts-by-owner.ts +61 -35
  23. package/server/methods/program/get-token-largest-accounts.ts +3 -2
  24. package/server/methods/program/get-token-supply.ts +3 -2
  25. package/server/methods/solforge/index.ts +9 -6
  26. package/server/methods/transaction/get-parsed-transaction.ts +3 -7
  27. package/server/methods/transaction/get-signature-statuses.ts +14 -7
  28. package/server/methods/transaction/get-signatures-for-address.ts +3 -7
  29. package/server/methods/transaction/get-transaction.ts +167 -81
  30. package/server/methods/transaction/send-transaction.ts +29 -16
  31. package/server/methods/transaction/simulate-transaction.ts +3 -2
  32. package/server/rpc-server.ts +47 -34
  33. package/server/types.ts +9 -6
  34. package/server/ws-server.ts +15 -8
  35. package/src/api-server-entry.ts +91 -91
  36. package/src/cli/commands/airdrop.ts +2 -2
  37. package/src/cli/commands/config.ts +2 -2
  38. package/src/cli/commands/mint.ts +3 -3
  39. package/src/cli/commands/program-clone.ts +9 -11
  40. package/src/cli/commands/program-load.ts +3 -3
  41. package/src/cli/commands/rpc-start.ts +8 -5
  42. package/src/cli/commands/token-adopt-authority.ts +1 -1
  43. package/src/cli/commands/token-clone.ts +5 -6
  44. package/src/cli/commands/token-create.ts +5 -5
  45. package/src/cli/main.ts +38 -37
  46. package/src/cli/run-solforge.ts +20 -6
  47. package/src/cli/setup-wizard.ts +8 -6
  48. package/src/commands/add-program.ts +324 -328
  49. package/src/commands/init.ts +106 -106
  50. package/src/commands/list.ts +125 -125
  51. package/src/commands/mint.ts +247 -248
  52. package/src/commands/start.ts +837 -833
  53. package/src/commands/status.ts +80 -80
  54. package/src/commands/stop.ts +381 -382
  55. package/src/config/index.ts +33 -17
  56. package/src/config/manager.ts +150 -150
  57. package/src/db/index.ts +2 -2
  58. package/src/db/tx-store.ts +12 -8
  59. package/src/gui/public/app.css +1556 -1
  60. package/src/gui/public/build/main.css +1569 -1
  61. package/src/gui/server.ts +21 -22
  62. package/src/gui/src/api.ts +1 -1
  63. package/src/gui/src/app.tsx +96 -45
  64. package/src/gui/src/components/airdrop-mint-form.tsx +49 -19
  65. package/src/gui/src/components/clone-program-modal.tsx +31 -12
  66. package/src/gui/src/components/clone-token-modal.tsx +32 -13
  67. package/src/gui/src/components/modal.tsx +18 -11
  68. package/src/gui/src/components/programs-panel.tsx +27 -15
  69. package/src/gui/src/components/status-panel.tsx +32 -18
  70. package/src/gui/src/components/tokens-panel.tsx +25 -19
  71. package/src/gui/src/index.css +491 -463
  72. package/src/index.ts +177 -149
  73. package/src/rpc/start.ts +1 -1
  74. package/src/services/api-server.ts +494 -475
  75. package/src/services/port-manager.ts +164 -167
  76. package/src/services/process-registry.ts +144 -145
  77. package/src/services/program-cloner.ts +312 -312
  78. package/src/services/token-cloner.ts +799 -797
  79. package/src/services/validator.ts +288 -290
  80. package/src/types/config.ts +72 -72
  81. package/src/utils/shell.ts +75 -75
  82. 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: any) {
46
- setError(err?.message ?? String(err));
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 ${(pending || mint.trim().length === 0) ? 'opacity-50 cursor-not-allowed' : ''}`}
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 className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
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 className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
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 className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">
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: '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',
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
+ <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 className={`w-10 h-10 rounded-xl bg-gradient-to-br ${colorClasses[iconColor as keyof typeof colorClasses]} flex items-center justify-center`}>
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 ? 'opacity-50 cursor-not-allowed' : ''}`}
30
+ className={`btn-secondary text-sm ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
31
31
  >
32
- <i className={`fas fa-sync-alt ${loading ? 'animate-spin' : ''}`}></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">Click "Add Program" to clone from mainnet</p>
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 key={program.programId} style={{animationDelay: `${index * 50}ms`}} className="animate-fadeIn">
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)}...{program.programId.slice(-6)}
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 className={`badge ${program.executable ? 'badge-success' : 'badge-warning'}`}>
84
- <i className={`fas fa-${program.executable ? 'check' : 'pause'} text-xs`}></i>
85
- <span>{program.executable ? "Executable" : "Data Only"}</span>
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)} SOL
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">Real-time blockchain metrics</p>
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 ? 'opacity-50 cursor-not-allowed' : ''}`}
30
+ className={`btn-secondary ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
29
31
  >
30
- <i className={`fas fa-sync-alt ${loading ? 'animate-spin' : ''}`}></i>
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">Latest Blockhash</span>
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">{status.latestBlockhash}</p>
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">Start the RPC server to see network status</p>
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: 'purple' | 'blue' | 'amber' | 'green';
104
+ color: "purple" | "blue" | "amber" | "green";
97
105
  }
98
106
 
99
107
  const colorClasses = {
100
- purple: 'from-purple-500/20 to-violet-500/20 text-purple-400',
101
- blue: 'from-blue-500/20 to-cyan-500/20 text-blue-400',
102
- amber: 'from-amber-500/20 to-orange-500/20 text-amber-400',
103
- green: 'from-green-500/20 to-emerald-500/20 text-green-400',
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 className={`w-10 h-10 rounded-xl bg-gradient-to-br ${colorClasses[color]} flex items-center justify-center group-hover:scale-110 transition-transform`}>
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">{title}</p>
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">{subtitle}</p>
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 ? 'opacity-50 cursor-not-allowed' : ''}`}
28
+ className={`btn-secondary text-sm ${loading ? "opacity-50 cursor-not-allowed" : ""}`}
31
29
  >
32
- <i className={`fas fa-sync-alt ${loading ? 'animate-spin' : ''}`}></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">Click "Add Token" to clone from mainnet</p>
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 key={token.mint} style={{animationDelay: `${index * 50}ms`}} className="animate-fadeIn">
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">No authority</span>
99
+ <span className="text-gray-500 text-sm">
100
+ No authority
101
+ </span>
100
102
  )}
101
103
  </td>
102
104
  <td>
103
- <span className={`badge ${token.isInitialized ? 'badge-success' : 'badge-warning'}`}>
104
- <i className={`fas fa-${token.isInitialized ? 'check-circle' : 'clock'} text-xs`}></i>
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>