3. go语言从零实现以太坊客户端 - 查询合约中账户余额

go语言从零实现以太坊客户端 - 查询合约中账户余额

  • [1. 环境配置和合约代码](#1. 环境配置和合约代码)
  • [2. 编写调试代码](#2. 编写调试代码)
    • [2.1 基类,用于初始化常量](#2.1 基类,用于初始化常量)
    • [2.2 核心业务类](#2.2 核心业务类)
    • [2.3 JSON-RPC库](#2.3 JSON-RPC库)
    • [2.4 组装传输对象](#2.4 组装传输对象)
    • [2.5 装饰器](#2.5 装饰器)
    • [2.6 工具类](#2.6 工具类)
    • [2.7 主函数](#2.7 主函数)
  • [3. 测试](#3. 测试)
    • [3.1 hardhat node节点](#3.1 hardhat node节点)
    • [3.2 sepolia节点](#3.2 sepolia节点)

代码链接

1. 环境配置和合约代码

参考文章12. Hardhat编写、编译、部署、测试Solidity ERC20合约 - 进阶篇 - Metamask导入代币,转账ETH,转账代币

GoLand 2025.2.4版本

go 1.25.4

2. 编写调试代码

可连接hardhat node本地网络和sepolia测试网络

为了突出业务逻辑,省略了错误处理。

2.1 基类,用于初始化常量

后面文章中的类均继承BaseService类

clike 复制代码
package service

import "client/utils/jsonrpc"

type BaseService struct {
	url          string
	contractAddr string
	from         string
	to           string
	privateKey   string
	chainID      uint64
}

func (this *BaseService) Init() {
	this.url = "http://localhost:8545"
	this.contractAddr = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
	// hardhat 默认账户地址1
	this.from = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
	// hardhat 默认账户地址2
	this.to = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
	this.privateKey = 你自己的私钥
	this.chainID = uint64(1337)

	//this.url = "https://ethereum-sepolia-rpc.publicnode.com"
	//this.contractAddr = "0x451Dc02Cee616361815253C858Df0a3028c42901"
	//// sepolia 账户地址1
	//this.from = "0xc312c07abEb8246510412a7ce87A295E0ceC5D48"
	//// sepolia 账户地址1
	//this.to = "0xE78Ff27498c9a6Fd8BC3ff8170Ecf9a13ECBE49e"
	//this.privateKey = 你自己的私钥
	//this.chainID = uint64(11155111)

	//this.url = "https://webhook.site/fc1b1d2d-7dcd-4a2a-b653-16d8f2b9de0e"
	jsonrpc.InitJsonRPCClient(this.url)
}

2.2 核心业务类

继承基类。查询合约中代币名称和账户余额

//go:decor decorator.LogJsonRPC是装饰器,用来做返回值类型转换。以太坊JSON-RPC返回的数据是十六进制字符串,要转换为对应的类型

clike 复制代码
package service

import (
	_ "client/domain/decorator"
	"client/utils/jsonrpc"

	_ "github.com/dengsgo/go-decorator/decor"
)

type ContractRead struct {
	BaseService
}

func (this *ContractRead) GetName() interface{} {

	return jsonrpc.SendJsonRPCContract("eth_call", this.contractAddr, "name()", nil)
}

func (this *ContractRead) GetBalance() interface{} {
	return jsonrpc.SendJsonRPCContract("eth_call", this.contractAddr, "balanceOf(address)", this.from)
}

2.3 JSON-RPC库

使用JSON-RPC库github.com/ybbus/jsonrpc/v3,组装

clike 复制代码
{
	jsonrpc: '2.0',
	method: "eth_getBalance"
	params: [this.from, "latest"]
	id: 1
}
clike 复制代码
package jsonrpc

import (
	_ "client/domain/decorator"
	"context"

	_ "github.com/dengsgo/go-decorator/decor"
	"github.com/ybbus/jsonrpc/v3"
)

var JsonRPCClient jsonrpc.RPCClient

func InitJsonRPCClient(url string) {
	JsonRPCClient = jsonrpc.NewClient(url)
}

func SendJsonRPC(funcName string, params ...interface{}) interface{} {

	var rsp interface{}
	err := JsonRPCClient.CallFor(context.Background(), &rsp, funcName, params)
	if err != nil {
		panic(err)
	}
	return rsp
}

2.4 组装传输对象

clike 复制代码
package jsonrpc

import (
	_ "client/domain/decorator"
	"client/utils"

	_ "github.com/dengsgo/go-decorator/decor"
)

// SendJsonRPCContract
// eth_call
// 1. Object - The transaction call object
// from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
// to: DATA, 20 Bytes - The address the transaction is directed to.
// gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
// gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas
// value: QUANTITY - (optional) Integer of the value sent with this transaction
// input: DATA - (optional) Hash of the method signature and encoded parameters.
// 2. QUANTITY|TAG - integer block number, or the string "latest", "earliest", "pending", "safe" or "finalized", see the block parameter
//
//go:decor decorator.LogJsonRPC
func SendJsonRPCContract(chainFuncName string, contractAddr string, contractFuncName string, contractParams ...interface{}) interface{} {

	jsonRpcMethod := chainFuncName
	rsp := interface{}(nil)
	if chainFuncName == "eth_call" {
		JsonRpcParams := utils.GetContractParamsRead(contractAddr, contractFuncName, contractParams)
		rsp = SendJsonRPC(jsonRpcMethod, JsonRpcParams, "latest")
	} else if chainFuncName == "eth_sendTransaction" {
		JsonRpcParams := utils.GetContractParamsRead(contractAddr, contractFuncName, contractParams)
		rsp = SendJsonRPC(jsonRpcMethod, JsonRpcParams)
	} else if chainFuncName == "eth_sendRawTransaction" {
		//input := JsonRpcParams["input"]
		//blockChainWrite := service.BlockChainWrite{}
		//blockChainWrite.Init()
		//encodedArray := blockChainWrite.GetRawTransaction(contractAddr, 0, input.(string))
		//rsp = SendJsonRPC(jsonRpcMethod, encodedArray)
	}

	return rsp
}

2.5 装饰器

以太坊的JSON-RPC返回的值是十六进制字符串,需要转换为对应的类型。为了分离非业务代码和业务代码,架构层面分层清晰,这里统一使用装饰器做返回值转换,这里选择了"github.com/dengsgo/go-decorator/decor"开源框架,只需要在被装饰的类上加//go:decor decorator.LogJsonRPC即可。

clike 复制代码
package decorator

import (
	"client/utils"

	"github.com/dengsgo/go-decorator/decor"
)

func LogJsonRPC(ctx *decor.Context) {
	if ctx.Func != nil {
		ctx.Func()
		if ctx.TargetOut == nil || len(ctx.TargetOut) == 0 {
			return
		}

		switch ctx.TargetIn[0] {
		case "eth_call":
			switch ctx.TargetIn[2] {
			case "name()":
				ctx.TargetOut[0] = utils.HexStrToAscii(ctx.TargetOut[0].(string))
			case "balanceOf(address)":
				wei := utils.HexStrToInt(ctx.TargetOut[0].(string))
				ctx.TargetOut[0] = utils.WeiToETH(wei)
			}
		case "eth_sendTransaction":
			switch ctx.TargetIn[2] {
			case "transfer(address,uint256)":
				//ctx.TargetOut[0] = ctx.TargetOut[0].(string)
			}
		}
	}
}

2.6 工具类

工具类

clike 复制代码
package utils

import (
	"encoding/hex"
	"fmt"
	"math/big"
	"strconv"
	"strings"

	"golang.org/x/crypto/sha3"
)

func HexStrToInt(hexStr string) *big.Int {

	data, _ := new(big.Int).SetString(strings.TrimPrefix(hexStr, "0x"), 16)
	return data
}

func WeiToETH(wei *big.Int) float64 {
	eth, _ := new(big.Float).Quo(new(big.Float).SetInt(wei), big.NewFloat(1e18)).Float64()
	return eth
}

// HexStrToAscii 解析 ABI 编码的字符串
// 0x
// 0000000000000000000000000000000000000000000000000000000000000020  // 偏移量:32字节
// 0000000000000000000000000000000000000000000000000000000000000007  // 长度:7字节
// 4d79546f6b656e00000000000000000000000000000000000000000000000000  // 数据:"MyToken"
func HexStrToAscii(hexStr string) string {

	hexStr = strings.TrimPrefix(hexStr, "0x")
	// 第2个32字节是长度
	lengthHex := hexStr[64:128]
	length, _ := strconv.ParseUint(lengthHex, 16, 32)

	// 提取字符串内容
	start := 128
	contentHex := hexStr[start : start+int(length)*2]
	bytes, _ := hex.DecodeString(contentHex)

	return string(bytes)
}

func GetContractParamsRead(contactAddr string, funcNameContract string, params []interface{}) map[string]any {
	hash := sha3.NewLegacyKeccak256()
	hash.Write([]byte(funcNameContract))
	hashBytes := hash.Sum(nil)
	// 2. 取前4个字节(8个十六进制字符)
	selector := hashBytes[:4]
	// 3. 转换为十六进制字符串(带 0x 前缀)
	funcABI := "0x" + hex.EncodeToString(selector)

	if params != nil && params[0] != nil && len(params) > 0 {
		for _, param := range params {
			switch param.(type) {
			case string:

				funcABI += fmt.Sprintf("%064s", strings.ToLower(strings.TrimPrefix(param.(string), "0x")))

			case uint64:
				funcABI += fmt.Sprintf("%064x", param)
			}
		}
	}

	return map[string]any{
		"to":    contactAddr,
		"input": funcABI,
	}
}

2.7 主函数

主函数

clike 复制代码
package main

import (
	"fmt"

	"client/ethereum.client.blockchain/service"
)

func main() {
	contractRead()

}

func contractRead() {
	contractRead := service.ContractRead{}
	contractRead.Init()
	fmt.Println(contractRead.GetName())
	fmt.Println(contractRead.GetBalance())
}

3. 测试

3.1 hardhat node节点


3.2 sepolia节点

代码链接

欢迎点赞、收藏、评论

相关推荐
nbsaas-boot6 小时前
slice / map 在 Go GC 与内存碎片上的真实成本
开发语言·后端·golang
源代码•宸7 小时前
Leetcode—1266. 访问所有点的最小时间【简单】
开发语言·后端·算法·leetcode·职场和发展·golang
Rockbean8 小时前
3分钟Solidity: 11.10 蜜罐
web3·智能合约·solidity
WZgold1419 小时前
黄金再创新高!2026 年金价走势预测
大数据·人工智能·经验分享·区块链
源代码•宸9 小时前
Leetcode—85. 最大矩形【困难】
经验分享·算法·leetcode·职场和发展·golang·单调栈
精神小伙就是猛9 小时前
C# Task/ThreadPool async/await对比Golang GMP
开发语言·golang·c#
techdashen10 小时前
Go 1.18+ slice 扩容机制详解
开发语言·后端·golang
「、皓子~10 小时前
AI创作系列35 海狸IM桌面版:本地数据库的设计艺术
数据库·golang·毕业设计·开源软件·im·社交软件
DICOM医学影像11 小时前
4. go语言从零实现以太坊客户端 - 区块链转账
golang·区块链·以太坊·web3.0·geth