first commit

This commit is contained in:
aaron 2026-01-24 22:22:11 +08:00
commit 2ff09f91b0
10 changed files with 3986 additions and 0 deletions

142
README.md Normal file
View File

@ -0,0 +1,142 @@
# BSC批量转账工具
一个现代化、简洁、美观的BSC链批量转账工具支持BNB和BEP-20代币的批量转账。
## ✨ 界面特色
- 🎨 **现代化设计** - 采用BSC品牌金黄色系简洁大方
- 🌈 **优雅配色** - 浅色主题,护眼舒适
- 📱 **响应式布局** - 完美适配桌面和移动设备
- ⚡ **流畅动画** - 微交互动画,提升用户体验
- 🎯 **清晰层次** - 卡片式设计,信息层次分明
## 功能特性
✅ 支持BNB原生代币批量转账
✅ 支持BEP-20代币批量转账
✅ 文本输入批量数据
✅ 实时转账进度显示
✅ 转账历史记录查看
✅ 交易记录导出
✅ Gas费用预估
## 运行方法
### 方式1直接打开
```bash
open index.html
```
### 方式2本地服务器推荐
```bash
# 使用Python
python3 -m http.server 8000
# 或使用Node.js
npx http-server -p 8000
```
然后访问 `http://localhost:8000`
## 使用步骤
### 1. 连接钱包
- 点击"连接钱包"按钮
- 确保已安装MetaMask
- 自动切换到BSC主网
### 2. 选择转账类型
#### BNB转账
1. 选择"BNB (原生代币)"
2. Gas Limit默认21000
#### BEP-20 Token转账
1. 选择"BEP-20 Token"
2. 输入Token合约地址
3. 点击"加载Token信息"
4. 确认Token信息正确
5. Gas Limit默认65000
### 3. 准备转账数据
格式:`地址,金额,备注`(每行一条)
示例:
```
0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb,0.001,用户A
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,0.002,用户B
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0.003,用户C
```
**注意:**
- BNB转账金额单位为BNB如0.1表示0.1个BNB
- Token转账金额为实际数量如100表示100个token
- 备注为可选项,可以留空
### 4. 输入转账数据
- 在文本框中输入或粘贴转账数据
- 每行一条记录,格式:地址,金额,备注
- 点击"解析数据"按钮
- 或使用快捷键 Ctrl/Cmd + Enter 快速解析
### 5. 配置参数
- **发送间隔**:每笔交易之间的延迟(毫秒)
- **Gas Limit**每笔交易的Gas限制
### 6. 开始转账
1. 点击"开始批量转账"
2. 确认转账信息
3. 勾选确认框
4. 点击"开始执行"
### 7. 查看转账历史
1. 点击右上角"转账历史"按钮
2. 查看所有历史转账记录
3. 可按状态、类型筛选
4. 支持搜索地址或交易哈希
5. 可导出历史记录为CSV
6. 点击"查看详情"查看单笔转账详情
7. 历史记录保存在浏览器本地存储中
## 常见Token合约地址BSC主网
- **USDT**: 0x55d398326f99059fF775485246999027B3197955
- **USDC**: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d
- **BUSD**: 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56
- **CAKE**: 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82
## 注意事项
⚠️ **安全提示**
- 转账前请仔细核对收款地址
- 确保钱包中有足够的BNB支付Gas费
- Token转账需要足够的Token余额
- 建议先小额测试
⚠️ **Gas费用**
- BNB转账约21000 Gas
- Token转账约65000 Gas
- 实际费用取决于网络拥堵情况
⚠️ **网络要求**
- 必须连接BSC主网Chain ID: 56
- RPC节点https://bsc-dataseed.binance.org/
## 技术栈
- HTML/CSS/JavaScript
- ethers.js v5.7.2
- MetaMask Web3 Provider
- BSC (Binance Smart Chain)
## 文件说明
- `index.html` - 主页面
- `history.html` - 转账历史页面
- `app.js` - 应用逻辑
- `batchTransfer.js` - 转账核心功能
- `history.js` - 历史记录管理
- `utils.js` - 工具函数
- `style.css` - 样式文件
- `example.csv` - CSV示例文件

696
app.js Normal file
View File

