update
This commit is contained in:
213
internal/blockchain/eth/batch_transfer.go
Normal file
213
internal/blockchain/eth/batch_transfer.go
Normal file
@@ -0,0 +1,213 @@
|
||||
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
|
||||
}
|
||||
101
internal/blockchain/eth/batch_transfer_example.md
Normal file
101
internal/blockchain/eth/batch_transfer_example.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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 费用
|
||||
- 交易按顺序发送,可能在高负载时较慢
|
||||
@@ -125,27 +125,25 @@ func NewETHNode(cfg message.ETHConfig, decodeKey string) (*ETHNode, error) {
|
||||
}
|
||||
|
||||
// ============================ 抽象接口 ============================
|
||||
func (e *ETHNode) AddAddress(address string, rmq_msg any) {
|
||||
func (e *ETHNode) AddAddress(address string, rmq_msg any) error {
|
||||
// 统一转换为小写
|
||||
address = strings.ToLower(address)
|
||||
log.Printf("新增钱包监听消息:%v", rmq_msg)
|
||||
e.ListenAddresses.Store(address, true)
|
||||
e.mu.Lock()
|
||||
if len(e.RmqMsgs[address]) == 0 {
|
||||
e.RmqMsgs[address] = []any{rmq_msg}
|
||||
} else {
|
||||
e.RmqMsgs[address] = append(e.RmqMsgs[address], rmq_msg)
|
||||
}
|
||||
e.RmqMsgs[address] = append(e.RmqMsgs[address], rmq_msg)
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ETHNode) RemoveAddress(address string) {
|
||||
func (e *ETHNode) RemoveAddress(address string) error {
|
||||
// 统一转换为小写
|
||||
address = strings.ToLower(address)
|
||||
e.ListenAddresses.Delete(address)
|
||||
e.mu.Lock()
|
||||
delete(e.RmqMsgs, address)
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ETHNode) Listen(symbol string, ch chan any) {
|
||||
@@ -174,6 +172,10 @@ func (e *ETHNode) Transfer(symbol string, msg any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ETHNode) Stop() {
|
||||
e.Cancel()
|
||||
}
|
||||
|
||||
// ============================ rpc节点方法 ============================
|
||||
|
||||
func (e *ETHNode) getETHBlance(address string) (*big.Int, error) {
|
||||
@@ -221,12 +223,11 @@ func (e *ETHNode) getUSDTBalance(address string) (float64, error) {
|
||||
return bal, nil
|
||||
}
|
||||
|
||||
func (e *ETHNode) getBlockHeight() (uint64, error) {
|
||||
header, err := e.RpcClient.HeaderByNumber(e.Ctx, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get latest block header: %w", err)
|
||||
}
|
||||
return header.Number.Uint64(), nil
|
||||
func (e *ETHNode) getGasLimit() (uint64, error) {
|
||||
// 对于ERC20转账,使用固定的gas limit
|
||||
// 通常ERC20 transfer需要约65,000-100,000 gas
|
||||
// 这里设置为80,000,足够覆盖大部分情况
|
||||
return 80000, nil
|
||||
}
|
||||
|
||||
func (e *ETHNode) getSuggestGasPrice() (*big.Int, error) {
|
||||
@@ -235,9 +236,56 @@ func (e *ETHNode) getSuggestGasPrice() (*big.Int, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get suggest-gasprice error:%v", err)
|
||||
}
|
||||
|
||||
// 设置gas price上限,避免在网络拥堵时费用过高
|
||||
// 这里设置为20 Gwei (20 * 10^9 wei)
|
||||
maxGasPrice := new(big.Int).SetUint64(20000000000) // 20 Gwei
|
||||
|
||||
if gasPrice.Cmp(maxGasPrice) > 0 {
|
||||
log.Printf("⚠️ 建议gas price过高 (%v Gwei),使用上限 20 Gwei", new(big.Int).Div(gasPrice, big.NewInt(1000000000)))
|
||||
return maxGasPrice, nil
|
||||
}
|
||||
|
||||
log.Printf("✅ 使用建议gas price: %v Gwei", new(big.Int).Div(gasPrice, big.NewInt(1000000000)))
|
||||
return gasPrice, nil
|
||||
}
|
||||
|
||||
// getEIP1559GasFees 获取EIP-1559的gas费用参数
|
||||
func (e *ETHNode) getEIP1559GasFees() (*big.Int, *big.Int, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 获取基础费用
|
||||
latestBlock, err := e.RpcClient.BlockByNumber(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get latest block: %w", err)
|
||||
}
|
||||
|
||||
baseFee := latestBlock.BaseFee()
|
||||
if baseFee == nil {
|
||||
return nil, nil, fmt.Errorf("base fee not available")
|
||||
}
|
||||
|
||||
// 设置优先级费用(tip),这里设置为2 Gwei
|
||||
maxPriorityFeePerGas := new(big.Int).SetUint64(2000000000) // 2 Gwei
|
||||
|
||||
// 计算最大费用 = 基础费用 + 优先级费用
|
||||
maxFeePerGas := new(big.Int).Add(baseFee, maxPriorityFeePerGas)
|
||||
|
||||
// 设置最大费用上限为30 Gwei
|
||||
maxFeeLimit := new(big.Int).SetUint64(30000000000) // 30 Gwei
|
||||
if maxFeePerGas.Cmp(maxFeeLimit) > 0 {
|
||||
log.Printf("⚠️ 计算的最大费用过高 (%v Gwei),使用上限 30 Gwei", new(big.Int).Div(maxFeePerGas, big.NewInt(1000000000)))
|
||||
maxFeePerGas = maxFeeLimit
|
||||
}
|
||||
|
||||
log.Printf("✅ EIP-1559 Gas费用: BaseFee=%v Gwei, MaxPriorityFee=%v Gwei, MaxFee=%v Gwei",
|
||||
new(big.Int).Div(baseFee, big.NewInt(1000000000)),
|
||||
new(big.Int).Div(maxPriorityFeePerGas, big.NewInt(1000000000)),
|
||||
new(big.Int).Div(maxFeePerGas, big.NewInt(1000000000)))
|
||||
|
||||
return maxFeePerGas, maxPriorityFeePerGas, nil
|
||||
}
|
||||
|
||||
// ============================ 业务方法 ============================
|
||||
func (e *ETHNode) listen_usdt(ch chan any) error {
|
||||
fmt.Println("🔍 ETH 开始监听 USDT Transfer 事件...")
|
||||
@@ -257,6 +305,7 @@ func (e *ETHNode) listen_usdt(ch chan any) error {
|
||||
fmt.Println("✅ 订阅成功")
|
||||
// 处理事件
|
||||
for {
|
||||
|
||||
select {
|
||||
case err := <-sub.Err():
|
||||
fmt.Println("⚠️ 订阅异常,准备重连:", err)
|
||||
@@ -578,7 +627,6 @@ func (e *ETHNode) decodePrivatekey(address string) string {
|
||||
}
|
||||
// 使用key解密
|
||||
privateKey := encryptedKey // 实际使用时替换成具体的解密代码
|
||||
// fmt.Println(privateKey)
|
||||
return privateKey
|
||||
}
|
||||
|
||||
@@ -624,7 +672,6 @@ func (e *ETHNode) usdt_transfer(msg any) error {
|
||||
if originalKey == "" {
|
||||
return fmt.Errorf("failed to query privatekey")
|
||||
}
|
||||
fmt.Println(originalKey)
|
||||
privateKey, err := crypto.HexToECDSA(originalKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse private key: %w", err)
|
||||
@@ -640,41 +687,125 @@ func (e *ETHNode) usdt_transfer(msg any) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to pack transfer data: %w", err)
|
||||
}
|
||||
gasPrice, err := e.getSuggestGasPrice() // 获得当前建议gasPrice
|
||||
gasLimit, err := e.getGasLimit() // 获得gasLimit
|
||||
if err != nil {
|
||||
return fmt.Errorf("get suggest-gasprice error:%v", err)
|
||||
return fmt.Errorf("get gas limit error:%v", err)
|
||||
}
|
||||
eth_balance, err := e.getETHBlance(final_from) // 获得钱包eth余额
|
||||
|
||||
// 获取EIP-1559 gas费用参数
|
||||
maxFeePerGas, maxPriorityFeePerGas, err := e.getEIP1559GasFees()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取EIP-1559费用失败,回退到传统gas price: %v", err)
|
||||
// 回退到传统gas price
|
||||
gasPrice, err := e.getSuggestGasPrice()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get suggest-gasprice error:%v", err)
|
||||
}
|
||||
|
||||
eth_balance, err := e.getETHBlance(final_from)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
|
||||
gasLimit_b := new(big.Int).SetUint64(gasLimit)
|
||||
gas := new(big.Int).Mul(gasLimit_b, gasPrice)
|
||||
|
||||
// 计算gas费用(以ETH为单位)
|
||||
gasInETH := new(big.Float).SetInt(gas)
|
||||
gasInETH.Quo(gasInETH, new(big.Float).SetInt64(1000000000000000000))
|
||||
|
||||
log.Printf("💰 传统Gas费用预估: Limit=%d, Price=%v Gwei, 总费用=%.6f ETH",
|
||||
gasLimit,
|
||||
new(big.Int).Div(gasPrice, big.NewInt(1000000000)),
|
||||
gasInETH)
|
||||
|
||||
// 判断钱包eth是否支持本次交易gas费用
|
||||
if eth_balance.Cmp(gas) == -1 {
|
||||
ethBalanceInETH := new(big.Float).SetInt(eth_balance)
|
||||
ethBalanceInETH.Quo(ethBalanceInETH, new(big.Float).SetInt64(1000000000000000000))
|
||||
return fmt.Errorf("❌ 地址 %s ETH余额不足: %.6f ETH < %.6f ETH (gas费用)",
|
||||
final_from, ethBalanceInETH, gasInETH)
|
||||
}
|
||||
|
||||
// 构造传统交易
|
||||
tx := types.NewTransaction(
|
||||
nonce,
|
||||
e.USDT.Address,
|
||||
big.NewInt(0),
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
data,
|
||||
)
|
||||
|
||||
// 签名并发送传统交易
|
||||
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(e.NetId), privateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign transaction: %w", err)
|
||||
}
|
||||
|
||||
txHash := signedTx.Hash().Hex()
|
||||
err = e.RpcClient.SendTransaction(e.Ctx, signedTx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send transaction: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ 传统交易已提交至mempool:%s,金额:%.2f USDT, 手续费:%.6f ETH", txHash, amount, gasInETH)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 使用EIP-1559交易
|
||||
eth_balance, err := e.getETHBlance(final_from)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w", err)
|
||||
}
|
||||
var gasLimit uint64 = 100000
|
||||
gasLimit_b := new(big.Int).SetUint64(gasLimit)
|
||||
gas := new(big.Int).Mul(gasLimit_b, gasPrice)
|
||||
|
||||
// 计算最大可能的gas费用
|
||||
maxGasCost := new(big.Int).Mul(new(big.Int).SetUint64(gasLimit), maxFeePerGas)
|
||||
|
||||
// 计算gas费用(以ETH为单位)
|
||||
maxGasCostInETH := new(big.Float).SetInt(maxGasCost)
|
||||
maxGasCostInETH.Quo(maxGasCostInETH, new(big.Float).SetInt64(1000000000000000000))
|
||||
|
||||
log.Printf("💰 EIP-1559 Gas费用预估: Limit=%d, MaxFee=%v Gwei, MaxPriorityFee=%v Gwei, 最大费用=%.6f ETH",
|
||||
gasLimit,
|
||||
new(big.Int).Div(maxFeePerGas, big.NewInt(1000000000)),
|
||||
new(big.Int).Div(maxPriorityFeePerGas, big.NewInt(1000000000)),
|
||||
maxGasCostInETH)
|
||||
|
||||
// 判断钱包eth是否支持本次交易gas费用
|
||||
if eth_balance.Cmp(gas) == -1 {
|
||||
return fmt.Errorf("address=%s balance less than gas=%v(wei)", final_from, eth_balance)
|
||||
if eth_balance.Cmp(maxGasCost) == -1 {
|
||||
ethBalanceInETH := new(big.Float).SetInt(eth_balance)
|
||||
ethBalanceInETH.Quo(ethBalanceInETH, new(big.Float).SetInt64(1000000000000000000))
|
||||
return fmt.Errorf("❌ 地址 %s ETH余额不足: %.6f ETH < %.6f ETH (最大gas费用)",
|
||||
final_from, ethBalanceInETH, maxGasCostInETH)
|
||||
}
|
||||
// 构造发送到 USDT 合约地址的交易
|
||||
tx := types.NewTransaction(
|
||||
nonce,
|
||||
e.USDT.Address, // 发送到USDT合约地址
|
||||
big.NewInt(0), // value为0(ERC20转账不需要ETH)
|
||||
gasLimit, // GasLimit设置为100000(ERC20转账需要更多gas)
|
||||
gasPrice, // GasPrice: 20 Gwei
|
||||
data, // 附加数据:transfer方法调用
|
||||
)
|
||||
// 6, 签名交易并获得txHash
|
||||
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(e.NetId), privateKey)
|
||||
// txHash := signedTx.Hash().Hex() // 通过签名信息解析出交易hash
|
||||
|
||||
// 构造EIP-1559交易
|
||||
tx := types.NewTx(&types.DynamicFeeTx{
|
||||
ChainID: e.NetId,
|
||||
Nonce: nonce,
|
||||
GasTipCap: maxPriorityFeePerGas,
|
||||
GasFeeCap: maxFeePerGas,
|
||||
Gas: gasLimit,
|
||||
To: &e.USDT.Address,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
})
|
||||
// 6, 签名EIP-1559交易并获得txHash
|
||||
signedTx, err := types.SignTx(tx, types.NewLondonSigner(e.NetId), privateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign transaction: %w", err)
|
||||
return fmt.Errorf("failed to sign EIP-1559 transaction: %w", err)
|
||||
}
|
||||
// 7, 发送交易
|
||||
|
||||
txHash := signedTx.Hash().Hex()
|
||||
|
||||
// 7, 发送EIP-1559交易
|
||||
err = e.RpcClient.SendTransaction(e.Ctx, signedTx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send transaction: %w", err)
|
||||
return fmt.Errorf("failed to send EIP-1559 transaction: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("✅ EIP-1559交易已提交至mempool:%s,金额:%.2f USDT, 最大手续费:%.6f ETH", txHash, amount, maxGasCostInETH)
|
||||
// // 8, 构造交易消息
|
||||
// tx_msg := message.Tx_msg{
|
||||
// TxType: tx_type,
|
||||
@@ -692,7 +823,3 @@ func (e *ETHNode) usdt_transfer(msg any) error {
|
||||
// e.UnConfirmTxs[txHash] = tx_msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *ETHNode) Stop() {
|
||||
e.Cancel()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user