sol转账模块是为了方便项目中多个账号资金管理需求管理多个地址,对同一个项目进行交易,但是不能被平台检测出来这是相关联的地址。通常一些数据平台检测地址是否有关联性,会通过资金来源来识别。这样我们就要避免多个地址的资金来源是同一个地址,或者很容易被发现是来源同一个地址,一般平台只会检测上一层的来
sol转账模块是为了方便项目中多个账号资金管理
管理多个地址,对同一个项目进行交易,但是不能被平台检测出来这是相关联的地址。 通常一些数据平台检测地址是否有关联性,会通过资金来源来识别。这样我们就要避免多个地址的资金来源是同一个地址,或者很容易被发现是来源同一个地址,一般平台只会检测上一层的来源。
我们可以通过中心化交易所,转出到每个链上地址。但是这个缺点很明显,费时费力。 通过代码实现SOL从多个地址一一对应转到多个地址,也就是多转多,为了避免被平台标记为相关地址(老鼠仓),可以多转几层。每次用不同的地址转到不同地址,最后转到我们真正使用的地址上。 由于初始的资金来源还是从一个地址中分发出来的,所以还要实现一转多。
我们先实现余额的查询,批量对多个地址查询(地址来源参考上一篇文章中)
const fs = require('fs-extra');
const path = require('path');
class SolanaBalanceChecker {
constructor() {
// 加载环境变量
try {
require('dotenv').config();
} catch (error) {
console.warn('无法加载dotenv,将使用默认网络配置');
}
// 使用环境变量中的RPC URL(如果存在),否则使用默认的API端点
const rpcUrl = process.env.SOLANA_RPC_URL || clusterApiUrl('mainnet-beta');
const network = process.env.SOLANA_NETWORK || 'mainnet-beta';
this.connection = new Connection(rpcUrl, {
commitment: 'confirmed',
confirmTransactionInitialTimeout: 60000, // 60秒超时
disableRetryOnRateLimit: false // 启用速率限制重试
});
console.log(`SolanaBalanceChecker初始化完成,使用网络: ${network}`);
console.log(`RPC URL: ${rpcUrl}`);
}
/**
* 设置网络连接
* @param {string} network - 网络名称 ('mainnet-beta', 'testnet', 'devnet')
*/
setNetwork(network) {
this.connection = new Connection(clusterApiUrl(network), {
commitment: 'confirmed',
confirmTransactionInitialTimeout: 60000, // 60秒超时
disableRetryOnRateLimit: false // 启用速率限制重试
});
console.log(`SolanaBalanceChecker网络已切换到: ${network}`);
}
/**
* 查询单个地址的SOL余额
* @param {string} address - 钱包地址
* @returns {Promise<number>} - SOL余额
*/
async checkBalance(address) {
try {
console.log(`开始查询地址 ${address} 的SOL余额...`);
// 验证地址格式
if (!address || typeof address !== 'string') {
throw new Error('无效的钱包地址');
}
// 尝试创建PublicKey对象,这会验证地址格式
let publicKey;
try {
publicKey = new PublicKey(address);
} catch (error) {
throw new Error(`无效的Solana地址格式: ${error.message}`);
}
// 使用重试机制查询余额
let retries = 3;
let lastError = null;
while (retries > 0) {
try {
const balance = await this.connection.getBalance(publicKey);
const solBalance = balance / LAMPORTS_PER_SOL;
console.log(`地址 ${address} 的SOL余额: ${solBalance}`);
return solBalance;
} catch (error) {
lastError = error;
console.error(`查询余额失败,剩余重试次数: ${retries - 1}, 错误: ${error.message}`);
retries--;
if (retries > 0) {
// 等待一段时间再重试
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
throw new Error(`多次尝试查询余额失败: ${lastError ? lastError.message : '未知错误'}`);
} catch (error) {
console.error(`查询SOL余额失败: ${error.message}`);
throw new Error(`查询SOL余额失败: ${error.message}`);
}
}
/**
* 批量查询多个地址的SOL余额
* @param {string[]} addresses - 钱包地址数组
* @returns {Promise<Object>} - 地址到余额的映射
*/
async checkMultipleBalances(addresses) {
try {
console.log(`开始批量查询 ${addresses.length} 个地址的SOL余额...`);
// 验证地址格式并创建PublicKey对象数组
const publicKeys = [];
const addressMap = {}; // 用于保存PublicKey字符串到原始地址的映射
for (const address of addresses) {
if (!address || typeof address !== 'string') {
console.warn(`跳过无效地址: ${address}`);
continue;
}
try {
const publicKey = new PublicKey(address);
publicKeys.push(publicKey);
addressMap[publicKey.toString()] = address; // 保存映射关系
} catch (error) {
console.warn(`跳过无效的Solana地址 ${address}: ${error.message}`);
}
}
if (publicKeys.length === 0) {
throw new Error('没有有效的地址可供查询');
}
// 分批查询,每批最多100个
const MAX_BATCH_SIZE = 100;
const balances = {};
let totalQueried = 0;
for (let i = 0; i < publicKeys.length; i += MAX_BATCH_SIZE) {
const batchKeys = publicKeys.slice(i, i + MAX_BATCH_SIZE);
// 使用重试机制批量查询账户信息
let retries = 3;
let lastError = null;
let accountInfos = null;
while (retries > 0 && accountInfos === null) {
try {
// 使用getMultipleAccountsInfo批量查询
accountInfos = await this.connection.getMultipleAccountsInfo(batchKeys);
} catch (error) {
lastError = error;
// console.error(`批量查询余额失败,剩余重试次数: ${retries - 1}, 错误: ${error.message}`);
console.error(`批量查询失败(第 ${i / MAX_BATCH_SIZE + 1} 批),剩余重试次数: ${retries - 1}, 错误: ${error.message}`);
retries--;
if (retries > 0) {
// 等待一段时间再重试
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
if (accountInfos === null) {
throw new Error(`多次尝试批量查询余额失败: ${lastError ? lastError.message : '未知错误'}`);
}
// 处理结果
publicKeys.forEach((publicKey, index) => {
const address = addressMap[publicKey.toString()];
const accountInfo = accountInfos[index];
// 如果账户存在,lamports属性包含余额
// 如果账户不存在,余额为0
const lamports = accountInfo ? accountInfo.lamports : 0;
const solBalance = lamports / LAMPORTS_PER_SOL;
balances[address] = solBalance;
totalQueried++;
});
}
console.log(`成功批量查询了 ${totalQueried} 个地址的SOL余额`);
return balances;
} catch (error) {
console.error(`批量查询SOL余额失败: ${error.message}`);
throw new Error(`批量查询SOL余额失败: ${error.message}`);
}
}
/**
* 查询钱包文件夹中所有钱包的SOL余额
* @param {string} folderNum - 钱包文件夹编号
* @returns {Promise<Array>} - 余额信息数组
*/
async checkFolderBalance(folderNum) {
try {
// 创建主钱包目录 - 修改为与wallet-routes.js一致的路径
const mainWalletDir = path.join(__dirname, 'web', 'wallets');
if (!fs.existsSync(mainWalletDir)) {
fs.mkdirSync(mainWalletDir, { recursive: true });
}
// 构建文件夹路径
const folderName = `wallets${folderNum}`;
const folderPath = path.join(mainWalletDir, folderName);
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} 中没有钱包文件`);
}
console.log(`找到 ${walletFiles.length} 个钱包文件,准备批量查询余额...`);
// 收集所有钱包地址
const addresses = [];
const walletDataList = [];
for (let i = 0; i < walletFiles.length; i++) {
try {
const walletFile = walletFiles[i];
const walletPath = path.join(folderPath, walletFile);
const walletData = fs.readJsonSync(walletPath);
addresses.push(walletData.address);
walletDataList.push({
index: i + 1,
address: walletData.address
});
} catch (error) {
console.error(`读取钱包文件 #${i + 1} 失败: ${error.message}`);
}
}
if (addresses.length === 0) {
throw new Error('没有有效的钱包地址可供查询');
}
// 批量查询余额
const balanceMap = await this.checkMultipleBalances(addresses);
// 处理结果
const balances = [];
let totalBalance = 0;
let nonZeroWallets = 0;
for (const walletData of walletDataList) {
const solBalance = balanceMap[walletData.address] || 0;
balances.push({
index: walletData.index,
address: walletData.address,
balance: solBalance
});
totalBalance += solBalance;
if (solBalance > 0) {
nonZeroWallets++;
}
}
if (balances.length === 0) {
throw new Error('没有成功查询到任何钱包余额');
}
// 返回余额信息和统计数据
return {
balances,
stats: {
totalWallets: balances.length,
nonZeroWallets,
totalBalance,
averageBalance: totalBalance / balances.length,
nonZeroAverage: nonZeroWallets > 0 ? totalBalance / nonZeroWallets : 0
}
};
} catch (error) {
throw new Error(`查询SOL余额失败: ${error.message}`);
}
}
/**
* 查询指定钱包的SOL余额
* @param {string} folderNum - 钱包文件夹编号
* @param {number} walletIndex - 钱包索引
* @returns {Promise<Object>} - 余额信息
*/
async checkWalletBalance(folderNum, walletIndex) {
try {
// 创建主钱包目录 - 修改为与wallet-routes.js一致的路径
const mainWalletDir = path.join(__dirname, 'web', 'wallets');
if (!fs.existsSync(mainWalletDir)) {
fs.mkdirSync(mainWalletDir, { recursive: true });
}
// 构建文件夹路径
const folderName = `wallets${folderNum}`;
const folderPath = path.join(mainWalletDir, folderName);
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 < 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 publicKey = new PublicKey(walletData.address);
const balance = await this.connection.getBalance(publicKey);
return {
index: walletIndex,
address: walletData.address,
balance: balance / LAMPORTS_PER_SOL
};
} catch (error) {
throw new Error(`查询SOL余额失败: ${error.message}`);
}
}
}
module.exports = SolanaBalanceChecker;
const bs58 = require('bs58');
const fs = require('fs-extra');
const path = require('path');
const inquirer = require('inquirer');
const ConfigManager = require('./config-manager').default;
const SolanaBalanceChecker = require('./solana-balance-checker');
const SolanaAddressGenerator = require('./solana-address-generator');
const moment = require('moment');
const CryptoJS = require('crypto-js');
const localWallets = require('./solana-wallets');
class SolanaTransfer {
constructor() {
this.config = new ConfigManager();
this.networkUrl = this.config.getNetworkUrl();
this.connection = new Connection(this.networkUrl);
this.balanceChecker = new SolanaBalanceChecker();
this.balanceChecker.connection = this.connection;
this.addressGenerator = new SolanaAddressGenerator();
this.minRentExemption = null; // 初始化最小租金豁免金额
this.feeReserve = 0.001; // 预留的交易费用(SOL)
this.localWallets = new localWallets();
this.transferFee = 0.000005; // 转账手续费
}
async run() {
try {
while (true) {
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'SOL 转账系统',
choices: [
{ name: '单笔转账', value: '1' },
{ name: '文件夹间相同编号钱包互转', value: '2' },
{ name: '一对多转账(相同金额)', value: '3' },
{ name: '一对多转账(随机金额)', value: '4' },
{ name: '退出', value: '5' }
],
loop: false // 禁用循环滚动
}
]);
if (action === '1') {
const { fromFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'fromFolderNum',
message: '请输入发送方文件夹编号:'
}
]);
const { fromWalletIndex } = await inquirer.prompt([
{
type: 'input',
name: 'fromWalletIndex',
message: '请输入发送方钱包编号:'
}
]);
const { toFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'toFolderNum',
message: '请输入接收方文件夹编号:'
}
]);
const { toWalletIndex } = await inquirer.prompt([
{
type: 'input',
name: 'toWalletIndex',
message: '请输入接收方钱包编号:'
}
]);
const { amount } = await inquirer.prompt([
{
type: 'input',
name: 'amount',
message: '请输入转账金额 (SOL):'
}
]);
const { password } = await inquirer.prompt([
{
type: 'password',
name: 'password',
message: '请输入钱包密码:',
mask: '*'
}
]);
try {
const txHash = await this.transferBetweenWallets(
fromFolderNum.trim(),
parseInt(fromWalletIndex),
toFolderNum.trim(),
parseInt(toWalletIndex),
parseFloat(amount),
password
);
} catch (error) {
console.error(`\n${error.message}`);
}
} else if (action === '2') {
const { fromFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'fromFolderNum',
message: '请输入发送方文件夹编号:'
}
]);
const { toFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'toFolderNum',
message: '请输入接收方文件夹编号:'
}
]);
const { password } = await inquirer.prompt([
{
type: 'password',
name: 'password',
message: '请输入钱包密码:',
mask: '*'
}
]);
try {
const results = await this.folderToFolderTransfer(fromFolderNum.trim(), toFolderNum.trim(), password);
this.displayTransferResults(results);
} catch (error) {
console.error(`\n错误: ${error.message}`);
}
} else if (action === '3') {
const { fromFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'fromFolderNum',
message: '请输入发送方文件夹编号:'
}
]);
const { fromWalletIndex } = await inquirer.prompt([
{
type: 'input',
name: 'fromWalletIndex',
message: '请输入发送方钱包编号:'
}
]);
const { toFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'toFolderNum',
message: '请输入接收方文件夹编号:'
}
]);
const { walletRange } = await inquirer.prompt([
{
type: 'input',
name: 'walletRange',
message: '请输入接收方钱包编号范围 (例如: 1-10):'
}
]);
const rangeParts = walletRange.split('-');
if (rangeParts.length !== 2) {
console.log('\n无效的范围格式!请使用 "起始-结束" 格式,例如: 1-10');
continue;
}
const toStart = parseInt(rangeParts[0].trim());
const toEnd = parseInt(rangeParts[1].trim());
if (isNaN(toStart) || isNaN(toEnd) || toStart < 1 || toEnd < toStart) {
console.log('\n无效的接收方钱包编号范围!');
continue;
}
const receiverCount = toEnd - toStart + 1;
console.log(`\n接收方钱包范围: ${toStart} 到 ${toEnd},共 ${receiverCount} 个钱包`);
let amounts = [];
if (action === '3') {
const { amount } = await inquirer.prompt([
{
type: 'input',
name: 'amount',
message: '请输入每个接收方的转账金额(SOL):'
}
]);
const amountFloat = parseFloat(amount);
if (isNaN(amountFloat) || amountFloat <= 0) {
console.log('\n请输入有效的正数金额!');
continue;
}
amounts = Array(receiverCount).fill(amountFloat);
console.log(`\n每个接收方转账金额: ${amountFloat} SOL`);
} else {
const { amountList } = await inquirer.prompt([
{
type: 'input',
name: 'amountList',
message: '请输入每个接收方的转账金额,用逗号分隔 (例如: 1.5,2.0,1.8):'
}
]);
amounts = amountList.split(',').map(a => parseFloat(a.trim()));
if (amounts.length !== receiverCount) {
console.log('\n转账金额数量与接收方数量不匹配!');
continue;
}
if (amounts.some(a => isNaN(a) || a <= 0)) {
console.log('\n请确保所有金额都是有效的正数!');
continue;
}
}
const totalAmount = amounts.reduce((sum, a) => sum + a, 0);
console.log(`\n总转账金额: ${totalAmount} SOL`);
const { password } = await inquirer.prompt([
{
type: 'password',
name: 'password',
message: '请输入钱包密码:',
mask: '*'
}
]);
try {
const results = await this.oneToManyTransferWithAmounts(
fromFolderNum.trim(),
parseInt(fromWalletIndex),
toFolderNum.trim(),
toStart,
toEnd,
amounts,
password
);
this.displayTransferResults(results);
} catch (error) {
console.error(`\n错误: ${error.message}`);
}
} else if (action === '4') {
// 一对多转账(随机金额)
const { fromFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'fromFolderNum',
message: '请输入发送方文件夹编号:'
}
]);
const { fromWalletIndex } = await inquirer.prompt([
{
type: 'input',
name: 'fromWalletIndex',
message: '请输入发送方钱包编号:'
}
]);
// 获取发送方钱包地址,用于检查余额
try {
const { address: fromAddress } = await this.getWalletFromNumber(fromFolderNum.trim(), parseInt(fromWalletIndex));
console.log(`\n发送方钱包地址: ${fromAddress}`);
// 检查发送方余额
const balance = await this.getBalance(fromAddress);
console.log(`发送方钱包余额: ${balance} SOL`);
if (balance <= 0) {
console.log('\n错误: 发送方钱包余额为零,无法进行转账!');
continue;
}
} catch (error) {
console.error(`\n错误: 获取发送方钱包信息失败: ${error.message}`);
continue;
}
const { toFolderNum } = await inquirer.prompt([
{
type: 'input',
name: 'toFolderNum',
message: '请输入接收方文件夹编号:'
}
]);
const { walletRange } = await inquirer.prompt([
{
type: 'input',
name: 'walletRange',
message: '请输入接收方钱包编号范围 (例如: 1-10):'
}
]);
const rangeParts = walletRange.split('-');
if (rangeParts.length !== 2) {
console.log('\n无效的范围格式!请使用 "起始-结束" 格式,例如: 1-10');
continue;
}
const toStart = parseInt(rangeParts[0].trim());
const toEnd = parseInt(rangeParts[1].trim());
if (isNaN(toStart) || isNaN(toEnd) || toStart < 1 || toEnd < toStart) {
console.log('\n无效的接收方钱包编号范围!');
continue;
}
const receiverCount = toEnd - toStart + 1;
console.log(`\n接收方钱包范围: ${toStart} 到 ${toEnd},共 ${receiverCount} 个钱包`);
const { minAmount } = await inquirer.prompt([
{
type: 'input',
name: 'minAmount',
message: '请输入最小转账金额 (SOL):'
}
]);
const { maxAmount } = await inquirer.prompt([
{
type: 'input',
name: 'maxAmount',
message: '请输入最大转账金额 (SOL):'
}
]);
const minAmountFloat = parseFloat(minAmount);
const maxAmountFloat = parseFloat(maxAmount);
if (isNaN(minAmountFloat) || isNaN(maxAmountFloat) || minAmountFloat <= 0 || maxAmountFloat <= 0) {
console.log('\n请输入有效的正数金额!');
continue;
}
if (minAmountFloat >= maxAmountFloat) {
console.log('\n最小金额必须小于最大金额!');
continue;
}
// 生成随机金额数组进行预估
let totalEstimatedAmount = 0;
const estimatedAmounts = [];
for (let i = 0; i < receiverCount; i++) {
const randomAmount = parseFloat((Math.random() * (maxAmountFloat - minAmountFloat) + minAmountFloat).toFixed(6));
estimatedAmounts.push(randomAmount);
totalEstimatedAmount += randomAmount;
}
// 计算估计的交易费用
const estimatedFees = this.feeReserve * receiverCount;
const totalRequired = totalEstimatedAmount + estimatedFees;
console.log(`\n随机金额范围: ${minAmountFloat} SOL 到 ${maxAmountFloat} SOL`);
console.log(`预计平均每笔转账: ${((minAmountFloat + maxAmountFloat) / 2).toFixed(6)} SOL`);
console.log(`预计总转账金额: ${totalEstimatedAmount.toFixed(6)} SOL`);
console.log(`估计交易费用: ${estimatedFees.toFixed(6)} SOL`);
console.log(`总计需要: ${totalRequired.toFixed(6)} SOL`);
// 再次检查发送方余额是否足够
try {
const { address: fromAddress } = await this.getWalletFromNumber(fromFolderNum.trim(), parseInt(fromWalletIndex));
const balance = await this.getBalance(fromAddress);
if (balance < totalRequired) {
console.log(`\n错误: 发送方钱包余额不足!`);
console.log(`需要: ${totalRequired.toFixed(6)} SOL`);
console.log(`当前余额: ${balance} SOL`);
console.log(`差额: ${(totalRequired - balance).toFixed(6)} SOL`);
continue;
}
console.log(`\n发送方余额充足,可以继续转账操作。`);
console.log(`当前余额: ${balance} SOL`);
console.log(`转账后预计剩余: ${(balance - totalRequired).toFixed(6)} SOL`);
} catch (error) {
console.error(`\n错误: 检查发送方钱包余额失败: ${error.message}`);
continue;
}
// 确认是否继续
const { confirmTransfer } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmTransfer',
message: '确认要执行以上转账操作吗?',
default: false
}
]);
if (!confirmTransfer) {
console.log('\n已取消转账操作。');
continue;
}
const { password } = await inquirer.prompt([
{
type: 'password',
name: 'password',
message: '请输入钱包密码:',
mask: '*'
}
]);
try {
const results = await this.oneToManyRandomTransfer(
fromFolderNum.trim(),
parseInt(fromWalletIndex),
toFolderNum.trim(),
toStart,
toEnd,
minAmountFloat,
maxAmountFloat,
password
);
this.displayTransferResults(results);
} catch (error) {
console.error(`\n错误: ${error.message}`);
}
} else if (action === '5') {
console.log('\n再见!');
break;
}
}
} catch (error) {
console.error(`发生错误: ${error.message}`);
}
}
displayTransferResults(results) {
console.log('\n=== 转账结果摘要 ===');
const successCount = results.filter(r => r.success).length;
const failCount = results.length - successCount;
console.log(`总计: ${results.length} 笔交易`);
console.log(`成功: ${successCount} 笔`);
console.log(`失败: ${failCount} 笔`);
if (failCount > 0) {
console.log('\n失败的交易:');
results.filter(r => !r.success).forEach(r => {
console.log(`- 钱包 #${r.pair}: 从 ${r.from} 到 ${r.to}, ${r.amount} SOL`);
console.log(` 错误: ${r.error}`);
});
}
}
async loadWallet(walletPath, password) {
try {
// 检查钱包文件是否存在
if (!fs.existsSync(walletPath)) {
throw new Error(`钱包文件不存在: ${walletPath}`);
}
// 读取钱包文件
let walletData;
try {
walletData = fs.readJsonSync(walletPath);
} catch (readError) {
throw new Error(`读取钱包文件失败: ${readError.message}`);
}
// 检查钱包文件格式
if (!walletData.encryptedPrivateKey) {
throw new Error(`钱包文件格式错误: 缺少加密私钥`);
}
// 使用与SolanaAddressGenerator完全相同的解密方法
let privateKey;
try {
// 使用相同的方法从密码生成密钥
const key = CryptoJS.PBKDF2(password, 'salt', {
keySize: 256 / 32,
iterations: 100000
});
// 使用密钥解密
const decrypted = CryptoJS.AES.decrypt(walletData.encryptedPrivateKey, key.toString());
privateKey = decrypted.toString(CryptoJS.enc.Utf8);
if (!privateKey) {
throw new Error('密码错误或私钥格式损坏');
}
} catch (decryptError) {
throw new Error(`解密私钥失败: ${decryptError.message}`);
}
// 创建 Keypair
try {
// 解码 base58 格式的私钥
const secretKey = bs58.decode(privateKey);
const keypair = Keypair.fromSecretKey(secretKey);
// 验证公钥是否与钱包文件中的地址匹配
if (walletData.address && keypair.publicKey.toString() !== walletData.address) {
throw new Error('解密的私钥与钱包地址不匹配');
}
return keypair;
} catch (keypairError) {
throw new Error(`创建密钥对失败: ${keypairError.message}`);
}
} catch (error) {
console.error(`加载钱包失败: ${error.message}`);
throw new Error(`加载钱包失败: ${error.message}`);
}
}
getBalance(address) {
try {
console.log(`正在查询地址 ${address} 的余额...`);
return this.balanceChecker.checkBalance(address);
} catch (error) {
console.error(`查询余额失败: ${error.message}`);
throw new Error(`查询余额失败: ${error.message}`);
}
}
async getMinimumRentExemption() {
if (this.minRentExemption === null) {
try {
// 获取最小账户大小的租金豁免金额
const rentExemptionInLamports = await this.connection.getMinimumBalanceForRentExemption(0);
// 转换为 SOL 并缓存
this.minRentExemption = rentExemptionInLamports / 1e9;
} catch (error) {
console.error(`获取最小租金豁免金额失败: ${error.message}`);
// 使用默认值
this.minRentExemption = 0.00089088;
}
}
return this.minRentExemption;
}
async transferSol(fromPrivateKey, toAddress, amount) {
try {
// 创建发送方密钥对
let fromKeypair;
try {
// 如果传入的是字符串格式的私钥,转换为Keypair对象
if (typeof fromPrivateKey === 'string') {
const secretKey = bs58.decode(fromPrivateKey);
fromKeypair = Keypair.fromSecretKey(secretKey);
} else if (fromPrivateKey instanceof Keypair) {
// 如果已经是Keypair对象,直接使用
fromKeypair = fromPrivateKey;
} else {
throw new Error('无效的私钥格式');
}
} catch (keypairError) {
throw new Error(`创建发送方密钥对失败: ${keypairError.message}`);
}
// 创建接收方公钥
let toPublicKey;
try {
toPublicKey = new PublicKey(toAddress);
} catch (pubkeyError) {
throw new Error(`无效的接收方地址: ${pubkeyError.message}`);
}
// 检查发送方余额是否足够
const fromAddress = fromKeypair.publicKey.toString();
const balance = await this.getBalance(fromAddress);
if (balance < amount + this.feeReserve) {
throw new Error(`余额不足以完成交易,需要 ${amount + this.feeReserve} SOL (包含估计费用),但只有 ${balance} SOL`);
}
// 创建转账指令
const lamports = Math.round(amount * LAMPORTS_PER_SOL);
const transferInstruction = SystemProgram.transfer({
fromPubkey: fromKeypair.publicKey,
toPubkey: toPublicKey,
lamports: lamports
});
// 创建交易
const transaction = new Transaction().add(transferInstruction);
// 获取最新的区块哈希
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = fromKeypair.publicKey;
// 签名交易
try {
transaction.sign(fromKeypair);
} catch (signError) {
throw new Error(`交易签名失败: ${signError.message}`);
}
// 发送交易
try {
const signature = await this.connection.sendRawTransaction(
transaction.serialize()
);
// 不等待交易确认,直接返回交易哈希
console.log(`交易已发送,哈希: ${signature}`);
console.log(`注意: 不等待链上确认,交易可能仍在处理中`);
return {
txId: signature,
signature: signature
};
} catch (sendError) {
// 提取更详细的错误信息
let errorMessage = sendError.message;
if (sendError.logs) {
errorMessage += `\n日志: ${JSON.stringify(sendError.logs, null, 2)}`;
}
throw new Error(`发送交易失败: ${errorMessage}`);
}
} catch (error) {
console.error(`转账SOL失败: ${error.message}`);
throw new Error(`转账SOL失败: ${error.message}`);
}
}
// 计算安全转账金额(考虑交易费用)
calculateSafeTransferAmount(balance, isTransferAll = false) {
if (isTransferAll && balance > this.feeReserve) {
return balance - this.feeReserve;
}
return balance;
}
// 获取钱包信息
async getWalletFromNumber(folderNum, walletIndex) {
try {
// 创建主钱包目录
const mainWalletDir = path.join(process.cwd(), 'wallets');
if (!fs.existsSync(mainWalletDir)) {
fs.mkdirSync(mainWalletDir, { recursive: true });
}
// 构建文件夹路径
const folderName = `wallets${folderNum}`;
const folderPath = path.join(mainWalletDir, folderName);
if (!fs.existsSync(folderPath)) {
throw new Error(`找不到文件夹 ${folderName}`);
}
// 获取文件夹中的所有钱包文件
const walletFiles = fs.readdirSync(folderPath)
.filter(file => file.endsWith('.json'))
.sort();
if (walletFiles.length === 0) {
throw new Error(`${folderName} 中没有钱包文件`);
}
if (walletIndex < 1 || walletIndex > walletFiles.length) {
throw new Error(`无效的钱包编号,应为 1 到 ${walletFiles.length}`);
}
// 获取指定的钱包文件
const walletFile = walletFiles[walletIndex - 1];
const walletPath = path.join(folderPath, walletFile);
// 读取钱包地址
const data = fs.readJsonSync(walletPath);
const address = data.address;
return { walletPath, address };
} catch (error) {
throw new Error(`获取钱包信息失败: ${error.message}`);
}
}
async getWalletsInFolder(folderNum) {
try {
// 创建主钱包目录
const mainWalletDir = path.join(process.cwd(), 'wallets');
if (!fs.existsSync(mainWalletDir)) {
fs.mkdirSync(mainWalletDir, { recursive: true });
}
// 构建文件夹路径
const folderName = `wallets${folderNum}`;
const folderPath = path.join(mainWalletDir, folderName);
if (!fs.existsSync(folderPath)) {
throw new Error(`找不到文件夹 ${folderName}`);
}
// 获取文件夹中的所有钱包文件
const walletFiles = fs.readdirSync(folderPath)
.filter(file => file.endsWith('.json'))
.sort();
if (walletFiles.length === 0) {
throw new Error(`${folderName} 中没有钱包文件`);
}
// 读取每个钱包文件的信息
const wallets = [];
for (let i = 0; i < walletFiles.length; i++) {
try {
const walletFile = walletFiles[i];
const walletPath = path.join(folderPath, walletFile);
// 读取钱包地址
const data = fs.readJsonSync(walletPath);
const address = data.address;
wallets.push({
index: i + 1,
file: walletFile,
path: walletPath,
address
});
} catch (error) {
console.error(`读取钱包 ${walletFiles[i]} 失败: ${error.message}`);
}
}
return wallets;
} catch (error) {
throw new Error(`获取文件夹钱包列表失败: ${error.message}`);
}
}
// 文件夹间转账(多转多)
async folderToFolderTransfer(fromFolderNum, toFolderNum, password) {
try {
// 获取两个文件夹中的钱包列表
const fromWallets = await this.getWalletsInFolder(fromFolderNum);
const toWallets = await this.getWalletsInFolder(toFolderNum);
// 检查两个文件夹中的钱包数量是否相同
if (fromWallets.length !== toWallets.length) {
throw new Error(`两个文件夹中的钱包数量不同: 发送方有 ${fromWallets.length} 个钱包,接收方有 ${toWallets.length} 个钱包`);
}
// 获取最小租金豁免金额
const minRentExemption = await this.getMinimumRentExemption();
// 执行转账
const results = [];
for (let i = 0; i < fromWallets.length; i++) {
const fromWallet = fromWallets[i];
const toWallet = toWallets[i];
try {
// 检查发送方余额
const balance = await this.getBalance(fromWallet.address);
if (balance <= 0) {
// 忽略余额为0的钱包,不添加到结果中
console.log(`\n跳过钱包 #${fromWallet.index}: 余额为零`);
continue;
}
// 检查接收方账户是否存在
const receiverExists = await this.isAccountExists(toWallet.address);
if (!receiverExists && balance < minRentExemption) {
results.push({
pair: fromWallet.index,
from: fromWallet.address,
to: toWallet.address,
amount: balance,
error: `接收方账户未激活,转账金额不足以支付租金 (最小 ${minRentExemption} SOL)`,
success: false
});
continue;
}
// 加载发送方钱包
const fromKeypair = await this.loadWallet(fromWallet.path, password);
const fromPrivateKey = bs58.encode(fromKeypair.secretKey);
// 预留交易费用
const transferAmount = balance - this.transferFee;
if (transferAmount <= 0) {
console.log(`\n跳过钱包 #${fromWallet.index}: 余额不足以支付交易费用`);
continue;
}
// 执行转账
console.log(`\n转账对 #${fromWallet.index}:`);
console.log(`从: ${fromWallet.address}`);
console.log(`到: ${toWallet.address}`);
console.log(`金额: ${transferAmount} SOL (预留 ${this.feeReserve} SOL 作为交易费用)`);
const txHash = await this.transferSol(fromPrivateKey, toWallet.address, transferAmount);
results.push({
pair: fromWallet.index,
from: fromWallet.address,
to: toWallet.address,
amount: transferAmount,
txHash: txHash.txId,
success: true
});
} catch (error) {
results.push({
pair: fromWallet.index,
from: fromWallet.address,
to: toWallet.address,
amount: 0,
error: error.message,
success: false
});
}
}
return results;
} catch (error) {
throw new Error(`文件夹间转账失败: ${error.message}`);
}
}
// 一对多固定金额转账
async oneToManyTransferWithAmounts(fromFolderNum, fromWalletIndex, toFolderNum, toStart, toEnd, amounts, password) {
try {
// 获取发送方钱包信息
const { walletPath: fromWalletPath, address: fromAddress } = await this.getWalletFromNumber(fromFolderNum, fromWalletIndex);
// 先验证密码是否正确
console.log(`\n正在验证钱包密码...`);
const verifyResult = await this.verifyWalletPassword(fromWalletPath, password);
if (!verifyResult.success) {
throw new Error(`密码验证失败: ${verifyResult.message}`);
}
console.log(`密码验证成功,继续转账操作。`);
// 加载发送方钱包
console.log(`调试信息: 加载发送方钱包...`);
const fromKeypair = await this.loadWallet(fromWalletPath, password);
console.log(`调试信息: 成功加载发送方钱包,公钥: ${fromKeypair.publicKey.toString()}`);
// 检查发送方余额
const balance = await this.getBalance(fromAddress);
// 计算总转账金额
const totalAmount = amounts.reduce((sum, a) => sum + a, 0);
// 检查余额是否足够
if (balance < totalAmount) {
throw new Error(`发送方余额不足,需要 ${totalAmount} SOL,但只有 ${balance} SOL`);
}
// 获取最小租金豁免金额
const minRentExemption = await this.getMinimumRentExemption();
// 检查每个接收方账户
const results = [];
for (let i = toStart; i <= toEnd; i++) {
try {
// 获取接收方钱包信息
const { walletPath: toWalletPath, address: toAddress } = await this.getWalletFromNumber(toFolderNum, i);
console.log(`\n转账对 #${i}:`);
console.log(`从: ${fromAddress}`);
console.log(`到: ${toAddress} (钱包 #${i})`);
console.log(`金额: ${amounts[i - toStart]} SOL`);
// 检查接收方账户是否存在
const receiverExists = await this.isAccountExists(toAddress);
// 如果接收方账户不存在,检查转账金额是否足够支付租金
if (!receiverExists && amounts[i - toStart] < minRentExemption) {
console.log(`警告: 接收方钱包 #${i} (${toAddress}) 未激活,转账金额 ${amounts[i - toStart]} SOL 不足以支付租金 (最小 ${minRentExemption} SOL)`);
results.push({
pair: i,
from: fromAddress,
to: toAddress,
amount: amounts[i - toStart],
error: `接收方账户未激活,转账金额不足以支付租金 (最小 ${minRentExemption} SOL)`,
success: false
});
continue;
}
// 执行转账 - 直接使用Keypair对象
const txHash = await this.transferSol(fromKeypair, toAddress, amounts[i - toStart]);
console.log(`交易成功: ${txHash}`);
results.push({
pair: i,
from: fromAddress,
to: toAddress,
amount: amounts[i - toStart],
txHash,
success: true
});
} catch (error) {
try {
const { address: toAddress } = await this.getWalletFromNumber(toFolderNum, i);
results.push({
pair: i,
from: fromAddress,
to: toAddress,
amount: amounts[i - toStart],
error: error.message,
success: false
});
} catch (e) {
results.push({
pair: i,
from: fromAddress,
to: '获取地址失败',
amount: amounts[i - toStart],
error: error.message,
success: false
});
}
console.error(`转账失败: ${error.message}`);
}
}
return results;
} catch (error) {
throw new Error(`一对多转账失败: ${error.message}`);
}
}
// 钱包间转账
async transferBetweenWallets(fromFolderNum, fromWalletIndex, toFolderNum, toWalletIndex, amount, password) {
try {
// 加载发送方钱包
let fromWallet;
try {
fromWallet = await this.localWallets.getWalletFromNumber(fromFolderNum, fromWalletIndex, password);
} catch (error) {
if (error.message.includes('密码错误') || error.message.includes('password')) {
throw new Error('密码错误,请重新输入');
} else if (error.message.includes('不存在')) {
throw new Error(`发送方钱包文件夹 ${fromFolderNum} 或钱包编号 ${fromWalletIndex} 不存在`);
} else {
throw new Error(`加载发送方钱包失败: ${error.message}`);
}
}
// 加载接收方钱包
let toWalletAddress;
try {
const { address } = await this.localWallets.getWalletAddressFromNumber(toFolderNum, toWalletIndex);
toWalletAddress = address;
console.log(`接收方钱包地址: ${toWalletAddress}`);
} catch (error) {
if (error.message.includes('不存在')) {
throw new Error(`接收方钱包文件夹 ${toFolderNum} 或钱包编号 ${toWalletIndex} 不存在`);
} else {
throw new Error(`加载接收方钱包失败: ${error.message}`);
}
}
// 执行转账
const result = await this.transferSol(
fromWallet,
toWalletAddress,
amount
);
return {
txId: result.txId,
signature: result.signature,
fromAddress: fromWallet.publicKey.toString(),
toAddress: toWalletAddress
};
} catch (error) {
console.error('钱包间转账失败:', error);
throw error;
}
}
// 创建交易
async createTransaction(fromPrivateKey, instructions) {
try {
const recentBlockhash = await this.connection.getLatestBlockhash();
const transaction = new Transaction({
feePayer: fromPrivateKey.publicKey,
...recentBlockhash,
});
// 添加所有指令
transaction.add(...instructions);
// 签名交易
transaction.sign(fromPrivateKey);
return transaction;
} catch (error) {
throw new Error(`创建交易失败: ${error.message}`);
}
}
// 发送交易
async sendTransaction(transaction) {
try {
// 发送交易
const signature = await this.connection.sendRawTransaction(
transaction.serialize()
);
// 不等待交易确认,直接返回交易哈希
console.log(`交易已发送,哈希: ${signature}`);
console.log(`注意: 不等待链上确认,交易可能仍在处理中`);
return signature;
} catch (error) {
throw new Error(`发送交易失败: ${error.message}`);
}
}
// 检查账户是否存在
async isAccountExists(address) {
try {
const pubkey = new PublicKey(address);
const accountInfo = await this.connection.getAccountInfo(pubkey);
return accountInfo !== null;
} catch (error) {
console.error(`检查账户存在失败: ${error.message}`);
return false;
}
}
// 验证钱包密码
async verifyWalletPassword(walletPath, password) {
try {
// 检查钱包文件是否存在
if (!fs.existsSync(walletPath)) {
return { success: false, message: `钱包文件不存在: ${walletPath}` };
}
// 读取钱包文件
let walletData;
try {
walletData = fs.readJsonSync(walletPath);
} catch (readError) {
return { success: false, message: `读取钱包文件失败: ${readError.message}` };
}
// 检查钱包文件格式
if (!walletData.encryptedPrivateKey) {
return { success: false, message: `钱包文件格式错误: 缺少加密私钥` };
}
// 使用与SolanaAddressGenerator完全相同的解密方法
let privateKey;
try {
// 使用相同的方法从密码生成密钥
const key = CryptoJS.PBKDF2(password, 'salt', {
keySize: 256 / 32,
iterations: 100000
});
// 使用密钥解密
const decrypted = CryptoJS.AES.decrypt(walletData.encryptedPrivateKey, key.toString());
privateKey = decrypted.toString(CryptoJS.enc.Utf8);
if (!privateKey) {
return { success: false, message: '密码错误' };
}
} catch (decryptError) {
return { success: false, message: `解密私钥失败: ${decryptError.message}` };
}
// 验证解密后的私钥
try {
// 解码 base58 格式的私钥
const secretKey = bs58.decode(privateKey);
const keypair = Keypair.fromSecretKey(secretKey);
// 验证公钥是否与钱包文件中的地址匹配
if (walletData.address && keypair.publicKey.toString() !== walletData.address) {
return { success: false, message: '解密的私钥与钱包地址不匹配' };
}
return { success: true, message: '密码验证成功' };
} catch (keypairError) {
return { success: false, message: `私钥无效: ${keypairError.message}` };
}
} catch (error) {
return { success: false, message: `验证密码失败: ${error.message}` };
}
}
// 一对多随机金额转账
async oneToManyRandomTransfer(fromFolderNum, fromWalletIndex, toFolderNum, toStart, toEnd, minAmount, maxAmount, password) {
try {
// 获取发送方钱包信息
const { walletPath: fromWalletPath, address: fromAddress } = await this.getWalletFromNumber(fromFolderNum, fromWalletIndex);
// 先验证密码是否正确
console.log(`\n正在验证钱包密码...`);
const verifyResult = await this.verifyWalletPassword(fromWalletPath, password);
if (!verifyResult.success) {
throw new Error(`密码验证失败: ${verifyResult.message}`);
}
console.log(`密码验证成功,继续转账操作。`);
// 检查发送方余额
let balance;
try {
console.log(`正在查询发送方钱包 ${fromAddress} 的余额...`);
balance = await this.getBalance(fromAddress);
console.log(`发送方钱包余额: ${balance} SOL`);
} catch (error) {
console.error(`查询发送方余额失败: ${error.message}`);
throw new Error(`查询发送方余额失败: ${error.message}`);
}
// 计算接收方数量
const receiverCount = toEnd - toStart + 1;
if (receiverCount <= 0) {
throw new Error('接收方钱包范围无效');
}
// 生成随机金额
const amounts = [];
let totalAmount = 0;
for (let i = 0; i < receiverCount; i++) {
// 生成minAmount到maxAmount之间的随机数
const randomAmount = minAmount + Math.random() * (maxAmount - minAmount);
// 保留6位小数
const roundedAmount = Math.round(randomAmount * 1000000) / 1000000;
amounts.push(roundedAmount);
totalAmount += roundedAmount;
}
console.log(`接收方钱包数量: ${receiverCount}`);
console.log(`随机金额范围: ${minAmount} - ${maxAmount} SOL`);
console.log(`总转账金额: ${totalAmount.toFixed(6)} SOL`);
// 检查余额是否足够
if (balance < totalAmount) {
throw new Error(`发送方余额不足,需要 ${totalAmount.toFixed(6)} SOL,但只有 ${balance} SOL`);
}
// 加载发送方钱包
console.log(`调试信息: 加载发送方钱包...`);
const fromKeypair = await this.loadWallet(fromWalletPath, password);
console.log(`调试信息: 成功加载发送方钱包,公钥: ${fromKeypair.publicKey.toString()}`);
// 获取最小租金豁免金额
const minRentExemption = await this.getMinimumRentExemption();
// 获取所有接收方钱包信息
console.log(`正在获取所有接收方钱包信息...`);
const receiverWallets = [];
const receiverAddresses = [];
for (let i = toStart; i <= toEnd; i++) {
try {
const wallet = await this.getWalletFromNumber(toFolderNum, i);
receiverWallets.push({
index: i,
...wallet
});
receiverAddresses.push(wallet.address);
} catch (error) {
console.error(`获取接收方钱包 #${i} 信息失败: ${error.message}`);
// 添加失败记录
receiverWallets.push({
index: i,
error: error.message
});
}
}
// 批量检查接收方账户是否存在
console.log(`正在批量检查 ${receiverAddresses.length} 个接收方账户...`);
let accountExistsMap = {};
try {
// 使用批量查询获取所有接收方账户的余额
const balances = await this.balanceChecker.checkMultipleBalances(receiverAddresses);
// 如果余额大于0,账户肯定存在
// 如果余额为0,我们需要进一步检查账户是否存在
for (const address of receiverAddresses) {
accountExistsMap[address] = balances[address] > 0;
}
// 对于余额为0的账户,我们需要进一步检查
const zeroBalanceAddresses = receiverAddresses.filter(addr => balances[addr] === 0);
if (zeroBalanceAddresses.length > 0) {
console.log(`有 ${zeroBalanceAddresses.length} 个账户余额为0,进一步检查账户是否存在...`);
// 将地址转换为PublicKey对象
const publicKeys = zeroBalanceAddresses.map(addr => new PublicKey(addr));
// 批量获取账户信息
const accountInfos = await this.connection.getMultipleAccountsInfo(publicKeys);
// 更新账户存在状态
zeroBalanceAddresses.forEach((addr, index) => {
accountExistsMap[addr] = accountInfos[index] !== null;
});
}
} catch (error) {
console.error(`批量检查接收方账户失败: ${error.message}`);
console.log(`将回退到单个检查每个账户...`);
// 如果批量检查失败,回退到单个检查
for (const address of receiverAddresses) {
try {
accountExistsMap[address] = await this.isAccountExists(address);
} catch (err) {
console.error(`检查账户 ${address} 失败: ${err.message}`);
accountExistsMap[address] = false;
}
}
}
// 执行转账
const results = [];
let successCount = 0;
let failCount = 0;
let totalTransferred = 0;
for (let j = 0; j < receiverWallets.length; j++) {
const receiverWallet = receiverWallets[j];
const amount = amounts[j];
// 如果获取钱包信息失败,添加失败记录
if (receiverWallet.error) {
results.push({
pair: receiverWallet.index,
from: fromAddress,
to: '获取地址失败',
amount: amount,
error: receiverWallet.error,
success: false
});
failCount++;
continue;
}
const toAddress = receiverWallet.address;
const i = receiverWallet.index;
try {
console.log(`\n转账对 #${i}:`);
console.log(`从: ${fromAddress}`);
console.log(`到: ${toAddress} (钱包 #${i})`);
console.log(`金额: ${amount} SOL`);
// 检查接收方账户是否存在
const receiverExists = accountExistsMap[toAddress];
// 如果接收方账户不存在,检查转账金额是否足够支付租金
if (!receiverExists && amount < minRentExemption) {
console.log(`警告: 接收方钱包 #${i} (${toAddress}) 未激活,转账金额 ${amount} SOL 不足以支付租金 (最小 ${minRentExemption} SOL)`);
results.push({
pair: i,
from: fromAddress,
to: toAddress,
amount: amount,
error: `接收方账户未激活,转账金额不足以支付租金 (最小 ${minRentExemption} SOL)`,
success: false
});
failCount++;
continue;
}
// 执行转账 - 直接使用Keypair对象
const txHash = await this.transferSol(fromKeypair, toAddress, amount);
console.log(`交易成功: ${txHash}`);
results.push({
pair: i,
from: fromAddress,
to: toAddress,
amount: amount,
txHash,
success: true
});
successCount++;
totalTransferred += amount;
} catch (error) {
console.error(`转账失败: ${error.message}`);
results.push({
pair: i,
from: fromAddress,
to: toAddress,
amount: amount,
error: error.message,
success: false
});
failCount++;
}
}
// 显示转账结果摘要
console.log(`\n转账操作完成:`);
console.log(`成功: ${successCount}/${receiverCount} 笔`);
console.log(`失败: ${failCount}/${receiverCount} 笔`);
console.log(`实际转账总金额: ${totalTransferred.toFixed(6)} SOL`);
return results;
} catch (error) {
throw new Error(`一对多随机金额转账失败: ${error.message}`);
}
}
}
if (require.main === module) {
const transfer = new SolanaTransfer();
transfer.run();
}
// 导出SolanaTransfer类
module.exports = SolanaTransfer;
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!