From dbe05df62707d4d334bd9617175ad96ee26e5c53 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Mon, 14 Oct 2019 09:53:38 +0800
Subject: [PATCH 01/44] all: use go mod

---
 go.mod | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 go.mod

diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0a30134
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/nanmu42/etherscan-api
+
+go 1.13

From 7638c35145154cf44742c068bc7b289cef900f0d Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Mon, 14 Oct 2019 10:03:48 +0800
Subject: [PATCH 02/44] account: add desc param to ERC20Transfers()

---
 account.go          |  8 +++++++-
 account_e2e_test.go | 15 +++++++++++----
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/account.go b/account.go
index 0effe2c..48c149b 100644
--- a/account.go
+++ b/account.go
@@ -87,7 +87,7 @@ func (c *Client) InternalTxByAddress(address string, startBlock *int, endBlock *
 //
 // More information can be found at:
 // https://github.com/nanmu42/etherscan-api/issues/8
-func (c *Client) ERC20Transfers(contractAddress, address *string, startBlock *int, endBlock *int, page int, offset int) (txs []ERC20Transfer, err error) {
+func (c *Client) ERC20Transfers(contractAddress, address *string, startBlock *int, endBlock *int, page int, offset int, desc bool) (txs []ERC20Transfer, err error) {
 	param := M{
 		"page":   page,
 		"offset": offset,
@@ -97,6 +97,12 @@ func (c *Client) ERC20Transfers(contractAddress, address *string, startBlock *in
 	compose(param, "startblock", startBlock)
 	compose(param, "endblock", endBlock)
 
+	if desc {
+		param["sort"] = "desc"
+	} else {
+		param["sort"] = "asc"
+	}
+
 	err = c.call("account", "tokentx", param, &txs)
 	return
 }
diff --git a/account_e2e_test.go b/account_e2e_test.go
index f3272ed..48632b1 100644
--- a/account_e2e_test.go
+++ b/account_e2e_test.go
@@ -78,7 +78,7 @@ func TestClient_ERC20Transfers(t *testing.T) {
 
 	var a, b = 3273004, 3328071
 	var contract, address = "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", "0x4e83362442b8d1bec281594cea3050c8eb01311c"
-	txs, err := api.ERC20Transfers(&contract, &address, &a, &b, 1, 500)
+	txs, err := api.ERC20Transfers(&contract, &address, &a, &b, 1, 500, false)
 	noError(t, err, "api.ERC20Transfers 1")
 
 	//j, _ := json.MarshalIndent(txs, "", "  ")
@@ -88,8 +88,15 @@ func TestClient_ERC20Transfers(t *testing.T) {
 		t.Errorf("got txs length %v, want %v", len(txs), wantLen1)
 	}
 
-	txs, err = api.ERC20Transfers(nil, &address, nil, &b, 1, 500)
-	noError(t, err, "api.ERC20Transfers 2")
+	txs, err = api.ERC20Transfers(nil, &address, nil, &b, 1, 500, false)
+	noError(t, err, "api.ERC20Transfers 2 asc")
+	if len(txs) != wantLen2 {
+		t.Errorf("got txs length %v, want %v", len(txs), wantLen2)
+	}
+
+	txs, err = api.ERC20Transfers(nil, &address, nil, &b, 1, 500, true)
+	noError(t, err, "api.ERC20Transfers 2 desc")
+
 	if len(txs) != wantLen2 {
 		t.Errorf("got txs length %v, want %v", len(txs), wantLen2)
 	}
@@ -99,7 +106,7 @@ func TestClient_ERC20Transfers(t *testing.T) {
 	var specialContract = "0x5eac95ad5b287cf44e058dcf694419333b796123"
 	var specialStartHeight = 6024142
 	var specialEndHeight = 6485274
-	txs, err = api.ERC20Transfers(&specialContract, nil, &specialStartHeight, &specialEndHeight, 1, 500)
+	txs, err = api.ERC20Transfers(&specialContract, nil, &specialStartHeight, &specialEndHeight, 1, 500, false)
 	noError(t, err, "api.ERC20Transfers 2")
 	if len(txs) != wantLen3 {
 		t.Errorf("got txs length %v, want %v", len(txs), wantLen3)

From b36ec451c2fbbe00791fa57e1b31b123bfbd9668 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Mon, 14 Oct 2019 10:07:49 +0800
Subject: [PATCH 03/44] CI: use go mod

---
 .travis.yml | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index e9a863d..297a5cc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,10 @@
 language: go
 
+env:
+  - GO111MODULE=on
+
 script:
-- go test -v -coverprofile=coverage.txt -covermode=count ./...
+  - go test -v -coverprofile=coverage.txt -covermode=count ./...
 
 after_success:
-- bash <(curl -s https://codecov.io/bash)
\ No newline at end of file
+  - bash <(curl -s https://codecov.io/bash)
\ No newline at end of file

From 84c69b5b3feac354ad71144e266a076f018e55c0 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 21 Feb 2020 10:17:02 +0800
Subject: [PATCH 04/44] cleanup: gosimple

---
 reflect.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/reflect.go b/reflect.go
index 328684a..ba0d54a 100644
--- a/reflect.go
+++ b/reflect.go
@@ -27,7 +27,7 @@ func extractValue(input interface{}) (output []string) {
 			output[i] = valueToStr(v.Index(i))
 		}
 	} else {
-		output = make([]string, 1, 1)
+		output = make([]string, 1)
 		output[0] = valueToStr(v)
 	}
 

From 42ada40c3d24212dba086b1ed6a4a7cccb7327a7 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 21 Feb 2020 10:17:24 +0800
Subject: [PATCH 05/44] test: input etherscan API key through env

---
 setup_e2e_test.go | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/setup_e2e_test.go b/setup_e2e_test.go
index 5712171..6489234 100644
--- a/setup_e2e_test.go
+++ b/setup_e2e_test.go
@@ -8,21 +8,31 @@
 package etherscan
 
 import (
+	"fmt"
+	"os"
 	"testing"
 	"time"
 )
 
+const apiKeyEnvName = "ETHERSCAN_API_KEY"
+
 var (
 	// api test client for many test cases
 	api *Client
 	// bucket default rate limiter
 	bucket *Bucket
+	// apiKey etherscan API key
+	apiKey string
 )
 
 func init() {
+	apiKey = os.Getenv(apiKeyEnvName)
+	if apiKey == "" {
+		panic(fmt.Sprintf("API key is empty, set env variable %q with a valid API key to proceed.", apiKeyEnvName))
+	}
 	bucket = NewBucket(200 * time.Millisecond)
 
-	api = New(Mainnet, "etherscan-api-e2e-test")
+	api = New(Mainnet, apiKey)
 	api.Verbose = true
 	api.BeforeRequest = func(module string, action string, param map[string]interface{}) error {
 		bucket.Take()

From 896daf171cb47fdfeaa69392300c9b57d45b6b0f Mon Sep 17 00:00:00 2001
From: LI Zhennan <i@nanmu.me>
Date: Wed, 23 Sep 2020 19:03:43 +0800
Subject: [PATCH 06/44] CI: increase cooling period to avoid rate limit

---
 setup_e2e_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup_e2e_test.go b/setup_e2e_test.go
index 6489234..86f5970 100644
--- a/setup_e2e_test.go
+++ b/setup_e2e_test.go
@@ -30,7 +30,7 @@ func init() {
 	if apiKey == "" {
 		panic(fmt.Sprintf("API key is empty, set env variable %q with a valid API key to proceed.", apiKeyEnvName))
 	}
-	bucket = NewBucket(200 * time.Millisecond)
+	bucket = NewBucket(300 * time.Millisecond)
 
 	api = New(Mainnet, apiKey)
 	api.Verbose = true

From 3ddd9ca4abdc66a16e3158833199cb06aa58d192 Mon Sep 17 00:00:00 2001
From: LI Zhennan <i@nanmu.me>
Date: Thu, 12 Nov 2020 12:36:55 +0800
Subject: [PATCH 07/44] CI: increase cooling period again

---
 setup_e2e_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup_e2e_test.go b/setup_e2e_test.go
index 86f5970..64f2702 100644
--- a/setup_e2e_test.go
+++ b/setup_e2e_test.go
@@ -30,7 +30,7 @@ func init() {
 	if apiKey == "" {
 		panic(fmt.Sprintf("API key is empty, set env variable %q with a valid API key to proceed.", apiKeyEnvName))
 	}
-	bucket = NewBucket(300 * time.Millisecond)
+	bucket = NewBucket(500 * time.Millisecond)
 
 	api = New(Mainnet, apiKey)
 	api.Verbose = true

From a4b1b45207b030713db39c1f6efe3904c178b19a Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 19 Feb 2021 14:18:07 +0800
Subject: [PATCH 08/44] feature: add NewCustomized() so that client works with
 etherscan-family API like BscScan

Thanks for Kevin J. Qiu(#23) for the idea.
---
 client.go | 41 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 5 deletions(-)

diff --git a/client.go b/client.go
index b981d59..9654538 100644
--- a/client.go
+++ b/client.go
@@ -22,7 +22,6 @@ import (
 // Clients are safe for concurrent use by multiple goroutines.
 type Client struct {
 	coon    *http.Client
-	network Network
 	key     string
 	baseURL string
 
@@ -41,13 +40,45 @@ type Client struct {
 // New initialize a new etherscan API client
 // please use pre-defined network value
 func New(network Network, APIKey string) *Client {
+	return NewCustomized(Customization{
+		Timeout: 30 * time.Second,
+		Key:     APIKey,
+		BaseURL: fmt.Sprintf(`https://%s.etherscan.io/api?`, network.SubDomain()),
+	})
+}
+
+// Customization is used in NewCustomized()
+type Customization struct {
+	// Timeout for API call
+	Timeout time.Duration
+	// API key applied from Etherscan
+	Key string
+	// Base URL like `https://api.etherscan.io/api?`
+	BaseURL string
+	// When true, talks a lot
+	Verbose bool
+
+	// BeforeRequest runs before every client request, in the same goroutine.
+	// May be used in rate limit.
+	// Request will be aborted, if BeforeRequest returns non-nil err.
+	BeforeRequest func(module, action string, param map[string]interface{}) error
+
+	// AfterRequest runs after every client request, even when there is an error.
+	AfterRequest func(module, action string, param map[string]interface{}, outcome interface{}, requestErr error)
+}
+
+// NewCustomized initialize a customized API client,
+// useful when calling against etherscan-family API like BscScan.
+func NewCustomized(config Customization) *Client {
 	return &Client{
 		coon: &http.Client{
-			Timeout: 30 * time.Second,
+			Timeout: config.Timeout,
 		},
-		network: network,
-		key:     APIKey,
-		baseURL: fmt.Sprintf(`https://%s.etherscan.io/api?`, network.SubDomain()),
+		key:           config.Key,
+		baseURL:       config.BaseURL,
+		Verbose:       config.Verbose,
+		BeforeRequest: config.BeforeRequest,
+		AfterRequest:  config.AfterRequest,
 	}
 }
 

From c453d3c991b8e9b4ec87bf9b07664a13515ea0da Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 19 Feb 2021 14:31:09 +0800
Subject: [PATCH 09/44] doc: add description for BscScan(ci skip)

---
 README.md | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 4087c3a..818b685 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 [![GoDoc](https://godoc.org/github.com/nanmu42/etherscan-api?status.svg)](https://godoc.org/github.com/nanmu42/etherscan-api)
 [中文文档](https://github.com/nanmu42/etherscan-api/blob/master/README_ZH.md)
 
-Go bindings to the Etherscan.io API, with nearly Full implementation(accounts, transactions, tokens, contracts, blocks, stats), full network support(Mainnet, Ropsten, Kovan, Rinkby, Tobalaba), and only depending on standard library. :wink:
+Go bindings to the Etherscan.io API(and its families like BscScan), with nearly Full implementation(accounts, transactions, tokens, contracts, blocks, stats), full network support(Mainnet, Ropsten, Kovan, Rinkby, Tobalaba), and only depending on standard library. :wink:
 
 # Usage
 
@@ -22,6 +22,15 @@ func main() {
 	// create a API client for specified ethereum net
 	// there are many pre-defined network in package
 	client := etherscan.New(etherscan.Mainnet, "[your API key]")
+	
+	// or, if you are working with etherscan-family API like BscScan
+	//
+	// client := etherscan.NewCustomized(etherscan.Customization{
+    // Timeout:       15 * time.Second,
+    // Key:           "You key here",
+    // BaseURL:       "https://api.bscscan.com/api?",
+    // Verbose:       false,
+    // })
 
 	// (optional) add hooks, e.g. for rate limit
 	client.BeforeRequest = func(module, action string, param map[string]interface{}) error {

From c01ef7f24b7a9d5ef215ba955f9268797fbaea5c Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 19 Feb 2021 14:36:25 +0800
Subject: [PATCH 10/44] fix: doc code block indentation

---
 README.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 818b685..69491d5 100644
--- a/README.md
+++ b/README.md
@@ -26,11 +26,11 @@ func main() {
 	// or, if you are working with etherscan-family API like BscScan
 	//
 	// client := etherscan.NewCustomized(etherscan.Customization{
-    // Timeout:       15 * time.Second,
-    // Key:           "You key here",
-    // BaseURL:       "https://api.bscscan.com/api?",
-    // Verbose:       false,
-    // })
+	// Timeout:       15 * time.Second,
+	// Key:           "You key here",
+	// BaseURL:       "https://api.bscscan.com/api?",
+	// Verbose:       false,
+	// })
 
 	// (optional) add hooks, e.g. for rate limit
 	client.BeforeRequest = func(module, action string, param map[string]interface{}) error {

From fee45c952bb88cfba6c50ee5dbab676e53da05b1 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 19 Feb 2021 14:38:42 +0800
Subject: [PATCH 11/44] doc: amend Chinese README

---
 README_ZH.md | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/README_ZH.md b/README_ZH.md
index 1e38dae..aeb71a1 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -24,7 +24,16 @@ import (
 func main() {
 	// 创建连接指定网络的客户端
 	client := etherscan.New(etherscan.Mainnet, "[your API key]")
-
+	
+	// 或者，如果你要调用的是EtherScan家族的BscScan：
+	//
+	// client := etherscan.NewCustomized(etherscan.Customization{
+	// Timeout:       15 * time.Second,
+	// Key:           "You key here",
+	// BaseURL:       "https://api.bscscan.com/api?",
+	// Verbose:       false,
+	// })	
+	
 	// （可选）按需注册钩子函数，例如用于速率控制
 	client.BeforeRequest = func(module, action string, param map[string]interface{}) error {
 		// ...

From 39daf32d6557c767a307a2215de4fd763200b34b Mon Sep 17 00:00:00 2001
From: William Flores <will7207@gmail.com>
Date: Sun, 16 May 2021 15:53:34 -0700
Subject: [PATCH 12/44] feat: allow passing in custom http client

---
 client.go | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/client.go b/client.go
index 9654538..e69679a 100644
--- a/client.go
+++ b/client.go
@@ -57,6 +57,9 @@ type Customization struct {
 	BaseURL string
 	// When true, talks a lot
 	Verbose bool
+	// HTTP Client to be used. Specifying this value will ignore the Timeout value set
+	// Set your own timeout.
+	Client *http.Client
 
 	// BeforeRequest runs before every client request, in the same goroutine.
 	// May be used in rate limit.
@@ -70,10 +73,16 @@ type Customization struct {
 // NewCustomized initialize a customized API client,
 // useful when calling against etherscan-family API like BscScan.
 func NewCustomized(config Customization) *Client {
-	return &Client{
-		coon: &http.Client{
+	var httpClient *http.Client
+	if config.Client != nil {
+		httpClient = config.Client
+	} else {
+		httpClient = &http.Client{
 			Timeout: config.Timeout,
-		},
+		}
+	}
+	return &Client{
+		coon:          httpClient,
 		key:           config.Key,
 		baseURL:       config.BaseURL,
 		Verbose:       config.Verbose,

From d2f683c2d35cfef1ca80bebe42ce2ca564f08353 Mon Sep 17 00:00:00 2001
From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com>
Date: Thu, 19 Aug 2021 12:44:40 +0300
Subject: [PATCH 13/44] add Goerli

Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com>
---
 README.md    | 2 +-
 README_ZH.md | 2 +-
 network.go   | 6 ++++--
 3 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 69491d5..38b149a 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 [![GoDoc](https://godoc.org/github.com/nanmu42/etherscan-api?status.svg)](https://godoc.org/github.com/nanmu42/etherscan-api)
 [中文文档](https://github.com/nanmu42/etherscan-api/blob/master/README_ZH.md)
 
-Go bindings to the Etherscan.io API(and its families like BscScan), with nearly Full implementation(accounts, transactions, tokens, contracts, blocks, stats), full network support(Mainnet, Ropsten, Kovan, Rinkby, Tobalaba), and only depending on standard library. :wink:
+Go bindings to the Etherscan.io API(and its families like BscScan), with nearly Full implementation(accounts, transactions, tokens, contracts, blocks, stats), full network support(Mainnet, Ropsten, Kovan, Rinkby, Goerli, Tobalaba), and only depending on standard library. :wink:
 
 # Usage
 
diff --git a/README_ZH.md b/README_ZH.md
index aeb71a1..72ace44 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -8,7 +8,7 @@
 
 Etherscan.io的Golang实现，
 支持几乎所有功能（accounts, transactions, tokens, contracts, blocks, stats），
-所有公共网络（Mainnet, Ropsten, Kovan, Rinkby, Tobalaba）。
+所有公共网络（Mainnet, Ropsten, Kovan, Rinkby, Goerli, Tobalaba）。
 本项目只依赖于官方库。 :wink:
 
 # Usage
diff --git a/network.go b/network.go
index 39f0f56..32372ca 100644
--- a/network.go
+++ b/network.go
@@ -18,6 +18,8 @@ const (
 	Kovan Network = "api-kovan"
 	// Rinkby Testnet(CLIQUE)
 	Rinkby Network = "api-rinkeby"
+	// Goerli Testnet(CLIQUE)
+	Goerli Network = "api-goerli"
 	// Tobalaba Testnet
 	Tobalaba Network = "api-tobalaba"
 )
@@ -27,6 +29,6 @@ type Network string
 
 // SubDomain returns the subdomain of  etherscan API
 // via n provided.
-func (n Network) SubDomain() (sub string)  {
+func (n Network) SubDomain() (sub string) {
 	return string(n)
-}
\ No newline at end of file
+}

From 5a363063823f70ab0cb55b423d0ffe419251cae1 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 14 Oct 2021 13:12:37 -0400
Subject: [PATCH 14/44] Added support for querying blocknumber by timestamp

---
 block.go    | 21 +++++++++++++++++++++
 response.go |  5 +++++
 2 files changed, 26 insertions(+)

diff --git a/block.go b/block.go
index 8c3736b..e445dc0 100644
--- a/block.go
+++ b/block.go
@@ -7,6 +7,8 @@
 
 package etherscan
 
+import "strconv"
+
 // BlockReward gets block and uncle rewards by block number
 func (c *Client) BlockReward(blockNum int) (rewards BlockRewards, err error) {
 	param := M{
@@ -16,3 +18,22 @@ func (c *Client) BlockReward(blockNum int) (rewards BlockRewards, err error) {
 	err = c.call("block", "getblockreward", param, &rewards)
 	return
 }
+
+// BlockReward gets closest block number by UNIX timestamp
+func (c *Client) BlockNumber(timestamp int64, closest string) (blockNumber BlockNumberFromTimestamp, err error) {
+	var result string
+	param := M{
+		"timestamp": strconv.Itoa(int(timestamp)),
+		"closest":   closest,
+	}
+
+	err = c.call("block", "getblocknobytime", param, &result)
+
+	if err != nil {
+		return
+	}
+
+	blockNum, err := strconv.ParseInt(result, 10, 64)
+	blockNumber.BlockNumber = int(blockNum)
+	return
+}
diff --git a/response.go b/response.go
index b1338d3..c37b63a 100644
--- a/response.go
+++ b/response.go
@@ -129,6 +129,11 @@ type BlockRewards struct {
 	UncleInclusionReward *BigInt `json:"uncleInclusionReward"`
 }
 
+// BlockNumberFromTimestamp holds info from query for block by UNIX timestamp
+type BlockNumberFromTimestamp struct {
+	BlockNumber int
+}
+
 // LatestPrice holds info from query for latest ether price
 type LatestPrice struct {
 	ETHBTC          float64 `json:"ethbtc,string"`

From 6fe45db8a77272b960b5d82eba9e3bb819b87c44 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 14 Oct 2021 13:48:41 -0400
Subject: [PATCH 15/44] Updated block_e2e_test to test querying BlockNumber By
 Time

---
 block_e2e_test.go | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/block_e2e_test.go b/block_e2e_test.go
index 26b603e..e455ea9 100644
--- a/block_e2e_test.go
+++ b/block_e2e_test.go
@@ -24,3 +24,23 @@ func TestClient_BlockReward(t *testing.T) {
 		t.Errorf("api.BlockReward not working, got %s, want %s", j, ans)
 	}
 }
+
+func TestClient_BlockNumber(t *testing.T) {
+	//Note: All values taken from docs.etherscan.io/api-endpoints/blocks
+	const ans_before = 9251482
+	const ans_after = 9251483
+
+	blockNumber, err := api.BlockNumber(1578638524, "before")
+	noError(t, err, "api.BlockNumber")
+
+	if blockNumber.BlockNumber != ans_before {
+		t.Errorf(`api.BlockNumber(1578638524, "before") not working, got %d, want %d`, blockNumber.BlockNumber, ans_before)
+	}
+
+	blockNumber, err = api.BlockNumber(1578638524, "after")
+	noError(t, err, "api.BlockNumber")
+
+	if blockNumber.BlockNumber != ans_after {
+		t.Errorf(`api.BlockNumber(1578638524,"after") not working, got %d, want %d`, blockNumber.BlockNumber, ans_after)
+	}
+}

From e7ca702e798dc87e09d25b54f183a6069b247c61 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 14 Oct 2021 16:54:21 -0400
Subject: [PATCH 16/44] Removed blocknumber struct wrapping since it really
 isn't necessary

---
 block.go          | 6 +++---
 block_e2e_test.go | 8 ++++----
 response.go       | 5 -----
 3 files changed, 7 insertions(+), 12 deletions(-)

diff --git a/block.go b/block.go
index e445dc0..9da0efa 100644
--- a/block.go
+++ b/block.go
@@ -19,8 +19,8 @@ func (c *Client) BlockReward(blockNum int) (rewards BlockRewards, err error) {
 	return
 }
 
-// BlockReward gets closest block number by UNIX timestamp
-func (c *Client) BlockNumber(timestamp int64, closest string) (blockNumber BlockNumberFromTimestamp, err error) {
+// BlockNumber gets closest block number by UNIX timestamp
+func (c *Client) BlockNumber(timestamp int64, closest string) (blockNumber int, err error) {
 	var result string
 	param := M{
 		"timestamp": strconv.Itoa(int(timestamp)),
@@ -34,6 +34,6 @@ func (c *Client) BlockNumber(timestamp int64, closest string) (blockNumber Block
 	}
 
 	blockNum, err := strconv.ParseInt(result, 10, 64)
-	blockNumber.BlockNumber = int(blockNum)
+	blockNumber = int(blockNum)
 	return
 }
diff --git a/block_e2e_test.go b/block_e2e_test.go
index e455ea9..e05ff05 100644
--- a/block_e2e_test.go
+++ b/block_e2e_test.go
@@ -33,14 +33,14 @@ func TestClient_BlockNumber(t *testing.T) {
 	blockNumber, err := api.BlockNumber(1578638524, "before")
 	noError(t, err, "api.BlockNumber")
 
-	if blockNumber.BlockNumber != ans_before {
-		t.Errorf(`api.BlockNumber(1578638524, "before") not working, got %d, want %d`, blockNumber.BlockNumber, ans_before)
+	if blockNumber != ans_before {
+		t.Errorf(`api.BlockNumber(1578638524, "before") not working, got %d, want %d`, blockNumber, ans_before)
 	}
 
 	blockNumber, err = api.BlockNumber(1578638524, "after")
 	noError(t, err, "api.BlockNumber")
 
-	if blockNumber.BlockNumber != ans_after {
-		t.Errorf(`api.BlockNumber(1578638524,"after") not working, got %d, want %d`, blockNumber.BlockNumber, ans_after)
+	if blockNumber != ans_after {
+		t.Errorf(`api.BlockNumber(1578638524,"after") not working, got %d, want %d`, blockNumber, ans_after)
 	}
 }
diff --git a/response.go b/response.go
index c37b63a..b1338d3 100644
--- a/response.go
+++ b/response.go
@@ -129,11 +129,6 @@ type BlockRewards struct {
 	UncleInclusionReward *BigInt `json:"uncleInclusionReward"`
 }
 
-// BlockNumberFromTimestamp holds info from query for block by UNIX timestamp
-type BlockNumberFromTimestamp struct {
-	BlockNumber int
-}
-
 // LatestPrice holds info from query for latest ether price
 type LatestPrice struct {
 	ETHBTC          float64 `json:"ethbtc,string"`

From b288f6d9aae7412122506b4b228d4099536e444e Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 15 Oct 2021 14:30:28 +0800
Subject: [PATCH 17/44] ci: use Github Actions

---
 .github/workflows/ci.yaml | 27 +++++++++++++++++++++++++++
 .travis.yml               | 10 ----------
 2 files changed, 27 insertions(+), 10 deletions(-)
 create mode 100644 .github/workflows/ci.yaml
 delete mode 100644 .travis.yml

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..5a3db42
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,27 @@
+name: CI
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: "1.x"
+
+      - name: Test
+        run: go test -v -coverprofile=coverage.txt -covermode=count ./...
+
+      - name: Codecov
+        uses: codecov/codecov-action@v2.1.0
+
+      - name: golangci-lint
+        uses: golangci/golangci-lint-action@v2
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 297a5cc..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: go
-
-env:
-  - GO111MODULE=on
-
-script:
-  - go test -v -coverprofile=coverage.txt -covermode=count ./...
-
-after_success:
-  - bash <(curl -s https://codecov.io/bash)
\ No newline at end of file

From f55880d9d62186535f3eb0f8cb611ef34dc1f2d2 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 15 Oct 2021 14:31:09 +0800
Subject: [PATCH 18/44] doc: update docs

---
 README.md    | 19 ++++++++++++-------
 README_ZH.md | 17 +++++++++++------
 2 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 38b149a..d300d3e 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,21 @@
+**English** | [中文](https://github.com/nanmu42/etherscan-api/blob/master/README_ZH.md)
+
 # etherscan-api
 
-[![Build Status](https://travis-ci.org/nanmu42/etherscan-api.svg?branch=master)](https://travis-ci.org/nanmu42/etherscan-api)
-[![Go Report Card](https://goreportcard.com/badge/github.com/nanmu42/etherscan-api)](https://goreportcard.com/report/github.com/nanmu42/etherscan-api)
-[![codecov](https://codecov.io/gh/nanmu42/etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/nanmu42/etherscan-api)
 [![GoDoc](https://godoc.org/github.com/nanmu42/etherscan-api?status.svg)](https://godoc.org/github.com/nanmu42/etherscan-api)
-[中文文档](https://github.com/nanmu42/etherscan-api/blob/master/README_ZH.md)
+[![CI status](https://github.com/nanmu42/etherscan-api/workflows/build/ci.svg)](https://github.com/nanmu42/etherscan-api/actions)
+[![codecov](https://codecov.io/gh/nanmu42/etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/nanmu42/etherscan-api)
+[![Go Report Card](https://goreportcard.com/badge/github.com/nanmu42/etherscan-api)](https://goreportcard.com/report/github.com/nanmu42/etherscan-api)
 
-Go bindings to the Etherscan.io API(and its families like BscScan), with nearly Full implementation(accounts, transactions, tokens, contracts, blocks, stats), full network support(Mainnet, Ropsten, Kovan, Rinkby, Goerli, Tobalaba), and only depending on standard library. :wink:
+Golang client for the Etherscan.io API(and its families like BscScan), with nearly full implementation(accounts, transactions, tokens, contracts, blocks, stats), full network support(Mainnet, Ropsten, Kovan, Rinkby, Goerli, Tobalaba), and only depending on standard library. :wink:
 
 # Usage
 
-Create a API instance and off you go. :rocket:
+```bash
+go get github.com/nanmu42/etherscan-api
+```
+
+Create an API instance and off you go. :rocket:
 
 ```go
 import (
@@ -70,6 +75,6 @@ I am not from Etherscan and I just find their service really useful, so I implem
 
 # License
 
-Use of this work is governed by a MIT License.
+Use of this work is governed by an MIT License.
 
 You may find a license copy in project root.
diff --git a/README_ZH.md b/README_ZH.md
index 72ace44..dc48f68 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -1,17 +1,22 @@
+[English](https://github.com/nanmu42/etherscan-api/blob/master/README.md) | **中文**
+
 # etherscan-api
 
-[![Build Status](https://travis-ci.org/nanmu42/etherscan-api.svg?branch=master)](https://travis-ci.org/nanmu42/etherscan-api)
-[![Go Report Card](https://goreportcard.com/badge/github.com/nanmu42/etherscan-api)](https://goreportcard.com/report/github.com/nanmu42/etherscan-api)
-[![codecov](https://codecov.io/gh/nanmu42/etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/nanmu42/etherscan-api)
 [![GoDoc](https://godoc.org/github.com/nanmu42/etherscan-api?status.svg)](https://godoc.org/github.com/nanmu42/etherscan-api)
-[English Readme](https://github.com/nanmu42/etherscan-api/blob/master/README.md)
+[![CI status](https://github.com/nanmu42/etherscan-api/workflows/build/ci.svg)](https://github.com/nanmu42/etherscan-api/actions)
+[![codecov](https://codecov.io/gh/nanmu42/etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/nanmu42/etherscan-api)
+[![Go Report Card](https://goreportcard.com/badge/github.com/nanmu42/etherscan-api)](https://goreportcard.com/report/github.com/nanmu42/etherscan-api)
 
-Etherscan.io的Golang实现，
+Etherscan API的Golang客户端，
 支持几乎所有功能（accounts, transactions, tokens, contracts, blocks, stats），
 所有公共网络（Mainnet, Ropsten, Kovan, Rinkby, Goerli, Tobalaba）。
 本项目只依赖于官方库。 :wink:
 
-# Usage
+# 使用方法
+
+```bash
+go get github.com/nanmu42/etherscan-api
+```
 
 填入网络选项和API Key即可开始使用。 :rocket:
 

From 4ee35b2ec340648713e17588381643c0cadd5813 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 15 Oct 2021 14:31:30 +0800
Subject: [PATCH 19/44] refactor: cleanup code

---
 block.go          | 24 +++++++++++++++++-------
 block_e2e_test.go | 14 +++++++-------
 2 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/block.go b/block.go
index 9da0efa..aea4ea1 100644
--- a/block.go
+++ b/block.go
@@ -7,7 +7,10 @@
 
 package etherscan
 
-import "strconv"
+import (
+	"fmt"
+	"strconv"
+)
 
 // BlockReward gets block and uncle rewards by block number
 func (c *Client) BlockReward(blockNum int) (rewards BlockRewards, err error) {
@@ -19,21 +22,28 @@ func (c *Client) BlockReward(blockNum int) (rewards BlockRewards, err error) {
 	return
 }
 
-// BlockNumber gets closest block number by UNIX timestamp
+// BlockNumber gets the closest block number by UNIX timestamp
+//
+// valid closest option: before, after
 func (c *Client) BlockNumber(timestamp int64, closest string) (blockNumber int, err error) {
-	var result string
+	var blockNumberStr string
+
 	param := M{
-		"timestamp": strconv.Itoa(int(timestamp)),
+		"timestamp": strconv.FormatInt(timestamp, 10),
 		"closest":   closest,
 	}
 
-	err = c.call("block", "getblocknobytime", param, &result)
+	err = c.call("block", "getblocknobytime", param, &blockNumberStr)
+
+	if err != nil {
+		return
+	}
 
+	blockNumber, err = strconv.Atoi(blockNumberStr)
 	if err != nil {
+		err = fmt.Errorf("parsing block number %q: %w", blockNumberStr, err)
 		return
 	}
 
-	blockNum, err := strconv.ParseInt(result, 10, 64)
-	blockNumber = int(blockNum)
 	return
 }
diff --git a/block_e2e_test.go b/block_e2e_test.go
index e05ff05..c49a8ac 100644
--- a/block_e2e_test.go
+++ b/block_e2e_test.go
@@ -26,21 +26,21 @@ func TestClient_BlockReward(t *testing.T) {
 }
 
 func TestClient_BlockNumber(t *testing.T) {
-	//Note: All values taken from docs.etherscan.io/api-endpoints/blocks
-	const ans_before = 9251482
-	const ans_after = 9251483
+	// Note: All values taken from docs.etherscan.io/api-endpoints/blocks
+	const ansBefore = 9251482
+	const ansAfter = 9251483
 
 	blockNumber, err := api.BlockNumber(1578638524, "before")
 	noError(t, err, "api.BlockNumber")
 
-	if blockNumber != ans_before {
-		t.Errorf(`api.BlockNumber(1578638524, "before") not working, got %d, want %d`, blockNumber, ans_before)
+	if blockNumber != ansBefore {
+		t.Errorf(`api.BlockNumber(1578638524, "before") not working, got %d, want %d`, blockNumber, ansBefore)
 	}
 
 	blockNumber, err = api.BlockNumber(1578638524, "after")
 	noError(t, err, "api.BlockNumber")
 
-	if blockNumber != ans_after {
-		t.Errorf(`api.BlockNumber(1578638524,"after") not working, got %d, want %d`, blockNumber, ans_after)
+	if blockNumber != ansAfter {
+		t.Errorf(`api.BlockNumber(1578638524,"after") not working, got %d, want %d`, blockNumber, ansAfter)
 	}
 }

From 8fe95ed636bd415247f9eace2fe0f61358401c91 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 15 Oct 2021 14:45:28 +0800
Subject: [PATCH 20/44] ci: fix env access, add cron

---
 .github/workflows/ci.yaml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5a3db42..269770b 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -5,6 +5,8 @@ on:
     branches: [ master ]
   pull_request:
     branches: [ master ]
+  schedule:
+    - cron: "42 6 * * 0"
 
 jobs:
   build:
@@ -18,6 +20,8 @@ jobs:
           go-version: "1.x"
 
       - name: Test
+        env:
+          ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
         run: go test -v -coverprofile=coverage.txt -covermode=count ./...
 
       - name: Codecov

From 31968a8322a6575b6d71273305596bf087b1ec07 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Fri, 15 Oct 2021 14:53:50 +0800
Subject: [PATCH 21/44] doc: fix ci badge link

---
 README.md    | 2 +-
 README_ZH.md | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index d300d3e..56acd1f 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 # etherscan-api
 
 [![GoDoc](https://godoc.org/github.com/nanmu42/etherscan-api?status.svg)](https://godoc.org/github.com/nanmu42/etherscan-api)
-[![CI status](https://github.com/nanmu42/etherscan-api/workflows/build/ci.svg)](https://github.com/nanmu42/etherscan-api/actions)
+[![CI status](https://github.com/nanmu42/etherscan-api/actions/workflows/ci.yaml/badge.svg)](https://github.com/nanmu42/etherscan-api/actions)
 [![codecov](https://codecov.io/gh/nanmu42/etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/nanmu42/etherscan-api)
 [![Go Report Card](https://goreportcard.com/badge/github.com/nanmu42/etherscan-api)](https://goreportcard.com/report/github.com/nanmu42/etherscan-api)
 
diff --git a/README_ZH.md b/README_ZH.md
index dc48f68..905a770 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -3,7 +3,7 @@
 # etherscan-api
 
 [![GoDoc](https://godoc.org/github.com/nanmu42/etherscan-api?status.svg)](https://godoc.org/github.com/nanmu42/etherscan-api)
-[![CI status](https://github.com/nanmu42/etherscan-api/workflows/build/ci.svg)](https://github.com/nanmu42/etherscan-api/actions)
+[![CI status](https://github.com/nanmu42/etherscan-api/actions/workflows/ci.yaml/badge.svg)](https://github.com/nanmu42/etherscan-api/actions)
 [![codecov](https://codecov.io/gh/nanmu42/etherscan-api/branch/master/graph/badge.svg)](https://codecov.io/gh/nanmu42/etherscan-api)
 [![Go Report Card](https://goreportcard.com/badge/github.com/nanmu42/etherscan-api)](https://goreportcard.com/report/github.com/nanmu42/etherscan-api)
 

From 57f5d0b5b54736ca8084782b57c45d572de51716 Mon Sep 17 00:00:00 2001
From: Giuseppe Valente <giusva@gmail.com>
Date: Thu, 2 Dec 2021 22:08:05 -0800
Subject: [PATCH 22/44] account: add ERC721Transfers()

---
 account.go  | 24 ++++++++++++++++++++++++
 response.go | 23 +++++++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/account.go b/account.go
index 48c149b..5c20824 100644
--- a/account.go
+++ b/account.go
@@ -107,6 +107,30 @@ func (c *Client) ERC20Transfers(contractAddress, address *string, startBlock *in
 	return
 }
 
+// ERC721Transfers get a list of "erc721 - token transfer events" by
+// contract address and/or from/to address.
+//
+// leave undesired condition to nil.
+func (c *Client) ERC721Transfers(contractAddress, address *string, startBlock *int, endBlock *int, page int, offset int, desc bool) (txs []ERC721Transfer, err error) {
+	param := M{
+		"page":   page,
+		"offset": offset,
+	}
+	compose(param, "contractaddress", contractAddress)
+	compose(param, "address", address)
+	compose(param, "startblock", startBlock)
+	compose(param, "endblock", endBlock)
+
+	if desc {
+		param["sort"] = "desc"
+	} else {
+		param["sort"] = "asc"
+	}
+
+	err = c.call("account", "tokennfttx", param, &txs)
+	return
+}
+
 // BlocksMinedByAddress gets list of blocks mined by address
 func (c *Client) BlocksMinedByAddress(address string, page int, offset int) (mined []MinedBlock, err error) {
 	param := M{
diff --git a/response.go b/response.go
index b1338d3..a5efb0a 100644
--- a/response.go
+++ b/response.go
@@ -88,6 +88,29 @@ type ERC20Transfer struct {
 	Confirmations     int     `json:"confirmations,string"`
 }
 
+// ERC721Transfer holds info from ERC721 token transfer event query
+type ERC721Transfer struct {
+	BlockNumber       int     `json:"blockNumber,string"`
+	TimeStamp         Time    `json:"timeStamp"`
+	Hash              string  `json:"hash"`
+	Nonce             int     `json:"nonce,string"`
+	BlockHash         string  `json:"blockHash"`
+	From              string  `json:"from"`
+	ContractAddress   string  `json:"contractAddress"`
+	To                string  `json:"to"`
+	TokenID           *BigInt `json:"tokenID"`
+	TokenName         string  `json:"tokenName"`
+	TokenSymbol       string  `json:"tokenSymbol"`
+	TokenDecimal      uint8   `json:"tokenDecimal,string"`
+	TransactionIndex  int     `json:"transactionIndex,string"`
+	Gas               int     `json:"gas,string"`
+	GasPrice          *BigInt `json:"gasPrice"`
+	GasUsed           int     `json:"gasUsed,string"`
+	CumulativeGasUsed int     `json:"cumulativeGasUsed,string"`
+	Input             string  `json:"input"`
+	Confirmations     int     `json:"confirmations,string"`
+}
+
 // MinedBlock holds info from query for mined block by address
 type MinedBlock struct {
 	BlockNumber int     `json:"blockNumber,string"`

From 6cf657cef58dd8bce1b10e40a0933b1818059b01 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Sun, 5 Dec 2021 12:15:10 +0800
Subject: [PATCH 23/44] ci: bump go version

fix #64
---
 .github/workflows/ci.yaml | 2 +-
 .gitignore                | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 269770b..01f01a0 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -17,7 +17,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v2
         with:
-          go-version: "1.x"
+          go-version: ">=1.17.4"
 
       - name: Test
         env:
diff --git a/.gitignore b/.gitignore
index e0ab53c..c7dafde 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
+coverage.txt
\ No newline at end of file

From ba279704c48b29456fff8bc7175484878a74666c Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Sun, 5 Dec 2021 12:32:03 +0800
Subject: [PATCH 24/44] account: add test for ERC721Transfers()

---
 account_e2e_test.go | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/account_e2e_test.go b/account_e2e_test.go
index 48632b1..ac9094b 100644
--- a/account_e2e_test.go
+++ b/account_e2e_test.go
@@ -8,6 +8,8 @@
 package etherscan
 
 import (
+	"encoding/json"
+	"fmt"
 	"math/big"
 	"testing"
 )
@@ -149,3 +151,21 @@ func TestClient_TokenBalance(t *testing.T) {
 		t.Errorf("api.TokenBalance not working, got balance %s", balance.Int().String())
 	}
 }
+
+func TestClient_ERC721Transfers(t *testing.T) {
+	const (
+		wantLen = 351
+	)
+
+	var a, b = 4708442, 9231168
+	var contract, address = "0x06012c8cf97bead5deae237070f9587f8e7a266d", "0x6975be450864c02b4613023c2152ee0743572325"
+	txs, err := api.ERC721Transfers(&contract, &address, &a, &b, 1, 500, true)
+	noError(t, err, "api.ERC721Transfers")
+
+	j, _ := json.MarshalIndent(txs, "", "  ")
+	fmt.Printf("%s\n", j)
+
+	if len(txs) != wantLen {
+		t.Errorf("got txs length %v, want %v", len(txs), wantLen)
+	}
+}

From 9e13132479ce7f4e0491d7518791b81799ea5b57 Mon Sep 17 00:00:00 2001
From: nanmu42 <i@nanmu.me>
Date: Sun, 5 Dec 2021 12:37:31 +0800
Subject: [PATCH 25/44] ci: specify env

---
 .github/workflows/ci.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 01f01a0..fc72752 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -10,6 +10,7 @@ on:
 
 jobs:
   build:
+    environment: "CI Test"
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2

From a1340f69aca21d6b160c6db94dc77f41c1293a11 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 3 Mar 2022 16:23:49 -0500
Subject: [PATCH 26/44] Added Log Data Structure for deserializing etherscan
 log events into

---
 response.go | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/response.go b/response.go
index a5efb0a..692867d 100644
--- a/response.go
+++ b/response.go
@@ -159,3 +159,15 @@ type LatestPrice struct {
 	ETHUSD          float64 `json:"ethusd,string"`
 	ETHUSDTimestamp Time    `json:"ethusd_timestamp"`
 }
+
+type Log struct {
+	Address         string   `json:"address"`
+	Topics          []string `json:"topics"`
+	Data            string   `json:"data"`
+	BlockNumber     string   `json:"blockNumber"`
+	TransactionHash string   `json:"transactionHash"`
+	BlockHash       string   `json:"blockHash"`
+	LogIndex        string   `json:"logIndex"`
+	Removed         bool     `json:"removed"`
+}
+

From ff54090281de6ee315c47486cc5759555f764638 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 3 Mar 2022 16:27:38 -0500
Subject: [PATCH 27/44] Added support for querying log events based on one
 topic

---
 logs.go          | 21 +++++++++++++++++++++
 logs_e2e_test.go | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+)
 create mode 100644 logs.go
 create mode 100644 logs_e2e_test.go

diff --git a/logs.go b/logs.go
new file mode 100644
index 0000000..0b21124
--- /dev/null
+++ b/logs.go
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022 Avi Misra
+ *
+ * Use of this work is governed by a MIT License.
+ * You may find a license copy in project root.
+ */
+
+package etherscan
+
+// GetLogs gets logs that match "topic" emitted by the specified "address" between the "fromBlock" and "toBlock"
+func (c *Client) GetLogs(fromBlock, toBlock int, address, topic string) (logs []Log, err error) {
+	param := M{
+		"fromBlock": fromBlock,
+		"toBlock":   toBlock,
+		"topic0":    topic,
+		"address":   address,
+	}
+
+	err = c.call("logs", "getLogs", param, &logs)
+	return
+}
diff --git a/logs_e2e_test.go b/logs_e2e_test.go
new file mode 100644
index 0000000..db026d8
--- /dev/null
+++ b/logs_e2e_test.go
@@ -0,0 +1,34 @@
+package etherscan
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestClient_GetLogs(t *testing.T) {
+	const ans = `[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":[],"data":"0x","blockNumber":"0x5c958","logIndex":"0x","blockHash":"","transactionHash":"0x0b03498648ae2da924f961dda00dc6bb0a8df15519262b7e012b7d67f4bb7e83", "removed":false}]`
+	expectedLogs := []Log{
+		Log{
+			Address:         "0x33990122638b9132ca29c723bdf037f1a891a70c",
+			Topics:          []string{"0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x000000000000000000000000d9b2f59f3b5c7b3c67047d2f03c3e8052470be92"},
+			Data:            "0x",
+			BlockNumber:     "0x5c958",
+			TransactionHash: "0x0b03498648ae2da924f961dda00dc6bb0a8df15519262b7e012b7d67f4bb7e83",
+			LogIndex:        "0x",
+		},
+	}
+
+	actualLogs, err := api.GetLogs(379224, 379225, "0x33990122638b9132ca29c723bdf037f1a891a70c", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545")
+
+	noError(t, err, "api.GetLogs")
+
+	var expected []map[string]interface{}
+	var actual []map[string]interface{}
+
+	equal := cmp.Equal(expectedLogs, actualLogs)
+
+	if !equal {
+		t.Errorf("api.GetLogs not working\n: %s\n", cmp.Diff(expected, actual))
+	}
+}

From 930058ab5987c89200f595e9636e27c2ae1c93e9 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 3 Mar 2022 17:00:57 -0500
Subject: [PATCH 28/44] Add missing dependency and remove unused const

---
 go.mod           | 2 ++
 logs_e2e_test.go | 1 -
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/go.mod b/go.mod
index 0a30134..3e16f01 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,5 @@
 module github.com/nanmu42/etherscan-api
 
 go 1.13
+
+require github.com/google/go-cmp v0.5.7 // indirect
diff --git a/logs_e2e_test.go b/logs_e2e_test.go
index db026d8..96901a8 100644
--- a/logs_e2e_test.go
+++ b/logs_e2e_test.go
@@ -7,7 +7,6 @@ import (
 )
 
 func TestClient_GetLogs(t *testing.T) {
-	const ans = `[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":[],"data":"0x","blockNumber":"0x5c958","logIndex":"0x","blockHash":"","transactionHash":"0x0b03498648ae2da924f961dda00dc6bb0a8df15519262b7e012b7d67f4bb7e83", "removed":false}]`
 	expectedLogs := []Log{
 		Log{
 			Address:         "0x33990122638b9132ca29c723bdf037f1a891a70c",

From 47c99e5e0cd2e3a4b82984ef52087eadb68ef35a Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 3 Mar 2022 17:05:39 -0500
Subject: [PATCH 29/44] Fixed typos and removed unused variables in
 logs_e2e_tests

---
 logs_e2e_test.go | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/logs_e2e_test.go b/logs_e2e_test.go
index 96901a8..d4894ed 100644
--- a/logs_e2e_test.go
+++ b/logs_e2e_test.go
@@ -22,12 +22,9 @@ func TestClient_GetLogs(t *testing.T) {
 
 	noError(t, err, "api.GetLogs")
 
-	var expected []map[string]interface{}
-	var actual []map[string]interface{}
-
 	equal := cmp.Equal(expectedLogs, actualLogs)
 
 	if !equal {
-		t.Errorf("api.GetLogs not working\n: %s\n", cmp.Diff(expected, actual))
+		t.Errorf("api.GetLogs not working\n: %s\n", cmp.Diff(expectedLogs, actualLogs))
 	}
 }

From 4f153f74097ef6f1f7dae8157f9afc26cbe2c0ad Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Thu, 3 Mar 2022 17:10:25 -0500
Subject: [PATCH 30/44] Added misisng go.sum file

---
 go.sum | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 go.sum

diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..a365b08
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,3 @@
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From e7972b83dc10289591e5414e7266735ab9ec776a Mon Sep 17 00:00:00 2001
From: rms rob <robyremzy@disroot.org>
Date: Sun, 10 Jul 2022 06:30:57 +0200
Subject: [PATCH 31/44] update ContractSource

---
 response.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/response.go b/response.go
index 692867d..e28007e 100644
--- a/response.go
+++ b/response.go
@@ -127,7 +127,11 @@ type ContractSource struct {
 	OptimizationUsed     int    `json:"OptimizationUsed,string"`
 	Runs                 int    `json:"Runs,string"`
 	ConstructorArguments string `json:"ConstructorArguments"`
+	EVMVersion           string `json:"EVMVersion"`
 	Library              string `json:"Library"`
+	LicenseType          string `json:"LicenseType"`
+	Proxy                string `json:"Proxy"`
+	Implementation       string `json:"Implementation"`
 	SwarmSource          string `json:"SwarmSource"`
 }
 

From dbb9f1b0a9930bc0d7986bac208dfbcce6e5eb12 Mon Sep 17 00:00:00 2001
From: LI Zhennan <i@nanmu.me>
Date: Tue, 9 Aug 2022 19:12:57 +0800
Subject: [PATCH 32/44] fix CI test

---
 logs_e2e_test.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/logs_e2e_test.go b/logs_e2e_test.go
index d4894ed..e4490d2 100644
--- a/logs_e2e_test.go
+++ b/logs_e2e_test.go
@@ -13,6 +13,7 @@ func TestClient_GetLogs(t *testing.T) {
 			Topics:          []string{"0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x000000000000000000000000d9b2f59f3b5c7b3c67047d2f03c3e8052470be92"},
 			Data:            "0x",
 			BlockNumber:     "0x5c958",
+			BlockHash:       "0xe32a9cac27f823b18454e8d69437d2af41a1b81179c6af2601f1040a72ad444b",
 			TransactionHash: "0x0b03498648ae2da924f961dda00dc6bb0a8df15519262b7e012b7d67f4bb7e83",
 			LogIndex:        "0x",
 		},

From caf06a33dbcb0723ee242d9fb880b1040c1ac6f6 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Mon, 24 Oct 2022 11:13:06 -0400
Subject: [PATCH 33/44] Implemented Free Gas Tracker Endpoints

---
 gas_tracker.go          | 21 ++++++++++++++++
 gas_tracker_e2e_test.go | 53 +++++++++++++++++++++++++++++++++++++++++
 response.go             | 10 ++++++++
 3 files changed, 84 insertions(+)
 create mode 100644 gas_tracker.go
 create mode 100644 gas_tracker_e2e_test.go

diff --git a/gas_tracker.go b/gas_tracker.go
new file mode 100644
index 0000000..6076b2a
--- /dev/null
+++ b/gas_tracker.go
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022 Avi Misra
+ *
+ * Use of this work is governed by a MIT License.
+ * You may find a license copy in project root.
+ */
+
+package etherscan
+
+// GasEstiamte gets estiamted confirmation time (in seconds) at the given gas price
+func (c *Client) GasEstimate(gasPrice int) (confirmationTimeInSec string, err error) {
+	params := M{"gasPrice": gasPrice}
+	err = c.call("gastracker", "gasestimate", params, &confirmationTimeInSec)
+	return
+}
+
+// GasOracle gets suggested gas prices (in Gwei)
+func (c *Client) GasOracle() (gasPrices GasPrices, err error) {
+	err = c.call("gastracker", "gasoracle", M{}, &gasPrices)
+	return
+}
diff --git a/gas_tracker_e2e_test.go b/gas_tracker_e2e_test.go
new file mode 100644
index 0000000..5dc6c4b
--- /dev/null
+++ b/gas_tracker_e2e_test.go
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 LI Zhennan
+ *
+ * Use of this work is governed by a MIT License.
+ * You may find a license copy in project root.
+ */
+
+package etherscan
+
+import (
+	"testing"
+)
+
+//GasEstiamte generates dynamic data. Best we can do is ensure all fields are populated
+func TestClient_GasEstimate(t *testing.T) {
+	confirmationTime, err := api.GasEstimate(20000000)
+	noError(t, err, "api.GasEstimate")
+
+	if 0 == len(confirmationTime) {
+		t.Errorf("confirmationTime empty string")
+	}
+}
+
+//GasOracle generates dynamic data. Best we can do is ensure all fields are populated
+func TestClient_GasOracle(t *testing.T) {
+	gasPrice, err := api.GasOracle()
+	noError(t, err, "api.GasOrcale")
+
+	if 0 == len(gasPrice.LastBlock) {
+		t.Errorf("gasPrice.LastBlock empty string")
+	}
+
+	if 0 == len(gasPrice.SafeGasPrice) {
+		t.Errorf("gasPrice.SafeGasPrice empty string")
+	}
+
+	if 0 == len(gasPrice.ProposeGasPrice) {
+		t.Errorf("gasPrice.ProposeGasPrice empty string")
+	}
+
+	if 0 == len(gasPrice.FastGasPrice) {
+		t.Errorf("gasPrice.FastGasPrice empty string")
+	}
+
+	if 0 == len(gasPrice.SuggestBaseFeeInGwei) {
+		t.Errorf("gasPrice.SuggestBaseFeeInGwei empty string")
+	}
+
+	if 0 == len(gasPrice.GasUsedRatio) {
+		t.Errorf("gasPrice.GasUsedRatio empty string")
+	}
+
+}
diff --git a/response.go b/response.go
index e28007e..9cd2570 100644
--- a/response.go
+++ b/response.go
@@ -175,3 +175,13 @@ type Log struct {
 	Removed         bool     `json:"removed"`
 }
 
+//GasPrices holds info for Gas Oracle queries
+//Gas Prices are returned in Gwei
+type GasPrices struct {
+	LastBlock            string
+	SafeGasPrice         string
+	ProposeGasPrice      string
+	FastGasPrice         string
+	SuggestBaseFeeInGwei string `json:"suggestBaseFee"`
+	GasUsedRatio         string `json:"gasUsedRatio"`
+}

From 384329f2261b7e66a1ab6dc175af1010a537a8e1 Mon Sep 17 00:00:00 2001
From: avislash <avislash@gmail.com>
Date: Tue, 25 Oct 2022 10:36:52 -0400
Subject: [PATCH 34/44] Use numeric data types for gas prices, confirmation
 times, and block numbers

---
 gas_tracker.go          | 12 +++++--
 gas_tracker_e2e_test.go | 28 ++--------------
 response.go             | 71 +++++++++++++++++++++++++++++++++++++----
 3 files changed, 75 insertions(+), 36 deletions(-)

diff --git a/gas_tracker.go b/gas_tracker.go
index 6076b2a..02fe5ca 100644
--- a/gas_tracker.go
+++ b/gas_tracker.go
@@ -7,11 +7,17 @@
 
 package etherscan
 
+import "time"
+
 // GasEstiamte gets estiamted confirmation time (in seconds) at the given gas price
-func (c *Client) GasEstimate(gasPrice int) (confirmationTimeInSec string, err error) {
+func (c *Client) GasEstimate(gasPrice int) (confirmationTimeInSec time.Duration, err error) {
 	params := M{"gasPrice": gasPrice}
-	err = c.call("gastracker", "gasestimate", params, &confirmationTimeInSec)
-	return
+	var confTime string
+	err = c.call("gastracker", "gasestimate", params, &confTime)
+	if err != nil {
+		return
+	}
+	return time.ParseDuration(confTime + "s")
 }
 
 // GasOracle gets suggested gas prices (in Gwei)
diff --git a/gas_tracker_e2e_test.go b/gas_tracker_e2e_test.go
index 5dc6c4b..3905bc3 100644
--- a/gas_tracker_e2e_test.go
+++ b/gas_tracker_e2e_test.go
@@ -13,12 +13,8 @@ import (
 
 //GasEstiamte generates dynamic data. Best we can do is ensure all fields are populated
 func TestClient_GasEstimate(t *testing.T) {
-	confirmationTime, err := api.GasEstimate(20000000)
+	_, err := api.GasEstimate(20000000)
 	noError(t, err, "api.GasEstimate")
-
-	if 0 == len(confirmationTime) {
-		t.Errorf("confirmationTime empty string")
-	}
 }
 
 //GasOracle generates dynamic data. Best we can do is ensure all fields are populated
@@ -26,28 +22,8 @@ func TestClient_GasOracle(t *testing.T) {
 	gasPrice, err := api.GasOracle()
 	noError(t, err, "api.GasOrcale")
 
-	if 0 == len(gasPrice.LastBlock) {
-		t.Errorf("gasPrice.LastBlock empty string")
-	}
-
-	if 0 == len(gasPrice.SafeGasPrice) {
-		t.Errorf("gasPrice.SafeGasPrice empty string")
-	}
-
-	if 0 == len(gasPrice.ProposeGasPrice) {
-		t.Errorf("gasPrice.ProposeGasPrice empty string")
-	}
-
-	if 0 == len(gasPrice.FastGasPrice) {
-		t.Errorf("gasPrice.FastGasPrice empty string")
-	}
-
-	if 0 == len(gasPrice.SuggestBaseFeeInGwei) {
-		t.Errorf("gasPrice.SuggestBaseFeeInGwei empty string")
-	}
-
 	if 0 == len(gasPrice.GasUsedRatio) {
-		t.Errorf("gasPrice.GasUsedRatio empty string")
+		t.Errorf("gasPrice.GasUsedRatio empty")
 	}
 
 }
diff --git a/response.go b/response.go
index 9cd2570..b0517cd 100644
--- a/response.go
+++ b/response.go
@@ -7,7 +7,12 @@
 
 package etherscan
 
-import "encoding/json"
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"strings"
+)
 
 // Envelope is the carrier of nearly every response
 type Envelope struct {
@@ -178,10 +183,62 @@ type Log struct {
 //GasPrices holds info for Gas Oracle queries
 //Gas Prices are returned in Gwei
 type GasPrices struct {
-	LastBlock            string
-	SafeGasPrice         string
-	ProposeGasPrice      string
-	FastGasPrice         string
-	SuggestBaseFeeInGwei string `json:"suggestBaseFee"`
-	GasUsedRatio         string `json:"gasUsedRatio"`
+	LastBlock            int
+	SafeGasPrice         float64
+	ProposeGasPrice      float64
+	FastGasPrice         float64
+	SuggestBaseFeeInGwei float64   `json:"suggestBaseFee"`
+	GasUsedRatio         []float64 `json:"gasUsedRatio"`
+}
+
+func (gp *GasPrices) UnmarshalJSON(data []byte) error {
+	_gp := struct {
+		LastBlock            string
+		SafeGasPrice         string
+		ProposeGasPrice      string
+		FastGasPrice         string
+		SuggestBaseFeeInGwei string `json:"suggestBaseFee"`
+		GasUsedRatio         string `json:"gasUsedRatio"`
+	}{}
+
+	err := json.Unmarshal(data, &_gp)
+	if err != nil {
+		return err
+	}
+
+	gp.LastBlock, err = strconv.Atoi(_gp.LastBlock)
+	if err != nil {
+		return fmt.Errorf("Unable to convert LastBlock %s to int: %w", _gp.LastBlock, err)
+	}
+
+	gp.SafeGasPrice, err = strconv.ParseFloat(_gp.SafeGasPrice, 64)
+	if err != nil {
+		return fmt.Errorf("Unable to convert SafeGasPrice %s to float64: %w", _gp.SafeGasPrice, err)
+	}
+
+	gp.ProposeGasPrice, err = strconv.ParseFloat(_gp.ProposeGasPrice, 64)
+	if err != nil {
+		return fmt.Errorf("Unable to convert ProposeGasPrice %s to float64: %w", _gp.ProposeGasPrice, err)
+	}
+
+	gp.FastGasPrice, err = strconv.ParseFloat(_gp.FastGasPrice, 64)
+	if err != nil {
+		return fmt.Errorf("Unable to convert FastGasPrice %s to float64: %w", _gp.FastGasPrice, err)
+	}
+
+	gp.SuggestBaseFeeInGwei, err = strconv.ParseFloat(_gp.SuggestBaseFeeInGwei, 64)
+	if err != nil {
+		return fmt.Errorf("Unable to convert SuggestBaseFeeInGwei %s to float64: %w", _gp.SuggestBaseFeeInGwei, err)
+	}
+
+	gasRatios := strings.Split(_gp.GasUsedRatio, ",")
+	gp.GasUsedRatio = make([]float64, len(gasRatios))
+	for i, gasRatio := range gasRatios {
+		gp.GasUsedRatio[i], err = strconv.ParseFloat(gasRatio, 64)
+		if err != nil {
+			return fmt.Errorf("Unable to convert gasRatio %s to float64: %w", gasRatio, err)
+		}
+	}
+
+	return nil
 }

From cf99fda6d3e7b8c40e2f2e93ed29499c0fed52af Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Mon, 12 Dec 2022 17:09:45 +0400
Subject: [PATCH 35/44] erc1155 tx struct and ERC1155Transfers added

---
 account.go          | 24 ++++++++++++++++++++++++
 account_e2e_test.go | 18 ++++++++++++++++++
 response.go         | 28 ++++++++++++++++++++++++++--
 3 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/account.go b/account.go
index 5c20824..e6a9ac1 100644
--- a/account.go
+++ b/account.go
@@ -131,6 +131,30 @@ func (c *Client) ERC721Transfers(contractAddress, address *string, startBlock *i
 	return
 }
 
+// ERC1155Transfers get a list of "erc1155 - token transfer events" by
+// contract address and/or from/to address.
+//
+// leave undesired condition to nil.
+func (c *Client) ERC1155Transfers(contractAddress, address *string, startBlock *int, endBlock *int, page int, offset int, desc bool) (txs []ERC1155Transfer, err error) {
+	param := M{
+		"page":   page,
+		"offset": offset,
+	}
+	compose(param, "contractaddress", contractAddress)
+	compose(param, "address", address)
+	compose(param, "startblock", startBlock)
+	compose(param, "endblock", endBlock)
+
+	if desc {
+		param["sort"] = "desc"
+	} else {
+		param["sort"] = "asc"
+	}
+
+	err = c.call("account", "token1155tx", param, &txs)
+	return
+}
+
 // BlocksMinedByAddress gets list of blocks mined by address
 func (c *Client) BlocksMinedByAddress(address string, page int, offset int) (mined []MinedBlock, err error) {
 	param := M{
diff --git a/account_e2e_test.go b/account_e2e_test.go
index ac9094b..f7688e3 100644
--- a/account_e2e_test.go
+++ b/account_e2e_test.go
@@ -169,3 +169,21 @@ func TestClient_ERC721Transfers(t *testing.T) {
 		t.Errorf("got txs length %v, want %v", len(txs), wantLen)
 	}
 }
+
+func TestClient_ERC1155Transfers(t *testing.T) {
+	const (
+		wantLen = 1
+	)
+
+	var a, b = 128135633, 1802672
+	var contract, address = "0x3edf71a31b80Ff6a45Fdb0858eC54DE98dF047AA", "0x4b986EF20Bb83532911521FB4F6F5605122a0721"
+	txs, err := api.ERC1155Transfers(&contract, &address, &b, &a, 0, 0, true)
+	noError(t, err, "api.ERC721Transfers")
+
+	j, _ := json.MarshalIndent(txs, "", "  ")
+	fmt.Printf("%s\n", j)
+
+	if len(txs) != wantLen {
+		t.Errorf("got txs length %v, want %v", len(txs), wantLen)
+	}
+}
diff --git a/response.go b/response.go
index b0517cd..d096aef 100644
--- a/response.go
+++ b/response.go
@@ -116,6 +116,30 @@ type ERC721Transfer struct {
 	Confirmations     int     `json:"confirmations,string"`
 }
 
+// ERC1155Transfer holds info from ERC1155 token transfer event query
+type ERC1155Transfer struct {
+	BlockNumber       int     `json:"blockNumber,string"`
+	TimeStamp         Time    `json:"timeStamp"`
+	Hash              string  `json:"hash"`
+	Nonce             int     `json:"nonce,string"`
+	BlockHash         string  `json:"blockHash"`
+	From              string  `json:"from"`
+	ContractAddress   string  `json:"contractAddress"`
+	To                string  `json:"to"`
+	TokenID           *BigInt `json:"tokenID"`
+	TokenName         string  `json:"tokenName"`
+	TokenSymbol       string  `json:"tokenSymbol"`
+	TokenDecimal      uint8   `json:"tokenDecimal,string"`
+	TokenValue        uint8   `json:"tokenValue,string"`
+	TransactionIndex  int     `json:"transactionIndex,string"`
+	Gas               int     `json:"gas,string"`
+	GasPrice          *BigInt `json:"gasPrice"`
+	GasUsed           int     `json:"gasUsed,string"`
+	CumulativeGasUsed int     `json:"cumulativeGasUsed,string"`
+	Input             string  `json:"input"`
+	Confirmations     int     `json:"confirmations,string"`
+}
+
 // MinedBlock holds info from query for mined block by address
 type MinedBlock struct {
 	BlockNumber int     `json:"blockNumber,string"`
@@ -180,8 +204,8 @@ type Log struct {
 	Removed         bool     `json:"removed"`
 }
 
-//GasPrices holds info for Gas Oracle queries
-//Gas Prices are returned in Gwei
+// GasPrices holds info for Gas Oracle queries
+// Gas Prices are returned in Gwei
 type GasPrices struct {
 	LastBlock            int
 	SafeGasPrice         float64

From 5b06ae309b15fd8f7c57c86826e71e939ed0be7c Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Mon, 12 Dec 2022 18:22:27 +0400
Subject: [PATCH 36/44] go mod replace

---
 go.mod | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/go.mod b/go.mod
index 3e16f01..4083741 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/nanmu42/etherscan-api
+module github.com/Pashteto/etherscan-api
 
 go 1.13
 

From f615701789b83f9e2e33270c8f99d519bb0514cc Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Mon, 12 Dec 2022 18:22:27 +0400
Subject: [PATCH 37/44] go mod replace

---
 go.mod | 4 +++-
 go.sum | 1 +
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/go.mod b/go.mod
index 3e16f01..b6871ed 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,6 @@ module github.com/nanmu42/etherscan-api
 
 go 1.13
 
-require github.com/google/go-cmp v0.5.7 // indirect
+require github.com/google/go-cmp v0.5.7
+
+replace github.com/nanmu42/etherscan-api => github.com/Pashteto/etherscan-api v0.0.0-20221212135811-6be05412311b
diff --git a/go.sum b/go.sum
index a365b08..a6ca3a4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,4 @@
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

From d7387da310f0d76973d3179acc66c194c3b3266f Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Mon, 12 Dec 2022 18:40:01 +0400
Subject: [PATCH 38/44] go mod replace

---
 go.mod | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/go.mod b/go.mod
index b6871ed..8978d26 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/nanmu42/etherscan-api
+module github.com/Pashteto/etherscan-api
 
 go 1.13
 

From 0e26ffd32b36d75c914e7a6857ea9da64f4bc02f Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Mon, 12 Dec 2022 18:44:47 +0400
Subject: [PATCH 39/44] go mod replace

---
 go.mod | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/go.mod b/go.mod
index 8978d26..9ecb506 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
-module github.com/Pashteto/etherscan-api
+module github.com/nanmu42/etherscan-api
 
-go 1.13
+go 1.19
 
 require github.com/google/go-cmp v0.5.7
 

From 6a8ea3a978b5db6796f814d1cbf2d5e7cac0b568 Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Mon, 12 Dec 2022 18:56:57 +0400
Subject: [PATCH 40/44] go mod replace

---
 go.mod | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/go.mod b/go.mod
index 9ecb506..dedf8f1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,4 +4,4 @@ go 1.19
 
 require github.com/google/go-cmp v0.5.7
 
-replace github.com/nanmu42/etherscan-api => github.com/Pashteto/etherscan-api v0.0.0-20221212135811-6be05412311b
+replace github.com/nanmu42/etherscan-api v1.9.0 => github.com/Pashteto/etherscan-api v1.0.0

From 890f9df7fab134dc25e3a08d1f83007df6fcd6b3 Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Tue, 13 Dec 2022 13:32:36 +0400
Subject: [PATCH 41/44] go mod replace

---
 go.mod | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index dedf8f1..be10176 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,5 @@
-module github.com/nanmu42/etherscan-api
+module github.com/Pashteto/etherscan-api
 
 go 1.19
 
 require github.com/google/go-cmp v0.5.7
-
-replace github.com/nanmu42/etherscan-api v1.9.0 => github.com/Pashteto/etherscan-api v1.0.0

From b8dd9f49cd38d336b0b7565c8cbc22c010585f8f Mon Sep 17 00:00:00 2001
From: Dodonov Pavel <Dodonov.P@citilink.ru>
Date: Tue, 13 Dec 2022 14:07:11 +0400
Subject: [PATCH 42/44] go mod replace

---
 go.mod | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/go.mod b/go.mod
index be10176..5837de6 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
-module github.com/Pashteto/etherscan-api
+module github.com/nanmu42/etherscan-api
 
-go 1.19
+go 1.13
 
 require github.com/google/go-cmp v0.5.7

From 07a1d97ed94a5c900a51eed10dac08f70b52096d Mon Sep 17 00:00:00 2001
From: LI Zhennan <i@nanmu.me>
Date: Tue, 14 Feb 2023 11:05:29 +0800
Subject: [PATCH 43/44] Create FUNDING.yml

---
 .github/FUNDING.yml | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 .github/FUNDING.yml

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..71b970a
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: nanmu42

From 67da1ed8aa6e01a995b0f1b21eb1cdb0c74099d5 Mon Sep 17 00:00:00 2001
From: coolestowl <97215481+coolestowl@users.noreply.github.com>
Date: Mon, 11 Mar 2024 11:44:46 +0800
Subject: [PATCH 44/44] Update response.go

NormalTx add fields
---
 response.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/response.go b/response.go
index d096aef..5c418cf 100644
--- a/response.go
+++ b/response.go
@@ -50,6 +50,8 @@ type NormalTx struct {
 	CumulativeGasUsed int     `json:"cumulativeGasUsed,string"`
 	GasUsed           int     `json:"gasUsed,string"`
 	Confirmations     int     `json:"confirmations,string"`
+	FunctionName      string  `json:"functionName"`
+	MethodId          string  `json:"methodId"`
 }
 
 // InternalTx holds info from internal tx query
