diff --git a/block_e2e_test.go b/block_e2e_test.go index c49a8ac..f907748 100644 --- a/block_e2e_test.go +++ b/block_e2e_test.go @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 LI Zhennan + * Copyright (c) 2022 Łukasz Rżanek * * Use of this work is governed by a MIT License. * You may find a license copy in project root. @@ -10,37 +11,79 @@ package etherscan import ( "encoding/json" "testing" + + "github.com/stretchr/testify/assert" ) func TestClient_BlockReward(t *testing.T) { - const ans = `{"blockNumber":"2165403","timeStamp":"1472533979","blockMiner":"0x13a06d3dfe21e0db5c016c03ea7d2509f7f8d1e3","blockReward":"5314181600000000000","uncles":[{"miner":"0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1","unclePosition":"0","blockreward":"3750000000000000000"},{"miner":"0x0d0c9855c722ff0c78f21e43aa275a5b8ea60dce","unclePosition":"1","blockreward":"3750000000000000000"}],"uncleInclusionReward":"312500000000000000"}` + const ( + ethAns = `{"blockNumber":"2165403","timeStamp":"1472533979","blockMiner":"0x13a06d3dfe21e0db5c016c03ea7d2509f7f8d1e3","blockReward":"5314181600000000000","uncles":[{"miner":"0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1","unclePosition":"0","blockreward":"3750000000000000000"},{"miner":"0x0d0c9855c722ff0c78f21e43aa275a5b8ea60dce","unclePosition":"1","blockreward":"3750000000000000000"}],"uncleInclusionReward":"312500000000000000"}` + maticAns = `{"blockNumber":"2165403","timeStamp":"1595322344","blockMiner":"0x0375b2fc7140977c9c76d45421564e354ed42277","blockReward":"0","uncles":[],"uncleInclusionReward":"0"}` + bscAns = `{"blockNumber":"2165403","timeStamp":"1605169045","blockMiner":"0x78f3adfc719c99674c072166708589033e2d9afe","blockReward":"0","uncles":[],"uncleInclusionReward":"0"}` + avaxAns = `{"blockNumber":"2165403","timeStamp":"1622557183","blockMiner":"0x0100000000000000000000000000000000000000","blockReward":"7963290000000000","uncles":[],"uncleInclusionReward":"0"}` + ftmAns = `{"blockNumber":"2165403","timeStamp":"1612983193","blockMiner":"0x0000000000000000000000000000000000000000","blockReward":"5225066000000000","uncles":[],"uncleInclusionReward":"0"}` + cronosAns = `{"blockNumber":"2165403","timeStamp":"1648843458","blockMiner":"0x4f87a3f99bd1e58d01de1c38b7f83cb967e816c2","blockReward":"49532402000000000000","uncles":[],"uncleInclusionReward":"0"}` + arbitrumAns = `{"blockNumber":"2165403","timeStamp":"1634069963","blockMiner":"0x0000000000000000000000000000000000000000","blockReward":"2065103660121552","uncles":[],"uncleInclusionReward":"0"}` + ) - reward, err := api.BlockReward(2165403) - noError(t, err, "api.BlockReward") + type Data struct { + BlockNumber int + Ans string + } - j, err := json.Marshal(reward) - noError(t, err, "json.Marshal") - if string(j) != ans { - t.Errorf("api.BlockReward not working, got %s, want %s", j, ans) + testData := map[string]Data{ + EthMainnet.CommonName: {2165403, ethAns}, + MaticMainnet.CommonName: {2165403, maticAns}, + BscMainnet.CommonName: {2165403, bscAns}, + AvaxMainnet.CommonName: {2165403, avaxAns}, + FantomMainnet.CommonName: {2165403, ftmAns}, + CronosMainnet.CommonName: {2165403, cronosAns}, + ArbitrumMainnet.CommonName: {2165403, arbitrumAns}, + } + + for _, network := range TestNetworks { + if td, ok := testData[network.Network.CommonName]; ok { + t.Run(network.Network.Name, func(t *testing.T) { + reward, err := network.client.BlockReward(td.BlockNumber) + assert.NoError(t, err) + + j, err := json.Marshal(reward) + assert.NoError(t, err) + assert.Equalf(t, td.Ans, string(j), "api.BlockReward not working, got %s, want %s", j, ethAns) + }) + } } } func TestClient_BlockNumber(t *testing.T) { - // 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") + type Data struct { + Timestamp int64 + AnsBefore int + AnsAfter int + } - if blockNumber != ansBefore { - t.Errorf(`api.BlockNumber(1578638524, "before") not working, got %d, want %d`, blockNumber, ansBefore) + testData := map[string]Data{ + // Note: All values taken from docs.etherscan.io/api-endpoints/blocks + EthMainnet.CommonName: {1578638524, 9251482, 9251483}, + MaticMainnet.CommonName: {1601510400, 5164199, 5164200}, + BscMainnet.CommonName: {1601510400, 946206, 946207}, + AvaxMainnet.CommonName: {1609455600, 18960, 18961}, + FantomMainnet.CommonName: {1609455600, 1632921, 1632921}, // it is the same! + CronosMainnet.CommonName: {1654034400, 3023556, 3023557}, + ArbitrumMainnet.CommonName: {1656626400, 16655953, 16655954}, } - blockNumber, err = api.BlockNumber(1578638524, "after") - noError(t, err, "api.BlockNumber") + for _, network := range TestNetworks { + if td, ok := testData[network.Network.CommonName]; ok { + t.Run(network.Network.Name, func(t *testing.T) { + blockNumber, err := network.client.BlockNumber(td.Timestamp, "before") + assert.NoError(t, err) + assert.Equalf(t, td.AnsBefore, blockNumber, `api.BlockNumber(%d, "before") not working, got %d, want %d`, td.Timestamp, blockNumber, td.AnsBefore) - if blockNumber != ansAfter { - t.Errorf(`api.BlockNumber(1578638524,"after") not working, got %d, want %d`, blockNumber, ansAfter) + blockNumber, err = network.client.BlockNumber(td.Timestamp, "after") + assert.NoError(t, err) + assert.Equalf(t, td.AnsAfter, blockNumber, `api.BlockNumber(%d, "after") not working, got %d, want %d`, td.Timestamp, blockNumber, td.AnsAfter) + }) + } } } diff --git a/client.go b/client.go index 950c532..5e95a68 100644 --- a/client.go +++ b/client.go @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 LI Zhennan + * Copyright (c) 2022 Łukasz Rżanek * * Use of this work is governed by a MIT License. * You may find a license copy in project root. @@ -48,7 +49,7 @@ func New(network Network, APIKey string) *Client { return NewCustomized(Customization{ Timeout: 30 * time.Second, Key: APIKey, - BaseURL: network.Domain(), + Network: &network, }) } @@ -60,6 +61,8 @@ type Customization struct { Key string // Base URL like `https://api.etherscan.io/api?` BaseURL string + // Network + Network *Network // When true, talks a lot Verbose bool // HTTP Client to be used. Specifying this value will ignore the Timeout value set @@ -86,8 +89,12 @@ func NewCustomized(config Customization) *Client { Timeout: config.Timeout, } } + if config.Network != nil { + config.BaseURL = config.Network.baseURL + } return &Client{ coon: httpClient, + network: *config.Network, key: config.Key, baseURL: config.BaseURL, Verbose: config.Verbose, diff --git a/go.mod b/go.mod index d00d41d..8defbc1 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,13 @@ module github.com/uded/etherscan-api go 1.18 -require github.com/google/go-cmp v0.5.7 +require ( + github.com/google/go-cmp v0.5.7 + github.com/stretchr/testify v1.8.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index a6ca3a4..e51298d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/network.go b/network.go index 2edca6f..b90c91a 100644 --- a/network.go +++ b/network.go @@ -15,41 +15,45 @@ import ( var ( // EthMainnet Ethereum mainnet for production - EthMainnet Network = Network{"Ethereum", "eth_main", "ETH", "https://api.etherscan.io/api?"} + EthMainnet Network = Network{"Ethereum", "eth_main", "ETH", "https://api.etherscan.io/api?", "0x1", 1} // EthRopsten Testnet(POW) - EthRopsten Network = Network{"Ethereum Ropsten", "eth_ropsten", "ETH", "https://api-ropsten.etherscan.io/api?"} + EthRopsten Network = Network{"Ethereum Ropsten", "eth_ropsten", "ETH", "https://api-ropsten.etherscan.io/api?", "0x3", 3} // EthKovan Testnet(POA) - EthKovan Network = Network{"Ethereum Kovan", "eth_kovan", "ETH", "https://api-kovan.etherscan.io/api?"} + EthKovan Network = Network{"Ethereum Kovan", "eth_kovan", "ETH", "https://api-kovan.etherscan.io/api?", "0x2a", 42} // EthRinkby Testnet(CLIQUE) - EthRinkby Network = Network{"Ethereum Rinkby", "eth_rinkeby", "ETH", "https://api-rinkeby.etherscan.io/api?"} + EthRinkby Network = Network{"Ethereum Rinkby", "eth_rinkeby", "ETH", "https://api-rinkeby.etherscan.io/api?", "0x4", 4} // EthGoerli Testnet(CLIQUE) - EthGoerli Network = Network{"Ethereum Goerli", "eth_goerli", "ETH", "https://api-goerli.etherscan.io/api?"} + EthGoerli Network = Network{"Ethereum Goerli", "eth_goerli", "ETH", "https://api-goerli.etherscan.io/api?", "0x5", 5} // EthTobalaba Testnet - EthTobalaba Network = Network{"Ethereum Tobalaba", "eth_tobalaba", "ETH", "https://api-tobalaba.etherscan.io/api?"} + EthTobalaba Network = Network{"Ethereum Tobalaba", "eth_tobalaba", "ETH", "https://api-tobalaba.etherscan.io/api?", "0x0", 0} // MaticMainnet Matic mainnet for production - MaticMainnet Network = Network{"Polygon", "polygon", "MATIC", "https://api.polygonscan.com/api?"} + MaticMainnet Network = Network{"Polygon", "polygon", "MATIC", "https://api.polygonscan.com/api?", "0x89", 137} // MaticTestnet Matic testnet for development - MaticTestnet Network = Network{"Polygon Mumbai", "polygon_mumbai", "C:\\Users\\lukas\\InfinityForceProjects\\etherscan-api\\network.go", "https://api-testnet.polygonscan.com/api?"} + MaticTestnet Network = Network{"Polygon Mumbai", "polygon_mumbai", "MATIC", "https://api-testnet.polygonscan.com/api?", "0x13881", 80001} // BscMainnet Bsc mainnet for production - BscMainnet Network = Network{"Binance", "bsc", "BNB", "https://api.bscscan.com/api?"} + BscMainnet Network = Network{"Binance", "bsc", "BNB", "https://api.bscscan.com/api?", "0x38", 56} // BscTestnet Bsc testnet for development - BscTestnet Network = Network{"Binance test", "bsc_test", "BNB", "https://api-testnet.bscscan.com/api?"} + BscTestnet Network = Network{"Binance test", "bsc_test", "BNB", "https://api-testnet.bscscan.com/api?", "0x61", 97} // AvaxMainnet Avalanche mainnet for production - AvaxMainnet Network = Network{"Avax", "avax", "AVAX", "https://api.snowtrace.io/api?"} + AvaxMainnet Network = Network{"Avax", "avax", "AVAX", "https://api.snowtrace.io/api?", "0xa86a", 43114} // AvaxTestnet Avalanche testnet for development - AvaxTestnet Network = Network{"Avax test", "avax_test", "AVAX", "https://api-testnet.snowtrace.io/api?"} - // Fantom mainnet for production - FantomMainnet Network = Network{"Fantom", "fantom", "FTM", "https://api.ftmscan.com/api?"} - // FantomTestNet - FantomTestnet Network = Network{"Fantom test", "fantom_test", "FTM", "https://api-testnet.ftmscan.com/api?"} + AvaxTestnet Network = Network{"Avax test", "avax_test", "AVAX", "https://api-testnet.snowtrace.io/api?", "0xa869", 43113} + // FantomMainnet for production + FantomMainnet Network = Network{"Fantom", "fantom", "FTM", "htstps://api.ftmscan.com/api?", "0xfa", 250} + // FantomTestnet + FantomTestnet Network = Network{"Fantom test", "fantom_test", "FTM", "https://api-testnet.ftmscan.com/api?", "0x0", 0} // Cronos mainnet for production - CronosMainnet Network = Network{"Cronos", "cronos", "CRO", "https://api.cronoscan.com/api?"} + CronosMainnet Network = Network{"Cronos", "cronos", "CRO", "https://api.cronoscan.com/api?", "0x19", 25} // Cronos test net - CronosTestnet Network = Network{"Cronos test", "cronos_test", "CRO", "https://api-testnet.cronoscan.com/api?"} + CronosTestnet Network = Network{"Cronos test", "cronos_test", "CRO", "https://api-testnet.cronoscan.com/api?", "0x152", 338} // Arbitrum mainnet for production - ArbitrumMainnet Network = Network{"Arbitrum", "arbitrum", "ETH", "https://api.arbiscan.io/api?"} + ArbitrumMainnet Network = Network{"Arbitrum", "arbitrum", "ETH", "https://api.arbiscan.io/api?", "0x0", 0} // Arbitrum test net - ArbitrumTestnet Network = Network{"Arbitrum test", "arbitrum_test", "ETH", "https://api-testnet.arbiscan.io/"} + ArbitrumTestnet Network = Network{"Arbitrum test", "arbitrum_test", "ETH", "https://api-testnet.arbiscan.io/", "0x0", 0} + // Optimism mainnet for production + OptimismMainnet Network = Network{"Optimsm", "optimism", "ETH", "https://api-optimistic.etherscan.io/", "0xa", 10} + // Optimism test net + OptimismTestnet Network = Network{"Optimism Goerli", "optimism_test", "ETH", "https://api-goerli-optimistic.etherscan.io/", "", 420} networks = map[string]*Network{ EthMainnet.Name: &EthMainnet, @@ -77,6 +81,7 @@ var ( MaticMainnet.CommonName: &MaticMainnet, "maticmainnet": &MaticMainnet, "polygon": &MaticMainnet, + "polygon-pos": &MaticMainnet, "matic": &MaticMainnet, MaticTestnet.Name: &MaticTestnet, MaticTestnet.CommonName: &MaticTestnet, @@ -86,6 +91,7 @@ var ( BscMainnet.CommonName: &BscMainnet, "bscmainnet": &BscMainnet, "binance": &BscMainnet, + "binance-smart-chain": &BscMainnet, BscTestnet.Name: &BscTestnet, BscTestnet.CommonName: &BscTestnet, "bsctestnet": &BscTestnet, @@ -123,6 +129,14 @@ var ( "arbitrumtest": &ArbitrumTestnet, "arbitrumtestnet": &ArbitrumTestnet, "arbitrum_rinkeby": &ArbitrumTestnet, + OptimismMainnet.Name: &OptimismMainnet, + OptimismMainnet.CommonName: &OptimismMainnet, + OptimismTestnet.Name: &OptimismTestnet, + OptimismTestnet.CommonName: &OptimismTestnet, + "optimismtest": &OptimismTestnet, + "optimismtestnet": &OptimismTestnet, + "optimism_goerli": &OptimismTestnet, + "optimismgoerli": &OptimismTestnet, } networkNames []string @@ -140,6 +154,8 @@ type Network struct { CommonName string // CommonName of the network or chain TokenName string // TokenName of the network baseURL string // baseURL for the API client + ChainIDHex string // ChainIDHex for identifing the chain + ChainID int // ChainID for identyfing the chain } // Domain returns the subdomain of etherscan API via n provided. @@ -151,7 +167,7 @@ func ParseNetworkName(network string) (Network, error) { if x, ok := networks[network]; ok { return *x, nil } - // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. + // Case-insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. if x, ok := networks[strings.ToLower(network)]; ok { return *x, nil }