Easy-Go-Web3
知识图谱Go 教程React Web3智能合约
需求分析系统设计设计模式Go 微服务
项目实战DevOps
Go 生态React 生态智能合约生态Web3 生态AI × Web3工具箱Web3 公司远程Web3求职
🎯 AA 工程师面试手册博客
GitHub
项目实战钱包余额查询 API
初级区块链开发1-2周

钱包余额查询 API

支持多链多代币余额查询,批量地址查询,历史余额快照

技术栈

GoGingo-ethereumRedisPostgreSQL

核心功能

多链余额查询(ETH/BSC/Polygon)
ERC20/BEP20 代币余额
批量地址查询
历史余额快照
API 限流与缓存
价格聚合显示

系统架构


┌──────────────┐     ┌─────────────┐     ┌──────────────┐
│   Clients    │────▶│  Gin API    │────▶│  Blockchain  │
│  (REST/HTTP) │     │  Service    │     │  (go-ethereum)│
└──────────────┘     └─────────────┘     └──────────────┘
                            │
                     ┌──────┴──────┐
                     ▼             ▼
               ┌──────────┐  ┌──────────┐
               │  Redis   │  │PostgreSQL│
               │ (Cache)  │  │ (History)│
               └──────────┘  └──────────┘
  

课程章节

第一章:API 服务架构设计

Gin 框架搭建1小时
路由设计45分钟
中间件配置30分钟

第二章:原生代币余额查询

连接以太坊节点30分钟
余额查询实现1小时
缓存策略45分钟

第三章:ERC20 代币余额查询

ERC20 ABI 调用1.5小时
合约交互1小时
数据格式化30分钟

第四章:批量查询优化

Multicall 合约介绍45分钟
批量查询实现1.5小时
性能优化1小时

核心代码实现

API 服务架构设计

go
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5)
6
7func main() {
8 r := gin.Default()
9
10 // 中间件
11 r.Use(RateLimitMiddleware())
12 r.Use(CacheMiddleware())
13
14 // 路由组
15 v1 := r.Group("/api/v1")
16 {
17 v1.GET("/balance/:chain/:address", GetBalance)
18 v1.POST("/balance/batch", GetBatchBalance)
19 v1.GET("/tokens/:chain/:address", GetTokenBalances)
20 v1.GET("/history/:chain/:address", GetBalanceHistory)
21 }
22
23 r.Run(":8080")
24}
25
26type BalanceResponse struct {
27 Address string `json:"address"`
28 Chain string `json:"chain"`
29 Native *BalanceInfo `json:"native"`
30 Tokens []*TokenBalance `json:"tokens,omitempty"`
31}
32
33type BalanceInfo struct {
34 Balance string `json:"balance"`
35 Symbol string `json:"symbol"`
36 Decimals int `json:"decimals"`
37 USD float64 `json:"usd,omitempty"`
38}

原生代币余额查询

go
1type BalanceService struct {
2 clients map[string]*ethclient.Client
3 cache *redis.Client
4}
5
6func (s *BalanceService) GetNativeBalance(ctx context.Context, chain, address string) (*BalanceInfo, error) {
7 // 检查缓存
8 cacheKey := fmt.Sprintf("balance:%s:%s:native", chain, address)
9 if cached, err := s.cache.Get(ctx, cacheKey).Result(); err == nil {
10 var info BalanceInfo
11 json.Unmarshal([]byte(cached), &info)
12 return &info, nil
13 }
14
15 client, ok := s.clients[chain]
16 if !ok {
17 return nil, fmt.Errorf("unsupported chain: %s", chain)
18 }
19
20 addr := common.HexToAddress(address)
21 balance, err := client.BalanceAt(ctx, addr, nil)
22 if err != nil {
23 return nil, err
24 }
25
26 info := &BalanceInfo{
27 Balance: weiToEther(balance),
28 Symbol: getChainSymbol(chain),
29 Decimals: 18,
30 }
31
32 // 缓存 30 秒
33 data, _ := json.Marshal(info)
34 s.cache.Set(ctx, cacheKey, data, 30*time.Second)
35
36 return info, nil
37}
38
39func weiToEther(wei *big.Int) string {
40 fbal := new(big.Float).SetInt(wei)
41 ethValue := new(big.Float).Quo(fbal, big.NewFloat(1e18))
42 return ethValue.Text('f', 8)
43}

ERC20 代币余额查询

go
1// ERC20 ABI (简化版)
2const erc20ABI = `[
3 {"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"},
4 {"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"},
5 {"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"type":"function"}
6]`
7
8func (s *BalanceService) GetTokenBalance(ctx context.Context, chain, wallet, token string) (*TokenBalance, error) {
9 client := s.clients[chain]
10
11 parsed, _ := abi.JSON(strings.NewReader(erc20ABI))
12 contract := common.HexToAddress(token)
13 walletAddr := common.HexToAddress(wallet)
14
15 // 查询余额
16 balanceData, _ := parsed.Pack("balanceOf", walletAddr)
17 result, err := client.CallContract(ctx, ethereum.CallMsg{
18 To: &contract,
19 Data: balanceData,
20 }, nil)
21 if err != nil {
22 return nil, err
23 }
24
25 balance := new(big.Int).SetBytes(result)
26
27 // 查询 decimals 和 symbol
28 decimals := s.getDecimals(ctx, client, contract)
29 symbol := s.getSymbol(ctx, client, contract)
30
31 return &TokenBalance{
32 Token: token,
33 Symbol: symbol,
34 Balance: formatBalance(balance, decimals),
35 Decimals: decimals,
36 }, nil
37}

批量查询优化

go
1// Multicall3 合约地址 (多链通用)
2const Multicall3Address = "0xcA11bde05977b3631167028862bE2a173976CA11"
3
4type MulticallService struct {
5 client *ethclient.Client
6 contract *bind.BoundContract
7}
8
9func (s *MulticallService) BatchGetBalances(ctx context.Context, wallet string, tokens []string) ([]*TokenBalance, error) {
10 calls := make([]Multicall3Call, len(tokens))
11
12 for i, token := range tokens {
13 data, _ := erc20ABI.Pack("balanceOf", common.HexToAddress(wallet))
14 calls[i] = Multicall3Call{
15 Target: common.HexToAddress(token),
16 CallData: data,
17 }
18 }
19
20 // 一次 RPC 调用获取所有余额
21 results, err := s.contract.Aggregate(ctx, calls)
22 if err != nil {
23 return nil, err
24 }
25
26 balances := make([]*TokenBalance, len(tokens))
27 for i, result := range results {
28 balance := new(big.Int).SetBytes(result)
29 balances[i] = &TokenBalance{
30 Token: tokens[i],
31 Balance: balance.String(),
32 }
33 }
34
35 return balances, nil
36}
区块链实时监控服务NFT 元数据服务
Easy-Go-Web3

构建 Go 后端与 Web3 的学习之路。从基础到进阶,从理论到实践,助你成为全栈区块链开发者。

学习路径

  • 知识图谱
  • Go 教程
  • Go 微服务
  • 面试手册

资源中心

  • 工具箱
  • DevOps 工具
  • Web3 生态
  • 博客

© 2025 Easy-Go-Web3. All rights reserved.

Created withbyhardybao