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 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/gas_tracker.go b/gas_tracker.go new file mode 100644 index 0000000..02fe5ca --- /dev/null +++ b/gas_tracker.go @@ -0,0 +1,27 @@ +/* + * 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 + +import "time" + +// GasEstiamte gets estiamted confirmation time (in seconds) at the given gas price +func (c *Client) GasEstimate(gasPrice int) (confirmationTimeInSec time.Duration, err error) { + params := M{"gasPrice": gasPrice} + 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) +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..3905bc3 --- /dev/null +++ b/gas_tracker_e2e_test.go @@ -0,0 +1,29 @@ +/* + * 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) { + _, err := api.GasEstimate(20000000) + noError(t, err, "api.GasEstimate") +} + +//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.GasUsedRatio) { + t.Errorf("gasPrice.GasUsedRatio empty") + } + +} diff --git a/go.mod b/go.mod index 3e16f01..5837de6 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ 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 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= diff --git a/response.go b/response.go index e28007e..5c418cf 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 { @@ -45,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 @@ -111,6 +118,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"` @@ -175,3 +206,65 @@ type Log struct { Removed bool `json:"removed"` } +// GasPrices holds info for Gas Oracle queries +// Gas Prices are returned in Gwei +type GasPrices struct { + 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 +}