solforge 0.2.3 → 0.2.5

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 (43) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +323 -364
  3. package/cli.cjs +126 -69
  4. package/package.json +1 -1
  5. package/scripts/install.sh +112 -0
  6. package/scripts/postinstall.cjs +66 -58
  7. package/server/methods/program/get-token-accounts-by-owner.ts +7 -2
  8. package/server/ws-server.ts +4 -1
  9. package/src/api-server-entry.ts +91 -91
  10. package/src/cli/commands/rpc-start.ts +4 -1
  11. package/src/cli/main.ts +39 -14
  12. package/src/cli/run-solforge.ts +20 -6
  13. package/src/commands/add-program.ts +324 -328
  14. package/src/commands/init.ts +106 -106
  15. package/src/commands/list.ts +125 -125
  16. package/src/commands/mint.ts +246 -246
  17. package/src/commands/start.ts +834 -831
  18. package/src/commands/status.ts +80 -80
  19. package/src/commands/stop.ts +381 -382
  20. package/src/config/manager.ts +149 -149
  21. package/src/gui/public/app.css +1556 -1
  22. package/src/gui/public/build/main.css +1569 -1
  23. package/src/gui/server.ts +20 -21
  24. package/src/gui/src/app.tsx +56 -37
  25. package/src/gui/src/components/airdrop-mint-form.tsx +17 -11
  26. package/src/gui/src/components/clone-program-modal.tsx +6 -6
  27. package/src/gui/src/components/clone-token-modal.tsx +7 -7
  28. package/src/gui/src/components/modal.tsx +13 -11
  29. package/src/gui/src/components/programs-panel.tsx +27 -15
  30. package/src/gui/src/components/status-panel.tsx +31 -17
  31. package/src/gui/src/components/tokens-panel.tsx +25 -19
  32. package/src/gui/src/index.css +491 -463
  33. package/src/index.ts +161 -146
  34. package/src/rpc/start.ts +1 -1
  35. package/src/services/api-server.ts +470 -473
  36. package/src/services/port-manager.ts +167 -167
  37. package/src/services/process-registry.ts +143 -143
  38. package/src/services/program-cloner.ts +312 -312
  39. package/src/services/token-cloner.ts +799 -797
  40. package/src/services/validator.ts +288 -288
  41. package/src/types/config.ts +71 -71
  42. package/src/utils/shell.ts +75 -75
  43. package/src/utils/token-loader.ts +77 -77
package/cli.cjs CHANGED
@@ -9,94 +9,151 @@ const https = require("node:https");
9
9
  const { spawn } = require("node:child_process");
10
10
 
11
11
  function pkg() {
12
- // Resolve package.json next to this file regardless of install location
13
- const p = path.join(__dirname, "package.json");
14
- try { return require(p); } catch { return { version: "" }; }
12
+ // Resolve package.json next to this file regardless of install location
13
+ const p = path.join(__dirname, "package.json");
14
+ try {
15
+ return require(p);
16
+ } catch {
17
+ return { version: "" };
18
+ }
15
19
  }
16
20
 
17
21
  function assetName() {
18
- const p = process.platform;
19
- const a = process.arch;
20
- if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
21
- if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
22
- if (p === "linux" && a === "x64") return "solforge-linux-x64";
23
- if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
24
- if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
25
- return null;
22
+ const p = process.platform;
23
+ const a = process.arch;
24
+ if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
25
+ if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
26
+ if (p === "linux" && a === "x64") return "solforge-linux-x64";
27
+ if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
28
+ if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
29
+ return null;
26
30
  }
27
31
 
28
32
  function vendorPath() {
29
- const name = assetName();
30
- if (!name) return null;
31
- return path.join(__dirname, "vendor", name);
33
+ const name = assetName();
34
+ if (!name) return null;
35
+ return path.join(__dirname, "vendor", name);
32
36
  }
33
37
 
