在 Go 專案使用 Etherscan API 取得 Ethereum 區塊鏈交易

做法

新增 .env 檔。

1
2
ETHERSCAN_URL=https://api-goerli.etherscan.io
ETHERSCAN_API_KEY=

新增 etherscan/client.go 檔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package etherscan

import (
"context"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"log"
"net/url"
"os"
"strings"
)

// 取得交易列表
func GetTransactionList(address, startBlock string, endBlock string) ([]*Transaction, error) {
txsResponse, err := fetchTransactionList(context.Background(), address, startBlock, endBlock)
if err != nil {
return nil, err
}
return txsResponse.Result, nil
}

// 取得合約介面
func GetContractABI(address string) (*abi.ABI, error) {
contractABIResponse, err := fetchContractABI(context.Background(), address)
if err != nil {
return nil, err
}
contractABI, err := abi.JSON(strings.NewReader(*contractABIResponse.Result))
if err != nil {
return nil, err
}
return &contractABI, nil
}

// 送出取得交易列表的請求
func fetchTransactionList(ctx context.Context, address, startBlock string, endBlock string) (*TransactionListResponse, error) {
data, err := fetch(ctx, map[string]string{
"module": "account",
"action": "txlist",
"address": address,
"startblock": startBlock,
"endblock": endBlock,
"apikey": os.Getenv("ETHERSCAN_API_KEY"),
})
if err != nil {
return nil, err
}
resp := TransactionListResponse{}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, fmt.Errorf("fetch transaction list failed: %s", string(data))
}
log.Printf("Fetching transaction list from %s to %s", startBlock, endBlock)
return &resp, err
}

// 送出取得合約介面的請求
func fetchContractABI(ctx context.Context, address string) (*ContractABIResponse, error) {
b, err := fetch(ctx, map[string]string{
"module": "contract",
"action": "getabi",
"address": address,
"apikey": os.Getenv("ETHERSCAN_API_KEY"),
})
if err != nil {
return nil, err
}
resp := ContractABIResponse{}
if err := json.Unmarshal(b, &resp); err != nil {
return nil, fmt.Errorf("fetch contract ABI failed: %s", string(b))
}
log.Printf("Fetching contract ABI: %s", address)
return &resp, nil
}

// 送出請求
func fetch(ctx context.Context, params map[string]string) ([]byte, error) {
ref, err := url.Parse("/api/")
if err != nil {
log.Fatal(err)
}
q := ref.Query()
for k, v := range params {
q.Set(k, v)
}
ref.RawQuery = q.Encode()
base, err := url.Parse(os.Getenv("ETHERSCAN_URL"))
if err != nil {
log.Fatal(err)
}
target := base.ResolveReference(ref).String()
return client.Get(ctx, target)
}

type ContractABIResponse struct {
Status *string `json:"status"`
Message *string `json:"message"`
Result *string `json:"result"`
}

type TransactionListResponse struct {
Status *string `json:"status"`
Message *string `json:"message"`
Result []*Transaction `json:"result"`
}

type Transaction struct {
BlockNumber string `json:"blockNumber"`
TimeStamp string `json:"timeStamp"`
Hash string `json:"hash"`
Nonce string `json:"nonce"`
BlockHash string `json:"blockHash"`
TransactionIndex string `json:"transactionIndex"`
From string `json:"from"`
To string `json:"to"`
Value string `json:"value"`
Gas string `json:"gas"`
GasPrice string `json:"gasPrice"`
IsError string `json:"isError"`
TxreceiptStatus string `json:"txreceipt_status"`
Input string `json:"input"`
ContractAddress string `json:"contractAddress"`
CumulativeGasUsed string `json:"cumulativeGasUsed"`
GasUsed string `json:"gasUsed"`
Confirmations string `json:"confirmations"`
}

參考資料