solforge 0.1.0 → 0.1.2

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.
@@ -1,259 +0,0 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import { existsSync, readFileSync } from "fs";
4
- import { join } from "path";
5
- import { input, select } from "@inquirer/prompts";
6
- import { runCommand } from "../utils/shell";
7
- import { Keypair } from "@solana/web3.js";
8
-
9
- interface TokenBalance {
10
- mint: string;
11
- symbol: string;
12
- address: string;
13
- balance: string;
14
- decimals: number;
15
- }
16
-
17
- interface TokenConfig {
18
- symbol: string;
19
- mainnetMint: string;
20
- mintAmount: number;
21
- }
22
-
23
- export const transferCommand = new Command()
24
- .name("transfer")
25
- .description(
26
- "Interactively transfer tokens from mint authority to any address"
27
- )
28
- .option("--rpc-url <url>", "RPC URL to use", "http://127.0.0.1:8899")
29
- .action(async (options) => {
30
- try {
31
- console.log(chalk.blue("🔄 Interactive Token Transfer"));
32
- console.log(
33
- chalk.gray("Send tokens from mint authority to any address\n")
34
- );
35
-
36
- // Check if solforge data exists
37
- const workDir = ".solforge";
38
- if (!existsSync(workDir)) {
39
- console.error(
40
- chalk.red("❌ No solforge data found. Run 'solforge start' first.")
41
- );
42
- process.exit(1);
43
- }
44
-
45
- // Load available tokens and their balances
46
- const tokens = await loadAvailableTokens(workDir, options.rpcUrl);
47
-
48
- if (tokens.length === 0) {
49
- console.error(
50
- chalk.red(
51
- "❌ No tokens found. Run 'solforge start' first to clone tokens."
52
- )
53
- );
54
- process.exit(1);
55
- }
56
-
57
- // Display available tokens
58
- console.log(chalk.cyan("📋 Available Tokens:"));
59
- tokens.forEach((token, index) => {
60
- console.log(
61
- chalk.gray(
62
- ` ${index + 1}. ${token.symbol} (${token.mint}) - Balance: ${
63
- token.balance
64
- }`
65
- )
66
- );
67
- });
68
- console.log();
69
-
70
- // Select token
71
- const selectedToken = await select({
72
- message: "Select a token to transfer:",
73
- choices: tokens.map((token, index) => ({
74
- name: `${token.symbol} - Balance: ${token.balance}`,
75
- value: token,
76
- })),
77
- });
78
-
79
- // Get recipient address
80
- const recipientAddress = await input({
81
- message: "Enter recipient address (wallet address or PDA):",
82
- validate: (value: string) => {
83
- if (!value.trim()) {
84
- return "Please enter a valid address";
85
- }
86
- // Basic length check for Solana addresses
87
- if (value.trim().length < 32 || value.trim().length > 44) {
88
- return "Please enter a valid Solana address (32-44 characters)";
89
- }
90
- return true;
91
- },
92
- });
93
-
94
- // Get amount to transfer
95
- const maxAmount = parseFloat(selectedToken.balance);
96
- const amount = await input({
97
- message: `Enter amount to transfer (max: ${selectedToken.balance}):`,
98
- validate: (value: string) => {
99
- const num = parseFloat(value);
100
- if (isNaN(num) || num <= 0) {
101
- return "Please enter a valid positive number";
102
- }
103
- if (num > maxAmount) {
104
- return `Amount cannot exceed available balance: ${selectedToken.balance}`;
105
- }
106
- return true;
107
- },
108
- });
109
-
110
- // Use amount as-is (spl-token expects UI amount, not base units)
111
- const transferAmount = amount;
112
-
113
- // Confirm transfer
114
- const confirm = await input({
115
- message: `Confirm transfer of ${amount} ${selectedToken.symbol} to ${recipientAddress}? (y/N):`,
116
- default: "N",
117
- });
118
-
119
- if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") {
120
- console.log(chalk.yellow("Transfer cancelled."));
121
- process.exit(0);
122
- }
123
-
124
- console.log(chalk.blue("🚀 Starting transfer..."));
125
-
126
- // Execute transfer
127
- await executeTransfer(
128
- selectedToken,
129
- recipientAddress,
130
- transferAmount.toString(),
131
- options.rpcUrl,
132
- workDir
133
- );
134
-
135
- console.log(
136
- chalk.green(
137
- `✅ Successfully transferred ${amount} ${selectedToken.symbol} to ${recipientAddress}`
138
- )
139
- );
140
- } catch (error) {
141
- console.error(chalk.red(`❌ Transfer failed: ${error}`));
142
- process.exit(1);
143
- }
144
- });
145
-
146
- async function loadAvailableTokens(
147
- workDir: string,
148
- rpcUrl: string
149
- ): Promise<TokenBalance[]> {
150
- const tokens: TokenBalance[] = [];
151
-
152
- try {
153
- // Load shared mint authority secret key and generate keypair
154
- const sharedMintAuthorityPath = join(workDir, "shared-mint-authority.json");
155
- if (!existsSync(sharedMintAuthorityPath)) {
156
- throw new Error("Shared mint authority not found");
157
- }
158
-
159
- const secretKeyArray = JSON.parse(
160
- readFileSync(sharedMintAuthorityPath, "utf8")
161
- );
162
- const mintAuthorityKeypair = Keypair.fromSecretKey(
163
- new Uint8Array(secretKeyArray)
164
- );
165
- const mintAuthorityAddress = mintAuthorityKeypair.publicKey.toBase58();
166
-
167
- // Load token config from sf.config.json
168
- const configPath = "sf.config.json";
169
- if (!existsSync(configPath)) {
170
- throw new Error("sf.config.json not found in current directory");
171
- }
172
-
173
- const config = JSON.parse(readFileSync(configPath, "utf8"));
174
- const tokenConfigs: TokenConfig[] = config.tokens || [];
175
-
176
- // Get token account balances
177
- const accountsResult = await runCommand(
178
- "spl-token",
179
- [
180
- "accounts",
181
- "--owner",
182
- mintAuthorityAddress,
183
- "--url",
184
- rpcUrl,
185
- "--output",
186
- "json",
187
- ],
188
- { silent: true }
189
- );
190
-
191
- if (!accountsResult.success) {
192
- throw new Error(
193
- `Failed to fetch token accounts: ${accountsResult.stderr}`
194
- );
195
- }
196
-
197
- const accountsData = JSON.parse(accountsResult.stdout);
198
-
199
- // Match accounts with token config
200
- for (const account of accountsData.accounts || []) {
201
- const tokenInfo = tokenConfigs.find(
202
- (token: TokenConfig) => token.mainnetMint === account.mint
203
- );
204
- if (tokenInfo) {
205
- tokens.push({
206
- mint: account.mint,
207
- symbol: tokenInfo.symbol,
208
- address: account.address,
209
- balance: account.tokenAmount.uiAmountString,
210
- decimals: account.tokenAmount.decimals,
211
- });
212
- }
213
- }
214
-
215
- return tokens;
216
- } catch (error) {
217
- throw new Error(`Failed to load tokens: ${error}`);
218
- }
219
- }
220
-
221
- async function executeTransfer(
222
- token: TokenBalance,
223
- recipientAddress: string,
224
- amount: string,
225
- rpcUrl: string,
226
- workDir: string
227
- ): Promise<void> {
228
- // Load mint authority keypair path
229
- const sharedMintAuthorityPath = join(workDir, "shared-mint-authority.json");
230
-
231
- // Transfer tokens directly to wallet address
232
- // The --fund-recipient flag will automatically create the associated token account if needed
233
- console.log(chalk.gray("💸 Transferring tokens..."));
234
-
235
- const transferResult = await runCommand(
236
- "spl-token",
237
- [
238
- "transfer",
239
- token.mint,
240
- amount,
241
- recipientAddress,
242
- "--from",
243
- token.address,
244
- "--owner",
245
- sharedMintAuthorityPath,
246
- "--fee-payer",
247
- sharedMintAuthorityPath,
248
- "--fund-recipient",
249
- "--allow-unfunded-recipient",
250
- "--url",
251
- rpcUrl,
252
- ],
253
- { silent: false }
254
- );
255
-
256
- if (!transferResult.success) {
257
- throw new Error(`Transfer failed: ${transferResult.stderr}`);
258
- }
259
- }