solforge 0.2.5 → 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/package.json +1 -1
- package/scripts/postinstall.cjs +3 -3
- 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 +54 -33
- 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 +11 -7
- package/src/api-server-entry.ts +5 -5
- 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 +7 -7
- 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 +33 -36
- package/src/cli/run-solforge.ts +3 -3
- package/src/cli/setup-wizard.ts +8 -6
- package/src/commands/add-program.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/mint.ts +5 -6
- package/src/commands/start.ts +10 -9
- package/src/commands/status.ts +1 -1
- package/src/commands/stop.ts +1 -1
- package/src/config/index.ts +33 -17
- package/src/config/manager.ts +3 -3
- package/src/db/index.ts +2 -2
- package/src/db/tx-store.ts +12 -8
- package/src/gui/public/app.css +13 -13
- package/src/gui/server.ts +1 -1
- package/src/gui/src/api.ts +1 -1
- package/src/gui/src/app.tsx +49 -17
- package/src/gui/src/components/airdrop-mint-form.tsx +32 -8
- package/src/gui/src/components/clone-program-modal.tsx +25 -6
- package/src/gui/src/components/clone-token-modal.tsx +25 -6
- package/src/gui/src/components/modal.tsx +6 -1
- package/src/gui/src/components/status-panel.tsx +1 -1
- package/src/index.ts +19 -6
- package/src/services/api-server.ts +41 -19
- package/src/services/port-manager.ts +7 -10
- package/src/services/process-registry.ts +4 -5
- package/src/services/program-cloner.ts +4 -4
- package/src/services/token-cloner.ts +4 -4
- package/src/services/validator.ts +2 -4
- package/src/types/config.ts +2 -2
- package/src/utils/shell.ts +1 -1
- package/src/utils/token-loader.ts +2 -2
package/src/cli/setup-wizard.ts
CHANGED
|
@@ -170,9 +170,10 @@ async function resolveTokens(selections: string[], existing: string[] = []) {
|
|
|
170
170
|
const set = new Set(existing);
|
|
171
171
|
for (const selection of selections) {
|
|
172
172
|
if (selection === "__custom__") {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
{
|
|
174
|
+
const values = await collectCustomEntries("token mint address");
|
|
175
|
+
for (const value of values) set.add(value);
|
|
176
|
+
}
|
|
176
177
|
continue;
|
|
177
178
|
}
|
|
178
179
|
const preset = TOKEN_PRESETS.find((token) => token.value === selection);
|
|
@@ -193,9 +194,10 @@ async function resolvePrograms(selections: string[], existing: string[] = []) {
|
|
|
193
194
|
const set = new Set(existing);
|
|
194
195
|
for (const selection of selections) {
|
|
195
196
|
if (selection === "__custom__") {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
{
|
|
198
|
+
const values = await collectCustomEntries("program id");
|
|
199
|
+
for (const value of values) set.add(value);
|
|
200
|
+
}
|
|
199
201
|
continue;
|
|
200
202
|
}
|
|
201
203
|
const preset = PROGRAM_PRESETS.find(
|
package/src/commands/init.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { existsSync, writeFileSync } from "fs";
|
|
2
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
3
3
|
import inquirer from "inquirer";
|
|
4
|
-
import { resolve } from "path";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
5
|
import type { Config } from "../types/config.js";
|
|
6
6
|
|
|
7
7
|
const defaultConfig: Config = {
|
package/src/commands/mint.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { input, select } from "@inquirer/prompts";
|
|
2
|
-
import {
|
|
2
|
+
import { PublicKey } from "@solana/web3.js";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { existsSync, readFileSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
6
|
import type { TokenConfig } from "../types/config.js";
|
|
8
7
|
import { runCommand } from "../utils/shell";
|
|
9
8
|
import {
|
|
@@ -115,7 +114,7 @@ export const mintCommand = new Command()
|
|
|
115
114
|
let amount: string;
|
|
116
115
|
if (options.amount) {
|
|
117
116
|
const num = parseFloat(options.amount);
|
|
118
|
-
if (isNaN(num) || num <= 0) {
|
|
117
|
+
if (Number.isNaN(num) || num <= 0) {
|
|
119
118
|
console.error(chalk.red("❌ Invalid amount"));
|
|
120
119
|
process.exit(1);
|
|
121
120
|
}
|
|
@@ -126,7 +125,7 @@ export const mintCommand = new Command()
|
|
|
126
125
|
message: "Enter amount to mint:",
|
|
127
126
|
validate: (value: string) => {
|
|
128
127
|
const num = parseFloat(value);
|
|
129
|
-
if (isNaN(num) || num <= 0) {
|
|
128
|
+
if (Number.isNaN(num) || num <= 0) {
|
|
130
129
|
return "Please enter a valid positive number";
|
|
131
130
|
}
|
|
132
131
|
return true;
|
|
@@ -219,7 +218,7 @@ export async function mintTokenToWallet(
|
|
|
219
218
|
break;
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
|
-
} catch (
|
|
221
|
+
} catch (_error) {
|
|
223
222
|
// No existing accounts found or parsing error, will create new account
|
|
224
223
|
}
|
|
225
224
|
}
|
package/src/commands/start.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { spawn } from "child_process";
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import ora from "ora";
|
|
5
|
-
import { join } from "path";
|
|
5
|
+
import { join } from "node:path";
|
|
6
6
|
import { configManager } from "../config/manager.js";
|
|
7
7
|
import { portManager } from "../services/port-manager.js";
|
|
8
8
|
import type { RunningValidator } from "../services/process-registry.js";
|
|
@@ -392,7 +392,7 @@ export async function startCommand(
|
|
|
392
392
|
if (pidResult.success && pidResult.stdout.trim()) {
|
|
393
393
|
const pidLine = pidResult.stdout.trim().split("\n")[0];
|
|
394
394
|
if (pidLine) {
|
|
395
|
-
apiServerPid = parseInt(pidLine);
|
|
395
|
+
apiServerPid = parseInt(pidLine, 10);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
398
|
} else {
|
|
@@ -434,7 +434,7 @@ export async function startCommand(
|
|
|
434
434
|
const runningValidator: RunningValidator = {
|
|
435
435
|
id: validatorId,
|
|
436
436
|
name: config.name,
|
|
437
|
-
pid: validatorProcess.pid
|
|
437
|
+
pid: validatorProcess.pid,
|
|
438
438
|
rpcPort: config.localnet.port,
|
|
439
439
|
faucetPort: config.localnet.faucetPort,
|
|
440
440
|
rpcUrl: `http://127.0.0.1:${config.localnet.port}`,
|
|
@@ -558,7 +558,7 @@ export async function startCommand(
|
|
|
558
558
|
console.log(chalk.yellow("\n📦 Cloned programs:"));
|
|
559
559
|
config.programs.forEach((program) => {
|
|
560
560
|
const name =
|
|
561
|
-
program.name || program.mainnetProgramId.slice(0, 8)
|
|
561
|
+
program.name || `${program.mainnetProgramId.slice(0, 8)}...`;
|
|
562
562
|
console.log(chalk.gray(` - ${name}: ${program.mainnetProgramId}`));
|
|
563
563
|
});
|
|
564
564
|
}
|
|
@@ -778,8 +778,9 @@ async function waitForValidatorReady(
|
|
|
778
778
|
/**
|
|
779
779
|
* Airdrop SOL to the mint authority for fee payments
|
|
780
780
|
*/
|
|
781
|
+
|
|
781
782
|
async function airdropSolToMintAuthority(
|
|
782
|
-
clonedToken:
|
|
783
|
+
clonedToken: ClonedToken,
|
|
783
784
|
rpcUrl: string,
|
|
784
785
|
debug: boolean = false,
|
|
785
786
|
): Promise<void> {
|
|
@@ -813,7 +814,7 @@ async function airdropSolToMintAuthority(
|
|
|
813
814
|
*/
|
|
814
815
|
async function checkExistingClonedTokens(
|
|
815
816
|
tokens: TokenConfig[],
|
|
816
|
-
|
|
817
|
+
_tokenCloner: TokenCloner,
|
|
817
818
|
): Promise<{ existingTokens: ClonedToken[]; tokensToClone: TokenConfig[] }> {
|
|
818
819
|
const existingTokens: ClonedToken[] = [];
|
|
819
820
|
const tokensToClone: TokenConfig[] = [];
|
|
@@ -851,7 +852,7 @@ async function checkExistingClonedTokens(
|
|
|
851
852
|
// Old format: file contains {publicKey, secretKey}
|
|
852
853
|
sharedMintAuthority = fileContent;
|
|
853
854
|
}
|
|
854
|
-
} catch (
|
|
855
|
+
} catch (_error) {
|
|
855
856
|
// If we can't read the shared mint authority, treat all tokens as needing to be cloned
|
|
856
857
|
sharedMintAuthority = null;
|
|
857
858
|
}
|
package/src/commands/status.ts
CHANGED
|
@@ -90,7 +90,7 @@ export async function statusCommand(): Promise<void> {
|
|
|
90
90
|
console.log(` 📝 Project: ${config.name}`);
|
|
91
91
|
console.log(` 🪙 Tokens: ${config.tokens.length}`);
|
|
92
92
|
console.log(` 📦 Programs: ${config.programs.length}`);
|
|
93
|
-
} catch (
|
|
93
|
+
} catch (_error) {
|
|
94
94
|
console.log(` ❌ No valid configuration found`);
|
|
95
95
|
console.log(` 💡 Run 'solforge init' to create one`);
|
|
96
96
|
}
|
package/src/commands/stop.ts
CHANGED
|
@@ -186,7 +186,7 @@ async function waitForProcessShutdown(
|
|
|
186
186
|
process.kill(pid, 0);
|
|
187
187
|
// If no error thrown, process is still running
|
|
188
188
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
189
|
-
} catch (
|
|
189
|
+
} catch (_error) {
|
|
190
190
|
// Process is gone
|
|
191
191
|
return { success: true };
|
|
192
192
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export async function writeDefaultConfig(opts: { force?: boolean } = {}) {
|
|
|
68
68
|
mkdirSync(dir, { recursive: true });
|
|
69
69
|
} catch {}
|
|
70
70
|
}
|
|
71
|
-
writeFileSync(p, JSON.stringify(defaultConfig, null, 2)
|
|
71
|
+
writeFileSync(p, `${JSON.stringify(defaultConfig, null, 2)}\n`);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export async function writeConfig(
|
|
@@ -81,50 +81,66 @@ export async function writeConfig(
|
|
|
81
81
|
mkdirSync(dir, { recursive: true });
|
|
82
82
|
} catch {}
|
|
83
83
|
}
|
|
84
|
-
await Bun.write(path, JSON.stringify(config, null, 2)
|
|
84
|
+
await Bun.write(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export function getConfigValue(
|
|
87
|
+
export function getConfigValue(
|
|
88
|
+
cfg: Record<string, unknown>,
|
|
89
|
+
path?: string,
|
|
90
|
+
): unknown {
|
|
88
91
|
if (!path) return cfg;
|
|
89
|
-
|
|
92
|
+
let cur: unknown = cfg;
|
|
93
|
+
for (const k of path.split(".")) {
|
|
94
|
+
if (
|
|
95
|
+
cur &&
|
|
96
|
+
typeof cur === "object" &&
|
|
97
|
+
k in (cur as Record<string, unknown>)
|
|
98
|
+
) {
|
|
99
|
+
cur = (cur as Record<string, unknown>)[k];
|
|
100
|
+
} else {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return cur;
|
|
90
105
|
}
|
|
91
106
|
|
|
92
|
-
export function setConfigValue<T extends Record<string,
|
|
107
|
+
export function setConfigValue<T extends Record<string, unknown>>(
|
|
93
108
|
cfg: T,
|
|
94
109
|
path: string,
|
|
95
|
-
value:
|
|
110
|
+
value: unknown,
|
|
96
111
|
): T {
|
|
97
112
|
const parts = path.split(".");
|
|
98
|
-
let node:
|
|
113
|
+
let node: Record<string, unknown> = cfg;
|
|
99
114
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
100
115
|
const k = parts[i];
|
|
101
116
|
if (!node[k] || typeof node[k] !== "object") node[k] = {};
|
|
102
|
-
node = node[k]
|
|
117
|
+
node = node[k] as Record<string, unknown>;
|
|
103
118
|
}
|
|
104
119
|
node[parts[parts.length - 1]] = coerceValue(value);
|
|
105
120
|
return cfg;
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
function coerceValue(v:
|
|
123
|
+
function coerceValue(v: unknown): unknown {
|
|
109
124
|
if (v === "true") return true;
|
|
110
125
|
if (v === "false") return false;
|
|
111
|
-
if (v !== "" && !isNaN(Number(v)))
|
|
126
|
+
if (typeof v === "string" && v !== "" && !Number.isNaN(Number(v)))
|
|
127
|
+
return Number(v);
|
|
112
128
|
try {
|
|
113
|
-
return JSON.parse(v);
|
|
129
|
+
return typeof v === "string" ? JSON.parse(v) : v;
|
|
114
130
|
} catch {
|
|
115
131
|
return v;
|
|
116
132
|
}
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
function deepMerge<T>(a: T, b: Partial<T>): T {
|
|
120
|
-
if (Array.isArray(a) || Array.isArray(b)) return (b
|
|
136
|
+
if (Array.isArray(a) || Array.isArray(b)) return (b ?? a) as unknown as T;
|
|
121
137
|
if (typeof a === "object" && typeof b === "object" && a && b) {
|
|
122
|
-
const out:
|
|
138
|
+
const out: Record<string, unknown> = { ...(a as Record<string, unknown>) };
|
|
123
139
|
for (const [k, v] of Object.entries(b)) {
|
|
124
|
-
const ak = (a as
|
|
125
|
-
out[k] = deepMerge(ak, v as
|
|
140
|
+
const ak = (a as Record<string, unknown>)[k];
|
|
141
|
+
out[k] = deepMerge(ak as unknown, v as unknown) as unknown;
|
|
126
142
|
}
|
|
127
|
-
return out;
|
|
143
|
+
return out as unknown as T;
|
|
128
144
|
}
|
|
129
|
-
return (b
|
|
145
|
+
return (b ?? a) as unknown as T;
|
|
130
146
|
}
|
package/src/config/manager.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
3
|
import type { Config, ValidationResult } from "../types/config.js";
|
|
4
4
|
import { ConfigSchema } from "../types/config.js";
|
|
5
5
|
|
|
@@ -77,7 +77,7 @@ export class ConfigManager {
|
|
|
77
77
|
/**
|
|
78
78
|
* Validate a configuration object
|
|
79
79
|
*/
|
|
80
|
-
validate(config:
|
|
80
|
+
validate(config: unknown): ValidationResult {
|
|
81
81
|
const result = ConfigSchema.safeParse(config);
|
|
82
82
|
|
|
83
83
|
if (result.success) {
|
package/src/db/index.ts
CHANGED
|
@@ -27,10 +27,10 @@ if (!PERSIST && DB_PATH !== ":memory:") {
|
|
|
27
27
|
if (existsSync(DB_PATH)) unlinkSync(DB_PATH);
|
|
28
28
|
} catch {}
|
|
29
29
|
try {
|
|
30
|
-
if (existsSync(DB_PATH
|
|
30
|
+
if (existsSync(`${DB_PATH}-wal`)) unlinkSync(`${DB_PATH}-wal`);
|
|
31
31
|
} catch {}
|
|
32
32
|
try {
|
|
33
|
-
if (existsSync(DB_PATH
|
|
33
|
+
if (existsSync(`${DB_PATH}-shm`)) unlinkSync(`${DB_PATH}-shm`);
|
|
34
34
|
} catch {}
|
|
35
35
|
}
|
|
36
36
|
|
package/src/db/tx-store.ts
CHANGED
|
@@ -23,8 +23,8 @@ export type InsertTxBundle = {
|
|
|
23
23
|
writable: boolean;
|
|
24
24
|
programIdIndex?: number;
|
|
25
25
|
}>;
|
|
26
|
-
preTokenBalances?:
|
|
27
|
-
postTokenBalances?:
|
|
26
|
+
preTokenBalances?: unknown[];
|
|
27
|
+
postTokenBalances?: unknown[];
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type AccountSnapshot = {
|
|
@@ -133,7 +133,7 @@ export class TxStore {
|
|
|
133
133
|
|
|
134
134
|
async getStatuses(signatures: string[]) {
|
|
135
135
|
if (!Array.isArray(signatures) || signatures.length === 0)
|
|
136
|
-
return new Map<string, { slot: number; err:
|
|
136
|
+
return new Map<string, { slot: number; err: unknown | null }>();
|
|
137
137
|
const results = await db
|
|
138
138
|
.select({
|
|
139
139
|
signature: transactions.signature,
|
|
@@ -142,7 +142,7 @@ export class TxStore {
|
|
|
142
142
|
})
|
|
143
143
|
.from(transactions)
|
|
144
144
|
.where(inArraySafe(transactions.signature, signatures));
|
|
145
|
-
const map = new Map<string, { slot: number; err:
|
|
145
|
+
const map = new Map<string, { slot: number; err: unknown | null }>();
|
|
146
146
|
for (const r of results)
|
|
147
147
|
map.set(r.signature, {
|
|
148
148
|
slot: Number(r.slot),
|
|
@@ -167,7 +167,7 @@ export class TxStore {
|
|
|
167
167
|
}
|
|
168
168
|
const limit = Math.min(Math.max(opts.limit ?? 1000, 1), 1000);
|
|
169
169
|
|
|
170
|
-
const whereClauses = [eq(addressSignatures.address, address)] as
|
|
170
|
+
const whereClauses = [eq(addressSignatures.address, address)] as unknown[];
|
|
171
171
|
if (typeof beforeSlot === "number")
|
|
172
172
|
whereClauses.push(lt(addressSignatures.slot, beforeSlot));
|
|
173
173
|
if (typeof untilSlot === "number")
|
|
@@ -216,7 +216,7 @@ export class TxStore {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
function safeParse<T =
|
|
219
|
+
function safeParse<T = unknown>(s: string): T | null {
|
|
220
220
|
try {
|
|
221
221
|
return JSON.parse(s) as T;
|
|
222
222
|
} catch {
|
|
@@ -224,6 +224,10 @@ function safeParse<T = any>(s: string): T | null {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
function inArraySafe<T>(col:
|
|
228
|
-
return arr.length > 0
|
|
227
|
+
function inArraySafe<T>(col: unknown, arr: T[]) {
|
|
228
|
+
return arr.length > 0
|
|
229
|
+
? // biome-ignore lint/suspicious/noExplicitAny: Drizzle generic typing workaround
|
|
230
|
+
(inArray as unknown as (c: unknown, a: T[]) => any)(col, arr)
|
|
231
|
+
: // biome-ignore lint/suspicious/noExplicitAny: Force an always-false predicate without over-constraining types
|
|
232
|
+
eq(col as any, "__never__");
|
|
229
233
|
}
|
package/src/gui/public/app.css
CHANGED
|
@@ -442,11 +442,11 @@ video {
|
|
|
442
442
|
line-height: 1.25rem;
|
|
443
443
|
}
|
|
444
444
|
.\!input {
|
|
445
|
-
background: var(--color-bg-surface)
|
|
446
|
-
border: 1px solid var(--color-border-subtle)
|
|
447
|
-
color: var(--color-text-primary)
|
|
448
|
-
transition: var(--transition-base)
|
|
449
|
-
font-family: Inter, sans-serif
|
|
445
|
+
background: var(--color-bg-surface);
|
|
446
|
+
border: 1px solid var(--color-border-subtle);
|
|
447
|
+
color: var(--color-text-primary);
|
|
448
|
+
transition: var(--transition-base);
|
|
449
|
+
font-family: Inter, sans-serif;
|
|
450
450
|
}
|
|
451
451
|
.input {
|
|
452
452
|
background: var(--color-bg-surface);
|
|
@@ -456,18 +456,18 @@ video {
|
|
|
456
456
|
font-family: Inter, sans-serif;
|
|
457
457
|
}
|
|
458
458
|
.\!input:hover {
|
|
459
|
-
background: var(--color-bg-elevated)
|
|
460
|
-
border-color: var(--color-border-default)
|
|
459
|
+
background: var(--color-bg-elevated);
|
|
460
|
+
border-color: var(--color-border-default);
|
|
461
461
|
}
|
|
462
462
|
.input:hover {
|
|
463
463
|
background: var(--color-bg-elevated);
|
|
464
464
|
border-color: var(--color-border-default);
|
|
465
465
|
}
|
|
466
466
|
.\!input:focus {
|
|
467
|
-
outline: none
|
|
468
|
-
border-color: var(--color-accent-primary)
|
|
469
|
-
box-shadow: 0 0 0 3px var(--color-accent-glow)
|
|
470
|
-
background: var(--color-bg-elevated)
|
|
467
|
+
outline: none;
|
|
468
|
+
border-color: var(--color-accent-primary);
|
|
469
|
+
box-shadow: 0 0 0 3px var(--color-accent-glow);
|
|
470
|
+
background: var(--color-bg-elevated);
|
|
471
471
|
}
|
|
472
472
|
.input:focus {
|
|
473
473
|
outline: none;
|
|
@@ -476,10 +476,10 @@ video {
|
|
|
476
476
|
background: var(--color-bg-elevated);
|
|
477
477
|
}
|
|
478
478
|
.\!input::-moz-placeholder {
|
|
479
|
-
color: var(--color-text-muted)
|
|
479
|
+
color: var(--color-text-muted);
|
|
480
480
|
}
|
|
481
481
|
.\!input::placeholder {
|
|
482
|
-
color: var(--color-text-muted)
|
|
482
|
+
color: var(--color-text-muted);
|
|
483
483
|
}
|
|
484
484
|
.input::-moz-placeholder {
|
|
485
485
|
color: var(--color-text-muted);
|
package/src/gui/server.ts
CHANGED
|
@@ -107,7 +107,7 @@ export function startGuiServer(opts: GuiStartOptions = {}) {
|
|
|
107
107
|
const rpcServer = opts.rpcServer;
|
|
108
108
|
const rpcUrl = `http://${host}:${rpcPort}`;
|
|
109
109
|
|
|
110
|
-
const callRpc = async (method: string, params:
|
|
110
|
+
const callRpc = async (method: string, params: unknown[] = []) => {
|
|
111
111
|
if (!rpcServer) throw new HttpError(503, "RPC server not available");
|
|
112
112
|
const response: JsonRpcResponse = await rpcServer.handleRequest({
|
|
113
113
|
jsonrpc: "2.0",
|
package/src/gui/src/api.ts
CHANGED
|
@@ -72,7 +72,7 @@ async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
|
72
72
|
if (!headers.has("content-type") && init.body)
|
|
73
73
|
headers.set("content-type", "application/json");
|
|
74
74
|
const response = await fetch(path, { ...init, headers });
|
|
75
|
-
let payload:
|
|
75
|
+
let payload: unknown = null;
|
|
76
76
|
const text = await response.text();
|
|
77
77
|
if (text) {
|
|
78
78
|
try {
|
package/src/gui/src/app.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect,
|
|
1
|
+
import { useCallback, useEffect, useId, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
type ApiConfig,
|
|
4
4
|
type ApiStatus,
|
|
@@ -40,8 +40,9 @@ export function App() {
|
|
|
40
40
|
const cfg = await fetchConfig();
|
|
41
41
|
setConfig(cfg);
|
|
42
42
|
setBannerError(null);
|
|
43
|
-
} catch (error
|
|
44
|
-
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
setBannerError(message);
|
|
45
46
|
}
|
|
46
47
|
}, []);
|
|
47
48
|
|
|
@@ -50,8 +51,9 @@ export function App() {
|
|
|
50
51
|
try {
|
|
51
52
|
const data = await fetchStatus();
|
|
52
53
|
setStatus(data);
|
|
53
|
-
} catch (error
|
|
54
|
-
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
setBannerError(message);
|
|
55
57
|
} finally {
|
|
56
58
|
setLoadingStatus(false);
|
|
57
59
|
}
|
|
@@ -62,8 +64,9 @@ export function App() {
|
|
|
62
64
|
try {
|
|
63
65
|
const data = await fetchPrograms();
|
|
64
66
|
setPrograms(data);
|
|
65
|
-
} catch (error
|
|
66
|
-
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
setBannerError(message);
|
|
67
70
|
} finally {
|
|
68
71
|
setLoadingPrograms(false);
|
|
69
72
|
}
|
|
@@ -74,8 +77,9 @@ export function App() {
|
|
|
74
77
|
try {
|
|
75
78
|
const data = await fetchTokens();
|
|
76
79
|
setTokens(data);
|
|
77
|
-
} catch (error
|
|
78
|
-
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
setBannerError(message);
|
|
79
83
|
} finally {
|
|
80
84
|
setLoadingTokens(false);
|
|
81
85
|
}
|
|
@@ -141,9 +145,20 @@ export function App() {
|
|
|
141
145
|
[loadTokens],
|
|
142
146
|
);
|
|
143
147
|
|
|
144
|
-
|
|
148
|
+
type SectionKey = "status" | "actions" | "programs" | "tokens";
|
|
149
|
+
const uid = useId();
|
|
150
|
+
const sectionIds: Record<SectionKey, string> = {
|
|
151
|
+
status: `${uid}-status`,
|
|
152
|
+
actions: `${uid}-actions`,
|
|
153
|
+
programs: `${uid}-programs`,
|
|
154
|
+
tokens: `${uid}-tokens`,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const scrollToSection = (sectionId: SectionKey) => {
|
|
145
158
|
setActiveSection(sectionId);
|
|
146
|
-
document
|
|
159
|
+
document
|
|
160
|
+
.getElementById(sectionIds[sectionId])
|
|
161
|
+
?.scrollIntoView({ behavior: "smooth" });
|
|
147
162
|
setSidebarOpen(false);
|
|
148
163
|
};
|
|
149
164
|
|
|
@@ -151,6 +166,7 @@ export function App() {
|
|
|
151
166
|
<div className="min-h-screen relative">
|
|
152
167
|
{/* Mobile Menu Button */}
|
|
153
168
|
<button
|
|
169
|
+
type="button"
|
|
154
170
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
155
171
|
className="lg:hidden fixed top-4 left-4 z-50 btn-icon bg-gradient-to-br from-purple-600 to-violet-600 border-purple-500/30"
|
|
156
172
|
aria-label="Menu"
|
|
@@ -183,6 +199,7 @@ export function App() {
|
|
|
183
199
|
{/* Navigation Items */}
|
|
184
200
|
<nav className="space-y-2">
|
|
185
201
|
<button
|
|
202
|
+
type="button"
|
|
186
203
|
onClick={() => scrollToSection("status")}
|
|
187
204
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
188
205
|
activeSection === "status"
|
|
@@ -194,6 +211,7 @@ export function App() {
|
|
|
194
211
|
<span className="font-medium">Network Status</span>
|
|
195
212
|
</button>
|
|
196
213
|
<button
|
|
214
|
+
type="button"
|
|
197
215
|
onClick={() => scrollToSection("actions")}
|
|
198
216
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
199
217
|
activeSection === "actions"
|
|
@@ -205,6 +223,7 @@ export function App() {
|
|
|
205
223
|
<span className="font-medium">Quick Actions</span>
|
|
206
224
|
</button>
|
|
207
225
|
<button
|
|
226
|
+
type="button"
|
|
208
227
|
onClick={() => scrollToSection("programs")}
|
|
209
228
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
210
229
|
activeSection === "programs"
|
|
@@ -216,6 +235,7 @@ export function App() {
|
|
|
216
235
|
<span className="font-medium">Programs</span>
|
|
217
236
|
</button>
|
|
218
237
|
<button
|
|
238
|
+
type="button"
|
|
219
239
|
onClick={() => scrollToSection("tokens")}
|
|
220
240
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all text-left ${
|
|
221
241
|
activeSection === "tokens"
|
|
@@ -250,9 +270,16 @@ export function App() {
|
|
|
250
270
|
|
|
251
271
|
{/* Overlay for mobile */}
|
|
252
272
|
{sidebarOpen && (
|
|
253
|
-
<
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
254
275
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30 lg:hidden"
|
|
276
|
+
aria-label="Close sidebar overlay"
|
|
255
277
|
onClick={() => setSidebarOpen(false)}
|
|
278
|
+
onKeyDown={(e) => {
|
|
279
|
+
if (e.key === "Escape" || e.key === "Enter" || e.key === " ") {
|
|
280
|
+
setSidebarOpen(false);
|
|
281
|
+
}
|
|
282
|
+
}}
|
|
256
283
|
/>
|
|
257
284
|
)}
|
|
258
285
|
|
|
@@ -270,7 +297,11 @@ export function App() {
|
|
|
270
297
|
Manage your local Solana development environment
|
|
271
298
|
</p>
|
|
272
299
|
</div>
|
|
273
|
-
<button
|
|
300
|
+
<button
|
|
301
|
+
type="button"
|
|
302
|
+
onClick={loadStatus}
|
|
303
|
+
className="btn-secondary"
|
|
304
|
+
>
|
|
274
305
|
<i
|
|
275
306
|
className={`fas fa-sync-alt ${loadingStatus ? "animate-spin" : ""}`}
|
|
276
307
|
></i>
|
|
@@ -286,6 +317,7 @@ export function App() {
|
|
|
286
317
|
<p className="text-sm text-red-300">{bannerError}</p>
|
|
287
318
|
</div>
|
|
288
319
|
<button
|
|
320
|
+
type="button"
|
|
289
321
|
onClick={() => setBannerError(null)}
|
|
290
322
|
className="text-red-400 hover:text-red-300"
|
|
291
323
|
aria-label="Close error"
|
|
@@ -309,7 +341,7 @@ export function App() {
|
|
|
309
341
|
</div>
|
|
310
342
|
|
|
311
343
|
{/* Status Panel */}
|
|
312
|
-
<div id=
|
|
344
|
+
<div id={sectionIds.status} className="animate-fadeIn scroll-mt-24">
|
|
313
345
|
<StatusPanel
|
|
314
346
|
status={status}
|
|
315
347
|
loading={loadingStatus}
|
|
@@ -319,7 +351,7 @@ export function App() {
|
|
|
319
351
|
|
|
320
352
|
{/* Quick Actions - Optional */}
|
|
321
353
|
<div
|
|
322
|
-
id=
|
|
354
|
+
id={sectionIds.actions}
|
|
323
355
|
className="glass-panel p-6 animate-fadeIn scroll-mt-24"
|
|
324
356
|
style={{ animationDelay: "0.1s" }}
|
|
325
357
|
>
|
|
@@ -333,7 +365,7 @@ export function App() {
|
|
|
333
365
|
{/* Programs and Tokens Stacked */}
|
|
334
366
|
<div className="space-y-6">
|
|
335
367
|
<div
|
|
336
|
-
id=
|
|
368
|
+
id={sectionIds.programs}
|
|
337
369
|
className="animate-fadeIn scroll-mt-24"
|
|
338
370
|
style={{ animationDelay: "0.2s" }}
|
|
339
371
|
>
|
|
@@ -345,7 +377,7 @@ export function App() {
|
|
|
345
377
|
/>
|
|
346
378
|
</div>
|
|
347
379
|
<div
|
|
348
|
-
id=
|
|
380
|
+
id={sectionIds.tokens}
|
|
349
381
|
className="animate-fadeIn scroll-mt-24"
|
|
350
382
|
style={{ animationDelay: "0.3s" }}
|
|
351
383
|
>
|