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.
Files changed (195) hide show
  1. package/.agi/agi.sqlite +0 -0
  2. package/.claude/settings.local.json +9 -0
  3. package/.github/workflows/release-binaries.yml +133 -0
  4. package/.tmp/.787ebcdbf7b8fde8-00000000.hm +0 -0
  5. package/.tmp/.bffe6efebdf8aedc-00000000.hm +0 -0
  6. package/AGENTS.md +271 -0
  7. package/CLAUDE.md +106 -0
  8. package/PROJECT_STRUCTURE.md +124 -0
  9. package/README.md +367 -393
  10. package/SOLANA_KIT_GUIDE.md +251 -0
  11. package/SOLFORGE.md +119 -0
  12. package/biome.json +34 -0
  13. package/bun.lock +743 -0
  14. package/docs/bun-single-file-executable.md +585 -0
  15. package/docs/cli-plan.md +154 -0
  16. package/docs/data-indexing-plan.md +214 -0
  17. package/docs/gui-roadmap.md +202 -0
  18. package/drizzle/0000_friendly_millenium_guard.sql +53 -0
  19. package/drizzle/0001_stale_sentinels.sql +2 -0
  20. package/drizzle/meta/0000_snapshot.json +329 -0
  21. package/drizzle/meta/0001_snapshot.json +345 -0
  22. package/drizzle/meta/_journal.json +20 -0
  23. package/drizzle.config.ts +12 -0
  24. package/index.ts +21 -0
  25. package/mint.sh +47 -0
  26. package/package.json +45 -69
  27. package/postcss.config.js +6 -0
  28. package/rpc-server.ts.backup +519 -0
  29. package/server/index.ts +5 -0
  30. package/server/lib/base58.ts +33 -0
  31. package/server/lib/faucet.ts +110 -0
  32. package/server/lib/spl-token.ts +57 -0
  33. package/server/methods/TEMPLATE.md +117 -0
  34. package/server/methods/account/get-account-info.ts +90 -0
  35. package/server/methods/account/get-balance.ts +27 -0
  36. package/server/methods/account/get-multiple-accounts.ts +83 -0
  37. package/server/methods/account/get-parsed-account-info.ts +21 -0
  38. package/server/methods/account/index.ts +12 -0
  39. package/server/methods/account/parsers/index.ts +52 -0
  40. package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
  41. package/server/methods/account/parsers/spl-token.ts +237 -0
  42. package/server/methods/account/parsers/system.ts +4 -0
  43. package/server/methods/account/request-airdrop.ts +219 -0
  44. package/server/methods/admin/adopt-mint-authority.ts +94 -0
  45. package/server/methods/admin/clone-program-accounts.ts +55 -0
  46. package/server/methods/admin/clone-program.ts +152 -0
  47. package/server/methods/admin/clone-token-accounts.ts +117 -0
  48. package/server/methods/admin/clone-token-mint.ts +82 -0
  49. package/server/methods/admin/create-mint.ts +114 -0
  50. package/server/methods/admin/create-token-account.ts +137 -0
  51. package/server/methods/admin/helpers.ts +70 -0
  52. package/server/methods/admin/index.ts +10 -0
  53. package/server/methods/admin/list-mints.ts +21 -0
  54. package/server/methods/admin/load-program.ts +52 -0
  55. package/server/methods/admin/mint-to.ts +278 -0
  56. package/server/methods/block/get-block-height.ts +5 -0
  57. package/server/methods/block/get-block.ts +35 -0
  58. package/server/methods/block/get-blocks-with-limit.ts +23 -0
  59. package/server/methods/block/get-latest-blockhash.ts +12 -0
  60. package/server/methods/block/get-slot.ts +5 -0
  61. package/server/methods/block/index.ts +6 -0
  62. package/server/methods/block/is-blockhash-valid.ts +23 -0
  63. package/server/methods/epoch/get-cluster-nodes.ts +17 -0
  64. package/server/methods/epoch/get-epoch-info.ts +16 -0
  65. package/server/methods/epoch/get-epoch-schedule.ts +15 -0
  66. package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
  67. package/server/methods/epoch/get-leader-schedule.ts +8 -0
  68. package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
  69. package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
  70. package/server/methods/epoch/get-slot-leader.ts +6 -0
  71. package/server/methods/epoch/get-slot-leaders.ts +9 -0
  72. package/server/methods/epoch/get-stake-activation.ts +9 -0
  73. package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
  74. package/server/methods/epoch/get-vote-accounts.ts +19 -0
  75. package/server/methods/epoch/index.ts +13 -0
  76. package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
  77. package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
  78. package/server/methods/fee/get-fee-for-message.ts +8 -0
  79. package/server/methods/fee/get-fee-rate-governor.ts +16 -0
  80. package/server/methods/fee/get-fees.ts +14 -0
  81. package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
  82. package/server/methods/fee/index.ts +5 -0
  83. package/server/methods/get-address-lookup-table.ts +31 -0
  84. package/server/methods/index.ts +265 -0
  85. package/server/methods/performance/get-recent-performance-samples.ts +25 -0
  86. package/server/methods/performance/get-transaction-count.ts +5 -0
  87. package/server/methods/performance/index.ts +2 -0
  88. package/server/methods/program/get-block-commitment.ts +9 -0
  89. package/server/methods/program/get-block-production.ts +14 -0
  90. package/server/methods/program/get-block-time.ts +21 -0
  91. package/server/methods/program/get-blocks.ts +11 -0
  92. package/server/methods/program/get-first-available-block.ts +9 -0
  93. package/server/methods/program/get-genesis-hash.ts +6 -0
  94. package/server/methods/program/get-identity.ts +6 -0
  95. package/server/methods/program/get-inflation-governor.ts +15 -0
  96. package/server/methods/program/get-inflation-rate.ts +10 -0
  97. package/server/methods/program/get-inflation-reward.ts +12 -0
  98. package/server/methods/program/get-largest-accounts.ts +8 -0
  99. package/server/methods/program/get-parsed-program-accounts.ts +12 -0
  100. package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
  101. package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
  102. package/server/methods/program/get-program-accounts.ts +221 -0
  103. package/server/methods/program/get-supply.ts +13 -0
  104. package/server/methods/program/get-token-account-balance.ts +64 -0
  105. package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
  106. package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
  107. package/server/methods/program/get-token-largest-accounts.ts +80 -0
  108. package/server/methods/program/get-token-supply.ts +38 -0
  109. package/server/methods/program/index.ts +21 -0
  110. package/server/methods/solforge/index.ts +155 -0
  111. package/server/methods/system/get-health.ts +5 -0
  112. package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
  113. package/server/methods/system/get-version.ts +9 -0
  114. package/server/methods/system/index.ts +3 -0
  115. package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
  116. package/server/methods/transaction/get-parsed-transaction.ts +21 -0
  117. package/server/methods/transaction/get-signature-statuses.ts +72 -0
  118. package/server/methods/transaction/get-signatures-for-address.ts +45 -0
  119. package/server/methods/transaction/get-transaction.ts +428 -0
  120. package/server/methods/transaction/index.ts +7 -0
  121. package/server/methods/transaction/send-transaction.ts +232 -0
  122. package/server/methods/transaction/simulate-transaction.ts +56 -0
  123. package/server/rpc-server.ts +474 -0
  124. package/server/types.ts +74 -0
  125. package/server/ws-server.ts +171 -0
  126. package/sf.config.json +38 -0
  127. package/src/cli/bootstrap.ts +67 -0
  128. package/src/cli/commands/airdrop.ts +37 -0
  129. package/src/cli/commands/config.ts +39 -0
  130. package/src/cli/commands/mint.ts +187 -0
  131. package/src/cli/commands/program-clone.ts +124 -0
  132. package/src/cli/commands/program-load.ts +64 -0
  133. package/src/cli/commands/rpc-start.ts +46 -0
  134. package/src/cli/commands/token-adopt-authority.ts +37 -0
  135. package/src/cli/commands/token-clone.ts +113 -0
  136. package/src/cli/commands/token-create.ts +81 -0
  137. package/src/cli/main.ts +130 -0
  138. package/src/cli/run-solforge.ts +98 -0
  139. package/src/cli/setup-utils.ts +54 -0
  140. package/src/cli/setup-wizard.ts +256 -0
  141. package/src/cli/utils/args.ts +15 -0
  142. package/src/config/index.ts +130 -0
  143. package/src/db/index.ts +83 -0
  144. package/src/db/schema/accounts.ts +23 -0
  145. package/src/db/schema/address-signatures.ts +31 -0
  146. package/src/db/schema/index.ts +5 -0
  147. package/src/db/schema/meta-kv.ts +9 -0
  148. package/src/db/schema/transactions.ts +29 -0
  149. package/src/db/schema/tx-accounts.ts +33 -0
  150. package/src/db/tx-store.ts +229 -0
  151. package/src/gui/public/app.css +1 -0
  152. package/src/gui/public/index.html +19 -0
  153. package/src/gui/server.ts +297 -0
  154. package/src/gui/src/api.ts +127 -0
  155. package/src/gui/src/app.tsx +390 -0
  156. package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
  157. package/src/gui/src/components/clone-program-modal.tsx +183 -0
  158. package/src/gui/src/components/clone-token-modal.tsx +211 -0
  159. package/src/gui/src/components/modal.tsx +127 -0
  160. package/src/gui/src/components/programs-panel.tsx +112 -0
  161. package/src/gui/src/components/status-panel.tsx +122 -0
  162. package/src/gui/src/components/tokens-panel.tsx +116 -0
  163. package/src/gui/src/hooks/use-interval.ts +17 -0
  164. package/src/gui/src/index.css +529 -0
  165. package/src/gui/src/main.tsx +17 -0
  166. package/src/migrations-bundled.ts +17 -0
  167. package/src/rpc/start.ts +44 -0
  168. package/tailwind.config.js +27 -0
  169. package/test-client.ts +120 -0
  170. package/tmp/inspect-html.ts +4 -0
  171. package/tmp/response-test.ts +5 -0
  172. package/tmp/test-html.ts +5 -0
  173. package/tmp/test-server.ts +13 -0
  174. package/tsconfig.json +24 -23
  175. package/LICENSE +0 -21
  176. package/scripts/postinstall.cjs +0 -103
  177. package/src/api-server-entry.ts +0 -109
  178. package/src/commands/add-program.ts +0 -337
  179. package/src/commands/init.ts +0 -122
  180. package/src/commands/list.ts +0 -136
  181. package/src/commands/mint.ts +0 -288
  182. package/src/commands/start.ts +0 -877
  183. package/src/commands/status.ts +0 -99
  184. package/src/commands/stop.ts +0 -406
  185. package/src/config/manager.ts +0 -157
  186. package/src/index.ts +0 -188
  187. package/src/services/api-server.ts +0 -485
  188. package/src/services/port-manager.ts +0 -177
  189. package/src/services/process-registry.ts +0 -154
  190. package/src/services/program-cloner.ts +0 -317
  191. package/src/services/token-cloner.ts +0 -809
  192. package/src/services/validator.ts +0 -295
  193. package/src/types/config.ts +0 -110
  194. package/src/utils/shell.ts +0 -110
  195. package/src/utils/token-loader.ts +0 -115