34
38
  function download(url, outPath) {
35
- return new Promise((resolve, reject) => {
36
- const req = https.get(url, (res) => {
37
- if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
38
- return resolve(download(res.headers.location, outPath));
39
- }
40
- if (res.statusCode !== 200) {
41
- return reject(new Error(`HTTP ${res.statusCode}`));
42
- }
43
- const file = fs.createWriteStream(outPath, { mode: process.platform === "win32" ? undefined : 0o755 });
44
- res.pipe(file);
45
- file.on("finish", () => file.close(() => resolve()));
46
- file.on("error", reject);
47
- });
48
- req.on("error", reject);
49
- });
39
+ return new Promise((resolve, reject) => {
40
+ const req = https.get(url, (res) => {
41
+ if (
42
+ [301, 302, 307, 308].includes(res.statusCode) &&
43
+ res.headers.location
44
+ ) {
45
+ return resolve(download(res.headers.location, outPath));
46
+ }
47
+ if (res.statusCode !== 200) {
48
+ return reject(new Error(`HTTP ${res.statusCode}`));
49
+ }
50
+ const file = fs.createWriteStream(outPath, {
51
+ mode: process.platform === "win32" ? undefined : 0o755,
52
+ });
53
+ res.pipe(file);
54
+ file.on("finish", () => file.close(() => resolve()));
55
+ file.on("error", reject);
56
+ });
57
+ req.on("error", reject);
58
+ });
50
59
  }
51
60
 
52
61
  async function ensureBinary() {
53
- const vp = vendorPath();
54
- if (!vp) return null;
55
- if (fs.existsSync(vp)) return vp;
62
+ const vp = vendorPath();
63
+ if (!vp) return null;
64
+ if (fs.existsSync(vp)) return vp;
56
65
 
57
- // Respect opt-out
58
- if (String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true") {
59
- return null;
60
- }
66
+ // Respect opt-out
67
+ if (
68
+ String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true"
69
+ ) {
70
+ return null;
71
+ }
61
72
 
62
- const { version, repository } = pkg();
63
- const repo = process.env.SOLFORGE_REPO || (repository && (typeof repository === "string" ? repository.replace(/^github:/, "") : (repository.url && (repository.url.match(/github\.com[:/](.+?)\.git$/) || [])[1]))) || "nitishxyz/solforge";
64
- if (!version) return null;
65
- const asset = path.basename(vp);
66
- const url = `https://github.com/${repo}/releases/download/v${version}/${asset}`;
73
+ const { version, repository } = pkg();
74
+ const repo =
75
+ process.env.SOLFORGE_REPO ||
76
+ (repository &&
77
+ (typeof repository === "string"
78
+ ? repository.replace(/^github:/, "")
79
+ : repository.url &&
80
+ (repository.url.match(/github\.com[:/](.+?)\.git$/) || [])[1])) ||
81
+ "nitishxyz/solforge";
82
+ if (!version) return null;
83
+ const asset = path.basename(vp);
84
+ const url = `https://github.com/${repo}/releases/download/v${version}/${asset}`;
67
85
 
68
- try {
69
- fs.mkdirSync(path.dirname(vp), { recursive: true });
70
- await download(url, vp);
71
- if (process.platform !== "win32") {
72
- try { fs.chmodSync(vp, 0o755); } catch {}
73
- }
74
- return fs.existsSync(vp) ? vp : null;
75
- } catch {
76
- return null;
77
- }
86
+ try {
87
+ fs.mkdirSync(path.dirname(vp), { recursive: true });
88
+ await download(url, vp);
89
+ if (process.platform !== "win32") {
90
+ try {
91
+ fs.chmodSync(vp, 0o755);
92
+ } catch {}
93
+ }
94
+ return fs.existsSync(vp) ? vp : null;
95
+ } catch {
96
+ return null;
97
+ }
78
98
  }
79
99
 
80
100
  function run(cmd, args) {
81
- return new Promise((resolve) => {
82
- const child = spawn(cmd, args, { stdio: "inherit" });
83
- child.on("exit", (code) => resolve(typeof code === "number" ? code : 0));
84
- });
101
+ return new Promise((resolve) => {
102
+ const child = spawn(cmd, args, { stdio: "inherit" });
103
+ child.on("exit", (code) => resolve(typeof code === "number" ? code : 0));
104
+ });
85
105
  }
86
106
 
