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 }