@@ -0,0 +1,127 @@
1
+ import { type ReactNode, useEffect } from "react";
2
+
3
+ interface ModalProps {
4
+ isOpen: boolean;
5
+ title: string;
6
+ onClose: () => void;
7
+ children: ReactNode;
8
+ footer?: ReactNode;
9
+ icon?: string;
10
+ iconColor?: string;
11
+ }
12
+
13
+ export function Modal({
14
+ isOpen,
15
+ title,
16
+ onClose,
17
+ children,
18
+ footer,
19
+ icon = "fa-window-maximize",
20
+ iconColor = "purple",
21
+ }: ModalProps) {
22
+ useEffect(() => {
23
+ if (!isOpen || typeof window === "undefined") return undefined;
24
+ const handler = (event: KeyboardEvent) => {
25
+ if (event.key === "Escape") onClose();
26
+ };
27
+ window.addEventListener("keydown", handler);
28
+ return () => window.removeEventListener("keydown", handler);
29
+ }, [isOpen, onClose]);
30
+
31
+ if (!isOpen) return null;
32
+
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',
38
+ };
39
+
40
+ return (
41
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-fadeIn">
42
+ {/* Backdrop */}
43
+ <div
44
+ className="absolute inset-0 bg-black/80 backdrop-blur-sm"
45
+ onClick={onClose}
46
+ />
47
+
48
+ {/* Modal */}
49
+ <div className="w-full max-w-lg p-0 relative animate-modalSlideIn overflow-hidden rounded-2xl border border-white/20 bg-gray-900/95 backdrop-blur-xl shadow-2xl">
50
+ {/* Header */}
51
+ <div className="p-6 border-b border-white/10 bg-gradient-to-r from-white/5 to-transparent">
52
+ <div className="flex items-center justify-between">
53
+ <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`}>
55
+ <i className={`fas ${icon}`}></i>
56
+ </div>
57
+ <h3 className="text-xl font-bold text-white">{title}</h3>
58
+ </div>
59
+ <button
60
+ type="button"
61
+ onClick={onClose}
62
+ className="btn-icon hover:bg-red-500/20 hover:border-red-500/30 hover:text-red-400"
63
+ aria-label="Close modal"
64
+ >
65
+ <i className="fas fa-times"></i>
66
+ </button>
67
+ </div>
68
+ </div>
69
+
70
+ {/* Content */}
71
+ <div className="p-6 max-h-[60vh] overflow-y-auto custom-scrollbar">
72
+ {children}
73
+ </div>
74
+
75
+ {/* Footer */}
76
+ {footer && (
77
+ <div className="p-6 border-t border-white/10 bg-gradient-to-r from-white/5 to-transparent">
78
+ {footer}
79
+ </div>
80
+ )}
81
+ </div>
82
+
83
+ <style jsx>{`
84
+ @keyframes fadeIn {
85
+ from {
86
+ opacity: 0;
87
+ }
88
+ to {
89
+ opacity: 1;
90
+ }
91
+ }
92
+
93
+ @keyframes modalSlideIn {
94
+ from {
95
+ opacity: 0;
96
+ transform: scale(0.95) translateY(20px);
97
+ }
98
+ to {
99
+ opacity: 1;
100
+ transform: scale(1) translateY(0);
101
+ }
102
+ }
103
+
104
+ .animate-fadeIn {
105
+ animation: fadeIn 0.2s ease-out;
106
+ }
107
+
108
+ .animate-modalSlideIn {
109
+ animation: modalSlideIn 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
110
+ }
111
+
112
+ .custom-scrollbar::-webkit-scrollbar {
113
+ width: 8px;
114
+ }
115
+
116
+ .custom-scrollbar::-webkit-scrollbar-track {
117
+ background: transparent;
118
+ }
119
+
120
+ .custom-scrollbar::-webkit-scrollbar-thumb {
121
+ background: linear-gradient(180deg, var(--color-accent-primary), var(--color-accent-secondary));
122
+ border-radius: 4px;
123
+ }
124
+ `}</style>
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,112 @@
1
+ import type { ProgramSummary } from "../api";
2
+
3
+ interface Props {
4
+ programs: ProgramSummary[];
5
+ loading: boolean;
6
+ onRefresh: () => void;
7
+ onAdd: () => void;
8
+ }
9
+
10
+ export function ProgramsPanel({ programs, loading, onRefresh, onAdd }: Props) {
11
+ return (
12
+ <section className="glass-panel p-6">
13
+ <header className="flex flex-wrap items-center justify-between gap-3 mb-6">
14
+ <div className="flex items-center gap-3">
15
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-500/20 to-cyan-500/20 flex items-center justify-center">
16
+ <i className="fas fa-code text-blue-400"></i>
17
+ </div>
18
+ <div>
19
+ <h2 className="text-xl font-bold text-white">Programs</h2>
20
+ <p className="text-xs text-gray-500">
21
+ {programs.length} deployed programs
22
+ </p>
23
+ </div>
24
+ </div>
25
+ <div className="flex items-center gap-2">
26
+ <button
27
+ type="button"
28
+ onClick={onRefresh}
29
+ disabled={loading}
30
+ className={`btn-secondary text-sm ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
31
+ >
32
+ <i className={`fas fa-sync-alt ${loading ? 'animate-spin' : ''}`}></i>
33
+ <span>{loading ? "Refreshing" : "Refresh"}</span>
34
+ </button>
35
+ <button
36
+ type="button"
37
+ onClick={onAdd}
38
+ className="btn-primary text-sm"
39
+ >
40
+ <i className="fas fa-plus"></i>
41
+ <span>Add Program</span>
42
+ </button>
43
+ </div>
44
+ </header>
45
+
46
+ <div className="overflow-x-auto rounded-xl">
47
+ {programs.length === 0 ? (
48
+ <div className="flex flex-col items-center justify-center py-12 text-center">
49
+ <div className="w-16 h-16 rounded-full bg-gradient-to-br from-blue-600/20 to-cyan-600/20 flex items-center justify-center mb-4">
50
+ <i className="fas fa-code text-blue-500 text-2xl"></i>
51
+ </div>
52
+ <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>
54
+ </div>
55
+ ) : (
56
+ <table className="table-modern">
57
+ <thead>
58
+ <tr>
59
+ <th>Program ID</th>
60
+ <th>Owner</th>
61
+ <th>Status</th>
62
+ <th>Data Size</th>
63
+ <th>Balance</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ {programs.map((program, index) => (
68
+ <tr key={program.programId} style={{animationDelay: `${index * 50}ms`}} className="animate-fadeIn">
69
+ <td>
70
+ <div className="flex items-center gap-2">
71
+ <i className="fas fa-cube text-blue-400 text-xs"></i>
72
+ <span className="font-mono text-xs text-blue-300">
73
+ {program.programId.slice(0, 8)}...{program.programId.slice(-6)}
74
+ </span>
75
+ </div>
76
+ </td>
77
+ <td>
78
+ <span className="font-mono text-xs text-gray-400">
79
+ {program.owner.slice(0, 8)}...
80
+ </span>
81
+ </td>
82
+ <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>
86
+ </span>
87
+ </td>
88
+ <td>
89
+ <div className="flex items-center gap-2">
90
+ <i className="fas fa-database text-gray-400 text-xs"></i>
91
+ <span className="text-gray-300">
92
+ {(program.dataLen / 1024).toFixed(1)} KB
93
+ </span>
94
+ </div>
95
+ </td>
96
+ <td>
97
+ <div className="flex items-center gap-2">
98
+ <i className="fas fa-wallet text-gray-400 text-xs"></i>
99
+ <span className="text-gray-300">
100
+ {(Number(BigInt(program.lamports)) / 1e9).toFixed(4)} SOL
101
+ </span>
102
+ </div>
103
+ </td>
104
+ </tr>
105
+ ))}
106
+ </tbody>
107
+ </table>
108
+ )}
109
+ </div>
110
+ </section>
111
+ );
112
+ }
@@ -0,0 +1,122 @@
1
+ import type { ApiStatus } from "../api";
2
+
3
+ interface Props {
4
+ status: ApiStatus | null;
5
+ loading: boolean;
6
+ onRefresh: () => void;
7
+ }
8
+
9
+ const formatter = new Intl.NumberFormat("en-US");
10
+
11
+ export function StatusPanel({ status, loading, onRefresh }: Props) {
12
+ return (
13
+ <section className="glass-panel p-6">
14
+ <div className="flex items-center justify-between mb-6">
15
+ <div className="flex items-center gap-3">
16
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-green-500/20 to-emerald-500/20 flex items-center justify-center">
17
+ <i className="fas fa-server text-green-400"></i>
18
+ </div>
19
+ <div>
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>
22
+ </div>
23
+ </div>
24
+ <button
25
+ type="button"
26
+ onClick={onRefresh}
27
+ disabled={loading}
28
+ className={`btn-secondary ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
29
+ >
30
+ <i className={`fas fa-sync-alt ${loading ? 'animate-spin' : ''}`}></i>
31
+ <span>{loading ? "Refreshing" : "Refresh"}</span>
32
+ </button>
33
+ </div>
34
+
35
+ {status ? (
36
+ <>
37
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
38
+ <StatusCard
39
+ title="Current Slot"
40
+ value={formatter.format(status.slot)}
41
+ subtitle={`Raw: ${status.slotBigint}`}
42
+ icon="fa-cube"
43
+ color="purple"
44
+ />
45
+ <StatusCard
46
+ title="Block Height"
47
+ value={formatter.format(status.blockHeight)}
48
+ subtitle={`Raw: ${status.blockHeightBigint}`}
49
+ icon="fa-layer-group"
50
+ color="blue"
51
+ />
52
+ <StatusCard
53
+ title="Transactions"
54
+ value={formatter.format(status.txCount)}
55
+ subtitle={`Raw: ${status.txCountBigint}`}
56
+ icon="fa-exchange-alt"
57
+ color="amber"
58
+ />
59
+ <StatusCard
60
+ title="Faucet Balance"
61
+ value={`${status.faucet.sol.toFixed(3)} SOL`}
62
+ subtitle={status.faucet.address.slice(0, 10) + "…"}
63
+ icon="fa-wallet"
64
+ color="green"
65
+ />
66
+ </div>
67
+
68
+ {status.latestBlockhash && (
69
+ <div className="mt-6 p-4 rounded-xl bg-white/5 border border-white/10">
70
+ <div className="flex items-center gap-2 mb-2">
71
+ <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>
73
+ </div>
74
+ <p className="text-sm font-mono text-violet-300 break-all">{status.latestBlockhash}</p>
75
+ </div>
76
+ )}
77
+ </>
78
+ ) : (
79
+ <div className="flex flex-col items-center justify-center py-12 text-center">
80
+ <div className="w-16 h-16 rounded-full bg-gradient-to-br from-gray-600/20 to-gray-700/20 flex items-center justify-center mb-4">
81
+ <i className="fas fa-server text-gray-500 text-2xl"></i>
82
+ </div>
83
+ <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>
85
+ </div>
86
+ )}
87
+ </section>
88
+ );
89
+ }
90
+
91
+ interface StatusCardProps {
92
+ title: string;
93
+ value: string;
94
+ subtitle?: string;
95
+ icon: string;
96
+ color: 'purple' | 'blue' | 'amber' | 'green';
97
+ }
98
+
99
+ 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',
104
+ };
105
+
106
+ function StatusCard({ title, value, subtitle, icon, color }: StatusCardProps) {
107
+ return (
108
+ <div className="card group hover:scale-[1.02] transition-all duration-200">
109
+ <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`}>
111
+ <i className={`fas ${icon} text-sm`}></i>
112
+ </div>
113
+ <span className="status-dot online"></span>
114
+ </div>
115
+ <p className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1">{title}</p>
116
+ <p className="text-2xl font-bold text-white">{value}</p>
117
+ {subtitle && (
118
+ <p className="mt-2 text-xs text-gray-500 font-mono truncate">{subtitle}</p>
119
+ )}
120
+ </div>
121
+ );
122
+ }
@@ -0,0 +1,116 @@
1
+ import type { TokenSummary } from "../api";
2
+
3
+ interface Props {
4
+ tokens: TokenSummary[];
5
+ loading: boolean;
6
+ onRefresh: () => void;
7
+ onAdd: () => void;
8
+ }
9
+
10
+ export function TokensPanel({ tokens, loading, onRefresh, onAdd }: Props) {
11
+ return (
12
+ <section className="glass-panel p-6">
13
+ <header className="flex flex-wrap items-center justify-between gap-3 mb-6">
14
+ <div className="flex items-center gap-3">
15
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500/20 to-orange-500/20 flex items-center justify-center">
16
+ <i className="fas fa-coins text-amber-400"></i>
17
+ </div>
18
+ <div>
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>
23
+ </div>
24
+ </div>
25
+ <div className="flex items-center gap-2">
26
+ <button
27
+ type="button"
28
+ onClick={onRefresh}
29
+ disabled={loading}
30
+ className={`btn-secondary text-sm ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
31
+ >
32
+ <i className={`fas fa-sync-alt ${loading ? 'animate-spin' : ''}`}></i>
33
+ <span>{loading ? "Refreshing" : "Refresh"}</span>
34
+ </button>
35
+ <button
36
+ type="button"
37
+ onClick={onAdd}
38
+ className="btn-primary text-sm"
39
+ >
40
+ <i className="fas fa-plus"></i>
41
+ <span>Add Token</span>
42
+ </button>
43
+ </div>
44
+ </header>
45
+
46
+ <div className="overflow-x-auto rounded-xl">
47
+ {tokens.length === 0 ? (
48
+ <div className="flex flex-col items-center justify-center py-12 text-center">
49
+ <div className="w-16 h-16 rounded-full bg-gradient-to-br from-amber-600/20 to-orange-600/20 flex items-center justify-center mb-4">
50
+ <i className="fas fa-coins text-amber-500 text-2xl"></i>
51
+ </div>
52
+ <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>
54
+ </div>
55
+ ) : (
56
+ <table className="table-modern">
57
+ <thead>
58
+ <tr>
59
+ <th>Token Mint</th>
60
+ <th>Supply</th>
61
+ <th>Decimals</th>
62
+ <th>Authority</th>
63
+ <th>Status</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ {tokens.map((token, index) => (
68
+ <tr key={token.mint} style={{animationDelay: `${index * 50}ms`}} className="animate-fadeIn">
69
+ <td>
70
+ <div className="flex items-center gap-2">
71
+ <i className="fas fa-coin text-amber-400 text-xs"></i>
72
+ <span className="font-mono text-xs text-amber-300">
73
+ {token.mint.slice(0, 8)}...{token.mint.slice(-6)}
74
+ </span>
75
+ </div>
76
+ </td>
77
+ <td>
78
+ <div className="flex items-center gap-2">
79
+ <i className="fas fa-chart-line text-gray-400 text-xs"></i>
80
+ <span className="text-gray-300 font-semibold">
81
+ {token.uiAmountString}
82
+ </span>
83
+ </div>
84
+ </td>
85
+ <td>
86
+ <span className="badge">
87
+ {token.decimals}
88
+ </span>
89
+ </td>
90
+ <td>
91
+ {token.mintAuthority ? (
92
+ <div className="flex items-center gap-2">
93
+ <i className="fas fa-key text-purple-400 text-xs"></i>
94
+ <span className="font-mono text-xs text-purple-300">
95
+ {token.mintAuthority.slice(0, 8)}...
96
+ </span>
97
+ </div>
98
+ ) : (
99
+ <span className="text-gray-500 text-sm">No authority</span>
100
+ )}
101
+ </td>
102
+ <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>{token.isInitialized ? "Active" : "Pending"}</span>
106
+ </span>
107
+ </td>
108
+ </tr>
109
+ ))}
110
+ </tbody>
111
+ </table>
112
+ )}
113
+ </div>
114
+ </section>
115
+ );
116
+ }
@@ -0,0 +1,17 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ // Minimal interval hook to avoid stale closures
4
+ export function useInterval(callback: () => void, delay: number | null) {
5
+ const savedCallback = useRef(callback);
6
+
7
+ useEffect(() => {
8
+ savedCallback.current = callback;
9
+ }, [callback]);
10
+
11
+ useEffect(() => {
12
+ if (delay === null) return undefined;
13
+ const tick = () => savedCallback.current();
14
+ const id = setInterval(tick, delay);
15
+ return () => clearInterval(id);
16
+ }, [delay]);
17
+ }