merge transactions and update

This commit is contained in:
lzx
2025-10-31 13:46:58 +08:00
parent 056bc05b75
commit 8d7da5d345
12 changed files with 1154 additions and 975 deletions

View File

@@ -6,10 +6,10 @@ import (
)
type IChainServer interface {
AddAddress(address string, msg any) error
RemoveAddress(address string) error
Listen(symbol string, ch chan any)
Transfer(symbol string, msg any) error
AddAddress(msg any) error
RemoveAddress(msg any) error
Listen(ch chan any)
Transfer(msg any) error
Stop()
}
@@ -30,40 +30,40 @@ func (b *BlockChainServer) RegisterChain(name string, chain IChainServer) {
b.chains[name] = chain
}
func (b *BlockChainServer) AddAddress(chain, address string, msg any) error {
func (b *BlockChainServer) AddAddress(chain string, msg any) error {
if srv, ok := b.chains[chain]; ok {
srv.AddAddress(address, msg)
fmt.Printf("✅ 添加监听地址: chain=%s, address=%s\n", chain, address)
srv.AddAddress(msg)
fmt.Printf("✅ 添加监听地址: chain=%s, msg=%v\n", chain, msg)
return nil
} else {
return fmt.Errorf("⚠️ 链未注册: %s\n", chain)
}
}
func (b *BlockChainServer) RemoveAddress(chain, address string) error {
func (b *BlockChainServer) RemoveAddress(chain string, msg any) error {
if srv, ok := b.chains[chain]; ok {
srv.RemoveAddress(address)
fmt.Printf("🗑️ 移除监听地址: chain=%s, address=%s\n", chain, address)
srv.RemoveAddress(msg)
fmt.Printf("🗑️ 移除监听地址: chain=%s, msg=%s\n", chain, msg)
return nil
} else {
return fmt.Errorf("⚠️ 链未注册: %s\n", chain)
}
}
func (b *BlockChainServer) Listen(chain, symbol string, ch chan any) error {
func (b *BlockChainServer) Listen(chain string, ch chan any) error {
if srv, ok := b.chains[chain]; ok {
go func() {
srv.Listen(symbol, ch)
srv.Listen(ch)
}()
return nil
}
return fmt.Errorf("链未注册: %s", chain)
}
func (b *BlockChainServer) Transfer(chain, symbol string, msg any) error {
func (b *BlockChainServer) Transfer(chain string, msg any) error {
if srv, ok := b.chains[chain]; ok {
fmt.Printf("💸 %s-%s发起转账: %+v\n", chain, symbol, msg)
return srv.Transfer(symbol, msg)
fmt.Printf("💸 %s发起转账: %+v\n", chain, msg)
return srv.Transfer(msg)
}
return fmt.Errorf("链未注册: %s", chain)
}

View File

@@ -1,213 +0,0 @@
package eth
import (
"crypto/ecdsa"
"fmt"
"log"
"m2pool-payment/internal/utils"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
// BatchTransferItem 批量转账单项
type BatchTransferItem struct {
ToAddress string // 接收地址
Amount float64 // 转账金额
}
// BatchTransferResult 批量转账结果
type BatchTransferResult struct {
TxHash string // 交易哈希
Success bool // 是否成功
TotalAmount float64 // 总转账金额
Count int // 转账笔数
}
// usdt_batch_transfer 批量转账ERC20-USDT
// from: 发送地址
// items: 批量转账列表
// returns: 交易哈希和错误信息
func (e *ETHNode) USDTBatchTransfer(from string, items []BatchTransferItem) (*BatchTransferResult, error) {
if len(items) == 0 {
return nil, fmt.Errorf("批量转账列表不能为空")
}
// 统一转换为小写
from = strings.ToLower(from)
// 计算总金额
var totalAmount float64
for _, item := range items {
if item.Amount <= 0 {
return nil, fmt.Errorf("转账金额必须大于0")
}
totalAmount += item.Amount
}
// 1. 校验钱包USDT余额
balance, err := e.getUSDTBalance(from)
log.Printf("🔄 批量转账 - 检测钱包=%s余额=%.2f USDT", from, balance)
if err != nil {
return nil, fmt.Errorf("获取余额失败: %w", err)
}
if balance < totalAmount {
return nil, fmt.Errorf("余额不足: 余额=%.2f USDT < 需要=%.2f USDT", balance, totalAmount)
}
// 2. 通过from地址前往数据库查找出对应加密后的私钥并解密真实的私钥
originalKey := e.decodePrivatekey(from)
if originalKey == "" {
return nil, fmt.Errorf("无法获取私钥")
}
privateKey, err := crypto.HexToECDSA(originalKey)
if err != nil {
return nil, fmt.Errorf("解析私钥失败: %w", err)
}
// 3. 获得nonce
nonce, err := e.RpcClient.PendingNonceAt(e.Ctx, common.HexToAddress(from))
if err != nil {
return nil, fmt.Errorf("获取nonce失败: %w", err)
}
// 4. 构造批量转账数据
// 使用 transfer(address[], uint256[]) 或多次transfer调用
// 这里使用多次transfer调用的方式因为标准ERC20没有批量转账方法
// 方法1: 构造多次transfer调用适合少量转账
if len(items) <= 3 {
return e.batchTransferMultipleCalls(from, privateKey, nonce, items)
}
// 方法2: 使用合约批量转账(需要部署代理合约,这里简化处理)
// 注意这里实现的是多次transaction方式
return e.batchTransferSeparateTransactions(from, privateKey, nonce, items)
}
// batchTransferMultipleCalls 使用一个交易多次调用transfer需要gas优化
func (e *ETHNode) batchTransferMultipleCalls(from string, privateKey *ecdsa.PrivateKey, nonce uint64, items []BatchTransferItem) (*BatchTransferResult, error) {
// 注意标准ERC20不支持批量transfer这里需要自定义合约
// 或者使用多次独立交易
log.Printf("⚠️ 标准ERC20不支持批量transfer改用多次独立交易")
// 回退到多次独立交易
return e.batchTransferSeparateTransactions(from, privateKey, nonce, items)
}
// batchTransferSeparateTransactions 执行多次独立的transfer交易
func (e *ETHNode) batchTransferSeparateTransactions(from string, privateKey *ecdsa.PrivateKey, nonce uint64, items []BatchTransferItem) (*BatchTransferResult, error) {
var totalAmount float64
var txHashes []string
var allSuccess bool = true
for i, item := range items {
// 构造单个transfer交易
amountBigInt := utils.Float64ToBigIntUSDT(item.Amount)
data, err := e.USDT.ABI.Pack("transfer", common.HexToAddress(strings.ToLower(item.ToAddress)), amountBigInt)
if err != nil {
log.Printf("❌ 批量转账第%d笔打包失败: %v", i+1, err)
allSuccess = false
continue
}
// 获取gas limit
gasLimit, err := e.getGasLimit()
if err != nil {
log.Printf("❌ 批量转账第%d笔获取gasLimit失败: %v", i+1, err)
allSuccess = false
continue
}
// 获取gas费用
maxFeePerGas, maxPriorityFeePerGas, err := e.getEIP1559GasFees()
var txHash string
if err != nil {
// 回退到传统gas price
gasPrice, err := e.getSuggestGasPrice()
if err != nil {
log.Printf("❌ 批量转账第%d笔获取gasPrice失败: %v", i+1, err)
allSuccess = false
continue
}
tx := types.NewTransaction(nonce+uint64(i), e.USDT.Address, big.NewInt(0), gasLimit, gasPrice, data)
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(e.NetId), privateKey)
if err != nil {
log.Printf("❌ 批量转账第%d笔签名失败: %v", i+1, err)
allSuccess = false
continue
}
txHash = signedTx.Hash().Hex()
err = e.RpcClient.SendTransaction(e.Ctx, signedTx)
if err != nil {
log.Printf("❌ 批量转账第%d笔发送失败: %v", i+1, err)
allSuccess = false
continue
}
} else {
// 使用EIP-1559交易
ethBalance, err := e.getETHBlance(from)
if err != nil {
log.Printf("❌ 批量转账第%d笔获取ETH余额失败: %v", i+1, err)
allSuccess = false
continue
}
maxGasCost := new(big.Int).Mul(new(big.Int).SetUint64(gasLimit), maxFeePerGas)
if ethBalance.Cmp(maxGasCost) == -1 {
log.Printf("❌ 批量转账第%d笔ETH余额不足", i+1)
allSuccess = false
continue
}
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: e.NetId,
Nonce: nonce + uint64(i),
GasTipCap: maxPriorityFeePerGas,
GasFeeCap: maxFeePerGas,
Gas: gasLimit,
To: &e.USDT.Address,
Value: big.NewInt(0),
Data: data,
})
signedTx, err := types.SignTx(tx, types.NewLondonSigner(e.NetId), privateKey)
if err != nil {
log.Printf("❌ 批量转账第%d笔签名失败: %v", i+1, err)
allSuccess = false
continue
}
txHash = signedTx.Hash().Hex()
err = e.RpcClient.SendTransaction(e.Ctx, signedTx)
if err != nil {
log.Printf("❌ 批量转账第%d笔发送失败: %v", i+1, err)
allSuccess = false
continue
}
}
txHashes = append(txHashes, txHash)
totalAmount += item.Amount
log.Printf("✅ 批量转账第%d笔已提交: %s, 金额=%.2f USDT, 收款地址=%s",
i+1, txHash, item.Amount, strings.ToLower(item.ToAddress))
}
log.Printf("📊 批量转账完成: 总计%d笔, 成功%d笔, 总金额=%.2f USDT",
len(items), len(txHashes), totalAmount)
return &BatchTransferResult{
TxHash: strings.Join(txHashes, ","),
Success: allSuccess && len(txHashes) == len(items),
TotalAmount: totalAmount,
Count: len(txHashes),
}, nil
}

View File

@@ -1,101 +0,0 @@
# ERC20-USDT 批量转账功能
## 功能说明
该文件 `batch_transfer.go` 提供了 ERC20-USDT 的批量转账功能,支持从同一个发送地址向多个不同的接收地址转账。
## 主要功能
### 1. 批量转账类型
```go
type BatchTransferItem struct {
ToAddress string // 接收地址
Amount float64 // 转账金额
}
type BatchTransferResult struct {
TxHash string // 交易哈希(多个用逗号分隔)
Success bool // 是否成功
TotalAmount float64 // 总转账金额
Count int // 转账笔数
}
```
### 2. 使用方法
```go
// 1. 准备批量转账列表
items := []eth.BatchTransferItem{
{ToAddress: "0xRecipient1", Amount: 100.0},
{ToAddress: "0xRecipient2", Amount: 200.0},
{ToAddress: "0xRecipient3", Amount: 50.0},
}
// 2. 调用批量转账
fromAddress := "0xYourAddress"
result, err := ethNode.USDTBatchTransfer(fromAddress, items)
if err != nil {
log.Fatalf("批量转账失败: %v", err)
}
// 3. 处理结果
fmt.Printf("批量转账完成: %d笔, 总金额: %.2f USDT", result.Count, result.TotalAmount)
fmt.Printf("交易哈希: %s", result.TxHash)
```
## 工作原理
由于标准 ERC20 合约不支持批量转账,本实现采用以下策略:
1. **多次独立交易**:对每笔转账创建一个独立的 ERC20 `transfer` 交易
2. **Nonce 管理**:自动管理 nonce确保交易按顺序广播
3. **Gas 费用**:支持 EIP-1559 动态费用和传统 gas price
4. **错误处理**:单笔失败不影响其他交易,返回成功和失败的详细统计
## 注意事项
### 1. Gas 费用
- 每笔转账需要独立的 gas 费用(约 65,000 gas
- 批量转账 10 笔需要约 650,000 gas
- 确保发送地址有足够的 ETH 作为 gas 费用
### 2. 余额检查
- 函数会自动检查 USDT 余额是否足够
- 如果余额不足,会返回错误并终止转账
### 3. 部分成功
- 如果某些转账失败,函数会继续执行其他转账
- 返回结果中包含成功笔数和详细交易哈希
### 4. 网络拥堵
- 在高网络拥堵时,某些交易可能被推迟
- 建议监控所有交易状态
## 性能优化建议
如果需要更高效的批量转账,考虑:
1. **部署批量转账代理合约**:实现一个合约方法 `batchTransfer(address[] to, uint256[] amounts)`
2. **使用多签钱包**:减少私钥管理风险
3. **Gas 优化**:使用更低的 gas price 分批发送
## 示例输出
```
🔄 批量转账 - 检测钱包=0x...,余额=1000.00 USDT
✅ 批量转账第1笔已提交: 0xabc123..., 金额=100.00 USDT, 收款地址=0x...
✅ 批量转账第2笔已提交: 0xdef456..., 金额=200.00 USDT, 收款地址=0x...
✅ 批量转账第3笔已提交: 0x789ghi..., 金额=50.00 USDT, 收款地址=0x...
📊 批量转账完成: 总计3笔, 成功3笔, 总金额=350.00 USDT
```
## 限制
- 标准 ERC20 不支持真正的批量转账(单笔交易)
- 需要确保发送地址有足够的 ETH 作为 gas 费用
- 交易按顺序发送,可能在高负载时较慢

View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MultiSend {
function multiTransfer(address[] calldata to, uint256[] calldata amounts) external payable {
uint256 length = to.length;
require(length == amounts.length, "Arrays must have the same length");
for (uint256 i = 0; i < length; i++) {
payable(to[i]).transfer(amounts[i]);
}
}
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
}
contract MultiSendUSDT {
address public tokenAddress;
constructor(address _tokenAddress) {
tokenAddress = _tokenAddress;
}
function multiTransfer(address[] calldata to, uint256[] calldata amounts) external {
IERC20 token = IERC20(tokenAddress);
uint256 length = to.length;
require(length == amounts.length, "Arrays must have the same length");
for (uint256 i = 0; i < length; i++) {
token.transfer(to[i], amounts[i]);
}
}
}

File diff suppressed because it is too large Load Diff