soluser 1.0.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.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # soluser
2
+ # 新建账号
3
+ soluser new --alias alice --word-length 12
4
+ soluser new --alias bob --word-length 24 --no-bip39-passphrase
5
+
6
+ # 切换账号
7
+ soluser switch --address alice
8
+
9
+ # 列出账号
10
+ soluser list
11
+
12
+ # 本地安装
13
+ ```shell
14
+ npm link
15
+ ```
package/bin/index.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const newAccount = require('../src/commands/new');
5
+ const switchAccount = require('../src/commands/switch');
6
+ const listAccounts = require('../src/commands/list');
7
+
8
+ // 定义新建账号命令
9
+ program
10
+ .command('new')
11
+ .description('Create a new Solana account')
12
+ .option('--alias <name>', 'Alias for the new account')
13
+ .option('--word-length <number>', 'Number of words in seed phrase (12,15,18,21,24)', 12)
14
+ .option('--no-bip39-passphrase', 'Do not prompt for BIP39 passphrase')
15
+ .action((options) => {
16
+ if (!options.alias) {
17
+ throw new Error('Alias is required (use --alias <name>)');
18
+ }
19
+ newAccount(
20
+ options.alias,
21
+ parseInt(options.wordLength, 10),
22
+ options.noBip39Passphrase
23
+ );
24
+ });
25
+
26
+ // 定义切换账号命令
27
+ program
28
+ .command('switch')
29
+ .description('Switch active Solana account')
30
+ .option('--address <alias>', 'Alias of the account to switch to', '')
31
+ .action((options) => {
32
+ if (!options.address) {
33
+ throw new Error('Alias is required (use --address <alias>)');
34
+ }
35
+ switchAccount(options.address);
36
+ });
37
+
38
+ // 定义列出账号命令
39
+ program
40
+ .command('list')
41
+ .description('List all Solana accounts')
42
+ .action(listAccounts);
43
+
44
+ // 解析命令行参数
45
+ program.parse(process.argv);
46
+
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "soluser",
3
+ "version": "1.0.0",
4
+ "description": "A CLI tool to manage Solana accounts (new, switch, list)",
5
+ "main": "bin/index.js",
6
+ "bin": {
7
+ "soluser": "./bin/index.js"
8
+ },
9
+ "keywords": ["solana", "cli", "account", "wallet"],
10
+ "author": "guahuzi",
11
+ "license": "MIT",
12
+ "dependencies": {
13
+ "commander": "^11.1.0",
14
+ "cli-table3": "^0.6.3"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/nextuser/soluser.git"
19
+ }
20
+ }
21
+
@@ -0,0 +1,46 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Table = require('cli-table3');
4
+ const { KEYS_DIR } = require('../utils/path');
5
+ const { getAddress, getActiveKeyPath } = require('../utils/solana');
6
+
7
+ function listAccounts() {
8
+ // 1. 读取密钥目录下的所有 json 文件
9
+ if (!fs.existsSync(KEYS_DIR)) {
10
+ console.log('No accounts found. Create one with "soluser new --alias <name>".');
11
+ return;
12
+ }
13
+
14
+ const files = fs.readdirSync(KEYS_DIR).filter(file => file.endsWith('.json'));
15
+ if (files.length === 0) {
16
+ console.log('No accounts found. Create one with "soluser new --alias <name>".');
17
+ return;
18
+ }
19
+
20
+ // 2. 获取当前活跃账号的别名
21
+ const activeKeyPath = getActiveKeyPath();
22
+ const activeAlias = activeKeyPath
23
+ ? path.basename(activeKeyPath, '.json')
24
+ : null;
25
+
26
+ // 3. 构建表格
27
+ const table = new Table({
28
+ head: ['alias', 'address', 'active'],
29
+ colWidths: [15, 45, 8],
30
+ });
31
+
32
+ // 4. 填充表格数据
33
+ files.forEach(file => {
34
+ const alias = path.basename(file, '.json');
35
+ const address = getAddress(alias);
36
+ const isActive = alias === activeAlias ? '*' : '';
37
+ console.log("alias: ", alias, "address: ", address, "isActive: ", isActive,'activeAlias',activeAlias )
38
+ table.push([alias, address, isActive]);
39
+ });
40
+
41
+ // 5. 打印表格
42
+ console.log(table.toString());
43
+ }
44
+
45
+ module.exports = listAccounts;
46
+
@@ -0,0 +1,31 @@
1
+ const fs = require('fs');
2
+ const { KEYS_DIR, validateAlias, getKeyFilePath } = require('../utils/path');
3
+ const { execCommand } = require('../utils/solana');
4
+
5
+ function newAccount(alias, wordLength = 12, noPassphrase = false) {
6
+ // 1. 确保密钥目录存在
7
+ if (!fs.existsSync(KEYS_DIR)) {
8
+ fs.mkdirSync(KEYS_DIR, { recursive: true });
9
+ }
10
+
11
+ // 2. 验证参数
12
+ validateAlias(alias);
13
+ const validWordLengths = [12, 15, 18, 21, 24];
14
+ if (!validWordLengths.includes(wordLength)) {
15
+ throw new Error(`Word length must be one of: ${validWordLengths.join(', ')}`);
16
+ }
17
+
18
+ // 3. 生成密钥对(调用 solana-keygen)
19
+ const keyPath = getKeyFilePath(alias);
20
+ let command = `solana-keygen new --word-count ${wordLength} --outfile ${keyPath}`;
21
+ if (noPassphrase) {
22
+ command += ' --no-bip39-passphrase';
23
+ }
24
+
25
+ console.log(`Generating key pair for ${alias}...`);
26
+ execCommand(command);
27
+ console.log(`Successfully created account: ${alias} (saved to ${keyPath})`);
28
+ }
29
+
30
+ module.exports = newAccount;
31
+
@@ -0,0 +1,19 @@
1
+ const fs = require('fs');
2
+ const { getKeyFilePath } = require('../utils/path');
3
+ const { execCommand } = require('../utils/solana');
4
+
5
+ function switchAccount(alias) {
6
+ const keyPath = getKeyFilePath(alias);
7
+
8
+ // 验证密钥文件是否存在
9
+ if (!fs.existsSync(keyPath)) {
10
+ throw new Error(`Account ${alias} not found. Check alias or create it with "soluser new".`);
11
+ }
12
+
13
+ // 执行切换(调用 solana config set)
14
+ execCommand(`solana config set --keypair ${keyPath}`);
15
+ console.log(`Switched active account to: ${alias}`);
16
+ }
17
+
18
+ module.exports = switchAccount;
19
+
@@ -0,0 +1,25 @@
1
+ const path = require('path');
2
+ const os = require('os');
3
+
4
+ // 密钥存储目录:~/.config/solana/keys
5
+ const KEYS_DIR = path.join(os.homedir(), '.config', 'solana', 'keys');
6
+
7
+ // 验证别名格式(字母开头,仅含字母、数字、-、_)
8
+ function validateAlias(alias) {
9
+ const regex = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
10
+ if (!regex.test(alias)) {
11
+ throw new Error('Alias must start with a letter and contain only letters, digits, hyphens, or underscores');
12
+ }
13
+ }
14
+
15
+ // 获取密钥文件路径
16
+ function getKeyFilePath(alias) {
17
+ return path.join(KEYS_DIR, `${alias}.json`);
18
+ }
19
+
20
+ module.exports = {
21
+ KEYS_DIR,
22
+ validateAlias,
23
+ getKeyFilePath,
24
+ };
25
+
@@ -0,0 +1,33 @@
1
+ const { execSync } = require('child_process');
2
+ const { getKeyFilePath } = require('./path');
3
+
4
+ // 执行 shell 命令并返回输出
5
+ function execCommand(command) {
6
+ try {
7
+ return execSync(command, { encoding: 'utf8' }).trim();
8
+ } catch (error) {
9
+ throw new Error(`Command failed: ${command}\n${error.stderr}`);
10
+ }
11
+ }
12
+
13
+ // 获取指定密钥的公钥(地址)
14
+ function getAddress(alias) {
15
+ const keyPath = getKeyFilePath(alias);
16
+ return execCommand(`solana-keygen pubkey ${keyPath}`);
17
+ }
18
+
19
+ // 获取当前活跃的密钥路径
20
+ function getActiveKeyPath() {
21
+ const config = execCommand('solana config get keypair');
22
+ // 从输出中提取路径(例如:"Keypair Path: /home/user/.config/solana/keys/alice.json")
23
+ const match = config.match(/Key Path: (.*)/);
24
+ console.log("config:",config,"config match :",match)
25
+ return match ? match[1] : null;
26
+ }
27
+
28
+ module.exports = {
29
+ execCommand,
30
+ getAddress,
31
+ getActiveKeyPath,
32
+ };
33
+