114 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			114 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package ethbalanceofhelper
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"go-admin/abis"
 | |
| 	"math/big"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/ethereum/go-ethereum"
 | |
| 	"github.com/ethereum/go-ethereum/accounts/abi"
 | |
| 	"github.com/ethereum/go-ethereum/common"
 | |
| 	"github.com/ethereum/go-ethereum/ethclient"
 | |
| 	"github.com/ethereum/go-ethereum/rpc"
 | |
| 	"github.com/shopspring/decimal"
 | |
| )
 | |
| 
 | |
| // ERC-20 代币合约 ABI (部分,只包含 balanceOf)
 | |
| // ERC-20 代币合约 ABI (部分,只包含 balanceOf 和 decimals)
 | |
| const erc20ABI = `[
 | |
|     {
 | |
|         "constant": true,
 | |
|         "inputs": [ { "name": "_owner", "type": "address" } ],
 | |
|         "name": "balanceOf",
 | |
|         "outputs": [ { "name": "balance", "type": "uint256" } ],
 | |
|         "type": "function"
 | |
|     },
 | |
|     {
 | |
|         "constant": true,
 | |
|         "inputs": [],
 | |
|         "name": "decimals",
 | |
|         "outputs": [ { "name": "", "type": "uint8" } ],
 | |
|         "type": "function"
 | |
|     }
 | |
| ]`
 | |
| 
 | |
| // GetERC20Balance 查询 ERC-20 代币余额并转换为正常单位 (使用 decimal)
 | |
| func GetERC20Balance(client *ethclient.Client, tokenAbi abis.TokenABI, accountAddress string) (decimal.Decimal, error) {
 | |
| 	// 1. 解析 ABI
 | |
| 	contractABI, err := abi.JSON(strings.NewReader(erc20ABI))
 | |
| 	if err != nil {
 | |
| 		return decimal.Zero, fmt.Errorf("解析 ABI 失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 2. 构造 balanceOf 函数调用数据
 | |
| 	balanceOfCallData, err := contractABI.Pack("balanceOf", common.HexToAddress(accountAddress))
 | |
| 	if err != nil {
 | |
| 		return decimal.Zero, fmt.Errorf("构造 balanceOf 调用数据失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	address := common.HexToAddress(tokenAbi.TestAddress)
 | |
| 	// 3. 执行 balanceOf 调用
 | |
| 	balanceResult, err := client.CallContract(context.Background(), ethereum.CallMsg{
 | |
| 		To:   &address,
 | |
| 		Data: balanceOfCallData,
 | |
| 	}, nil)
 | |
| 	if err != nil {
 | |
| 		return decimal.Zero, fmt.Errorf("调用 balanceOf 失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 4. 解析 balanceOf 结果
 | |
| 	unpackedBalance, err := contractABI.Unpack("balanceOf", balanceResult)
 | |
| 	if err != nil {
 | |
| 		return decimal.Zero, fmt.Errorf("解析 balanceOf 结果失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	balance, ok := unpackedBalance[0].(*big.Int)
 | |
| 	if !ok {
 | |
| 		return decimal.Zero, errors.New("解析 balanceOf 结果为 *big.Int 失败")
 | |
| 	}
 | |
| 
 | |
| 	// 8. 转换为正常单位 (使用 decimal)
 | |
| 	balanceDecimal := decimal.NewFromBigInt(balance, 0) // Create decimal from big.Int
 | |
| 	decimalFactor := decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(tokenAbi.Decimals)))
 | |
| 	readableBalance := balanceDecimal.Div(decimalFactor)
 | |
| 
 | |
| 	return readableBalance, nil
 | |
| }
 | |
| 
 | |
| // 初始化以太坊客户端连接代理
 | |
| func EthClientWithProxy(rawURL string, proxyURL string) (*ethclient.Client, error) {
 | |
| 	u, err := url.Parse(rawURL)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("invalid URL: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if proxyURL == "" {
 | |
| 		return ethclient.Dial(rawURL)
 | |
| 	}
 | |
| 
 | |
| 	proxy, err := url.Parse(proxyURL)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("invalid proxy URL: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	httpClient := &http.Client{
 | |
| 		Transport: &http.Transport{
 | |
| 			Proxy: http.ProxyURL(proxy),
 | |
| 		},
 | |
| 	}
 | |
| 	dialOptions := []rpc.ClientOption{
 | |
| 		rpc.WithHTTPClient(httpClient),
 | |
| 	}
 | |
| 	rpcClient, err := rpc.DialOptions(context.Background(), u.String(), dialOptions...)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to dial RPC client with options: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return ethclient.NewClient(rpcClient), nil
 | |
| }
 |