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
|
||
|
|
}
|