@ -0,0 +1,696 @@
class BatchTransferApp {
constructor() {
this.transferData = [];
this.transferResults = [];
this.isTransferring = false;
this.isTokenTransfer = false;
this.tokenInfo = null;
this.initElements();
this.initEventListeners();
this.initUI();
}
initElements() {
// 钱包相关
this.connectWalletBtn = document.getElementById('connectWallet');
this.disconnectWalletBtn = document.getElementById('disconnectWallet');
this.walletInfo = document.getElementById('connectedInfo');
this.accountAddress = document.getElementById('accountAddress');
this.walletBalance = document.getElementById('walletBalance');
this.networkInfo = document.getElementById('networkInfo');
// 转账类型
this.transferTypeRadios = document.querySelectorAll('input[name="transferType"]');
this.tokenConfig = document.getElementById('tokenConfig');
this.tokenAddress = document.getElementById('tokenAddress');
this.loadTokenInfoBtn = document.getElementById('loadTokenInfo');
this.tokenInfoElement = document.getElementById('tokenInfo');
this.tokenName = document.getElementById('tokenName');
this.tokenSymbol = document.getElementById('tokenSymbol');
this.tokenBalance = document.getElementById('tokenBalance');
// 上传相关
this.transferInput = document.getElementById('transferInput');
this.parseDataBtn = document.getElementById('parseData');
this.clearInputBtn = document.getElementById('clearInput');
this.dataPreview = document.getElementById('dataPreview');
this.dataTable = document.getElementById('dataTable');
this.dataCount = document.getElementById('dataCount');
this.clearDataBtn = document.getElementById('clearData');
// 汇总信息
this.totalCount = document.getElementById('totalCount');
this.totalAmount = document.getElementById('totalAmount');
this.amountUnit = document.getElementById('amountUnit');
this.estimatedGas = document.getElementById('estimatedGas');
this.totalCost = document.getElementById('totalCost');
// 高级设置
this.delayTime = document.getElementById('delayTime');
this.gasLimit = document.getElementById('gasLimit');
this.gasPrice = document.getElementById('gasPrice');
// 转账按钮
this.startTransferBtn = document.getElementById('startTransfer');
this.stopTransferBtn = document.getElementById('stopTransfer');
// 进度相关
this.progressSection = document.getElementById('progressSection');
this.progressFill = document.getElementById('progressFill');
this.progressInfo = document.getElementById('progressInfo');
this.progressPercent = document.getElementById('progressPercent');
this.successCount = document.getElementById('successCount');
this.failedCount = document.getElementById('failedCount');
this.pendingCount = document.getElementById('pendingCount');
// 日志相关
this.transferLog = document.getElementById('transferLog');
this.clearLogBtn = document.getElementById('clearLog');
// 模态框
this.confirmModal = document.getElementById('confirmModal');
this.confirmCount = document.getElementById('confirmCount');
this.confirmAmount = document.getElementById('confirmAmount');
this.confirmGas = document.getElementById('confirmGas');
this.confirmBalance = document.getElementById('confirmBalance');
this.confirmCheck = document.getElementById('confirmCheck');
this.cancelTransferBtn = document.getElementById('cancelTransfer');
this.executeTransferBtn = document.getElementById('executeTransfer');
this.modalCloseBtn = document.querySelector('.modal-close');
}
initEventListeners() {
// 钱包连接
this.connectWalletBtn.addEventListener('click', () => this.connectWallet());
this.disconnectWalletBtn.addEventListener('click', () => this.disconnectWallet());
// 转账类型切换
this.transferTypeRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
if (e.target.checked) {
this.onTransferTypeChange(e.target.value);
}
});
});
// Token相关
this.loadTokenInfoBtn.addEventListener('click', () => this.loadTokenInfo());
this.tokenAddress.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.loadTokenInfo();
}
});
// 数据输入
this.parseDataBtn.addEventListener('click', () => this.parseTransferData());
this.clearInputBtn.addEventListener('click', () => this.clearInput());
this.transferInput.addEventListener('keydown', (e) => {
// Ctrl/Cmd + Enter 快捷键解析数据
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
this.parseTransferData();
}
});
// 清空数据
if (this.clearDataBtn) {
this.clearDataBtn.addEventListener('click', () => this.clearData());
}
// 转账控制
this.startTransferBtn.addEventListener('click', () => this.showConfirmModal());
this.stopTransferBtn.addEventListener('click', () => this.stopTransfer());
// 日志控制
this.clearLogBtn.addEventListener('click', () => this.clearLog());
// 配置变化
this.delayTime.addEventListener('change', () => this.updateSummary());
this.gasLimit.addEventListener('change', () => this.updateSummary());
// 模态框
this.confirmCheck.addEventListener('change', (e) => {
this.executeTransferBtn.disabled = !e.target.checked;
});
this.cancelTransferBtn.addEventListener('click', () => this.hideModal());
this.executeTransferBtn.addEventListener('click', () => this.executeTransfer());
this.modalCloseBtn.addEventListener('click', () => this.hideModal());
// 点击模态框外部关闭
this.confirmModal.addEventListener('click', (e) => {
if (e.target === this.confirmModal) {
this.hideModal();
}
});
}
initUI() {
this.updateUI();
// 尝试恢复钱包连接
this.restoreWalletConnection();
// 定期更新Gas价格
setInterval(() => {
if (window.BatchTransfer.provider) {
this.updateGasPrice();
}
}, 30000);
}
async restoreWalletConnection() {
try {
// 检查是否有保存的钱包连接状态
const wasConnected = localStorage.getItem('walletConnected');
if (wasConnected === 'true' && window.ethereum) {
// 尝试自动连接
const accounts = await window.ethereum.request({
method: 'eth_accounts'
});
if (accounts && accounts.length > 0) {
// 钱包已授权,自动连接
await this.connectWallet();
}
}
} catch (error) {
console.log('无法恢复钱包连接:', error);
localStorage.removeItem('walletConnected');
}
}
async connectWallet() {
try {
this.addLog('正在连接钱包...', 'info');
const account = await window.BatchTransfer.connectWallet();
// 保存连接状态到localStorage
localStorage.setItem('walletConnected', 'true');
// 更新UI
this.connectWalletBtn.style.display = 'none';
this.walletInfo.style.display = 'flex';
this.accountAddress.textContent = window.TransferUtils.formatAddress(account);
// 获取余额和网络信息
await this.updateWalletInfo();
this.addLog('钱包连接成功', 'success');
} catch (error) {
this.addLog(`连接失败: ${error.message}`, 'error');
alert(`连接钱包失败: ${error.message}`);
}
}
async disconnectWallet() {
await window.BatchTransfer.disconnectWallet();
// 清除保存的连接状态
localStorage.removeItem('walletConnected');
// 重置UI
this.connectWalletBtn.style.display = 'block';
this.walletInfo.style.display = 'none';
// 检查networkInfo元素是否存在因为我们已经从页面移除了它
if (this.networkInfo) {
this.networkInfo.textContent = '未连接';
}
this.gasPrice.value = '0';
this.addLog('已断开钱包连接', 'info');
}
async updateWalletInfo() {
try {
// 更新余额
const balance = await window.BatchTransfer.getWalletBalance();
this.walletBalance.textContent = window.TransferUtils.formatNumber(balance, 4);
// 更新网络信息(如果元素存在)
if (this.networkInfo) {
const network = await window.BatchTransfer.getNetworkInfo();
this.networkInfo.textContent = `${network.name} (Chain ID: ${network.chainId})`;
}
// 更新Gas价格
await this.updateGasPrice();
} catch (error) {
console.error('更新钱包信息失败:', error);
}
}
async updateGasPrice() {
try {
const gasPrice = await window.BatchTransfer.getGasPrice();
this.gasPrice.value = window.TransferUtils.formatNumber(parseFloat(gasPrice), 2);
this.updateSummary();
} catch (error) {
console.error('获取Gas价格失败:', error);
}
}
onTransferTypeChange(type) {
this.isTokenTransfer = (type === 'token');
if (this.isTokenTransfer) {
// 显示Token配置
this.tokenConfig.style.display = 'block';
// 更新Gas Limit默认值
this.gasLimit.value = '65000';
this.amountUnit.textContent = 'Token';
} else {
// 隐藏Token配置
this.tokenConfig.style.display = 'none';
this.tokenInfoElement.style.display = 'none';
// 恢复BNB转账的Gas Limit
this.gasLimit.value = '21000';
this.amountUnit.textContent = 'BNB';
// 清空token信息
this.tokenInfo = null;
}
this.updateSummary();
}
async loadTokenInfo() {
const address = this.tokenAddress.value.trim();
if (!address) {
alert('请输入Token合约地址');
return;
}
if (!window.BatchTransfer.provider) {
alert('请先连接钱包');
return;
}
try {
this.addLog('正在加载Token信息...', 'info');
this.loadTokenInfoBtn.disabled = true;
this.loadTokenInfoBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 加载中...';
// 加载Token合约
const tokenInfo = await window.BatchTransfer.loadTokenContract(address);
// 获取Token余额
const balance = await window.BatchTransfer.getTokenBalance();
// 更新UI
this.tokenName.textContent = tokenInfo.name;
this.tokenSymbol.textContent = tokenInfo.symbol;
this.tokenBalance.textContent = window.TransferUtils.formatNumber(parseFloat(balance), 4);
this.tokenInfoElement.style.display = 'block';
this.tokenInfo = tokenInfo;
// 更新金额单位
this.amountUnit.textContent = tokenInfo.symbol;
this.addLog(`Token加载成功: ${tokenInfo.name} (${tokenInfo.symbol})`, 'success');
// 更新汇总信息
this.updateSummary();
} catch (error) {
this.addLog(`加载Token失败: ${error.message}`, 'error');
alert(`加载Token失败: ${error.message}`);
this.tokenInfoElement.style.display = 'none';
} finally {
this.loadTokenInfoBtn.disabled = false;
this.loadTokenInfoBtn.innerHTML = '<i class="fas fa-sync"></i> 加载';
}
}
loadCSVFile(file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const csvText = e.target.result;
this.transferData = window.TransferUtils.parseCSV(csvText);
// 验证数据
const errors = window.BatchTransfer.validateTransferData(this.transferData);
if (errors.length > 0) {
this.addLog('CSV文件解析错误:', 'error');
errors.forEach(error => this.addLog(error, 'error'));
alert(`发现${errors.length}个错误请检查CSV文件格式`);
return;
}
// 显示数据预览
this.dataPreview.style.display = 'block';
// 更新表格显示
this.updateDataTable();
// 更新汇总信息
this.updateSummary();
this.addLog(`成功加载 ${this.transferData.length} 条转账记录`, 'success');
} catch (error) {
this.addLog(`解析CSV文件失败: ${error.message}`, 'error');
alert('解析CSV文件失败请检查文件格式');
}
};
reader.onerror = () => {
this.addLog('读取文件失败', 'error');
};
reader.readAsText(file);
}
parseTransferData() {
const inputText = this.transferInput.value.trim();
if (!inputText) {
alert('请输入转账数据');
return;
}
try {
// 使用相同的CSV解析函数
this.transferData = window.TransferUtils.parseCSV(inputText);
// 验证数据
const errors = window.BatchTransfer.validateTransferData(this.transferData);
if (errors.length > 0) {
this.addLog('数据解析错误:', 'error');
errors.forEach(error => this.addLog(error, 'error'));
alert(`发现${errors.length}个错误,请检查数据格式\n\n格式:地址,金额,备注\n示例0x123...,0.1,用户A`);
return;
}
// 显示数据预览
this.dataPreview.style.display = 'block';
// 更新表格显示
this.updateDataTable();
// 更新汇总信息
this.updateSummary();
this.addLog(`成功解析 ${this.transferData.length} 条转账记录`, 'success');
} catch (error) {
this.addLog(`解析数据失败: ${error.message}`, 'error');
alert('解析数据失败,请检查数据格式\n\n格式地址,金额,备注\n示例0x123...,0.1,用户A');
}
}
clearInput() {
this.transferInput.value = '';
this.clearData();
}
updateDataTable() {
const tbody = this.dataTable.querySelector('tbody');
tbody.innerHTML = '';
this.transferData.forEach((item, index) => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${index + 1}</td>
<td title="${item.address}">${window.TransferUtils.formatAddress(item.address)}</td>
<td>${item.amount}</td>
<td>${item.note || '-'}</td>
`;
tbody.appendChild(row);
});
this.dataCount.textContent = this.transferData.length;
}
clearData() {
this.transferData = [];
this.dataPreview.style.display = 'none';
this.updateSummary();
this.addLog('已清空转账数据', 'info');
}
async updateSummary() {
if (this.transferData.length === 0) {
this.totalCount.textContent = '0';
this.totalAmount.textContent = '0';
this.estimatedGas.textContent = '0';
this.totalCost.textContent = '0';
this.startTransferBtn.disabled = true;
return;
}
// 检查Token转账模式下是否已加载Token信息
if (this.isTokenTransfer && !this.tokenInfo) {
this.startTransferBtn.disabled = true;
return;
}
// 计算总金额
const totalAmount = this.transferData.reduce((sum, item) => sum + item.amount, 0);
this.totalCount.textContent = this.transferData.length;
this.totalAmount.textContent = window.TransferUtils.formatNumber(totalAmount, 4);
// 计算Gas费用
if (window.BatchTransfer.provider && this.gasPrice.value) {
const gasPrice = parseFloat(this.gasPrice.value);
const gasLimit = parseInt(this.gasLimit.value);
try {
const cost = await window.BatchTransfer.calculateTotalCost(
this.transferData,
gasLimit,
gasPrice,
this.isTokenTransfer
);
this.estimatedGas.textContent = window.TransferUtils.formatNumber(cost.totalGasCost, 4);
this.totalCost.textContent = window.TransferUtils.formatNumber(cost.totalCost, 4);
} catch (error) {
console.error('计算Gas费用失败:', error);
}
}
// 启用开始按钮
this.startTransferBtn.disabled = false;
}
showConfirmModal() {
if (!window.BatchTransfer.provider) {
alert('请先连接钱包');
return;
}
if (this.transferData.length === 0) {
alert('请先上传转账数据');
return;
}
// 更新确认信息
this.confirmCount.textContent = this.transferData.length;
this.confirmAmount.textContent = this.totalAmount.textContent + ' ' + this.amountUnit.textContent;
this.confirmGas.textContent = this.estimatedGas.textContent;
this.confirmBalance.textContent = this.walletBalance.textContent;
// 重置确认勾选
this.confirmCheck.checked = false;
this.executeTransferBtn.disabled = true;
// 显示模态框
this.confirmModal.style.display = 'flex';
}
hideModal() {
this.confirmModal.style.display = 'none';
}
async executeTransfer() {
this.hideModal();
if (!window.BatchTransfer.provider) {
this.addLog('请先连接钱包', 'error');
return;
}
// Token转账时检查Token余额
if (this.isTokenTransfer) {
if (!this.tokenInfo) {
this.addLog('请先加载Token信息', 'error');
return;
}
const tokenBalance = parseFloat(this.tokenBalance.textContent);
const requiredAmount = parseFloat(this.totalAmount.textContent);
if (tokenBalance < requiredAmount) {
this.addLog(`Token余额不足需要 ${requiredAmount} ${this.tokenInfo.symbol},当前余额 ${tokenBalance} ${this.tokenInfo.symbol}`, 'error');
alert(`Token余额不足请确保钱包中有足够的${this.tokenInfo.symbol}`);
return;
}
// 检查BNB余额是否足够支付Gas费
const bnbBalance = parseFloat(this.walletBalance.textContent);
const requiredGas = parseFloat(this.estimatedGas.textContent);
if (bnbBalance < requiredGas) {
this.addLog(`BNB余额不足以支付Gas费需要 ${requiredGas} BNB当前余额 ${bnbBalance} BNB`, 'error');
alert('BNB余额不足以支付Gas费用');
return;
}
} else {
// BNB转账时检查余额
const balance = parseFloat(this.walletBalance.textContent);
const required = parseFloat(this.totalCost.textContent);
if (balance < required) {
this.addLog(`余额不足:需要 ${required} BNB当前余额 ${balance} BNB`, 'error');
alert('余额不足请确保钱包中有足够的BNB支付转账费用');
return;
}
}
// 显示进度区域
this.progressSection.style.display = 'block';
this.progressSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// 开始转账
this.isTransferring = true;
this.startTransferBtn.style.display = 'none';
this.stopTransferBtn.style.display = 'block';
// 重置进度
this.resetProgress();
const transferType = this.isTokenTransfer ? `Token (${this.tokenInfo.symbol})` : 'BNB';
this.addLog(`开始批量转账 ${transferType}...`, 'info');
try {
const options = {
delay: parseInt(this.delayTime.value),
gasLimit: parseInt(this.gasLimit.value),
isTokenTransfer: this.isTokenTransfer,
onProgress: (progress) => this.updateProgress(progress),
onComplete: (results) => this.onTransferComplete(results),
onError: (error) => this.onTransferError(error)
};
this.transferResults = await window.BatchTransfer.executeBatchTransfer(
this.transferData,
options
);
} catch (error) {
this.addLog(`转账过程出错: ${error.message}`, 'error');
this.stopTransfer();
}
}
stopTransfer() {
window.BatchTransfer.stopTransfer();
this.isTransferring = false;
this.startTransferBtn.style.display = 'block';
this.stopTransferBtn.style.display = 'none';
this.addLog('转账已停止', 'warning');
}
resetProgress() {
this.progressFill.style.width = '0%';
this.progressInfo.textContent = '0/0';
this.progressPercent.textContent = '0%';
this.successCount.textContent = '0';
this.failedCount.textContent = '0';
this.pendingCount.textContent = this.transferData.length.toString();
}
updateProgress(progress) {
const percent = Math.round((progress.current / progress.total) * 100);
this.progressFill.style.width = `${percent}%`;
this.progressInfo.textContent = `${progress.current}/${progress.total}`;
this.progressPercent.textContent = `${percent}%`;
this.successCount.textContent = progress.success.toString();
this.failedCount.textContent = progress.failed.toString();
this.pendingCount.textContent = (progress.total - progress.current).toString();
// 更新余额每5笔更新一次
if (progress.current % 5 === 0) {
this.updateWalletInfo();
}
}
onTransferComplete(results) {
this.isTransferring = false;
this.startTransferBtn.style.display = 'block';
this.stopTransferBtn.style.display = 'none';
const successCount = results.filter(r => r.success).length;
const failedCount = results.filter(r => !r.success).length;
this.addLog(`转账完成!成功: ${successCount},失败: ${failedCount}`, 'success');
// 更新钱包余额
this.updateWalletInfo();
// 询问是否导出结果
if (confirm('转账完成,是否导出详细结果?')) {
window.TransferUtils.exportToCSV(results);
}
}
onTransferError(error) {
this.addLog(`转账过程中出错: ${error.message}`, 'error');
this.isTransferring = false;
this.startTransferBtn.style.display = 'block';
this.stopTransferBtn.style.display = 'none';
}
addLog(message, type = 'info') {
const log = window.TransferUtils.generateLogMessage(type, message);
// 移除空状态
const emptyState = this.transferLog.querySelector('.log-empty');
if (emptyState) {
emptyState.remove();
}
// 添加到日志顶部
this.transferLog.insertAdjacentHTML('afterbegin', log.html);
// 限制日志数量
const logItems = this.transferLog.querySelectorAll('.log-item');
if (logItems.length > 100) {
logItems[logItems.length - 1].remove();
}
}
clearLog() {
this.transferLog.innerHTML = `
<div class="log-empty">
<i class="fas fa-info-circle"></i>
<p>暂无日志</p>
</div>
`;
}
updateUI() {
// 初始化UI状态
this.startTransferBtn.disabled = true;
this.stopTransferBtn.style.display = 'none';
this.progressSection.style.display = 'none';
this.dataPreview.style.display = 'none';
}
}
// 应用初始化
document.addEventListener('DOMContentLoaded', () => {
window.app = new BatchTransferApp();
});

435
batchTransfer.js Normal file
View File

@ -0,0 +1,435 @@
class BSCBatchTransfer {
constructor() {
this.provider = null;
this.signer = null;
this.account = '';
this.isTransferring = false;
this.currentBatch = 0;
this.totalBatches = 0;
// BEP-20 Token ABI (与ERC-20兼容)
this.BEP20_ABI = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)"
];
this.tokenContract = null;
this.tokenInfo = null;
}
async waitForEthereum(maxWait = 3000) {
return new Promise((resolve) => {
if (window.ethereum) {
resolve(true);
return;
}
let waited = 0;
const interval = setInterval(() => {
if (window.ethereum) {
clearInterval(interval);
resolve(true);
} else if (waited >= maxWait) {
clearInterval(interval);
resolve(false);
} else {
waited += 100;
}
}, 100);
});
}
async connectWallet() {
try {
console.log('开始连接钱包...');
// 等待MetaMask注入最多等待3秒
const ethereumAvailable = await this.waitForEthereum();
if (!ethereumAvailable || !window.ethereum) {
console.error('未检测到MetaMask');
throw new Error('请安装MetaMask或支持BSC的钱包插件刷新页面后重试');
}
console.log('检测到钱包:', window.ethereum.isMetaMask ? 'MetaMask' : '其他钱包');
// 请求连接钱包
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
this.account = accounts[0];
// 切换到BSC网络
await this.switchToBSCNetwork();
// 创建Provider和Signer
this.provider = new ethers.providers.Web3Provider(window.ethereum);
this.signer = this.provider.getSigner();
// 监听账户变化
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
this.disconnectWallet();
} else {
this.account = accounts[0];
this.updateWalletInfo();
}
});
// 监听网络变化
window.ethereum.on('chainChanged', (chainId) => {
window.location.reload();
});
return this.account;
} catch (error) {
console.error('连接钱包失败:', error);
throw error;
}
}
async disconnectWallet() {
this.provider = null;
this.signer = null;
this.account = '';
this.isTransferring = false;
// 移除事件监听
if (window.ethereum) {
window.ethereum.removeAllListeners('accountsChanged');
window.ethereum.removeAllListeners('chainChanged');
}
}
async switchToBSCNetwork() {
const chainId = await window.ethereum.request({ method: 'eth_chainId' });
// BSC主网chainId: 0x38 (56)
if (chainId !== '0x38') {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x38' }]
});
} catch (switchError) {
// 如果网络不存在,添加网络
if (switchError.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x38',
chainName: 'Binance Smart Chain',
nativeCurrency: {
name: 'BNB',
symbol: 'BNB',
decimals: 18
},
rpcUrls: ['https://bsc-dataseed.binance.org/'],
blockExplorerUrls: ['https://bscscan.com/']
}]
});
} else {
throw switchError;
}
}
}
}
async getWalletBalance() {
if (!this.provider) throw new Error('请先连接钱包');
const balance = await this.provider.getBalance(this.account);
return ethers.utils.formatEther(balance);
}
async loadTokenContract(tokenAddress) {
if (!this.provider) throw new Error('请先连接钱包');
if (!ethers.utils.isAddress(tokenAddress)) {
throw new Error('无效的Token合约地址');
}
try {
this.tokenContract = new ethers.Contract(tokenAddress, this.BEP20_ABI, this.signer);
// 获取Token信息
const [name, symbol, decimals] = await Promise.all([
this.tokenContract.name(),
this.tokenContract.symbol(),
this.tokenContract.decimals()
]);
this.tokenInfo = {
address: tokenAddress,
name,
symbol,
decimals
};
return this.tokenInfo;
} catch (error) {
this.tokenContract = null;
this.tokenInfo = null;
throw new Error('无法加载Token信息请检查合约地址是否正确');
}
}
async getTokenBalance() {
if (!this.tokenContract || !this.tokenInfo) {
throw new Error('请先加载Token信息');
}
const balance = await this.tokenContract.balanceOf(this.account);
return ethers.utils.formatUnits(balance, this.tokenInfo.decimals);
}
async getGasPrice() {
if (!this.provider) throw new Error('请先连接钱包');
const gasPrice = await this.provider.getGasPrice();
return ethers.utils.formatUnits(gasPrice, 'gwei');
}
async getNetworkInfo() {
if (!this.provider) throw new Error('请先连接钱包');
const network = await this.provider.getNetwork();
return {
name: network.name,
chainId: network.chainId
};
}
async validateTransferData(transferData) {
const errors = [];
for (let i = 0; i < transferData.length; i++) {
const item = transferData[i];
// 验证地址
if (!ethers.utils.isAddress(item.address)) {
errors.push(`${i + 1}行:无效的地址 ${item.address}`);
}
// 验证金额
try {
const amount = parseFloat(item.amount);
if (isNaN(amount) || amount <= 0) {
errors.push(`${i + 1}行:无效的金额 ${item.amount}`);
}
} catch (error) {
errors.push(`${i + 1}行:无效的金额格式`);
}
}
return errors;
}
async calculateTotalCost(transferData, gasLimit, gasPrice, isTokenTransfer = false) {
if (!this.provider) throw new Error('请先连接钱包');
const totalAmount = transferData.reduce((sum, item) => {
return sum + parseFloat(item.amount);
}, 0);
// 计算Gas费用
const gasCostPerTx = ethers.utils.parseUnits(gasPrice.toString(), 'gwei')
.mul(gasLimit)
.mul(transferData.length);
const totalGasCost = parseFloat(ethers.utils.formatEther(gasCostPerTx));
return {
totalAmount,
totalGasCost,
// Token转账时总成本只包含Gas费用因为token金额不是BNB
totalCost: isTokenTransfer ? totalGasCost : (totalAmount + totalGasCost)
};
}
async executeBatchTransfer(transferData, options) {
if (!this.signer) throw new Error('请先连接钱包');
const {
delay = 1000,
gasLimit = 21000,
isTokenTransfer = false,
onProgress,
onComplete,
onError
} = options;
// 如果是Token转账检查是否已加载Token合约
if (isTokenTransfer && !this.tokenContract) {
throw new Error('请先加载Token合约信息');
}
this.isTransferring = true;
const results = [];
try {
for (let i = 0; i < transferData.length; i++) {
if (!this.isTransferring) {
throw new Error('转账被用户停止');
}
const transfer = transferData[i];
try {
// 获取当前Gas价格
const currentGasPrice = await this.provider.getGasPrice();
let txResponse;
if (isTokenTransfer) {
// Token转账
const amount = ethers.utils.parseUnits(
transfer.amount.toString(),
this.tokenInfo.decimals
);
// 发送Token转账交易
txResponse = await this.tokenContract.transfer(
transfer.address,
amount,
{
gasLimit: gasLimit,
gasPrice: currentGasPrice
}
);
} else {
// BNB转账
const tx = {
to: transfer.address,
value: ethers.utils.parseEther(transfer.amount.toString()),
gasLimit: gasLimit,
gasPrice: currentGasPrice
};
txResponse = await this.signer.sendTransaction(tx);
}
// 等待交易确认
const receipt = await txResponse.wait();
results.push({
index: i,
success: true,
hash: txResponse.hash,
to: transfer.address,
amount: transfer.amount,
receipt: receipt
});
// 保存到历史记录
this.saveTransferToHistory(transfer, results[results.length - 1], isTokenTransfer);
// 更新进度
if (onProgress) {
onProgress({
current: i + 1,
total: transferData.length,
success: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length
});
}
} catch (error) {
results.push({
index: i,
success: false,
to: transfer.address,
amount: transfer.amount,
error: error.message
});
// 保存到历史记录(包括失败的)
this.saveTransferToHistory(transfer, results[results.length - 1], isTokenTransfer);
// 更新进度
if (onProgress) {
onProgress({
current: i + 1,
total: transferData.length,
success: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length
});
}
}
// 延迟处理下一笔交易
if (i < transferData.length - 1) {
await this.delay(delay);
}
}
// 完成回调
if (onComplete) {
onComplete(results);
}
return results;
} catch (error) {
if (onError) {
onError(error);
}
throw error;
} finally {
this.isTransferring = false;
}
}
stopTransfer() {
this.isTransferring = false;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 保存转账记录到历史
saveTransferToHistory(transferData, result, isTokenTransfer = false) {
try {
const storageKey = 'bsc_transfer_history';
const history = JSON.parse(localStorage.getItem(storageKey) || '[]');
const record = {
id: `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: Date.now(),
type: isTokenTransfer ? 'token' : 'bnb',
address: result.to,
amount: result.amount,
status: result.success ? 'success' : 'failed',
txHash: result.hash || null,
note: transferData.note || '',
gasCost: result.receipt ? ethers.utils.formatEther(
result.receipt.gasUsed.mul(result.receipt.effectiveGasPrice)
) : null,
tokenAddress: isTokenTransfer && this.tokenInfo ? this.tokenInfo.address : null,
tokenSymbol: isTokenTransfer && this.tokenInfo ? this.tokenInfo.symbol : null,
error: result.error || null
};
history.push(record);
localStorage.setItem(storageKey, JSON.stringify(history));
} catch (error) {
console.error('保存转账历史失败:', error);
}
}
updateWalletInfo() {
// 这个方法需要在UI层实现这里只提供接口
}
}
// 导出单例实例
window.BatchTransfer = new BSCBatchTransfer();

367
developer.html Normal file
View File

@ -0,0 +1,367 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>开发者介绍 | 区块链龙哥 出品</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="style.css">
<style>
.developer-container {
max-width: 800px;
margin: 0 auto;
}
.profile-card {
background: var(--card-bg);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
text-align: center;
}
.profile-avatar {
width: 120px;
height: 120px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--spacing-lg);
font-size: 48px;
color: white;
box-shadow: var(--shadow-lg);
}
.profile-name {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.profile-title {
font-size: 16px;
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
}
.profile-bio {
font-size: 15px;
color: var(--text-primary);
line-height: 1.8;
max-width: 600px;
margin: 0 auto var(--spacing-lg);
}
.contact-card {
background: var(--card-bg);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
}
.contact-card h2 {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.contact-card h2 i {
color: var(--primary-color);
}
.contact-list {
display: grid;
gap: var(--spacing-md);
}
.contact-item {
display: flex;
align-items: center;
padding: var(--spacing-md);
background: var(--bg-color);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
}
.contact-item:hover {
border-color: var(--primary-color);
box-shadow: var(--shadow-sm);
}
.contact-icon {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: white;
margin-right: var(--spacing-md);
}
.contact-info {
flex: 1;
}
.contact-label {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.contact-value {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
font-family: 'Courier New', monospace;
}
.contact-action {
padding: 8px 16px;
background: var(--primary-color);
color: var(--text-primary);
border: none;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.contact-action:hover {
background: var(--primary-dark);
}
.tools-card {
background: var(--card-bg);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
margin-bottom: var(--spacing-lg);
box-shadow: var(--shadow-md);
border: 1px solid var(--border-color);
}
.tools-card h2 {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.tools-card h2 i {
color: var(--primary-color);
}
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-md);
}
.tool-link {
display: flex;
align-items: center;
padding: var(--spacing-md);
background: var(--bg-color);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
text-decoration: none;
color: var(--text-primary);
transition: all 0.3s ease;
}
.tool-link:hover {
border-color: var(--primary-color);
box-shadow: var(--shadow-sm);
transform: translateY(-2px);
}
.tool-link i {
font-size: 24px;
color: var(--primary-color);
margin-right: var(--spacing-sm);
}
.tool-link-text {
flex: 1;
}
.tool-link-title {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
}
.tool-link-desc {
font-size: 13px;
color: var(--text-secondary);
}
.back-button {
display: inline-flex;
align-items: center;
gap: var(--spacing-sm);
padding: 12px 24px;
background: var(--card-bg);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
margin-bottom: var(--spacing-lg);
}
.back-button:hover {
border-color: var(--primary-color);
box-shadow: var(--shadow-sm);
}
.footer {
text-align: center;
padding: var(--spacing-lg);
color: var(--text-secondary);
font-size: 14px;
}
</style>
</head>
<body>
<div class="developer-container">
<!-- 返回按钮 -->
<a href="index.html" class="back-button">
<i class="fas fa-arrow-left"></i>
返回主页
</a>
<!-- 个人资料卡片 -->
<div class="profile-card">
<div class="profile-avatar">
<i class="fas fa-dragon"></i>
</div>
<h1 class="profile-name">区块链龙哥</h1>
<p class="profile-title">区块链开发者 · Web3工具开发</p>
<p class="profile-bio">
专注于区块链技术开发和Web3工具创建致力于为用户提供简单、安全、高效的区块链应用解决方案。
擅长智能合约开发、DApp开发以及区块链工具开发。
</p>
</div>
<!-- 联系方式卡片 -->
<div class="contact-card">
<h2><i class="fas fa-address-book"></i> 联系方式</h2>
<div class="contact-list">
<!-- 微信 -->
<div class="contact-item">
<div class="contact-icon">
<i class="fab fa-weixin"></i>
</div>
<div class="contact-info">
<div class="contact-label">微信号</div>
<div class="contact-value">aaronlzhou</div>
</div>
<button class="contact-action" onclick="copyToClipboard('aaronlzhou', '微信号')">
<i class="fas fa-copy"></i> 复制
</button>
</div>
<!-- 邮箱 -->
<div class="contact-item">
<div class="contact-icon">
<i class="fas fa-envelope"></i>
</div>
<div class="contact-info">
<div class="contact-label">电子邮箱</div>
<div class="contact-value">aaron.l.zhou@gmail.com</div>
</div>
<button class="contact-action" onclick="copyToClipboard('aaron.l.zhou@gmail.com', '邮箱地址')">
<i class="fas fa-copy"></i> 复制
</button>
</div>
</div>
</div>
<!-- 底部 -->
<div class="footer">
<p><i class="fas fa-code"></i> 使用区块链技术构建更好的未来</p>
<p style="margin-top: 8px; color: var(--text-tertiary);">© 2026 区块链龙哥 · All Rights Reserved</p>
</div>
</div>
<script>
// 复制到剪贴板功能
function copyToClipboard(text, label) {
navigator.clipboard.writeText(text).then(() => {
// 创建提示消息
const toast = document.createElement('div');
toast.textContent = `${label}已复制到剪贴板`;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: var(--success-color);
color: white;
padding: 12px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 9999;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(toast);
// 3秒后移除
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}).catch(err => {
alert('复制失败,请手动复制');
});
}
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`;
document.head.appendChild(style);
</script>
</body>
</html>

3
example.csv Normal file
View File

@ -0,0 +1,3 @@
0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb,0.001,测试地址1
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,0.002,测试地址2
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0.003,测试地址3
1 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb 0.001 测试地址1
2 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 0.002 测试地址2
3 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 0.003 测试地址3

202
history.html Normal file
View File

@ -0,0 +1,202 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>转账历史 | 区块链龙哥 出品</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script>
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<div class="header-content">
<h1><i class="fas fa-history"></i> 转账历史</h1>
<p class="subtitle">查看所有批量转账记录</p>
</div>
<div class="wallet-section">
<a href="index.html" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> 返回主页
</a>
</div>
</header>
<!-- 主内容 -->
<main class="main-content">
<!-- 筛选和操作栏 -->
<div class="step-card">
<div class="history-toolbar">
<div class="filter-group">
<div class="filter-item">
<label>状态筛选</label>
<select id="statusFilter" class="form-control">
<option value="all">全部</option>
<option value="success">成功</option>
<option value="failed">失败</option>
<option value="pending">待确认</option>
</select>
</div>
<div class="filter-item">
<label>类型筛选</label>
<select id="typeFilter" class="form-control">
<option value="all">全部</option>
<option value="bnb">BNB</option>
<option value="token">Token</option>
</select>
</div>
<div class="filter-item">
<label>搜索</label>
<input type="text" id="searchInput" class="form-control" placeholder="搜索地址或交易哈希...">
</div>
</div>
<div class="action-group">
<button id="exportHistory" class="btn btn-secondary">
<i class="fas fa-download"></i> 导出CSV
</button>
<button id="clearHistory" class="btn btn-danger">
<i class="fas fa-trash"></i> 清空历史
</button>
</div>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-info">
<span class="stat-label">成功转账</span>
<span class="stat-value" id="totalSuccess">0</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon failed">
<i class="fas fa-times-circle"></i>
</div>
<div class="stat-info">
<span class="stat-label">失败转账</span>
<span class="stat-value" id="totalFailed">0</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon pending">
<i class="fas fa-clock"></i>
</div>
<div class="stat-info">
<span class="stat-label">待确认</span>
<span class="stat-value" id="totalPending">0</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon total">
<i class="fas fa-list"></i>
</div>
<div class="stat-info">
<span class="stat-label">总转账数</span>
<span class="stat-value" id="totalTransfers">0</span>
</div>
</div>
</div>
<!-- 历史记录表格 -->
<div class="step-card">
<div class="history-header">
<h3><i class="fas fa-list"></i> 转账记录</h3>
<span class="record-count"><strong id="recordCount">0</strong> 条记录</span>
</div>
<div class="history-table-container">
<table class="history-table" id="historyTable">
<thead>
<tr>
<th>时间</th>
<th>类型</th>
<th>收款地址</th>
<th>金额</th>
<th>状态</th>
<th>交易哈希</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody id="historyTableBody">
<tr class="empty-state">
<td colspan="8">
<div class="empty-content">
<i class="fas fa-inbox"></i>
<p>暂无转账记录</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
<!-- 底部信息 -->
<footer class="footer">
<p><i class="fas fa-info-circle"></i> 历史记录保存在浏览器本地存储中</p>
<p style="margin-top: 12px;"><a href="developer.html" style="color: var(--primary-color); text-decoration: none;"><i class="fas fa-user-circle"></i> 关于开发者</a></p>
</footer>
</div>
<!-- 详情模态框 -->
<div id="detailModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-info-circle"></i> 转账详情</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="detail-grid">
<div class="detail-item">
<span class="label">时间</span>
<span class="value" id="detailTime">-</span>
</div>
<div class="detail-item">
<span class="label">类型</span>
<span class="value" id="detailType">-</span>
</div>
<div class="detail-item">
<span class="label">收款地址</span>
<span class="value" id="detailAddress">-</span>
</div>
<div class="detail-item">
<span class="label">金额</span>
<span class="value" id="detailAmount">-</span>
</div>
<div class="detail-item">
<span class="label">状态</span>
<span class="value" id="detailStatus">-</span>
</div>
<div class="detail-item">
<span class="label">交易哈希</span>
<span class="value" id="detailTxHash">-</span>
</div>
<div class="detail-item">
<span class="label">备注</span>
<span class="value" id="detailNote">-</span>
</div>
<div class="detail-item">
<span class="label">Gas费用</span>
<span class="value" id="detailGas">-</span>
</div>
</div>
</div>
<div class="modal-footer">
<button id="closeDetail" class="btn btn-secondary">关闭</button>
<a id="viewOnBscscan" href="#" target="_blank" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> 在BSCScan查看
</a>
</div>
</div>
</div>
<script src="utils.js"></script>
<script src="history.js"></script>
</body>
</html>

322
history.js Normal file
View File

@ -0,0 +1,322 @@
// 转账历史管理
class TransferHistory {
constructor() {
this.storageKey = 'bsc_transfer_history';
this.history = this.loadHistory();
this.filteredHistory = [...this.history];
this.initElements();
this.bindEvents();
this.render();
}
initElements() {
// 筛选器
this.statusFilter = document.getElementById('statusFilter');
this.typeFilter = document.getElementById('typeFilter');
this.searchInput = document.getElementById('searchInput');
// 操作按钮
this.exportBtn = document.getElementById('exportHistory');
this.clearBtn = document.getElementById('clearHistory');
// 统计
this.totalSuccess = document.getElementById('totalSuccess');
this.totalFailed = document.getElementById('totalFailed');
this.totalPending = document.getElementById('totalPending');
this.totalTransfers = document.getElementById('totalTransfers');
this.recordCount = document.getElementById('recordCount');
// 表格
this.tableBody = document.getElementById('historyTableBody');
// 模态框
this.detailModal = document.getElementById('detailModal');
this.closeDetailBtn = document.getElementById('closeDetail');
this.viewOnBscscanBtn = document.getElementById('viewOnBscscan');
}
bindEvents() {
// 筛选器事件
this.statusFilter.addEventListener('change', () => this.applyFilters());
this.typeFilter.addEventListener('change', () => this.applyFilters());
this.searchInput.addEventListener('input', () => this.applyFilters());
// 操作按钮
this.exportBtn.addEventListener('click', () => this.exportToCSV());
this.clearBtn.addEventListener('click', () => this.clearHistory());
// 模态框
this.closeDetailBtn.addEventListener('click', () => this.closeModal());
this.detailModal.querySelector('.modal-close').addEventListener('click', () => this.closeModal());
this.detailModal.addEventListener('click', (e) => {
if (e.target === this.detailModal) this.closeModal();
});
}
// 从localStorage加载历史记录
loadHistory() {
try {
const data = localStorage.getItem(this.storageKey);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error('加载历史记录失败:', error);
return [];
}
}
// 保存历史记录到localStorage
saveHistory() {
try {
localStorage.setItem(this.storageKey, JSON.stringify(this.history));
} catch (error) {
console.error('保存历史记录失败:', error);
showNotification('保存失败', 'error');
}
}
// 应用筛选
applyFilters() {
const status = this.statusFilter.value;
const type = this.typeFilter.value;
const search = this.searchInput.value.toLowerCase().trim();
this.filteredHistory = this.history.filter(record => {
// 状态筛选
if (status !== 'all' && record.status !== status) return false;
// 类型筛选
if (type !== 'all' && record.type !== type) return false;
// 搜索筛选
if (search) {
const searchableText = [
record.address,
record.txHash,
record.note
].join(' ').toLowerCase();
if (!searchableText.includes(search)) return false;
}
return true;
});
this.render();
}
// 渲染页面
render() {
this.renderStats();
this.renderTable();
}
// 渲染统计数据
renderStats() {
const stats = {
success: 0,
failed: 0,
pending: 0,
total: this.history.length
};
this.history.forEach(record => {
if (record.status === 'success') stats.success++;
else if (record.status === 'failed') stats.failed++;
else if (record.status === 'pending') stats.pending++;
});
this.totalSuccess.textContent = stats.success;
this.totalFailed.textContent = stats.failed;
this.totalPending.textContent = stats.pending;
this.totalTransfers.textContent = stats.total;
this.recordCount.textContent = this.filteredHistory.length;
}
// 渲染表格
renderTable() {
if (this.filteredHistory.length === 0) {
this.tableBody.innerHTML = `
<tr class="empty-state">
<td colspan="8">
<div class="empty-content">
<i class="fas fa-inbox"></i>
<p>${this.history.length === 0 ? '暂无转账记录' : '没有符合条件的记录'}</p>
</div>
</td>
</tr>
`;
return;
}
// 按时间倒序排列
const sortedHistory = [...this.filteredHistory].sort((a, b) => b.timestamp - a.timestamp);
this.tableBody.innerHTML = sortedHistory.map(record => `
<tr>
<td>${this.formatTime(record.timestamp)}</td>
<td>
<span class="type-badge ${record.type}">
${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || 'Token'}
</span>
</td>
<td>
<span class="address-cell" title="${record.address}">
${this.formatAddress(record.address)}
</span>
</td>
<td>
<strong>${record.amount}</strong> ${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || ''}
</td>
<td>
<span class="status-badge ${record.status}">
${this.getStatusText(record.status)}
</span>
</td>
<td>
${record.txHash ? `
<a href="https://bscscan.com/tx/${record.txHash}" target="_blank" class="tx-link" title="${record.txHash}">
${this.formatTxHash(record.txHash)}
<i class="fas fa-external-link-alt"></i>
</a>
` : '-'}
</td>
<td>${record.note || '-'}</td>
<td>
<button class="btn-icon" onclick="history.showDetail('${record.id}')" title="查看详情">
<i class="fas fa-eye"></i>
</button>
</td>
</tr>
`).join('');
}
// 显示详情模态框
showDetail(recordId) {
const record = this.history.find(r => r.id === recordId);
if (!record) return;
document.getElementById('detailTime').textContent = this.formatTime(record.timestamp, true);
document.getElementById('detailType').textContent = record.type === 'bnb' ? 'BNB' : `${record.tokenSymbol || 'Token'} (${record.tokenAddress})`;
document.getElementById('detailAddress').textContent = record.address;
document.getElementById('detailAmount').textContent = `${record.amount} ${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || ''}`;
document.getElementById('detailStatus').innerHTML = `<span class="status-badge ${record.status}">${this.getStatusText(record.status)}</span>`;
document.getElementById('detailTxHash').textContent = record.txHash || '-';
document.getElementById('detailNote').textContent = record.note || '-';
document.getElementById('detailGas').textContent = record.gasCost ? `${record.gasCost} BNB` : '-';
if (record.txHash) {
this.viewOnBscscanBtn.href = `https://bscscan.com/tx/${record.txHash}`;
this.viewOnBscscanBtn.style.display = 'inline-flex';
} else {
this.viewOnBscscanBtn.style.display = 'none';
}
this.detailModal.style.display = 'flex';
}
// 关闭模态框
closeModal() {
this.detailModal.style.display = 'none';
}
// 导出为CSV
exportToCSV() {
if (this.filteredHistory.length === 0) {
showNotification('没有可导出的记录', 'warning');
return;
}
const headers = ['时间', '类型', '收款地址', '金额', '状态', '交易哈希', '备注', 'Gas费用'];
const rows = this.filteredHistory.map(record => [
this.formatTime(record.timestamp, true),
record.type === 'bnb' ? 'BNB' : record.tokenSymbol || 'Token',
record.address,
`${record.amount} ${record.type === 'bnb' ? 'BNB' : record.tokenSymbol || ''}`,
this.getStatusText(record.status),
record.txHash || '',
record.note || '',
record.gasCost || ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(cell => `"${cell}"`).join(','))
.join('\n');
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `transfer_history_${Date.now()}.csv`;
link.click();
showNotification('导出成功', 'success');
}
// 清空历史记录
clearHistory() {
if (this.history.length === 0) {
showNotification('没有可清空的记录', 'warning');
return;
}
if (!confirm('确定要清空所有历史记录吗?此操作不可恢复!')) {
return;
}
this.history = [];
this.filteredHistory = [];
this.saveHistory();
this.render();
showNotification('历史记录已清空', 'success');
}
// 格式化时间
formatTime(timestamp, full = false) {
const date = new Date(timestamp);
if (full) {
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
// 格式化地址
formatAddress(address) {
if (!address) return '-';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// 格式化交易哈希
formatTxHash(txHash) {
if (!txHash) return '-';
return `${txHash.slice(0, 8)}...${txHash.slice(-6)}`;
}
// 获取状态文本
getStatusText(status) {
const statusMap = {
'success': '成功',
'failed': '失败',
'pending': '待确认'
};
return statusMap[status] || status;
}
}
// 初始化
let historyManager;
document.addEventListener('DOMContentLoaded', () => {
historyManager = new TransferHistory();
// 将实例暴露到全局供onclick使用
window.history = historyManager;
});

308
index.html Normal file
View File

@ -0,0 +1,308 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BSC批量转账工具 | 区块链龙哥 出品</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="style.css">
<script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script>
</head>
<body>
<div class="container">
<!-- 头部 -->
<header class="header">
<div class="header-content">
<h1><i class="fas fa-paper-plane"></i> BSC批量转账</h1>
<p class="subtitle">简单、快速、安全的批量转账工具</p>
<div style="margin-top: 12px; display: flex; gap: 8px;">
<a href="developer.html" class="btn btn-secondary" style="font-size: 13px; padding: 6px 12px; text-decoration: none;">
<i class="fas fa-user-circle"></i> 联系开发者
</a>
<a href="history.html" class="btn btn-secondary" style="font-size: 13px; padding: 6px 12px; text-decoration: none;">
<i class="fas fa-history"></i> 转账历史
</a>
</div>
</div>
<div class="wallet-section">
<button id="connectWallet" class="btn btn-primary">
<i class="fas fa-wallet"></i> 连接钱包
</button>
<div id="connectedInfo" class="wallet-connected" style="display: none;">
<div class="wallet-details">
<span class="wallet-address" id="accountAddress"></span>
<span class="wallet-balance"><i class="fas fa-coins"></i> <span id="walletBalance">0</span> BNB</span>
</div>
<button id="disconnectWallet" class="btn-icon" title="断开连接">
<i class="fas fa-sign-out-alt"></i>
</button>
</div>
</div>
</header>
<!-- 主内容 - 步骤式布局 -->
<main class="main-content">
<!-- 步骤1: 选择转账类型 -->
<div class="step-card">
<div class="step-header">
<span class="step-number">1</span>
<h2>选择转账类型</h2>
</div>
<div class="step-content">
<div class="transfer-type-selector">
<label class="type-option" data-type="bnb">
<input type="radio" name="transferType" value="bnb" checked>
<div class="type-card">
<i class="fas fa-coins"></i>
<h3>BNB</h3>
<p>原生代币转账</p>
</div>
</label>
<label class="type-option" data-type="token">
<input type="radio" name="transferType" value="token">
<div class="type-card">
<i class="fas fa-certificate"></i>
<h3>BEP-20 Token</h3>
<p>代币合约转账</p>
</div>
</label>
</div>
<!-- Token配置区域 -->
<div id="tokenConfig" class="token-config" style="display: none;">
<div class="input-group">
<label>Token合约地址</label>
<div class="input-with-button">
<input type="text" id="tokenAddress" class="form-control" placeholder="0x...">
<button id="loadTokenInfo" class="btn btn-secondary">
<i class="fas fa-sync"></i> 加载
</button>
</div>
</div>
<div id="tokenInfo" class="token-info" style="display: none;">
<div class="info-grid">
<div class="info-item">
<span class="label">名称</span>
<span class="value" id="tokenName">-</span>
</div>
<div class="info-item">
<span class="label">符号</span>
<span class="value" id="tokenSymbol">-</span>
</div>
<div class="info-item">
<span class="label">余额</span>
<span class="value" id="tokenBalance">-</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 步骤2: 输入转账数据 -->
<div class="step-card">
<div class="step-header">
<span class="step-number">2</span>
<h2>输入转账数据</h2>
</div>
<div class="step-content">
<div class="input-group">
<label>
转账列表
<span class="format-hint-inline">格式:地址,金额,备注(每行一条)</span>
</label>
<textarea
id="transferInput"
class="transfer-textarea"
placeholder="0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb,0.001,测试地址1&#10;0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,0.002,测试地址2&#10;0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0.003,测试地址3"
rows="8"
></textarea>
<div class="input-actions">
<button id="parseData" class="btn btn-primary">
<i class="fas fa-check"></i> 解析数据
</button>
<button id="clearInput" class="btn btn-secondary">
<i class="fas fa-eraser"></i> 清空
</button>
</div>
</div>
<!-- 数据预览 -->
<div id="dataPreview" class="data-preview" style="display: none;">
<div class="preview-header">
<h3>数据预览 <span id="dataCount" class="count-badge">0</span></h3>
<button id="clearData" class="btn-text">
<i class="fas fa-times"></i> 清空
</button>
</div>
<div class="preview-table">
<table id="dataTable">
<thead>
<tr>
<th>序号</th>
<th>收款地址</th>
<th>金额</th>
<th>备注</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 步骤3: 确认并转账 -->
<div class="step-card">
<div class="step-header">
<span class="step-number">3</span>
<h2>确认并开始转账</h2>
</div>
<div class="step-content">
<!-- 转账汇总 -->
<div class="transfer-summary">
<div class="summary-row">
<span>转账数量</span>
<strong><span id="totalCount">0</span></strong>
</div>
<div class="summary-row">
<span>转账总额</span>
<strong><span id="totalAmount">0</span> <span id="amountUnit">BNB</span></strong>
</div>
<div class="summary-row">
<span>预计Gas费</span>
<strong><span id="estimatedGas">0</span> BNB</strong>
</div>
<div class="summary-row total">
<span>总计费用</span>
<strong><span id="totalCost">0</span> BNB</strong>
</div>
</div>
<!-- 高级设置 -->
<details class="advanced-settings">
<summary>
<i class="fas fa-cog"></i> 高级设置
</summary>
<div class="settings-grid">
<div class="setting-item">
<label>发送间隔 (毫秒)</label>
<input type="number" id="delayTime" class="form-control" value="1000" min="500" max="5000">
</div>
<div class="setting-item">
<label>Gas Limit</label>
<input type="number" id="gasLimit" class="form-control" value="21000" min="21000" max="100000">
</div>
<div class="setting-item">
<label>Gas Price</label>
<div class="input-with-unit">
<input type="text" id="gasPrice" class="form-control" readonly value="0">
<span>Gwei</span>
</div>
</div>
</div>
</details>
<!-- 操作按钮 -->
<div class="action-buttons">
<button id="startTransfer" class="btn btn-primary btn-large" disabled>
<i class="fas fa-rocket"></i> 开始批量转账
</button>
<button id="stopTransfer" class="btn btn-danger btn-large" style="display: none;">
<i class="fas fa-stop"></i> 停止转账
</button>
</div>
</div>
</div>
<!-- 转账进度 (只在转账时显示) -->
<div id="progressSection" class="progress-section" style="display: none;">
<div class="progress-card">
<h3><i class="fas fa-tasks"></i> 转账进度</h3>
<div class="progress-bar-container">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-text">
<span id="progressInfo">0/0</span>
<span id="progressPercent">0%</span>
</div>
</div>
<div class="progress-stats">
<div class="stat success">
<i class="fas fa-check-circle"></i>
<span>成功 <strong id="successCount">0</strong></span>
</div>
<div class="stat failed">
<i class="fas fa-times-circle"></i>
<span>失败 <strong id="failedCount">0</strong></span>
</div>
<div class="stat pending">
<i class="fas fa-clock"></i>
<span>等待 <strong id="pendingCount">0</strong></span>
</div>
</div>
</div>
<!-- 实时日志 -->
<div class="log-card">
<div class="log-header">
<h3><i class="fas fa-list"></i> 转账日志</h3>
<div class="log-actions">
<button id="clearLog" class="btn-text">
<i class="fas fa-trash"></i> 清空
</button>
</div>
</div>
<div class="log-content" id="transferLog">
<div class="log-empty">
<i class="fas fa-info-circle"></i>
<p>暂无日志</p>
</div>
</div>
</div>
</div>
</main>
<!-- 底部信息 -->
<footer class="footer">
<p style="color: var(--text-tertiary); font-size: 13px;">© 2026 区块链龙哥 · All Rights Reserved</p>
</footer>
</div>
<!-- 确认模态框 -->
<div id="confirmModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-exclamation-triangle"></i> 确认转账</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p>请仔细核对以下信息:</p>
<div class="confirm-details">
<p><span>转账数量</span><strong id="confirmCount">0</strong></p>
<p><span>转账金额</span><strong id="confirmAmount">0</strong></p>
<p><span>Gas费用</span><strong id="confirmGas">0</strong> BNB</p>
<p><span>账户余额</span><strong id="confirmBalance">0</strong> BNB</p>
</div>
<div class="warning">
<i class="fas fa-exclamation-circle"></i>
转账一旦开始将无法撤销,请确保信息正确!
</div>
<label class="checkbox-label">
<input type="checkbox" id="confirmCheck">
<span>我已核对所有信息并确认转账</span>
</label>
</div>
<div class="modal-footer">
<button id="cancelTransfer" class="btn btn-secondary">取消</button>
<button id="executeTransfer" class="btn btn-primary" disabled>确认转账</button>
</div>
</div>
</div>
<script src="utils.js"></script>
<script src="batchTransfer.js"></script>
<script src="app.js"></script>
</body>
</html>

1394
style.css Normal file

File diff suppressed because it is too large Load Diff

117
utils.js Normal file
View File

@ -0,0 +1,117 @@
class TransferUtils {
static parseCSV(csvText) {
const lines = csvText.split('\n');
const transfers = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
// 支持逗号分隔或制表符分隔
const parts = line.includes('\t') ? line.split('\t') : line.split(',');
if (parts.length >= 2) {
const address = parts[0].trim();
const amount = parts[1].trim();
const note = parts[2] ? parts[2].trim() : '';
// 基本验证
if (address && amount) {
transfers.push({
address,
amount: parseFloat(amount),
note
});
}
}
}
return transfers;
}
static formatAddress(address) {
if (!address || address.length < 10) return address;
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
static formatNumber(num, decimals = 4) {
if (isNaN(num)) return '0';
return parseFloat(num.toFixed(decimals)).toString();
}
static generateLogMessage(type, message) {
const time = new Date().toLocaleTimeString();
const types = {
success: { icon: '✅', class: 'success' },
error: { icon: '❌', class: 'error' },
info: { icon: '', class: 'info' },
warning: { icon: '⚠️', class: 'warning' }
};
const typeInfo = types[type] || types.info;
return {
time,
type: typeInfo.class,
message: `${typeInfo.icon} ${message}`,
html: `
<div class="log-item ${typeInfo.class}">
<div class="log-time">${time}</div>
<div class="log-message">${typeInfo.icon} ${message}</div>
</div>
`
};
}
static exportToCSV(data, filename = 'transfer-results.csv') {
const headers = ['序号', '收款地址', '金额(BNB)', '状态', '交易哈希', '备注'];
const rows = data.map(item => [
item.index + 1,
item.to,
item.amount,
item.success ? '成功' : '失败',
item.hash || '',
item.error || ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(cell => `"${cell}"`).join(','))
.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
} else {
link.href = URL.createObjectURL(blob);
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
static validateEthereumAddress(address) {
try {
return ethers.utils.isAddress(address);
} catch {
return false;
}
}
static async checkBalanceSufficient(provider, address, requiredAmount) {
try {
const balance = await provider.getBalance(address);
const required = ethers.utils.parseEther(requiredAmount.toString());
return balance.gte(required);
} catch (error) {
console.error('检查余额失败:', error);
return false;
}
}
}
// 导出工具类
window.TransferUtils = TransferUtils;