From 333cfe4e4a08fb8c45973327370abb253a83c44b Mon Sep 17 00:00:00 2001 From: Blackmorse Date: Mon, 25 Sep 2023 00:02:09 +0400 Subject: [PATCH 01/12] Contract creation API --- .../api/etherscan/AccountAPIProvider.java | 6 +- .../goodforgod/api/etherscan/ContractAPI.java | 11 +++ .../api/etherscan/ContractAPIProvider.java | 30 +++++++ .../api/etherscan/model/ContractCreation.java | 80 +++++++++++++++++++ .../response/ContractCreationResponseTO.java | 4 + .../model/response/ContractCreationTO.java | 20 +++++ .../api/etherscan/util/BasicUtils.java | 4 + .../etherscan/contract/ContractApiTests.java | 46 +++++++++++ 8 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 442edff..d36baf7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -95,7 +95,7 @@ public List balances(@NotNull List addresses) throws EtherScanE final List> addressesAsBatches = BasicUtils.partition(addresses, 20); for (final List batch : addressesAsBatches) { - final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + toAddressParam(batch); + final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + BasicUtils.toAddressParam(batch); final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); if (response.getStatus() != 1) { throw new EtherScanResponseException(response); @@ -111,10 +111,6 @@ public List balances(@NotNull List addresses) throws EtherScanE return balances; } - private String toAddressParam(List addresses) { - return String.join(",", addresses); - } - @NotNull @Override public List txs(@NotNull String address) throws EtherScanException { diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java index af0852c..45ecb1e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java @@ -2,8 +2,11 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.model.Abi; +import io.goodforgod.api.etherscan.model.ContractCreation; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * EtherScan - API Descriptions ... * @@ -21,4 +24,12 @@ public interface ContractAPI { */ @NotNull Abi contractAbi(@NotNull String address) throws EtherScanException; + + /** + * Returns a contract's deployer address and transaction hash it was created, up to 5 at a time. + * @param contractAddresses - list of addresses to fetch + * @throws EtherScanException parent exception class + */ + @NotNull + List contractCreation(@NotNull List contractAddresses) throws EtherScanException; } diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java index 6b4404a..fda1b0d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -5,10 +5,15 @@ import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.Abi; +import io.goodforgod.api.etherscan.model.ContractCreation; +import io.goodforgod.api.etherscan.model.response.ContractCreationResponseTO; import io.goodforgod.api.etherscan.model.response.StringResponseTO; import io.goodforgod.api.etherscan.util.BasicUtils; import org.jetbrains.annotations.NotNull; +import java.util.List; +import java.util.stream.Collectors; + /** * Contract API Implementation * @@ -22,6 +27,12 @@ final class ContractAPIProvider extends BasicProvider implements ContractAPI { private static final String ADDRESS_PARAM = "&address="; + private static final String ACT_CONTRACT_CREATION_PARAM = "getcontractcreation"; + + private static final String ACT_CONTRACT_CREATION = ACT_PREFIX + ACT_CONTRACT_CREATION_PARAM; + + private static final String ACT_CONTRACT_ADDRESSES_PARAM = "&contractaddresses="; + ContractAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, EthHttpClient executor, @@ -44,4 +55,23 @@ public Abi contractAbi(@NotNull String address) throws EtherScanException { ? Abi.nonVerified() : Abi.verified(response.getResult()); } + + @NotNull + @Override + public List contractCreation(@NotNull List contractAddresses) throws EtherScanException { + BasicUtils.validateAddresses(contractAddresses); + final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM + BasicUtils.toAddressParam(contractAddresses); + final ContractCreationResponseTO response = getRequest(urlParam, ContractCreationResponseTO.class); + if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) { + throw new EtherScanResponseException(response); + } + + return response.getResult().stream() + .map(to -> ContractCreation.builder() + .withContractCreator(to.getContractCreator()) + .withContractAddress(to.getContractAddress()) + .withTxHash(to.getTxHash()) + .build() + ).collect(Collectors.toList()); + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java new file mode 100644 index 0000000..747aefb --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java @@ -0,0 +1,80 @@ +package io.goodforgod.api.etherscan.model; + +import java.util.Objects; + +public class ContractCreation { + private final String contractAddress; + private final String contractCreator; + private final String txHash; + + private ContractCreation(String contractAddress, String contractCreator, String txHash) { + this.contractAddress = contractAddress; + this.contractCreator = contractCreator; + this.txHash = txHash; + } + + public String getContractAddress() { + return contractAddress; + } + + public String getContractCreator() { + return contractCreator; + } + + public String getTxHash() { + return txHash; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ContractCreation that = (ContractCreation) o; + return Objects.equals(contractAddress, that.contractAddress) && Objects.equals(contractCreator, that.contractCreator) && Objects.equals(txHash, that.txHash); + } + + @Override + public int hashCode() { + return Objects.hash(contractAddress, contractCreator, txHash); + } + + @Override + public String toString() { + return "ContractCreation{" + + "contractAddress='" + contractAddress + '\'' + + ", contractCreator='" + contractCreator + '\'' + + ", txHash='" + txHash + '\'' + + '}'; + } + + public static ContractCreationBuilder builder() { + return new ContractCreationBuilder(); + } + + public static final class ContractCreationBuilder { + private String contractAddress; + private String contractCreator; + private String txHash; + + private ContractCreationBuilder() {} + + public ContractCreationBuilder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public ContractCreationBuilder withContractCreator(String contractCreator) { + this.contractCreator = contractCreator; + return this; + } + + public ContractCreationBuilder withTxHash(String txHash) { + this.txHash = txHash; + return this; + } + + public ContractCreation build() { + return new ContractCreation(contractAddress, contractCreator, txHash); + } + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java new file mode 100644 index 0000000..7cf28fc --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java @@ -0,0 +1,4 @@ +package io.goodforgod.api.etherscan.model.response; + +public class ContractCreationResponseTO extends BaseListResponseTO { +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java new file mode 100644 index 0000000..9e1551e --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java @@ -0,0 +1,20 @@ +package io.goodforgod.api.etherscan.model.response; + +public class ContractCreationTO { + + private String contractAddress; + private String contractCreator; + private String txHash; + + public String getContractAddress() { + return contractAddress; + } + + public String getContractCreator() { + return contractCreator; + } + + public String getTxHash() { + return txHash; + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java index 216ab62..916d4ab 100644 --- a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java @@ -149,4 +149,8 @@ public static List> partition(List list, int pairSize) { return partitioned; } + + public static String toAddressParam(List addresses) { + return String.join(",", addresses); + } } diff --git a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java index 4fd0fdb..49e8f07 100644 --- a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java @@ -3,8 +3,13 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; import io.goodforgod.api.etherscan.model.Abi; +import io.goodforgod.api.etherscan.model.ContractCreation; import org.junit.jupiter.api.Test; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * @author GoodforGod * @since 03.11.2018 @@ -37,4 +42,45 @@ void correctParamWithEmptyExpectedResult() { assertNotNull(abi); assertTrue(abi.isVerified()); } + + @Test + void correctContractCreation() { + List contractCreations = + getApi().contract().contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413")); + + assertEquals(1, contractCreations.size()); + ContractCreation contractCreation = contractCreations.get(0); + + assertEquals("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", contractCreation.getContractAddress()); + assertEquals("0x793ea9692ada1900fbd0b80fffec6e431fe8b391", contractCreation.getContractCreator()); + assertEquals("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9", contractCreation.getTxHash()); + } + + @Test + void correctMultipleContractCreation() { + List contractCreations = + getApi().contract().contractCreation(Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123")); + assertEquals(2, contractCreations.size()); + + ContractCreation contractCreation1 = ContractCreation.builder() + .withContractAddress("0xbb9bc244d798123fde783fcc1c72d3bb8c189413") + .withContractCreator("0x793ea9692ada1900fbd0b80fffec6e431fe8b391") + .withTxHash("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9") + .build(); + + ContractCreation contractCreation2 = ContractCreation.builder() + .withContractAddress("0x5eac95ad5b287cf44e058dcf694419333b796123") + .withContractCreator("0x7c675b7450e878e5af8550b41df42d134674e61f") + .withTxHash("0x79cdfec19e5a86d9022680a4d1c86d3d8cd76c21c01903a2f02c127a0a7dbfb3") + .build(); + + assertTrue(contractCreations.contains(contractCreation1)); + assertTrue(contractCreations.contains(contractCreation2)); + } + + @Test + void contractCreationInvalidParamWithError() { + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().contract().contractCreation(Collections.singletonList("0xBBbc244D798123fDe783fCc1C72d3Bb8C189414"))); + } } From 234cce4cadb71e1dfd45969a7709b45533d2656f Mon Sep 17 00:00:00 2001 From: Blackmorse Date: Fri, 29 Sep 2023 12:51:00 +0400 Subject: [PATCH 02/12] Formatting --- .../api/etherscan/AccountAPIProvider.java | 3 ++- .../io/goodforgod/api/etherscan/ContractAPI.java | 4 ++-- .../api/etherscan/ContractAPIProvider.java | 10 +++++----- .../io/goodforgod/api/etherscan/StatisticAPI.java | 3 +++ .../api/etherscan/model/ContractCreation.java | 11 ++++++++--- .../model/response/ContractCreationResponseTO.java | 3 +-- .../api/etherscan/contract/ContractApiTests.java | 14 +++++++------- 7 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index d36baf7..f968c1d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -95,7 +95,8 @@ public List balances(@NotNull List addresses) throws EtherScanE final List> addressesAsBatches = BasicUtils.partition(addresses, 20); for (final List batch : addressesAsBatches) { - final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + BasicUtils.toAddressParam(batch); + final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + + BasicUtils.toAddressParam(batch); final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); if (response.getStatus() != 1) { throw new EtherScanResponseException(response); diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java index 45ecb1e..c076b74 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java @@ -3,9 +3,8 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.model.Abi; import io.goodforgod.api.etherscan.model.ContractCreation; -import org.jetbrains.annotations.NotNull; - import java.util.List; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions ... @@ -27,6 +26,7 @@ public interface ContractAPI { /** * Returns a contract's deployer address and transaction hash it was created, up to 5 at a time. + * * @param contractAddresses - list of addresses to fetch * @throws EtherScanException parent exception class */ diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java index fda1b0d..0493f45 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -9,10 +9,9 @@ import io.goodforgod.api.etherscan.model.response.ContractCreationResponseTO; import io.goodforgod.api.etherscan.model.response.StringResponseTO; import io.goodforgod.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.util.List; import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; /** * Contract API Implementation @@ -60,7 +59,8 @@ public Abi contractAbi(@NotNull String address) throws EtherScanException { @Override public List contractCreation(@NotNull List contractAddresses) throws EtherScanException { BasicUtils.validateAddresses(contractAddresses); - final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM + BasicUtils.toAddressParam(contractAddresses); + final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM + + BasicUtils.toAddressParam(contractAddresses); final ContractCreationResponseTO response = getRequest(urlParam, ContractCreationResponseTO.class); if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) { throw new EtherScanResponseException(response); @@ -71,7 +71,7 @@ public List contractCreation(@NotNull List contractAdd .withContractCreator(to.getContractCreator()) .withContractAddress(to.getContractAddress()) .withTxHash(to.getTxHash()) - .build() - ).collect(Collectors.toList()); + .build()) + .collect(Collectors.toList()); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java index d7b48b8..b6db82e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java @@ -15,6 +15,9 @@ public interface StatisticAPI { /** + * ERC20 token total Supply + * EtherScan * Returns the current amount of an ERC-20 token in circulation. * * @param contract contract address diff --git a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java index 747aefb..0f3d822 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java @@ -3,6 +3,7 @@ import java.util.Objects; public class ContractCreation { + private final String contractAddress; private final String contractCreator; private final String txHash; @@ -27,10 +28,13 @@ public String getTxHash() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; ContractCreation that = (ContractCreation) o; - return Objects.equals(contractAddress, that.contractAddress) && Objects.equals(contractCreator, that.contractCreator) && Objects.equals(txHash, that.txHash); + return Objects.equals(contractAddress, that.contractAddress) && Objects.equals(contractCreator, that.contractCreator) + && Objects.equals(txHash, that.txHash); } @Override @@ -52,6 +56,7 @@ public static ContractCreationBuilder builder() { } public static final class ContractCreationBuilder { + private String contractAddress; private String contractCreator; private String txHash; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java index 7cf28fc..e3766c3 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java @@ -1,4 +1,3 @@ package io.goodforgod.api.etherscan.model.response; -public class ContractCreationResponseTO extends BaseListResponseTO { -} +public class ContractCreationResponseTO extends BaseListResponseTO {} diff --git a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java index 49e8f07..d1e4de4 100644 --- a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java @@ -4,11 +4,10 @@ import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; import io.goodforgod.api.etherscan.model.Abi; import io.goodforgod.api.etherscan.model.ContractCreation; -import org.junit.jupiter.api.Test; - import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.junit.jupiter.api.Test; /** * @author GoodforGod @@ -45,8 +44,8 @@ void correctParamWithEmptyExpectedResult() { @Test void correctContractCreation() { - List contractCreations = - getApi().contract().contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413")); + List contractCreations = getApi().contract() + .contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413")); assertEquals(1, contractCreations.size()); ContractCreation contractCreation = contractCreations.get(0); @@ -58,8 +57,8 @@ void correctContractCreation() { @Test void correctMultipleContractCreation() { - List contractCreations = - getApi().contract().contractCreation(Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123")); + List contractCreations = getApi().contract().contractCreation( + Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123")); assertEquals(2, contractCreations.size()); ContractCreation contractCreation1 = ContractCreation.builder() @@ -81,6 +80,7 @@ void correctMultipleContractCreation() { @Test void contractCreationInvalidParamWithError() { assertThrows(EtherScanInvalidAddressException.class, - () -> getApi().contract().contractCreation(Collections.singletonList("0xBBbc244D798123fDe783fCc1C72d3Bb8C189414"))); + () -> getApi().contract() + .contractCreation(Collections.singletonList("0xBBbc244D798123fDe783fCc1C72d3Bb8C189414"))); } } From 64540b8499b4cfdc284d04db01ecadbbc1a1e360 Mon Sep 17 00:00:00 2001 From: Blackmorse Date: Sun, 1 Oct 2023 16:59:32 +0400 Subject: [PATCH 03/12] filtering out empty env --- src/test/java/io/goodforgod/api/etherscan/ApiRunner.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index 4b52c00..fd933c2 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -2,6 +2,8 @@ import io.goodforgod.api.etherscan.manager.RequestQueueManager; import java.util.Map; + +import io.goodforgod.api.etherscan.util.BasicUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -15,6 +17,7 @@ public class ApiRunner extends Assertions { static { API_KEY = System.getenv().entrySet().stream() .filter(e -> e.getKey().startsWith("ETHERSCAN_API_KEY")) + .filter(e -> !BasicUtils.isBlank(e.getValue())) .map(Map.Entry::getValue) .findFirst() .orElse(DEFAULT_KEY); From 06464f87498713c03ab27b5232586057bd066138 Mon Sep 17 00:00:00 2001 From: Blackmorse Date: Thu, 5 Oct 2023 00:58:32 +0400 Subject: [PATCH 04/12] Fix codestyle --- src/test/java/io/goodforgod/api/etherscan/ApiRunner.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index fd933c2..a6c43ac 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -1,9 +1,8 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.manager.RequestQueueManager; -import java.util.Map; - import io.goodforgod.api.etherscan.util.BasicUtils; +import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; From 3a87ca83cda95a61415bc5226651b5d18448900f Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:10:05 +0300 Subject: [PATCH 05/12] [2.1.0-SNAPSHOT] EtherScanAPI.Builder#withRetryOnLimitReach added --- gradle.properties | 2 +- .../api/etherscan/AccountAPIProvider.java | 5 +- .../api/etherscan/BasicProvider.java | 51 ++++++++++++++++--- .../api/etherscan/BlockAPIProvider.java | 12 ++--- .../api/etherscan/ContractAPIProvider.java | 5 +- .../api/etherscan/EthScanAPIBuilder.java | 13 ++++- .../api/etherscan/EtherScanAPI.java | 11 ++++ .../api/etherscan/EtherScanAPIProvider.java | 19 +++---- .../api/etherscan/GasTrackerAPIProvider.java | 5 +- .../api/etherscan/LogsAPIProvider.java | 5 +- .../api/etherscan/ProxyAPIProvider.java | 5 +- .../api/etherscan/StatisticAPIProvider.java | 5 +- .../api/etherscan/TransactionAPIProvider.java | 5 +- .../manager/RequestQueueManager.java | 10 ++-- .../goodforgod/api/etherscan/model/Abi.java | 2 +- .../api/etherscan/model/Balance.java | 2 +- .../goodforgod/api/etherscan/model/Block.java | 2 +- .../api/etherscan/model/BlockUncle.java | 8 +-- .../api/etherscan/model/ContractCreation.java | 29 ++++++----- .../api/etherscan/model/EthSupply.java | 8 +-- .../goodforgod/api/etherscan/model/Log.java | 18 +++---- .../goodforgod/api/etherscan/model/Price.java | 4 +- .../api/etherscan/model/Status.java | 2 +- .../api/etherscan/model/TokenBalance.java | 2 +- .../io/goodforgod/api/etherscan/model/Tx.java | 18 +++---- .../api/etherscan/model/TxErc1155.java | 22 ++++---- .../api/etherscan/model/TxErc20.java | 20 ++++---- .../api/etherscan/model/TxErc721.java | 22 ++++---- .../api/etherscan/model/TxInternal.java | 18 +++---- .../api/etherscan/model/proxy/BlockProxy.java | 36 ++++++------- .../etherscan/model/proxy/ReceiptProxy.java | 22 ++++---- .../api/etherscan/model/proxy/TxProxy.java | 28 +++++----- .../goodforgod/api/etherscan/ApiRunner.java | 1 + 33 files changed, 242 insertions(+), 175 deletions(-) diff --git a/gradle.properties b/gradle.properties index 821da06..ee5fd3b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=2.0.0 +artifactVersion=2.1.0-SNAPSHOT ##### GRADLE ##### diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index f968c1d..750d525 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -50,8 +50,9 @@ final class AccountAPIProvider extends BasicProvider implements AccountAPI { AccountAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, EthHttpClient executor, - Converter converter) { - super(requestQueueManager, "account", baseUrl, executor, converter); + Converter converter, + int retryCount) { + super(requestQueueManager, "account", baseUrl, executor, converter, retryCount); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 5c61aad..41abd16 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -30,20 +30,23 @@ abstract class BasicProvider { private final EthHttpClient executor; private final RequestQueueManager queue; private final Converter converter; + private final int retryCountLimit; BasicProvider(RequestQueueManager requestQueueManager, String module, String baseUrl, EthHttpClient ethHttpClient, - Converter converter) { + Converter converter, + int retryCountLimit) { this.queue = requestQueueManager; this.module = "&module=" + module; this.baseUrl = baseUrl; this.executor = ethHttpClient; this.converter = converter; + this.retryCountLimit = retryCountLimit; } - T convert(byte[] json, Class tClass) { + private T convert(byte[] json, Class tClass) { try { final T t = converter.fromJson(json, tClass); if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith(MAX_RATE_LIMIT_REACHED)) { @@ -66,23 +69,59 @@ T convert(byte[] json, Class tClass) { } } - byte[] getRequest(String urlParameters) { + private byte[] getRequest(String urlParameters) { queue.takeTurn(); final URI uri = URI.create(baseUrl + module + urlParameters); return executor.get(uri); } - byte[] postRequest(String urlParameters, String dataToPost) { + private byte[] postRequest(String urlParameters, String dataToPost) { queue.takeTurn(); final URI uri = URI.create(baseUrl + module + urlParameters); return executor.post(uri, dataToPost.getBytes(StandardCharsets.UTF_8)); } T getRequest(String urlParameters, Class tClass) { - return convert(getRequest(urlParameters), tClass); + return getRequest(urlParameters, tClass, 0); + } + + private T getRequest(String urlParameters, Class tClass, int retryCount) { + try { + return convert(getRequest(urlParameters), tClass); + } catch (Exception e) { + if (retryCount < retryCountLimit) { + try { + Thread.sleep(1150); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + + return getRequest(urlParameters, tClass, retryCount + 1); + } else { + throw e; + } + } } T postRequest(String urlParameters, String dataToPost, Class tClass) { - return convert(postRequest(urlParameters, dataToPost), tClass); + return postRequest(urlParameters, dataToPost, tClass, 0); + } + + private T postRequest(String urlParameters, String dataToPost, Class tClass, int retryCount) { + try { + return convert(postRequest(urlParameters, dataToPost), tClass); + } catch (EtherScanRateLimitException e) { + if (retryCount < retryCountLimit) { + try { + Thread.sleep(1150); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + + return postRequest(urlParameters, dataToPost, tClass, retryCount + 1); + } else { + throw e; + } + } } } diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java index 406ac19..b3604a7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -26,21 +26,17 @@ final class BlockAPIProvider extends BasicProvider implements BlockAPI { BlockAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, EthHttpClient executor, - Converter converter) { - super(requestQueueManager, "block", baseUrl, executor, converter); + Converter converter, + int retryCount) { + super(requestQueueManager, "block", baseUrl, executor, converter, retryCount); } @NotNull @Override public Optional uncles(long blockNumber) throws EtherScanException { final String urlParam = ACT_BLOCK_PARAM + BLOCKNO_PARAM + blockNumber; - final byte[] response = getRequest(urlParam); - if (response.length == 0) { - return Optional.empty(); - } - try { - final UncleBlockResponseTO responseTO = convert(response, UncleBlockResponseTO.class); + final UncleBlockResponseTO responseTO = getRequest(urlParam, UncleBlockResponseTO.class); if (responseTO.getMessage().startsWith("NOTOK")) { return Optional.empty(); } diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java index 0493f45..898a7b7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -35,8 +35,9 @@ final class ContractAPIProvider extends BasicProvider implements ContractAPI { ContractAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, EthHttpClient executor, - Converter converter) { - super(requestQueueManager, "contract", baseUrl, executor, converter); + Converter converter, + int retryCount) { + super(requestQueueManager, "contract", baseUrl, executor, converter, retryCount); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index dad9c50..70d9a01 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -26,6 +26,7 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { private final Gson gson = new GsonConfiguration().builder().create(); + private int retryCountOnLimitReach = 0; private String apiKey = DEFAULT_KEY; private RequestQueueManager queueManager; private EthNetwork ethNetwork = EthNetworks.MAINNET; @@ -87,6 +88,16 @@ public EtherScanAPI.Builder withConverter(@NotNull Supplier converter return this; } + @NotNull + public EtherScanAPI.Builder withRetryOnLimitReach(int maxRetryCount) { + if (maxRetryCount < 0 || maxRetryCount > 20) { + throw new IllegalStateException("maxRetryCount value must be in range from 0 to 20, but was: " + maxRetryCount); + } + + this.retryCountOnLimitReach = maxRetryCount; + return this; + } + @Override public @NotNull EtherScanAPI build() { RequestQueueManager requestQueueManager; @@ -99,6 +110,6 @@ public EtherScanAPI.Builder withConverter(@NotNull Supplier converter } return new EtherScanAPIProvider(apiKey, ethNetwork, requestQueueManager, ethHttpClientSupplier.get(), - converterSupplier.get()); + converterSupplier.get(), retryCountOnLimitReach); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java index 6da3d8f..4e6bc57 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -1,9 +1,11 @@ package io.goodforgod.api.etherscan; +import io.goodforgod.api.etherscan.error.EtherScanRateLimitException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Range; /** * EtherScan full API Description ... @@ -62,6 +64,15 @@ interface Builder { @NotNull Builder withConverter(@NotNull Supplier converterSupplier); + /** + * By default is disabled + * + * @param maxRetryCount to retry if {@link EtherScanRateLimitException} thrown + * @return self + */ + @NotNull + EtherScanAPI.Builder withRetryOnLimitReach(@Range(from = 0, to = 20) int maxRetryCount); + @NotNull EtherScanAPI build(); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java index e698f45..ab6e863 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java @@ -26,19 +26,20 @@ final class EtherScanAPIProvider implements EtherScanAPI { EthNetwork network, RequestQueueManager queue, EthHttpClient ethHttpClient, - Converter converter) { + Converter converter, + int retryCount) { // EtherScan 1request\5sec limit support by queue manager final String baseUrl = network.domain() + "?apikey=" + apiKey; this.requestQueueManager = queue; - this.account = new AccountAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.block = new BlockAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.contract = new ContractAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.logs = new LogsAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.proxy = new ProxyAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.stats = new StatisticAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.txs = new TransactionAPIProvider(queue, baseUrl, ethHttpClient, converter); - this.gasTracker = new GasTrackerAPIProvider(queue, baseUrl, ethHttpClient, converter); + this.account = new AccountAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.block = new BlockAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.contract = new ContractAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.logs = new LogsAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.proxy = new ProxyAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.stats = new StatisticAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.txs = new TransactionAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); + this.gasTracker = new GasTrackerAPIProvider(queue, baseUrl, ethHttpClient, converter, retryCount); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java index cbe0a75..ed717a9 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -28,8 +28,9 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI GasTrackerAPIProvider(RequestQueueManager queue, String baseUrl, EthHttpClient ethHttpClient, - Converter converter) { - super(queue, "gastracker", baseUrl, ethHttpClient, converter); + Converter converter, + int retryCount) { + super(queue, "gastracker", baseUrl, ethHttpClient, converter, retryCount); } @Override diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java index d294fb5..237cafd 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java @@ -25,8 +25,9 @@ final class LogsAPIProvider extends BasicProvider implements LogsAPI { LogsAPIProvider(RequestQueueManager queue, String baseUrl, EthHttpClient executor, - Converter converter) { - super(queue, "logs", baseUrl, executor, converter); + Converter converter, + int retryCount) { + super(queue, "logs", baseUrl, executor, converter, retryCount); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index 4dff589..428b48f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -60,8 +60,9 @@ final class ProxyAPIProvider extends BasicProvider implements ProxyAPI { ProxyAPIProvider(RequestQueueManager queue, String baseUrl, EthHttpClient executor, - Converter converter) { - super(queue, "proxy", baseUrl, executor, converter); + Converter converter, + int retryCount) { + super(queue, "proxy", baseUrl, executor, converter, retryCount); } @Override diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java index 131df71..a2bba16 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java @@ -33,8 +33,9 @@ final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { StatisticAPIProvider(RequestQueueManager queue, String baseUrl, EthHttpClient executor, - Converter converter) { - super(queue, "stats", baseUrl, executor, converter); + Converter converter, + int retry) { + super(queue, "stats", baseUrl, executor, converter, retry); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java index da26b51..7374335 100644 --- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java @@ -27,8 +27,9 @@ final class TransactionAPIProvider extends BasicProvider implements TransactionA TransactionAPIProvider(RequestQueueManager queue, String baseUrl, EthHttpClient executor, - Converter converter) { - super(queue, "transaction", baseUrl, executor, converter); + Converter converter, + int retryCount) { + super(queue, "transaction", baseUrl, executor, converter, retryCount); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java index 0f36b23..92875d0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -17,7 +17,7 @@ public interface RequestQueueManager extends AutoCloseable { * Is used by default when no API KEY is provided */ static RequestQueueManager anonymous() { - return new SemaphoreRequestQueueManager(1, Duration.ofMillis(5015L)); + return new SemaphoreRequestQueueManager(1, Duration.ofMillis(5045L)); } /** @@ -25,19 +25,19 @@ static RequestQueueManager anonymous() { * Free API KEY */ static RequestQueueManager planFree() { - return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1015L)); + return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1045L)); } static RequestQueueManager planStandard() { - return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1015L)); + return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1045L)); } static RequestQueueManager planAdvanced() { - return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1015L)); + return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1045L)); } static RequestQueueManager planProfessional() { - return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1015L)); + return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1045L)); } static RequestQueueManager unlimited() { diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java index 3536bf9..fbf71be 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java @@ -55,7 +55,7 @@ public int hashCode() { @Override public String toString() { return "Abi{" + - "contractAbi='" + contractAbi + '\'' + + "contractAbi=" + contractAbi + ", isVerified=" + isVerified + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java index 079d4b6..1d2f743 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java @@ -45,7 +45,7 @@ public int hashCode() { @Override public String toString() { return "Balance{" + - "address='" + address + '\'' + + "address=" + address + ", balance=" + balance + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Block.java b/src/main/java/io/goodforgod/api/etherscan/model/Block.java index 0550000..da1184b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -58,7 +58,7 @@ public String toString() { return "Block{" + "blockNumber=" + blockNumber + ", blockReward=" + blockReward + - ", timeStamp='" + timeStamp + '\'' + + ", timeStamp=" + timeStamp + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java index 9b110d9..961db7e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -19,7 +19,7 @@ public static class Uncle { private BigInteger blockreward; private int unclePosition; - private Uncle() {} + protected Uncle() {} // public String getMiner() { @@ -54,7 +54,7 @@ public int hashCode() { @Override public String toString() { return "Uncle{" + - "miner='" + miner + '\'' + + "miner=" + miner + ", blockreward=" + blockreward + ", unclePosition=" + unclePosition + '}'; @@ -128,9 +128,9 @@ public String getUncleInclusionReward() { @Override public String toString() { return "UncleBlock{" + - "blockMiner='" + blockMiner + '\'' + + "blockMiner=" + blockMiner + ", uncles=" + uncles + - ", uncleInclusionReward='" + uncleInclusionReward + '\'' + + ", uncleInclusionReward=" + uncleInclusionReward + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java index 0f3d822..2082883 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java @@ -4,15 +4,11 @@ public class ContractCreation { - private final String contractAddress; - private final String contractCreator; - private final String txHash; - - private ContractCreation(String contractAddress, String contractCreator, String txHash) { - this.contractAddress = contractAddress; - this.contractCreator = contractCreator; - this.txHash = txHash; - } + private String contractAddress; + private String contractCreator; + private String txHash; + + protected ContractCreation() {} public String getContractAddress() { return contractAddress; @@ -33,7 +29,8 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; ContractCreation that = (ContractCreation) o; - return Objects.equals(contractAddress, that.contractAddress) && Objects.equals(contractCreator, that.contractCreator) + return Objects.equals(contractAddress, that.contractAddress) + && Objects.equals(contractCreator, that.contractCreator) && Objects.equals(txHash, that.txHash); } @@ -45,9 +42,9 @@ public int hashCode() { @Override public String toString() { return "ContractCreation{" + - "contractAddress='" + contractAddress + '\'' + - ", contractCreator='" + contractCreator + '\'' + - ", txHash='" + txHash + '\'' + + "contractAddress=" + contractAddress + + ", contractCreator=" + contractCreator + + ", txHash=" + txHash + '}'; } @@ -79,7 +76,11 @@ public ContractCreationBuilder withTxHash(String txHash) { } public ContractCreation build() { - return new ContractCreation(contractAddress, contractCreator, txHash); + ContractCreation contractCreation = new ContractCreation(); + contractCreation.contractAddress = contractAddress; + contractCreation.contractCreator = contractCreator; + contractCreation.txHash = txHash; + return contractCreation; } } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java b/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java index 344e754..c626069 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java @@ -56,10 +56,10 @@ public int hashCode() { @Override public String toString() { return "EthSupply{" + - "EthSupply='" + EthSupply + '\'' + - ", Eth2Staking='" + Eth2Staking + '\'' + - ", BurntFees='" + BurntFees + '\'' + - ", WithdrawnTotal='" + WithdrawnTotal + '\'' + + "EthSupply=" + EthSupply + + ", Eth2Staking=" + Eth2Staking + + ", BurntFees=" + BurntFees + + ", WithdrawnTotal=" + WithdrawnTotal + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Log.java b/src/main/java/io/goodforgod/api/etherscan/model/Log.java index da6c295..d54766c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -138,16 +138,16 @@ public int hashCode() { @Override public String toString() { return "Log{" + - "blockNumber='" + blockNumber + '\'' + - ", address='" + address + '\'' + - ", transactionHash='" + transactionHash + '\'' + - ", transactionIndex='" + transactionIndex + '\'' + - ", timeStamp='" + timeStamp + '\'' + - ", data='" + data + '\'' + - ", gasPrice='" + gasPrice + '\'' + - ", gasUsed='" + gasUsed + '\'' + + "blockNumber=" + blockNumber + + ", address=" + address + + ", transactionHash=" + transactionHash + + ", transactionIndex=" + transactionIndex + + ", timeStamp=" + timeStamp + + ", data=" + data + + ", gasPrice=" + gasPrice + + ", gasUsed=" + gasUsed + ", topics=" + topics + - ", logIndex='" + logIndex + '\'' + + ", logIndex=" + logIndex + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Price.java b/src/main/java/io/goodforgod/api/etherscan/model/Price.java index 565dbed..403b705 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -67,8 +67,8 @@ public String toString() { return "Price{" + "ethusd=" + ethusd + ", ethbtc=" + ethbtc + - ", ethusd_timestamp='" + ethusd_timestamp + '\'' + - ", ethbtc_timestamp='" + ethbtc_timestamp + '\'' + + ", ethusd_timestamp=" + ethusd_timestamp + + ", ethbtc_timestamp=" + ethbtc_timestamp + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Status.java b/src/main/java/io/goodforgod/api/etherscan/model/Status.java index 052c187..41b598a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Status.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Status.java @@ -45,7 +45,7 @@ public int hashCode() { public String toString() { return "Status{" + "isError=" + isError + - ", errDescription='" + errDescription + '\'' + + ", errDescription=" + errDescription + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java b/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java index bb40ee2..c257654 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java @@ -39,7 +39,7 @@ public int hashCode() { @Override public String toString() { return "TokenBalance{" + - "tokenContract='" + tokenContract + '\'' + + "tokenContract=" + tokenContract + '}'; } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java index 7ef0e22..0a836d1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -35,21 +35,21 @@ public String getTxReceiptStatus() { public String toString() { return "Tx{" + "value=" + value + - ", isError='" + isError + '\'' + - ", txreceipt_status='" + txreceipt_status + '\'' + + ", isError=" + isError + + ", txreceipt_status=" + txreceipt_status + ", nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + + ", blockHash=" + blockHash + ", transactionIndex=" + transactionIndex + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + ", blockNumber=" + blockNumber + - ", timeStamp='" + timeStamp + '\'' + - ", hash='" + hash + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", contractAddress='" + contractAddress + '\'' + - ", input='" + input + '\'' + + ", timeStamp=" + timeStamp + + ", hash=" + hash + + ", from=" + from + + ", to=" + to + + ", contractAddress=" + contractAddress + + ", input=" + input + ", gas=" + gas + ", gasUsed=" + gasUsed + '}'; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java index 16d4457..f0b1ce4 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -56,23 +56,23 @@ public int hashCode() { @Override public String toString() { return "TxErc1155{" + - "tokenID='" + tokenID + '\'' + - ", tokenName='" + tokenName + '\'' + - ", tokenSymbol='" + tokenSymbol + '\'' + - ", tokenValue='" + tokenValue + '\'' + + "tokenID=" + tokenID + + ", tokenName=" + tokenName + + ", tokenSymbol=" + tokenSymbol + + ", tokenValue=" + tokenValue + ", nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + + ", blockHash=" + blockHash + ", transactionIndex=" + transactionIndex + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + ", blockNumber=" + blockNumber + - ", timeStamp='" + timeStamp + '\'' + - ", hash='" + hash + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", contractAddress='" + contractAddress + '\'' + - ", input='" + input + '\'' + + ", timeStamp=" + timeStamp + + ", hash=" + hash + + ", from=" + from + + ", to=" + to + + ", contractAddress=" + contractAddress + + ", input=" + input + ", gas=" + gas + ", gasUsed=" + gasUsed + '}'; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java index 3dc22fd..1d6080e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -58,22 +58,22 @@ public int hashCode() { public String toString() { return "TxErc20{" + "value=" + value + - ", tokenName='" + tokenName + '\'' + - ", tokenSymbol='" + tokenSymbol + '\'' + - ", tokenDecimal='" + tokenDecimal + '\'' + + ", tokenName=" + tokenName + + ", tokenSymbol=" + tokenSymbol + + ", tokenDecimal=" + tokenDecimal + ", nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + + ", blockHash=" + blockHash + ", transactionIndex=" + transactionIndex + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + ", blockNumber=" + blockNumber + - ", timeStamp='" + timeStamp + '\'' + - ", hash='" + hash + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", contractAddress='" + contractAddress + '\'' + - ", input='" + input + '\'' + + ", timeStamp=" + timeStamp + + ", hash=" + hash + + ", from=" + from + + ", to=" + to + + ", contractAddress=" + contractAddress + + ", input=" + input + ", gas=" + gas + ", gasUsed=" + gasUsed + '}'; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java index 2180019..1ac49a0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -56,23 +56,23 @@ public int hashCode() { @Override public String toString() { return "TxErc721{" + - "tokenID='" + tokenID + '\'' + - ", tokenName='" + tokenName + '\'' + - ", tokenSymbol='" + tokenSymbol + '\'' + - ", tokenDecimal='" + tokenDecimal + '\'' + + "tokenID=" + tokenID + + ", tokenName=" + tokenName + + ", tokenSymbol=" + tokenSymbol + + ", tokenDecimal=" + tokenDecimal + ", nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + + ", blockHash=" + blockHash + ", transactionIndex=" + transactionIndex + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + ", blockNumber=" + blockNumber + - ", timeStamp='" + timeStamp + '\'' + - ", hash='" + hash + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", contractAddress='" + contractAddress + '\'' + - ", input='" + input + '\'' + + ", timeStamp=" + timeStamp + + ", hash=" + hash + + ", from=" + from + + ", to=" + to + + ", contractAddress=" + contractAddress + + ", input=" + input + ", gas=" + gas + ", gasUsed=" + gasUsed + '}'; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java index a61cf83..389f456 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -69,17 +69,17 @@ public int hashCode() { public String toString() { return "TxInternal{" + "value=" + value + - ", type='" + type + '\'' + - ", traceId='" + traceId + '\'' + + ", type=" + type + + ", traceId=" + traceId + ", isError=" + isError + - ", errCode='" + errCode + '\'' + + ", errCode=" + errCode + ", blockNumber=" + blockNumber + - ", timeStamp='" + timeStamp + '\'' + - ", hash='" + hash + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", contractAddress='" + contractAddress + '\'' + - ", input='" + input + '\'' + + ", timeStamp=" + timeStamp + + ", hash=" + hash + + ", from=" + from + + ", to=" + to + + ", contractAddress=" + contractAddress + + ", input=" + input + ", gas=" + gas + ", gasUsed=" + gasUsed + '}'; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java index 4a2b624..bee4d64 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java @@ -162,25 +162,25 @@ public int hashCode() { @Override public String toString() { return "BlockProxy{" + - "number='" + number + '\'' + - ", hash='" + hash + '\'' + - ", parentHash='" + parentHash + '\'' + - ", stateRoot='" + stateRoot + '\'' + - ", size='" + size + '\'' + - ", difficulty='" + difficulty + '\'' + - ", totalDifficulty='" + totalDifficulty + '\'' + - ", timestamp='" + timestamp + '\'' + - ", miner='" + miner + '\'' + - ", nonce='" + nonce + '\'' + - ", extraData='" + extraData + '\'' + - ", logsBloom='" + logsBloom + '\'' + - ", mixHash='" + mixHash + '\'' + - ", gasUsed='" + gasUsed + '\'' + - ", gasLimit='" + gasLimit + '\'' + - ", sha3Uncles='" + sha3Uncles + '\'' + + "number=" + number + + ", hash=" + hash + + ", parentHash=" + parentHash + + ", stateRoot=" + stateRoot + + ", size=" + size + + ", difficulty=" + difficulty + + ", totalDifficulty=" + totalDifficulty + + ", timestamp=" + timestamp + + ", miner=" + miner + + ", nonce=" + nonce + + ", extraData=" + extraData + + ", logsBloom=" + logsBloom + + ", mixHash=" + mixHash + + ", gasUsed=" + gasUsed + + ", gasLimit=" + gasLimit + + ", sha3Uncles=" + sha3Uncles + ", uncles=" + uncles + - ", receiptsRoot='" + receiptsRoot + '\'' + - ", transactionsRoot='" + transactionsRoot + '\'' + + ", receiptsRoot=" + receiptsRoot + + ", transactionsRoot=" + transactionsRoot + ", transactions=" + transactions + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java index e6df01c..d88fd6d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java @@ -115,18 +115,18 @@ public int hashCode() { @Override public String toString() { return "ReceiptProxy{" + - "root='" + root + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", blockNumber='" + blockNumber + '\'' + - ", blockHash='" + blockHash + '\'' + - ", transactionHash='" + transactionHash + '\'' + - ", transactionIndex='" + transactionIndex + '\'' + - ", gasUsed='" + gasUsed + '\'' + - ", cumulativeGasUsed='" + cumulativeGasUsed + '\'' + - ", contractAddress='" + contractAddress + '\'' + + "root=" + root + + ", from=" + from + + ", to=" + to + + ", blockNumber=" + blockNumber + + ", blockHash=" + blockHash + + ", transactionHash=" + transactionHash + + ", transactionIndex=" + transactionIndex + + ", gasUsed=" + gasUsed + + ", cumulativeGasUsed=" + cumulativeGasUsed + + ", contractAddress=" + contractAddress + ", logs=" + logs + - ", logsBloom='" + logsBloom + '\'' + + ", logsBloom=" + logsBloom + '}'; } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java index 70b4fd7..0a89921 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java @@ -127,20 +127,20 @@ public int hashCode() { @Override public String toString() { return "TxProxy{" + - "to='" + to + '\'' + - ", hash='" + hash + '\'' + - ", transactionIndex='" + transactionIndex + '\'' + - ", from='" + from + '\'' + - ", v='" + v + '\'' + - ", input='" + input + '\'' + - ", s='" + s + '\'' + - ", r='" + r + '\'' + - ", nonce='" + nonce + '\'' + - ", value='" + value + '\'' + - ", gas='" + gas + '\'' + - ", gasPrice='" + gasPrice + '\'' + - ", blockHash='" + blockHash + '\'' + - ", blockNumber='" + blockNumber + '\'' + + "to=" + to + + ", hash=" + hash + + ", transactionIndex=" + transactionIndex + + ", from=" + from + + ", v=" + v + + ", input=" + input + + ", s=" + s + + ", r=" + r + + ", nonce=" + nonce + + ", value=" + value + + ", gas=" + gas + + ", gasPrice=" + gasPrice + + ", blockHash=" + blockHash + + ", blockNumber=" + blockNumber + '}'; } diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index a6c43ac..72aeeff 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -29,6 +29,7 @@ public class ApiRunner extends Assertions { .withApiKey(ApiRunner.API_KEY) .withNetwork(EthNetworks.MAINNET) .withQueue(queueManager) + .withRetryOnLimitReach(5) .build(); } From 3405883f524a98b8c5c5848ddd862b140cac28aa Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:10:53 +0300 Subject: [PATCH 06/12] [2.1.0-SNAPSHOT] build.gradle updated for new CI CONTRIBUTING.md added README.md updated --- CONTRIBUTING.md | 23 +++++++++++ README.md | 7 ++-- _config.yml | 1 - build.gradle | 48 +++++++++++++++++++---- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 18 +++++++-- gradlew.bat | 15 ++++--- 8 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 CONTRIBUTING.md delete mode 100644 _config.yml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5abd8dc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing Code or Documentation Guide + +## Running Tests + +The new code should contain tests that check new behavior. + +Run tests `./gradlew test` to check that code works as behavior. + +## Code Style + +The code base should remain clean, following industry best practices for organization, javadoc and style, as much as possible. + +To run the Code Style check use `./gradlew spotlessCheck`. + +If check found any errors, you can apply Code Style by running `./gradlew spotlessApply` + +## Creating a pull request + +Once you are satisfied with your changes: + +- Commit changes to the local branch you created. +- Push that branch with changes to the corresponding remote branch on GitHub +- Submit a [pull request](https://help.github.com/articles/creating-a-pull-request) to `dev` branch. \ No newline at end of file diff --git a/README.md b/README.md index dd244b5..c086a6b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Java EtherScan API [![Minimum required Java version](https://img.shields.io/badge/Java-1.8%2B-blue?logo=openjdk)](https://openjdk.org/projects/jdk8/) -[![GitHub Action](https://github.com/goodforgod/java-etherscan-api/workflows/Java%20CI/badge.svg)](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3A%22Java+CI%22) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.goodforgod/java-etherscan-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.goodforgod/java-etherscan-api) +[![Java CI](https://github.com/GoodforGod/java-etherscan-api/workflows/CI%20Master/badge.svg)](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3ACI+Master) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=GoodforGod_java-etherscan-api&metric=coverage)](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=GoodforGod_java-etherscan-api&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=GoodforGod_java-etherscan-api&metric=ncloc)](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) @@ -14,7 +15,7 @@ Library supports EtherScan *API* for all available *Ethereum Networks* for *ethe **Gradle** ```groovy -implementation "com.github.goodforgod:java-etherscan-api:2.0.0" +implementation "com.github.goodforgod:java-etherscan-api:2.1.0" ``` **Maven** @@ -22,7 +23,7 @@ implementation "com.github.goodforgod:java-etherscan-api:2.0.0" com.github.goodforgod java-etherscan-api - 2.0.0 + 2.1.0 ``` diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 2f7efbe..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-minimal \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3d766c2..7dcf4c7 100644 --- a/build.gradle +++ b/build.gradle @@ -3,8 +3,9 @@ plugins { id "java-library" id "maven-publish" - id "org.sonarqube" version "3.3" - id "com.diffplug.spotless" version "6.12.0" + id "org.sonarqube" version "4.3.0.3225" + id "com.diffplug.spotless" version "6.19.0" + id "io.github.gradle-nexus.publish-plugin" version "1.3.0" } repositories { @@ -13,7 +14,8 @@ repositories { } group = groupId -version = artifactVersion +var ver = System.getenv().getOrDefault("RELEASE_VERSION", artifactVersion) +version = ver.startsWith("v") ? ver.substring(1) : ver sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -28,6 +30,7 @@ dependencies { } test { + failFast(false) useJUnitPlatform() testLogging { events("passed", "skipped", "failed") @@ -36,9 +39,13 @@ test { } reports { - html.enabled(false) - junitXml.enabled(false) + html.required = false + junitXml.required = false } + + environment([ + "": "", + ]) } spotless { @@ -46,7 +53,7 @@ spotless { encoding("UTF-8") importOrder() removeUnusedImports() - eclipse("4.21.0").configFile("${rootDir}/config/codestyle.xml") + eclipse("4.21").configFile("${rootDir}/config/codestyle.xml") } } @@ -58,6 +65,18 @@ sonarqube { } } +nexusPublishing { + packageGroup = groupId + repositories { + sonatype { + username = System.getenv("OSS_USERNAME") + password = System.getenv("OSS_PASSWORD") + nexusUrl.set(uri("https://oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://oss.sonatype.org/content/repositories/snapshots/")) + } + } +} + publishing { publications { mavenJava(MavenPublication) { @@ -99,6 +118,16 @@ publishing { password System.getenv("OSS_PASSWORD") } } + if (!version.endsWith("SNAPSHOT")) { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/GoodforGod/$artifactId" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } } } @@ -116,7 +145,7 @@ tasks.withType(JavaCompile) { check.dependsOn jacocoTestReport jacocoTestReport { reports { - xml.enabled true + xml.required = true html.destination file("${buildDir}/jacocoHtml") } } @@ -128,9 +157,12 @@ javadoc { } } -if (project.hasProperty("signing.keyId")) { +if (project.hasProperty("signingKey")) { apply plugin: "signing" signing { + def signingKey = findProperty("signingKey") + def signingPassword = findProperty("signingPassword") + useInMemoryPgpKeys(signingKey, signingPassword) sign publishing.publications.mavenJava } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36900 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

yHlr~Qj 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 GTKgBj+ffbx diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb70..a363877 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..65dcd68 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -205,6 +209,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd3..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 40059f487c86ab9f10b13c7191556102760a2011 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:11:01 +0300 Subject: [PATCH 07/12] [2.1.0-SNAPSHOT] CI updated --- .github/workflows/publish-release.yml | 49 +++++++++++++++++++ .github/workflows/publish-snapshot.yml | 44 +++++++++++++++++ .../{gradle.yml => pull-request.yml} | 34 +++++++------ 3 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/publish-snapshot.yml rename .github/workflows/{gradle.yml => pull-request.yml} (54%) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..3aa7884 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,49 @@ +name: CI Master + +on: + release: + types: [ published ] + +jobs: + publish-release: + runs-on: ubuntu-latest + name: Publish Release + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + + - name: Build + run: './gradlew classes' + + - name: Test + run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} + + - name: SonarQube + run: './gradlew sonar --info' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: Publish Release to GitHub Packages + run: './gradlew publishMavenJavaPublicationToGitHubPackagesRepository' + env: + RELEASE_VERSION: ${{ github.ref_name }} + GITHUB_TOKEN: ${{ secrets.OSS_GITHUB_TOKEN }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSS_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSS_SIGNING_PASSWORD }} + + - name: Publish Release to OSSRH + run: './gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository' + env: + RELEASE_VERSION: ${{ github.ref_name }} + OSS_USERNAME: ${{ secrets.OSS_USERNAME }} + OSS_PASSWORD: ${{ secrets.OSS_PASSWORD }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSS_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSS_SIGNING_PASSWORD }} diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml new file mode 100644 index 0000000..d181a6c --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,44 @@ +name: CI Dev + +on: + push: + paths: + - '**/workflows/*.yml' + - '**/java/**' + - '*.java' + - '*.gradle' + - '*.properties' + branches: + - dev + +jobs: + publish-snapshot: + runs-on: ubuntu-latest + name: Publish Snapshot + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + + - name: Code Style + run: './gradlew spotlessCheck' + + - name: Build + run: './gradlew classes' + + - name: Test + run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} + + - name: Publish Snapshot + run: './gradlew publish' + env: + OSS_USERNAME: ${{ secrets.OSS_USERNAME }} + OSS_PASSWORD: ${{ secrets.OSS_PASSWORD }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSS_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSS_SIGNING_PASSWORD }} diff --git a/.github/workflows/gradle.yml b/.github/workflows/pull-request.yml similarity index 54% rename from .github/workflows/gradle.yml rename to .github/workflows/pull-request.yml index 31c42f0..570e503 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/pull-request.yml @@ -1,9 +1,6 @@ -name: Java CI +name: CI Pull Request on: - push: - branches: - - master pull_request: branches: - master @@ -15,37 +12,44 @@ jobs: strategy: matrix: java: [ '11', '17' ] - name: Java ${{ matrix.java }} setup + name: Java ${{ matrix.java }} Pull Request setup steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up JDK - uses: actions/setup-java@v1 - + uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }} + distribution: 'adopt' - - name: Build - run: ./gradlew classes + - name: Code Style + run: './gradlew spotlessCheck' - - name: Codestyle - run: ./gradlew spotlessCheck + - name: Build + run: './gradlew classes' - name: Test if: matrix.java == '11' - run: ./gradlew test jacocoTestReport + run: './gradlew test jacocoTestReport' env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_1 }} - name: Test if: matrix.java == '17' - run: ./gradlew test jacocoTestReport + run: './gradlew test jacocoTestReport' env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - name: SonarQube if: matrix.java == '17' - run: ./gradlew sonarqube + run: './gradlew sonar --info' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: Test Report + if: matrix.java == '17' + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: | + **/test-results/**/*.xml From 519c26ae22fdc79e76aab423000b76be91bef339 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:15:47 +0300 Subject: [PATCH 08/12] [2.1.0-SNAPSHOT] Javadoc fixed --- src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java index b6db82e..3f48127 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java @@ -17,7 +17,7 @@ public interface StatisticAPI { /** * ERC20 token total Supply * EtherScan + * "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan * Returns the current amount of an ERC-20 token in circulation. * * @param contract contract address From b6e9ba5e988fefa805db78f10bdac90cb1b98c6e Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:21:15 +0300 Subject: [PATCH 09/12] [2.1.0-SNAPSHOT] Method renamed --- .../java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java | 2 +- src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java | 2 +- src/test/java/io/goodforgod/api/etherscan/ApiRunner.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 70d9a01..2b70711 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -89,7 +89,7 @@ public EtherScanAPI.Builder withConverter(@NotNull Supplier converter } @NotNull - public EtherScanAPI.Builder withRetryOnLimitReach(int maxRetryCount) { + public EtherScanAPI.Builder withRetryOnRateLimit(int maxRetryCount) { if (maxRetryCount < 0 || maxRetryCount > 20) { throw new IllegalStateException("maxRetryCount value must be in range from 0 to 20, but was: " + maxRetryCount); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java index 4e6bc57..bae1902 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -71,7 +71,7 @@ interface Builder { * @return self */ @NotNull - EtherScanAPI.Builder withRetryOnLimitReach(@Range(from = 0, to = 20) int maxRetryCount); + EtherScanAPI.Builder withRetryOnRateLimit(@Range(from = 0, to = 20) int maxRetryCount); @NotNull EtherScanAPI build(); diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index 72aeeff..bc4f334 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -29,7 +29,7 @@ public class ApiRunner extends Assertions { .withApiKey(ApiRunner.API_KEY) .withNetwork(EthNetworks.MAINNET) .withQueue(queueManager) - .withRetryOnLimitReach(5) + .withRetryOnRateLimit(5) .build(); } From c855695fdc61fb978bf0c86669ee476c051915b9 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:21:55 +0300 Subject: [PATCH 10/12] [2.1.0-SNAPSHOT] Method renamed --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7dcf4c7..7e28207 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ test { reports { html.required = false - junitXml.required = false + junitXml.required = true } environment([ From 3a252b4e32ebc5e4408ca25c903fb3776e5458ac Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Fri, 6 Oct 2023 02:22:51 +0300 Subject: [PATCH 11/12] [2.1.0-SNAPSHOT] CI updated --- .github/workflows/pull-request.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 570e503..0b49f50 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -40,16 +40,16 @@ jobs: env: ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - - name: SonarQube - if: matrix.java == '17' - run: './gradlew sonar --info' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - name: Test Report if: matrix.java == '17' uses: EnricoMi/publish-unit-test-result-action@v2 with: files: | **/test-results/**/*.xml + + - name: SonarQube + if: matrix.java == '17' + run: './gradlew sonar --info' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From 3de7b242b9f65daaaedbec0ef67ea83fd45c0706 Mon Sep 17 00:00:00 2001 From: Anton Kurako Date: Mon, 15 Jan 2024 12:39:58 +0300 Subject: [PATCH 12/12] [2.1.0-SNAPSHOT] README.md updated --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c086a6b..0d06c99 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ API support all Ethereum [default networks](https://docs.etherscan.io/getting-st - [Sepolia](https://api-sepolia.etherscan.io/) ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); EtherScanAPI apiGoerli = EtherScanAPI.builder().withNetwork(EthNetworks.GORLI).build(); EtherScanAPI apiSepolia = EtherScanAPI.builder().withNetwork(EthNetworks.SEPOLIA).build(); ``` @@ -97,7 +97,7 @@ Below are examples for each API category. **Get Ether Balance for a single Address** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3b4F"); ``` @@ -105,14 +105,14 @@ Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3 **Get uncles block for block height** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional uncles = api.block().uncles(200000); ``` ### Contract API **Request contract ABI from [verified codes](https://etherscan.io/contractsVerified)** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); ``` @@ -120,7 +120,7 @@ Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413 **Get event logs for single topic** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); LogQuery query = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); @@ -129,7 +129,7 @@ List logs = api.logs().logs(query); **Get event logs for 3 topics with respectful operations** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); LogQuery query = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withBlockFrom(379224) .withBlockTo(400000) @@ -148,13 +148,13 @@ List logs = api.logs().logs(query); **Get tx details with proxy endpoint** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); ``` **Get block info with proxy endpoint** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional block = api.proxy().block(15215); ``` @@ -162,7 +162,7 @@ Optional block = api.proxy().block(15215); **Statistic about last price** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Price price = api.stats().priceLast(); ``` @@ -170,7 +170,7 @@ Price price = api.stats().priceLast(); **Request receipt status for tx** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional status = api.txs().receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); ``` pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy