solana钱包管理模块

使用web3js实现solana钱包管理

需求
  1. 批量创建solana钱包地址
  2. 获取私钥时需要输入密码
  3. 可以查询钱包地址
解决
  1. 生成钱包地址时需要输入自定义密码,使用钱包时也需要输入密码
  2. 生成的钱包地址保存到本地,保存内容是加密私钥的文件,使用时获取文件中的私钥进行解密
  3. 批量生成钱包地址文件保存到同一文件夹中,文件夹名称后缀用数字表示第几批钱包
  4. 查询钱包地址时,输入文件夹编号即可查看
  5. 查询某个地址私钥时,输入文件夹编号、地址编号和密码
实现效果

image.png

实现代码
const { Keypair } = require('@solana/web3.js');
const bs58 = require('bs58');
const CryptoJS = require('crypto-js');
const fs = require('fs-extra');
const path = require('path');
const inquirer = require('inquirer');
const moment = require('moment');
const { Connection, PublicKey } = require('@solana/web3.js');
const { clusterApiUrl } = require('@solana/web3.js');
// 引入配置管理器
const ConfigManager = require('./config-manager').default;
const configManager = new ConfigManager();

if (typeof configManager === 'object') {
    console.log('配置管理器属性:', Object.keys(configManager));
    if (configManager.network) {
        console.log('网络配置:', configManager.network);
    }
}

class SolanaAddressGenerator {
    constructor() {
        this.walletDir = '';
        this.configManager = configManager;
    }

    // 加密私钥
    encryptPrivateKey(privateKey, password) {
        try {
            // 使用密码生成加密密钥
            const key = CryptoJS.PBKDF2(password, 'salt', {
                keySize: 256 / 32,
                iterations: 100000
            });

            // 使用密钥加密私钥
            const encrypted = CryptoJS.AES.encrypt(privateKey, key.toString());
            return encrypted.toString();
        } catch (error) {
            throw new Error(`加密失败: ${error.message}`);
        }
    }

    // 解密私钥
    decryptPrivateKey(encryptedData, password) {
        try {
            // 使用相同的方法从密码生成密钥
            const key = CryptoJS.PBKDF2(password, 'salt', {
                keySize: 256 / 32,
                iterations: 100000
            });

            // 使用密钥解密
            const decrypted = CryptoJS.AES.decrypt(encryptedData, key.toString());
            const privateKey = decrypted.toString(CryptoJS.enc.Utf8);

            if (!privateKey) {
                throw new Error('密码错误');
            }

            return privateKey;
        } catch (error) {
            throw new Error('密码错误或数据已损坏');
        }
    }

    // 生成新的Solana地址
    generateSolanaAddress(password) {
        // 生成新的密钥对
        const keypair = Keypair.generate();

        // 获取公钥(地址)和私钥
        const publicKey = keypair.publicKey.toString();
        const secretKey = bs58.encode(keypair.secretKey);

        // 加密私钥
        const encryptedPrivateKey = this.encryptPrivateKey(secretKey, password);

        return {
            address: publicKey,
            encryptedPrivateKey
        };
    }

    // 获取钱包目录         
    async getWalletDirectory(checkExists = true) {
        const folderNumStr = await inquirer.prompt([
            {
                type: 'input',
                name: 'folderNum',
                message: '请输入文件夹编号 (例如: 1):',
                validate: input => !isNaN(parseInt(input)) ? true : '请输入有效的数字'
            }
        ]);
        const folderNum = folderNumStr.folderNum.trim(); // 直接使用返回的字符串,而不是尝试解构

        // 创建主钱包目录
        const mainWalletDir = path.join(process.cwd(), 'wallets');
        if (!fs.existsSync(mainWalletDir)) {
            fs.mkdirSync(mainWalletDir, { recursive: true });
            console.log(`创建主钱包目录: ${mainWalletDir}`);
        }

        const folderName = `wallets${folderNum}`;
        const walletDir = path.join(mainWalletDir, folderName);

        console.log(`调试信息: 钱包目录路径: ${walletDir}`);

        if (fs.existsSync(walletDir)) {
            if (checkExists) {
                const useExistingStr = await inquirer.prompt([
                    {
                        type: 'confirm',
                        name: 'useExisting',
                        message: '文件夹 ' + folderName + ' 已存在。是否使用该文件夹? (y/n):'
                    }
                ]);
                const useExisting = useExistingStr.useExisting;

                if (useExisting === false) {
                    return this.getWalletDirectory(checkExists);
                }
            }
        } else {
            if (checkExists) {
                fs.mkdirSync(walletDir, { recursive: true });
                console.log(`创建文件夹: ${walletDir}`);
            } else {
                throw new Error(`找不到文件夹 ${folderName}`);
            }
        }

        this.walletDir = walletDir;
        return walletDir;
    }