87
107
  (async () => {
88
- const vp = await ensureBinary();
89
- if (vp) {
90
- const code = await run(vp, process.argv.slice(2));
91
- process.exit(code);
92
- }
93
- // Fallback: try to run TS entry via Bun
94
- const bun = process.env.SOLFORGE_BUN || "bun";
95
- const entry = path.join(__dirname, "src", "cli", "main.ts");
96
- const code = await run(bun, [entry, ...process.argv.slice(2)]);
97
- if (code !== 0) {
98
- console.error("solforge: failed to run binary and Bun fallback. Install Bun or ensure a release asset exists.");
99
- }
100
- process.exit(code);
101
- })();
108
+ // Fast path for --version/--help without booting the app
109
+ const args = process.argv.slice(2);
110
+ if (
111
+ args.includes("-v") ||
112
+ args.includes("--version") ||
113
+ args[0] === "version"
114
+ ) {
115
+ console.log(pkg().version || "");
116
+ process.exit(0);
117
+ }
118
+ if (args.includes("-h") || args.includes("--help") || args[0] === "help") {
119
+ console.log(`
120
+ solforge <command>
121
+
122
+ Commands:
123
+ (no command) Run setup then start RPC & WS servers
124
+ rpc start Start RPC & WS servers
125
+ start Alias for 'rpc start'
126
+ config init Create sf.config.json in CWD
127
+ config get <key> Read a config value (dot path)
128
+ config set <k> <v> Set a config value
129
+ airdrop --to <pubkey> --sol <amount> Airdrop SOL via RPC faucet
130
+ mint Interactive: pick mint, receiver, amount
131
+ token clone <mint> Clone SPL token mint + accounts
132
+ program clone <programId> Clone program code (and optionally accounts)
133
+ program accounts clone <programId> Clone accounts owned by program
102
134
 
135
+ Options:
136
+ -h, --help Show help
137
+ -v, --version Show version
138
+ --network Bind servers to 0.0.0.0 (LAN access)
139
+ -y, --ci Non-interactive; auto-accept prompts (use existing config)
140
+ `);
141
+ process.exit(0);
142
+ }
143
+
144
+ const vp = await ensureBinary();
145
+ if (vp) {
146
+ const code = await run(vp, process.argv.slice(2));
147
+ process.exit(code);
148
+ }
149
+ // Fallback: try to run TS entry via Bun
150
+ const bun = process.env.SOLFORGE_BUN || "bun";
151
+ const entry = path.join(__dirname, "src", "cli", "main.ts");
152
+ const code = await run(bun, [entry, ...process.argv.slice(2)]);
153
+ if (code !== 0) {
154
+ console.error(
155
+ "solforge: failed to run binary and Bun fallback. Install Bun or ensure a release asset exists.",
156
+ );
157
+ }
158
+ process.exit(code);
159
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env sh
2
+ set -e
3
+
4
+ # SolForge installer (downloads GitHub release binary)
5
+ # Usage: curl -fsSL https://sh.solforge.sh | sh
6
+ # Optional: SOLFORGE_VERSION=v0.2.5 curl -fsSL https://sh.solforge.sh | sh
7
+
8
+ REPO="nitishxyz/solforge"
9
+ BIN_NAME="solforge"
10
+ VERSION="${SOLFORGE_VERSION:-latest}"
11
+
12
+ info() { printf "\033[1;34m[i]\033[0m %s\n" "$*"; }
13
+ warn() { printf "\033[1;33m[!]\033[0m %s\n" "$*"; }
14
+ err() { printf "\033[1;31m[x]\033[0m %s\n" "$*" 1>&2; }
15
+
16
+ # Detect downloader
17
+ http_get() {
18
+ if command -v curl >/dev/null 2>&1; then
19
+ curl -fsSL "$1"
20
+ elif command -v wget >/dev/null 2>&1; then
21
+ wget -qO- "$1"
22
+ else
23
+ err "Need 'curl' or 'wget' to download binaries"
24
+ exit 1
25
+ fi
26
+ }
27
+
28
+ http_down() {
29
+ dest="$2"
30
+ if command -v curl >/dev/null 2>&1; then
31
+ curl -fL --progress-bar -o "$dest" "$1"
32
+ else
33
+ wget -qO "$dest" "$1"
34
+ fi
35
+ }
36
+
37
+ # OS/arch detection
38
+ uname_s=$(uname -s 2>/dev/null || echo unknown)
39
+ uname_m=$(uname -m 2>/dev/null || echo unknown)
40
+
41
+ case "$uname_s" in
42
+ Linux) os="linux" ;;
43
+ Darwin) os="darwin" ;;
44
+ *) err "Unsupported OS: $uname_s"; exit 1 ;;
45
+ esac
46
+
47
+ case "$uname_m" in
48
+ x86_64|amd64) arch="x64" ;;
49
+ arm64|aarch64) arch="arm64" ;;
50
+ *) err "Unsupported architecture: $uname_m"; exit 1 ;;
51
+ esac
52
+
53
+ asset="${BIN_NAME}-${os}-${arch}"
54
+ ext=""
55
+ filename="$asset$ext"
56
+
57
+ # Build URL
58
+ if [ "$VERSION" = "latest" ]; then
59
+ base="https://github.com/$REPO/releases/latest/download"
60
+ else
61
+ base="https://github.com/$REPO/releases/download/$VERSION"
62
+ fi
63
+
64
+ url="$base/$filename"
65
+
66
+ info "Installing $BIN_NAME ($os/$arch) from: $url"
67
+
68
+ # Download
69
+ tmpdir=${TMPDIR:-/tmp}
70
+ tmpfile="$tmpdir/$filename"
71
+ http_down "$url" "$tmpfile"
72
+
73
+ # Make executable
74
+ chmod +x "$tmpfile"
75
+
76
+ # Choose install dir
77
+ install_dir="/usr/local/bin"
78
+ if [ ! -w "$install_dir" ]; then
79
+ if command -v sudo >/dev/null 2>&1; then
80
+ sudo_cmd="sudo"
81
+ else
82
+ sudo_cmd=""
83
+ fi
84
+ fi
85
+
86
+ if [ -n "$sudo_cmd" ] && [ -d "$install_dir" ] && [ ! -w "$install_dir" ]; then
87
+ info "Moving binary to $install_dir (requires sudo)"
88
+ $sudo_cmd mv "$tmpfile" "$install_dir/$BIN_NAME"
89
+ else
90
+ # Fallback to user bin
91
+ user_bin="$HOME/.local/bin"
92
+ mkdir -p "$user_bin"
93
+ info "Moving binary to $user_bin"
94
+ mv "$tmpfile" "$user_bin/$BIN_NAME"
95
+ install_dir="$user_bin"
96
+ fi
97
+
98
+ # Verify
99
+ if "$install_dir/$BIN_NAME" --version >/dev/null 2>&1; then
100
+ ver=$("$install_dir/$BIN_NAME" --version 2>/dev/null || true)
101
+ info "$BIN_NAME installed: $ver"
102
+ else
103
+ warn "Installed, but failed to run --version"
104
+ fi
105
+
106
+ # PATH hint
107
+ case ":$PATH:" in
108
+ *":$install_dir:"*) :;;
109
+ *) warn "Add $install_dir to your PATH to use '$BIN_NAME'" ;;
110
+ esac
111
+
112
+ info "Done. Run: $BIN_NAME --help"
@@ -9,61 +9,62 @@ const path = require("path");
9
9
  const https = require("https");
