package ethbalanceofhelper import ( "context" "errors" "fmt" "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" } ]` // GetBalance 查询 余额并转换为正常单位 (使用 decimal) func GetBalance(client *ethclient.Client, tokenAddress, accountAddress string, decimals int) (decimal.Decimal, error) { switch tokenAddress { case "0": return GetEthBalance(client, accountAddress, decimals) default: return GetERC20Balance(client, tokenAddress, accountAddress, decimals) } } // GetEthBalance 查询以太坊余额并转换为正常单位 (使用 decimal) func GetEthBalance(client *ethclient.Client, accountAddress string, decimals int) (decimal.Decimal, error) { account := common.HexToAddress(accountAddress) balance, err := client.BalanceAt(context.Background(), account, nil) if err != nil { return decimal.Zero, fmt.Errorf("查询余额失败 err:%v", err) } balanceDecimal := decimal.NewFromBigInt(balance, 0) // Create decimal from big.Int decimalFactor := decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimals))) readableBalance := balanceDecimal.Div(decimalFactor) return readableBalance, nil } // GetERC20Balance 查询 ERC-20 代币余额并转换为正常单位 (使用 decimal) // tokenAddress: 代币合约地址 // accountAddress: 账户地址 // decimals: 代币精度 // 返回值: 代币余额 (decimal.Decimal) func GetERC20Balance(client *ethclient.Client, tokenAddress, accountAddress string, decimals int) (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(tokenAddress) // 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(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 }