    // 生成文件名
    getFilename(address) {
        // 生成时间戳(精确到毫秒)
        const timestamp = moment().format('YYYYMMDD_HHmmss_SSS');
        // 使用时间戳和地址后8位组合成文件名
        return `wallet_${timestamp}_${address.slice(-8)}.json`;
    }

    // 保存钱包到文件   
    saveWalletToFile(wallet, walletDir) {
        // 生成文件名
        const filename = this.getFilename(wallet.address);
        const filepath = path.join(walletDir, filename);

        // 保存到文件
        fs.writeJsonSync(filepath, {
            address: wallet.address,
            encryptedPrivateKey: wallet.encryptedPrivateKey
        }, { spaces: 2 });

        return filepath;
    }

    // 从文件加载钱包   
    loadWalletFromFile(filepath) {
        return fs.readJsonSync(filepath);
    }

    // 列出钱包文件
    listWalletFiles(walletDir) {
        return fs.readdirSync(walletDir)
            .filter(file => file.endsWith('.json'))
            .sort();
    }

    // 查看私钥
    async viewPrivateKeys(folderNum, walletIndex, password) {
        try {
            // 创建主钱包目录
            const mainWalletDir = path.join(process.cwd(), 'wallets');
            if (!fs.existsSync(mainWalletDir)) {
                fs.mkdirSync(mainWalletDir, { recursive: true });
                console.log(`创建主钱包目录: ${mainWalletDir}`);
            }

            // 构建文件夹路径
            const folderName = `wallets${folderNum}`;
            const folderPath = path.join(mainWalletDir, folderName);

            console.log(`调试信息: 查询私钥的文件夹路径: ${folderPath}`);

            if (!fs.existsSync(folderPath)) {
                throw new Error(`找不到文件夹: ${folderPath}`);
            }

            // 获取所有钱包文件
            const walletFiles = fs.readdirSync(folderPath)
                .filter(file => file.endsWith('.json'))
                .sort();

            if (walletFiles.length === 0) {
                throw new Error(`文件夹 ${folderName} 中没有钱包文件`);
            }

            // 如果指定了钱包索引,只查询该钱包的私钥
            if (walletIndex) {
                if (walletIndex < 1 || walletIndex > walletFiles.length) {
                    throw new Error(`无效的钱包索引,应为 1 到 ${walletFiles.length}`);
                }

                const walletFile = walletFiles[walletIndex - 1];
                const walletPath = path.join(folderPath, walletFile);
                const walletData = fs.readJsonSync(walletPath);

                // 解密私钥
                const privateKey = this.decryptPrivateKey(walletData.encryptedPrivateKey, password);

                return [{
                    index: walletIndex,
                    address: walletData.address,
                    privateKey: privateKey
                }];
            } else {
                // 获取所有钱包文件
                const wallets = [];

                for (let i = 0; i < walletFiles.length; i++) {
                    try {
                        const walletFile = walletFiles[i];
                        const walletPath = path.join(folderPath, walletFile);
                        const walletData = fs.readJsonSync(walletPath);

                        // 解密私钥
                        const privateKey = this.decryptPrivateKey(walletData.encryptedPrivateKey, password);

                        wallets.push({
                            index: i + 1,
                            address: walletData.address,
                            privateKey: privateKey
                        });
                    } catch (error) {
                        console.error(`处理钱包 #${i + 1} 失败: ${error.message}`);
                    }
                }

                if (wallets.length === 0) {
                    throw new Error('没有成功解密任何钱包,请检查密码是否正确');
                }

                return wallets;
            }
        } catch (error) {
            throw new Error(`查看私钥失败: ${error.message}`);
        }
    }

    // 获取项目根目录(与solana-js同级的目录)
    getProjectRoot() {
        // 当前工作目录是 solana-js/src,需要回退两级到项目根目录
        return path.join(process.cwd(), '..');
    }