10
10
 
11
11
  function log(msg) {
12
- console.log(`[solforge] ${msg}`);
12
+ console.log(`[solforge] ${msg}`);
13
13
  }
14
14
  function warn(msg) {
15
- console.warn(`[solforge] ${msg}`);
15
+ console.warn(`[solforge] ${msg}`);
16
16
  }
17
17
 
18
18
  if (String(process.env.SOLFORGE_SKIP_DOWNLOAD || "").toLowerCase() === "true") {
19
- log("Skipping binary download due to SOLFORGE_SKIP_DOWNLOAD=true");
20
- process.exit(0);
19
+ log("Skipping binary download due to SOLFORGE_SKIP_DOWNLOAD=true");
20
+ process.exit(0);
21
21
  }
22
22
 
23
23
  function assetName() {
24
- const p = process.platform;
25
- const a = process.arch;
26
- if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
27
- if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
28
- if (p === "linux" && a === "x64") return "solforge-linux-x64";
29
- if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
30
- if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
31
- return null;
24
+ const p = process.platform;
25
+ const a = process.arch;
26
+ if (p === "darwin" && a === "arm64") return "solforge-darwin-arm64";
27
+ if (p === "darwin" && a === "x64") return "solforge-darwin-x64";
28
+ if (p === "linux" && a === "x64") return "solforge-linux-x64";
29
+ if (p === "linux" && a === "arm64") return "solforge-linux-arm64";
30
+ if (p === "win32" && a === "x64") return "solforge-windows-x64.exe";
31
+ return null;
32
32
  }
