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
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ChangeEvent,
|
|
3
|
+
type FormEvent,
|
|
4
|
+
useId,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import type { TokenSummary } from "../api";
|
|
3
9
|
|
|
4
10
|
interface Props {
|
|
5
11
|
tokens: TokenSummary[];
|
|
6
|
-
onAirdrop: (address: string, lamports: string) => Promise<string |
|
|
12
|
+
onAirdrop: (address: string, lamports: string) => Promise<string | undefined>;
|
|
7
13
|
onMint: (
|
|
8
14
|
mint: string,
|
|
9
15
|
owner: string,
|
|
10
16
|
amountRaw: string,
|
|
11
|
-
) => Promise<string |
|
|
17
|
+
) => Promise<string | undefined>;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
const SOL_OPTION = {
|
|
@@ -53,6 +59,11 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
53
59
|
const [error, setError] = useState<string | null>(null);
|
|
54
60
|
const [message, setMessage] = useState<string | null>(null);
|
|
55
61
|
|
|
62
|
+
const uid = useId();
|
|
63
|
+
const recipientId = `${uid}-recipient`;
|
|
64
|
+
const assetId = `${uid}-asset`;
|
|
65
|
+
const amountId = `${uid}-amount`;
|
|
66
|
+
|
|
56
67
|
const options = useMemo(() => {
|
|
57
68
|
const tokenOpts = tokens.map((token) => ({
|
|
58
69
|
value: token.mint,
|
|
@@ -87,8 +98,9 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
87
98
|
try {
|
|
88
99
|
const note = await submit();
|
|
89
100
|
setMessage(note);
|
|
90
|
-
} catch (err
|
|
91
|
-
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
103
|
+
setError(message);
|
|
92
104
|
} finally {
|
|
93
105
|
setPending(false);
|
|
94
106
|
}
|
|
@@ -116,11 +128,15 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
116
128
|
|
|
117
129
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
118
130
|
<div className="space-y-2">
|
|
119
|
-
<label
|
|
131
|
+
<label
|
|
132
|
+
htmlFor={recipientId}
|
|
133
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
134
|
+
>
|
|
120
135
|
Recipient Address
|
|
121
136
|
</label>
|
|
122
137
|
<div className="relative">
|
|
123
138
|
<input
|
|
139
|
+
id={recipientId}
|
|
124
140
|
value={recipient}
|
|
125
141
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
126
142
|
setRecipient(event.target.value)
|
|
@@ -133,11 +149,15 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
133
149
|
</div>
|
|
134
150
|
|
|
135
151
|
<div className="space-y-2">
|
|
136
|
-
<label
|
|
152
|
+
<label
|
|
153
|
+
htmlFor={assetId}
|
|
154
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
155
|
+
>
|
|
137
156
|
Asset
|
|
138
157
|
</label>
|
|
139
158
|
<div className="relative">
|
|
140
159
|
<select
|
|
160
|
+
id={assetId}
|
|
141
161
|
value={asset}
|
|
142
162
|
onChange={(event: ChangeEvent<HTMLSelectElement>) =>
|
|
143
163
|
setAsset(event.target.value)
|
|
@@ -155,11 +175,15 @@ export function AirdropMintForm({ tokens, onAirdrop, onMint }: Props) {
|
|
|
155
175
|
</div>
|
|
156
176
|
|
|
157
177
|
<div className="space-y-2">
|
|
158
|
-
<label
|
|
178
|
+
<label
|
|
179
|
+
htmlFor={amountId}
|
|
180
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
181
|
+
>
|
|
159
182
|
Amount
|
|
160
183
|
</label>
|
|
161
184
|
<div className="relative">
|
|
162
185
|
<input
|
|
186
|
+
id={amountId}
|
|
163
187
|
value={amount}
|
|
164
188
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
165
189
|
setAmount(event.target.value)
|
|
@@ -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 {
|
|
@@ -13,6 +13,9 @@ interface Props {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
16
|
+
const programIdId = useId();
|
|
17
|
+
const endpointId = useId();
|
|
18
|
+
const accountLimitId = useId();
|
|
16
19
|
const [programId, setProgramId] = useState("");
|
|
17
20
|
const [endpoint, setEndpoint] = useState("");
|
|
18
21
|
const [withAccounts, setWithAccounts] = useState(true);
|
|
@@ -37,8 +40,12 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
37
40
|
setProgramId("");
|
|
38
41
|
setEndpoint("");
|
|
39
42
|
setAccountsLimit("100");
|
|
40
|
-
} catch (err:
|
|
41
|
-
|
|
43
|
+
} catch (err: unknown) {
|
|
44
|
+
const message =
|
|
45
|
+
err && typeof err === "object" && "message" in err
|
|
46
|
+
? String((err as { message?: unknown }).message)
|
|
47
|
+
: String(err);
|
|
48
|
+
setError(message);
|
|
42
49
|
} finally {
|
|
43
50
|
setPending(false);
|
|
44
51
|
}
|
|
@@ -92,11 +99,15 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
92
99
|
>
|
|
93
100
|
<div className="space-y-5">
|
|
94
101
|
<div className="space-y-2">
|
|
95
|
-
<label
|
|
102
|
+
<label
|
|
103
|
+
htmlFor={programIdId}
|
|
104
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
105
|
+
>
|
|
96
106
|
Program ID *
|
|
97
107
|
</label>
|
|
98
108
|
<div className="relative">
|
|
99
109
|
<input
|
|
110
|
+
id={programIdId}
|
|
100
111
|
value={programId}
|
|
101
112
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
102
113
|
setProgramId(event.target.value)
|
|
@@ -109,11 +120,15 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
109
120
|
</div>
|
|
110
121
|
|
|
111
122
|
<div className="space-y-2">
|
|
112
|
-
<label
|
|
123
|
+
<label
|
|
124
|
+
htmlFor={endpointId}
|
|
125
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
126
|
+
>
|
|
113
127
|
RPC Endpoint (Optional)
|
|
114
128
|
</label>
|
|
115
129
|
<div className="relative">
|
|
116
130
|
<input
|
|
131
|
+
id={endpointId}
|
|
117
132
|
value={endpoint}
|
|
118
133
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
119
134
|
setEndpoint(event.target.value)
|
|
@@ -147,11 +162,15 @@ export function CloneProgramModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
147
162
|
|
|
148
163
|
{withAccounts && (
|
|
149
164
|
<div className="ml-8 space-y-2 pt-2 border-t border-white/5">
|
|
150
|
-
<label
|
|
165
|
+
<label
|
|
166
|
+
htmlFor={accountLimitId}
|
|
167
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
168
|
+
>
|
|
151
169
|
Account Limit
|
|
152
170
|
</label>
|
|
153
171
|
<div className="relative">
|
|
154
172
|
<input
|
|
173
|
+
id={accountLimitId}
|
|
155
174
|
value={accountsLimit}
|
|
156
175
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
157
176
|
setAccountsLimit(event.target.value)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChangeEvent, useState } from "react";
|
|
1
|
+
import { type ChangeEvent, useId, useState } from "react";
|
|
2
2
|
import { Modal } from "./modal";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
@@ -14,6 +14,9 @@ interface Props {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
17
|
+
const mintId = useId();
|
|
18
|
+
const endpointId = useId();
|
|
19
|
+
const holdersId = useId();
|
|
17
20
|
const [mint, setMint] = useState("");
|
|
18
21
|
const [endpoint, setEndpoint] = useState("");
|
|
19
22
|
// Default OFF to avoid hitting public RPC rate limits by cloning holders.
|
|
@@ -42,8 +45,12 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
42
45
|
setEndpoint("");
|
|
43
46
|
setHolders("20");
|
|
44
47
|
setAllAccounts(false);
|
|
45
|
-
} catch (err:
|
|
46
|
-
|
|
48
|
+
} catch (err: unknown) {
|
|
49
|
+
const message =
|
|
50
|
+
err && typeof err === "object" && "message" in err
|
|
51
|
+
? String((err as { message?: unknown }).message)
|
|
52
|
+
: String(err);
|
|
53
|
+
setError(message);
|
|
47
54
|
} finally {
|
|
48
55
|
setPending(false);
|
|
49
56
|
}
|
|
@@ -97,11 +104,15 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
97
104
|
>
|
|
98
105
|
<div className="space-y-5">
|
|
99
106
|
<div className="space-y-2">
|
|
100
|
-
<label
|
|
107
|
+
<label
|
|
108
|
+
htmlFor={mintId}
|
|
109
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
110
|
+
>
|
|
101
111
|
Mint Address *
|
|
102
112
|
</label>
|
|
103
113
|
<div className="relative">
|
|
104
114
|
<input
|
|
115
|
+
id={mintId}
|
|
105
116
|
value={mint}
|
|
106
117
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
107
118
|
setMint(event.target.value)
|
|
@@ -114,11 +125,15 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
114
125
|
</div>
|
|
115
126
|
|
|
116
127
|
<div className="space-y-2">
|
|
117
|
-
<label
|
|
128
|
+
<label
|
|
129
|
+
htmlFor={endpointId}
|
|
130
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
131
|
+
>
|
|
118
132
|
RPC Endpoint (Optional)
|
|
119
133
|
</label>
|
|
120
134
|
<div className="relative">
|
|
121
135
|
<input
|
|
136
|
+
id={endpointId}
|
|
122
137
|
value={endpoint}
|
|
123
138
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
124
139
|
setEndpoint(event.target.value)
|
|
@@ -173,11 +188,15 @@ export function CloneTokenModal({ isOpen, onClose, onSubmit }: Props) {
|
|
|
173
188
|
|
|
174
189
|
{!allAccounts && (
|
|
175
190
|
<div className="space-y-2">
|
|
176
|
-
<label
|
|
191
|
+
<label
|
|
192
|
+
htmlFor={holdersId}
|
|
193
|
+
className="block text-xs font-semibold text-gray-400 uppercase tracking-wider"
|
|
194
|
+
>
|
|
177
195
|
Top Holders Limit
|
|
178
196
|
</label>
|
|
179
197
|
<div className="relative">
|
|
180
198
|
<input
|
|
199
|
+
id={holdersId}
|
|
181
200
|
value={holders}
|
|
182
201
|
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
|
183
202
|
setHolders(event.target.value)
|
|
@@ -40,9 +40,14 @@ export function Modal({
|
|
|
40
40
|
return (
|
|
41
41
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-fadeIn">
|
|
42
42
|
{/* Backdrop */}
|
|
43
|
-
<
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
44
45
|
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
|
46
|
+
aria-label="Close modal"
|
|
45
47
|
onClick={onClose}
|
|
48
|
+
onKeyDown={(e) => {
|
|
49
|
+
if (e.key === "Enter" || e.key === " ") onClose();
|
|
50
|
+
}}
|
|
46
51
|
/>
|
|
47
52
|
|
|
48
53
|
{/* Modal */}
|
|
@@ -61,7 +61,7 @@ export function StatusPanel({ status, loading, onRefresh }: Props) {
|
|
|
61
61
|
<StatusCard
|
|
62
62
|
title="Faucet Balance"
|
|
63
63
|
value={`${status.faucet.sol.toFixed(3)} SOL`}
|
|
64
|
-
subtitle={status.faucet.address.slice(0, 10)
|
|
64
|
+
subtitle={`${status.faucet.address.slice(0, 10)}…`}
|
|
65
65
|
icon="fa-wallet"
|
|
66
66
|
color="green"
|
|
67
67
|
/>
|
package/src/index.ts
CHANGED
|
@@ -2,20 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
// Suppress bigint-buffer warning
|
|
4
4
|
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
5
|
-
process.stderr.write = (
|
|
5
|
+
process.stderr.write = ((
|
|
6
|
+
chunk: unknown,
|
|
7
|
+
encoding?: unknown,
|
|
8
|
+
callback?: unknown,
|
|
9
|
+
) => {
|
|
6
10
|
if (
|
|
7
11
|
typeof chunk === "string" &&
|
|
8
12
|
chunk.includes("bigint: Failed to load bindings")
|
|
9
13
|
) {
|
|
10
14
|
return true; // Suppress this specific warning
|
|
11
15
|
}
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
const writer = originalStderrWrite as unknown as (
|
|
17
|
+
chunk: string | Uint8Array,
|
|
18
|
+
encoding?: BufferEncoding,
|
|
19
|
+
callback?: ((err?: Error) => void) | undefined,
|
|
20
|
+
) => boolean;
|
|
21
|
+
return writer(
|
|
22
|
+
chunk as string | Uint8Array,
|
|
23
|
+
encoding as BufferEncoding | undefined,
|
|
24
|
+
callback as ((err?: Error) => void) | undefined,
|
|
25
|
+
);
|
|
26
|
+
}) as typeof process.stderr.write;
|
|
14
27
|
|
|
15
28
|
import chalk from "chalk";
|
|
16
29
|
import { Command } from "commander";
|
|
17
|
-
import { existsSync } from "fs";
|
|
18
|
-
import { resolve } from "path";
|
|
30
|
+
import { existsSync } from "node:fs";
|
|
31
|
+
import { resolve } from "node:path";
|
|
19
32
|
import packageJson from "../package.json" with { type: "json" };
|
|
20
33
|
import { addProgramCommand } from "./commands/add-program.js";
|
|
21
34
|
import { initCommand } from "./commands/init.js";
|
|
@@ -123,7 +136,7 @@ program
|
|
|
123
136
|
const config = configManager.getConfig();
|
|
124
137
|
|
|
125
138
|
const apiServer = new APIServer({
|
|
126
|
-
port: parseInt(options.port),
|
|
139
|
+
port: parseInt(options.port, 10),
|
|
127
140
|
host: options.host,
|
|
128
141
|
validatorRpcUrl: options.rpcUrl,
|
|
129
142
|
validatorFaucetUrl: options.faucetUrl,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
2
|
-
import { Connection,
|
|
2
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { ChildProcess, spawn } from "child_process";
|
|
5
4
|
import cors from "cors";
|
|
6
5
|
import express from "express";
|
|
7
|
-
import { existsSync
|
|
8
|
-
import type { Server } from "http";
|
|
9
|
-
import { join } from "path";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import type { Server } from "node:http";
|
|
8
|
+
import { join } from "node:path";
|
|
10
9
|
import { mintTokenToWallet as mintTokenToWalletShared } from "../commands/mint.js";
|
|
11
10
|
import type { Config } from "../types/config.js";
|
|
12
11
|
import { runCommand } from "../utils/shell.js";
|
|
@@ -31,8 +30,6 @@ export class APIServer {
|
|
|
31
30
|
private app: express.Application;
|
|
32
31
|
private server: Server | null = null;
|
|
33
32
|
private config: APIServerConfig;
|
|
34
|
-
private tokenCloner: TokenCloner;
|
|
35
|
-
private programCloner: ProgramCloner;
|
|
36
33
|
private connection: Connection;
|
|
37
34
|
|
|
38
35
|
constructor(config: APIServerConfig) {
|
|
@@ -51,7 +48,7 @@ export class APIServer {
|
|
|
51
48
|
this.app.use(express.json());
|
|
52
49
|
|
|
53
50
|
// Request logging
|
|
54
|
-
this.app.use((req,
|
|
51
|
+
this.app.use((req, _res, next) => {
|
|
55
52
|
console.log(chalk.gray(`🌐 API: ${req.method} ${req.path}`));
|
|
56
53
|
next();
|
|
57
54
|
});
|
|
@@ -61,12 +58,12 @@ export class APIServer {
|
|
|
61
58
|
const router = express.Router();
|
|
62
59
|
|
|
63
60
|
// Health check
|
|
64
|
-
router.get("/health", (
|
|
61
|
+
router.get("/health", (_req, res) => {
|
|
65
62
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
66
63
|
});
|
|
67
64
|
|
|
68
65
|
// Get validator info
|
|
69
|
-
router.get("/validator/info", async (
|
|
66
|
+
router.get("/validator/info", async (_req, res) => {
|
|
70
67
|
try {
|
|
71
68
|
const version = await this.connection.getVersion();
|
|
72
69
|
const blockHeight = await this.connection.getBlockHeight();
|
|
@@ -88,7 +85,7 @@ export class APIServer {
|
|
|
88
85
|
});
|
|
89
86
|
|
|
90
87
|
// Get all cloned tokens
|
|
91
|
-
router.get("/tokens", async (
|
|
88
|
+
router.get("/tokens", async (_req, res) => {
|
|
92
89
|
try {
|
|
93
90
|
const clonedTokens = await this.getClonedTokens();
|
|
94
91
|
res.json({
|
|
@@ -110,7 +107,7 @@ export class APIServer {
|
|
|
110
107
|
});
|
|
111
108
|
|
|
112
109
|
// Get all cloned programs
|
|
113
|
-
router.get("/programs", async (
|
|
110
|
+
router.get("/programs", async (_req, res) => {
|
|
114
111
|
try {
|
|
115
112
|
const clonedPrograms = await this.getClonedPrograms();
|
|
116
113
|
res.json({
|
|
@@ -233,7 +230,10 @@ export class APIServer {
|
|
|
233
230
|
// Get recent transactions
|
|
234
231
|
router.get("/transactions/recent", async (req, res) => {
|
|
235
232
|
try {
|
|
236
|
-
const limit = Math.min(
|
|
233
|
+
const limit = Math.min(
|
|
234
|
+
parseInt(req.query.limit as string, 10) || 10,
|
|
235
|
+
100,
|
|
236
|
+
);
|
|
237
237
|
const signatures = await this.connection.getSignaturesForAddress(
|
|
238
238
|
new PublicKey("11111111111111111111111111111111"), // System program
|
|
239
239
|
{ limit },
|
|
@@ -254,7 +254,7 @@ export class APIServer {
|
|
|
254
254
|
this.app.use("/api", router);
|
|
255
255
|
|
|
256
256
|
// 404 handler
|
|
257
|
-
this.app.use("*", (
|
|
257
|
+
this.app.use("*", (_req, res) => {
|
|
258
258
|
res.status(404).json({ error: "Endpoint not found" });
|
|
259
259
|
});
|
|
260
260
|
}
|
|
@@ -296,7 +296,13 @@ export class APIServer {
|
|
|
296
296
|
mintAddress: string,
|
|
297
297
|
walletAddress: string,
|
|
298
298
|
amount: number,
|
|
299
|
-
): Promise<
|
|
299
|
+
): Promise<{
|
|
300
|
+
success: true;
|
|
301
|
+
symbol: string;
|
|
302
|
+
amount: number;
|
|
303
|
+
walletAddress: string;
|
|
304
|
+
mintAddress: string;
|
|
305
|
+
}> {
|
|
300
306
|
const clonedTokens = await this.getClonedTokens();
|
|
301
307
|
const token = findTokenByMint(clonedTokens, mintAddress);
|
|
302
308
|
|
|
@@ -321,7 +327,18 @@ export class APIServer {
|
|
|
321
327
|
};
|
|
322
328
|
}
|
|
323
329
|
|
|
324
|
-
private async getWalletBalances(walletAddress: string): Promise<
|
|
330
|
+
private async getWalletBalances(walletAddress: string): Promise<{
|
|
331
|
+
walletAddress: string;
|
|
332
|
+
solBalance: { lamports: number; sol: number };
|
|
333
|
+
tokenBalances: Array<{
|
|
334
|
+
mint: string;
|
|
335
|
+
symbol: string;
|
|
336
|
+
balance: string;
|
|
337
|
+
decimals: number;
|
|
338
|
+
uiAmount: number | null;
|
|
339
|
+
}>;
|
|
340
|
+
timestamp: string;
|
|
341
|
+
}> {
|
|
325
342
|
try {
|
|
326
343
|
const publicKey = new PublicKey(walletAddress);
|
|
327
344
|
|
|
@@ -371,7 +388,7 @@ export class APIServer {
|
|
|
371
388
|
}
|
|
372
389
|
}
|
|
373
390
|
}
|
|
374
|
-
} catch (
|
|
391
|
+
} catch (_error) {}
|
|
375
392
|
}
|
|
376
393
|
|
|
377
394
|
return {
|
|
@@ -395,7 +412,12 @@ export class APIServer {
|
|
|
395
412
|
private async airdropSol(
|
|
396
413
|
walletAddress: string,
|
|
397
414
|
amount: number,
|
|
398
|
-
): Promise<
|
|
415
|
+
): Promise<{
|
|
416
|
+
success: true;
|
|
417
|
+
amount: number;
|
|
418
|
+
walletAddress: string;
|
|
419
|
+
signature: string;
|
|
420
|
+
}> {
|
|
399
421
|
const result = await runCommand(
|
|
400
422
|
"solana",
|
|
401
423
|
[
|
|
@@ -477,6 +499,6 @@ export class APIServer {
|
|
|
477
499
|
}
|
|
478
500
|
|
|
479
501
|
isRunning(): boolean {
|
|
480
|
-
return this.server
|
|
502
|
+
return this.server?.listening;
|
|
481
503
|
}
|
|
482
504
|
}
|
|
@@ -7,7 +7,6 @@ export interface PortAllocation {
|
|
|
7
7
|
|
|
8
8
|
export class PortManager {
|
|
9
9
|
private readonly defaultRpcPort = 8899;
|
|
10
|
-
private readonly defaultFaucetPort = 9900;
|
|
11
10
|
private readonly portRangeStart = 8000;
|
|
12
11
|
private readonly portRangeEnd = 9999;
|
|
13
12
|
|
|
@@ -147,19 +146,17 @@ export class PortManager {
|
|
|
147
146
|
*/
|
|
148
147
|
private async checkPortActuallyFree(port: number): Promise<boolean> {
|
|
149
148
|
return new Promise((resolve) => {
|
|
150
|
-
const net = require("net");
|
|
149
|
+
const net = require("node:net");
|
|
151
150
|
const server = net.createServer();
|
|
152
151
|
|
|
153
|
-
server.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} else {
|
|
157
|
-
server.once("close", () => resolve(true));
|
|
158
|
-
server.close();
|
|
159
|
-
}
|
|
152
|
+
server.once("listening", () => {
|
|
153
|
+
server.once("close", () => resolve(true));
|
|
154
|
+
server.close();
|
|
160
155
|
});
|
|
161
156
|
|
|
162
|
-
server.
|
|
157
|
+
server.once("error", () => resolve(false));
|
|
158
|
+
|
|
159
|
+
server.listen(port);
|
|
163
160
|
});
|
|
164
161
|
}
|
|
165
162
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import type { Config } from "../types/config.js";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
5
4
|
|
|
6
5
|
export interface RunningValidator {
|
|
7
6
|
id: string;
|
|
@@ -143,7 +142,7 @@ export class ProcessRegistry {
|
|
|
143
142
|
// Ensure directory exists
|
|
144
143
|
const dir = join(homedir(), ".solforge");
|
|
145
144
|
if (!existsSync(dir)) {
|
|
146
|
-
require("fs").mkdirSync(dir, { recursive: true });
|
|
145
|
+
require("node:fs").mkdirSync(dir, { recursive: true });
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
writeFileSync(this.registryPath, JSON.stringify(validators, null, 2));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Connection, PublicKey } from "@solana/web3.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { existsSync, mkdirSync,
|
|
4
|
-
import { join } from "path";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
import type { ProgramConfig } from "../types/config.js";
|
|
6
6
|
import { runCommand } from "../utils/shell.js";
|
|
7
7
|
|
|
@@ -276,7 +276,7 @@ export class ProgramCloner {
|
|
|
276
276
|
const programAccount = await connection.getAccountInfo(
|
|
277
277
|
new PublicKey(programId),
|
|
278
278
|
);
|
|
279
|
-
return programAccount
|
|
279
|
+
return programAccount?.executable;
|
|
280
280
|
} catch {
|
|
281
281
|
return false;
|
|
282
282
|
}
|
|
@@ -310,7 +310,7 @@ export class ProgramCloner {
|
|
|
310
310
|
owner: programAccount.owner.toBase58(),
|
|
311
311
|
size: programAccount.data.length,
|
|
312
312
|
};
|
|
313
|
-
} catch (
|
|
313
|
+
} catch (_error) {
|
|
314
314
|
return { exists: false };
|
|
315
315
|
}
|
|
316
316
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
-
import { join
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
import type { TokenConfig } from "../types/config.js";
|
|
6
6
|
import { runCommand } from "../utils/shell.js";
|
|
7
7
|
|
|
@@ -614,7 +614,7 @@ export class TokenCloner {
|
|
|
614
614
|
);
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
|
-
} catch (
|
|
617
|
+
} catch (_error) {
|
|
618
618
|
if (debug) {
|
|
619
619
|
console.log(
|
|
620
620
|
chalk.gray(` ℹ️ No existing accounts found or parsing error`),
|
|
@@ -749,7 +749,7 @@ export class TokenCloner {
|
|
|
749
749
|
break;
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
|
-
} catch (
|
|
752
|
+
} catch (_error) {
|
|
753
753
|
// No existing accounts found or parsing error, will create new account
|
|
754
754
|
}
|
|
755
755
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { type ChildProcess, spawn } from "child_process";
|
|
2
|
-
import { existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
1
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
4
2
|
import type {
|
|
5
3
|
LocalnetConfig,
|
|
6
4
|
OperationResult,
|
|
@@ -251,7 +249,7 @@ export class ValidatorService {
|
|
|
251
249
|
if (response.ok) {
|
|
252
250
|
return; // Validator is ready
|
|
253
251
|
}
|
|
254
|
-
} catch (
|
|
252
|
+
} catch (_error) {
|
|
255
253
|
// Continue waiting
|
|
256
254
|
}
|
|
257
255
|
|
package/src/types/config.ts
CHANGED
|
@@ -86,11 +86,11 @@ export interface ValidatorState {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Operation result types
|
|
89
|
-
export interface OperationResult<T =
|
|
89
|
+
export interface OperationResult<T = unknown> {
|
|
90
90
|
success: boolean;
|
|
91
91
|
data?: T;
|
|
92
92
|
error?: string;
|
|
93
|
-
details?:
|
|
93
|
+
details?: unknown;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export interface CloneResult {
|
package/src/utils/shell.ts
CHANGED
|
@@ -56,7 +56,7 @@ export async function runCommand(
|
|
|
56
56
|
if (jsonOutput && success && stdout.trim()) {
|
|
57
57
|
try {
|
|
58
58
|
parsedOutput = JSON.parse(stdout);
|
|
59
|
-
} catch (
|
|
59
|
+
} catch (_e) {
|
|
60
60
|
// If JSON parsing fails, keep original stdout
|
|
61
61
|
console.warn(
|
|
62
62
|
chalk.yellow("Warning: Expected JSON output but got invalid JSON"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Keypair } from "@solana/web3.js";
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import type { TokenConfig } from "../types/config.js";
|
|
5
5
|
|
|
6
6
|
export interface ClonedToken {
|