    // 生成钱包
    async generateWallets(folderNum, count, password) {
        try {
            // 如果是交互模式,获取参数
            if (arguments.length === 0) {
                const folderNumStr = await inquirer.prompt([
                    {
                        type: 'input',
                        name: 'folderNum',
                        message: '请输入文件夹编号:',
                        validate: input => !isNaN(parseInt(input)) ? true : '请输入有效的数字'
                    }
                ]);
                folderNum = folderNumStr.folderNum.trim();

                const countStr = await inquirer.prompt([
                    {
                        type: 'input',
                        name: 'count',
                        message: '请输入要生成的钱包数量:',
                        default: '1',
                        validate: input => !isNaN(parseInt(input)) && parseInt(input) > 0 ? true : '请输入有效的正整数'
                    }
                ]);
                count = parseInt(countStr.count);

                // 添加密码输入和确认
                const passwordPrompt = await inquirer.prompt([
                    {
                        type: 'password',
                        name: 'password',
                        message: '请输入钱包密码:',
                        mask: '*',
                        validate: input => input.length >= 6 ? true : '密码长度至少为6个字符'
                    }
                ]);

                // 添加密码确认
                const confirmPasswordPrompt = await inquirer.prompt([
                    {
                        type: 'password',
                        name: 'confirmPassword',
                        message: '请再次输入钱包密码:',
                        mask: '*',
                        validate: input => {
                            if (input !== passwordPrompt.password) {
                                return '两次输入的密码不一致,请重新输入';
                            }
                            return true;
                        }
                    }
                ]);

                password = passwordPrompt.password;

                // 最后确认一次所有信息
                console.log('\n请确认以下信息:');
                console.log(`文件夹编号: wallets${folderNum}`);
                console.log(`钱包数量: ${count}`);
                console.log(`密码长度: ${password.length}位`);

                const confirmGenerate = await inquirer.prompt([
                    {
                        type: 'confirm',
                        name: 'confirm',
                        message: '确认生成钱包?',
                        default: true
                    }
                ]);

                if (!confirmGenerate.confirm) {
                    console.log('已取消生成钱包');
                    return [];
                }
            }

            // 创建主钱包目录
            const mainWalletDir = path.join(process.cwd(), 'wallets');
            if (!fs.existsSync(mainWalletDir)) {
                fs.mkdirSync(mainWalletDir, { recursive: true });
                console.log(`创建主钱包目录: ${mainWalletDir}`);
            }

            // 创建钱包文件夹
            const folderName = `wallets${folderNum}`;
            const folderPath = path.join(mainWalletDir, folderName);

            console.log(`调试信息: 检查钱包文件夹路径: ${folderPath}`);

            // 检查文件夹是否已存在,如果存在则直接返回错误
            if (fs.existsSync(folderPath)) {
                console.error(`错误: 文件夹 ${folderName} 已存在,为避免覆盖现有钱包,请使用其他文件夹编号`);
                throw new Error(`文件夹 ${folderName} 已存在,为避免覆盖现有钱包,请使用其他文件夹编号`);
            }

            // 创建新文件夹
            fs.mkdirSync(folderPath, { recursive: true });
            console.log(`创建文件夹: ${folderPath}`);

            console.log(`开始生成 ${count} 个钱包`);

            // 生成钱包
            const wallets = [];
            for (let i = 0; i < count; i++) {
                const keypair = Keypair.generate();
                const publicKey = keypair.publicKey.toString();

                // 将私钥转换为base58格式
                const privateKey = bs58.encode(keypair.secretKey);

                // 加密私钥
                const key = CryptoJS.PBKDF2(password, 'salt', {
                    keySize: 256 / 32,
                    iterations: 100000
                });

                const encrypted = CryptoJS.AES.encrypt(privateKey, key.toString()).toString();

                // 创建钱包文件 - 使用日期_时间_地址后8位的格式
                const timestamp = moment().format('YYYYMMDD_HHmmss_SSS');
                const walletFileName = `wallet_${timestamp}_${publicKey.slice(-8)}.json`;
                const walletFilePath = path.join(folderPath, walletFileName);

                // 保存钱包信息
                const walletData = {
                    address: publicKey,
                    encryptedPrivateKey: encrypted,
                    createdAt: new Date().toISOString()
                };

                fs.writeFileSync(walletFilePath, JSON.stringify(walletData, null, 2));

                wallets.push({
                    index: i + 1,
                    address: publicKey,
                    filePath: walletFilePath
                });

                console.log(`生成钱包 #${i + 1}: ${publicKey}`);
            }

            console.log(`\n成功生成 ${count} 个钱包,保存在文件夹 ${folderPath}`);

            // 保存地址列表到文件
            await this.saveAddressListFile(folderNum, wallets);

            return wallets;
        } catch (error) {
            console.error(`生成钱包失败: ${error.message}`);
            throw new Error(`生成钱包失败: ${error.message}`);
        }
    }

    /**
     * 保存地址列表文件
     * @param {string} folderNum 文件夹编号
     * @param {Array} wallets 钱包数组
     */
    async saveAddressListFile(folderNum, wallets) {
        try {
            // 创建主钱包目录
            const mainWalletDir = path.join(process.cwd(), 'wallets');
            if (!fs.existsSync(mainWalletDir)) {
                fs.mkdirSync(mainWalletDir, { recursive: true });
                console.log(`创建主钱包目录: ${mainWalletDir}`);
            }

            // 在主钱包目录下创建导出目录
            const exportDir = path.join(mainWalletDir, 'address_exports');
            if (!fs.existsSync(exportDir)) {
                fs.mkdirSync(exportDir, { recursive: true });
            }

            const timestamp = moment().format('YYYYMMDD_HHmmss');
            const fileName = `export_${timestamp}_wallets${folderNum}_${wallets[0].index}-${wallets[wallets.length - 1].index}.txt`;
            const filePath = path.join(exportDir, fileName);

            // 创建地址列表内容
            let content = '';
            wallets.forEach(wallet => {
                content += `${wallet.index}: ${wallet.address}\n`;
            });

            // 写入文件
            fs.writeFileSync(filePath, content);
            console.log(`地址列表已保存到: ${filePath}`);

            return filePath;
        } catch (error) {
            console.error(`保存地址列表失败: ${error.message}`);
            return null;
        }
    }

    // 查找地址在所有钱包文件夹中
    async findAddressInAllFolders(address) {
        try {
            // 创建主钱包目录
            const mainWalletDir = path.join(process.cwd(), 'wallets');
            if (!fs.existsSync(mainWalletDir)) {
                fs.mkdirSync(mainWalletDir, { recursive: true });
                console.log(`创建主钱包目录: ${mainWalletDir}`);
                return { found: false, folders: [] };
            }

            // 获取所有钱包文件夹
            const folders = fs.readdirSync(mainWalletDir)
                .filter(folder => folder.startsWith('wallets') && fs.statSync(path.join(mainWalletDir, folder)).isDirectory());

            if (folders.length === 0) {
                console.log('没有找到任何钱包文件夹');
                return { found: false, folders: [] };
            }

            console.log(`找到 ${folders.length} 个钱包文件夹,开始搜索...`);

            const results = [];
            let found = false;

            // 在每个文件夹中查找地址
            for (const folder of folders) {
                const folderPath = path.join(mainWalletDir, folder);
                const walletFiles = fs.readdirSync(folderPath)
                    .filter(file => file.endsWith('.json'));

                if (walletFiles.length === 0) {
                    console.log(`文件夹 ${folder} 中没有钱包文件,跳过`);
                    continue;
                }

                // 检查每个钱包文件
                for (const file of walletFiles) {
                    try {
                        const walletPath = path.join(folderPath, file);
                        const data = fs.readJsonSync(walletPath);

                        if (data.address === address) {
                            found = true;
                            const folderNum = folder.replace('wallets', '');
                            results.push({
                                folder: folder,
                                folderNum: folderNum,
                                file: file,
                                path: walletPath
                            });
                            console.log(`✅ 在文件夹 ${folder} 中找到地址: ${file}`);
                        }
                    } catch (error) {
                        console.error(`读取钱包文件 ${folder}/${file} 失败: ${error.message}`);
                    }
                }
            }

            return { found, folders: results };
        } catch (error) {
            throw new Error(`搜索地址失败: ${error.message}`);
        }
    }

}

module.exports = SolanaAddressGenerator;
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
0 订阅 1 篇文章

0 条评论

请先 登录 后评论
stephenCheung
stephenCheung
0xacd5...DA49
java,go,solidity,rust智能合约开发者,专注defi领域研究