33
33
 
34
34
  function getRepo() {
35
- try {
36
- const pkg = require(path.join(__dirname, "..", "package.json"));
37
- if (pkg.repository) {
38
- if (typeof pkg.repository === "string") return pkg.repository.replace(/^github:/, "");
39
- if (pkg.repository.url) {
40
- const m = pkg.repository.url.match(/github\.com[:/](.+?)\.git$/);
41
- if (m) return m[1];
42
- }
43
- }
44
- } catch {}
45
- return process.env.SOLFORGE_REPO || "nitishxyz/solforge";
35
+ try {
36
+ const pkg = require(path.join(__dirname, "..", "package.json"));
37
+ if (pkg.repository) {
38
+ if (typeof pkg.repository === "string")
39
+ return pkg.repository.replace(/^github:/, "");
40
+ if (pkg.repository.url) {
41
+ const m = pkg.repository.url.match(/github\.com[:/](.+?)\.git$/);
42
+ if (m) return m[1];
43
+ }
44
+ }
45
+ } catch {}
46
+ return process.env.SOLFORGE_REPO || "nitishxyz/solforge";
46
47
  }
47
48
 
48
49
  function getVersion() {
49
- try {
50
- const pkg = require(path.join(__dirname, "..", "package.json"));
51
- return pkg.version;
52
- } catch {
53
- return process.env.npm_package_version || "";
54
- }
50
+ try {
51
+ const pkg = require(path.join(__dirname, "..", "package.json"));
52
+ return pkg.version;
53
+ } catch {
54
+ return process.env.npm_package_version || "";
55
+ }
55
56
  }
56
57
 
57
58
  const name = assetName();
58
59
  if (!name) {
59
- warn(`No prebuilt binary for ${process.platform}/${process.arch}; skipping`);
60
- process.exit(0);
60
+ warn(`No prebuilt binary for ${process.platform}/${process.arch}; skipping`);
61
+ process.exit(0);
61
62
  }
62
63
 
63
64
  const version = getVersion();
64
65
  if (!version) {
65
- warn("Unable to determine package version; skipping binary download");
66
- process.exit(0);
66
+ warn("Unable to determine package version; skipping binary download");
67
+ process.exit(0);
67
68
  }
68
69
 
69
70
  const repo = getRepo();
@@ -73,38 +74,45 @@ const vendorDir = path.join(__dirname, "..", "vendor");
73
74
  const outPath = path.join(vendorDir, name);
74
75
 
75
76
  if (fs.existsSync(outPath)) {
76
- log(`Binary already present at vendor/${name}`);
77
- process.exit(0);
77
+ log(`Binary already present at vendor/${name}`);
78
+ process.exit(0);
78
79
  }
79
80
 
80
81
  fs.mkdirSync(vendorDir, { recursive: true });
81
82
 
