214 lines
6.5 KiB
Go
214 lines
6.5 KiB
Go
|
|
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
|
|||
|
|
}
|