82
83
  function download(to, from, cb, redirects = 0) {
83
- const req = https.get(from, (res) => {
84
- if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location && redirects < 5) {
85
- return download(to, res.headers.location, cb, redirects + 1);
86
- }
87
- if (res.statusCode !== 200) {
88
- return cb(new Error(`HTTP ${res.statusCode} for ${from}`));
89
- }
90
- const file = fs.createWriteStream(to, { mode: 0o755 });
91
- res.pipe(file);
92
- file.on("finish", () => file.close(cb));
93
- });
94
- req.on("error", (err) => cb(err));
84
+ const req = https.get(from, (res) => {
85
+ if (
86
+ [301, 302, 307, 308].includes(res.statusCode) &&
87
+ res.headers.location &&
88
+ redirects < 5
89
+ ) {
90
+ return download(to, res.headers.location, cb, redirects + 1);
91
+ }
92
+ if (res.statusCode !== 200) {
93
+ return cb(new Error(`HTTP ${res.statusCode} for ${from}`));
94
+ }
95
+ const file = fs.createWriteStream(to, { mode: 0o755 });
96
+ res.pipe(file);
97
+ file.on("finish", () => file.close(cb));
98
+ });
99
+ req.on("error", (err) => cb(err));
95
100
  }
96
101
 
97
102
  log(`Fetching ${name} for v${version}...`);
98
103
  download(outPath, url, (err) => {
99
- if (err) {
100
- warn(`Could not download prebuilt binary: ${err.message}`);
101
- warn("CLI will fall back to running via Bun if available.");
102
- try { fs.unlinkSync(outPath); } catch {}
103
- process.exit(0);
104
- }
105
- if (process.platform !== "win32") {
106
- try { fs.chmodSync(outPath, 0o755); } catch {}
107
- }
108
- log(`Installed vendor/${name}`);
104
+ if (err) {
105
+ warn(`Could not download prebuilt binary: ${err.message}`);
106
+ warn("CLI will fall back to running via Bun if available.");
107
+ try {
108
+ fs.unlinkSync(outPath);
109
+ } catch {}
110
+ process.exit(0);
111
+ }
112
+ if (process.platform !== "win32") {
113
+ try {
114
+ fs.chmodSync(outPath, 0o755);
115
+ } catch {}
116
+ }
117
+ log(`Installed vendor/${name}`);
109
118
  });
110
-
@@ -359,12 +359,17 @@ export const getTokenAccountsByOwner: RpcMethodHandler = async (
359
359
  }
360
360
 
361
361
  // Build filtered list keeping only chosen indices
362
- const keep = new Set<number>(Array.from(pick.values()).filter((v) => typeof v === "number" && v >= 0));
362
+ const keep = new Set<number>(
363
+ Array.from(pick.values()).filter(
364
+ (v) => typeof v === "number" && v >= 0,
365
+ ),
366
+ );
363
367
  const filtered: any[] = [];
364
368
  for (let i = 0; i < out.length; i++) {
365
369
  const e = out[i];
366
370
  const info = e.account?.data?.parsed?.info;
367
- const key = info?.owner && info?.mint ? `${info.owner}:${info.mint}` : null;
371
+ const key =
372
+ info?.owner && info?.mint ? `${info.owner}:${info.mint}` : null;
368
373
  if (key && keep.has(i)) filtered.push(e);
369
374
  else if (!key) filtered.push(e); // non-parsed or unexpected shape
370
375
  }
@@ -6,6 +6,7 @@ type Sub = { id: number; type: "signature"; signature: string };
6
6
  export function createLiteSVMWebSocketServer(
7
7
  rpcServer: LiteSVMRpcServer,
8
8
  port: number = 8900,
9
+ host?: string,
9
10
  ) {
10
11
  let nextSubId = 1;
11
12
  const subs = new Map<number, Sub>();
@@ -67,6 +68,7 @@ export function createLiteSVMWebSocketServer(
67
68
 
68
69
  const server: Server = Bun.serve({
69
70
  port,
71
+ hostname: host || process.env.RPC_HOST || "127.0.0.1",
70
72
  fetch(req, srv) {
71
73
  if (srv.upgrade(req)) return undefined as any;
72
74
  return new Response("Not a websocket", { status: 400 });
@@ -160,7 +162,8 @@ export function createLiteSVMWebSocketServer(
160
162
  },
161
163
  });
162
164
 
163
- console.log(`📣 LiteSVM RPC PubSub running on ws://localhost:${port}`);
165
+ const hostname = (host || process.env.RPC_HOST || "127.0.0.1").toString();
166
+ console.log(`📣 LiteSVM RPC PubSub running on ws://${hostname}:${port}`);
164
167
  return {
165
168
  wsServer: server,
166
169
  stop: () => {