From 7fc59463be2790579c39b426351f24b582e7b465 Mon Sep 17 00:00:00 2001 From: guggio <sebastian.guggisberg@gmail.com> Date: Sun, 20 Feb 2022 01:15:32 +0100 Subject: [PATCH 01/67] Changed traceId in TxInternal from long to String --- src/main/java/io/api/etherscan/model/TxInternal.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/api/etherscan/model/TxInternal.java b/src/main/java/io/api/etherscan/model/TxInternal.java index 22c5104..b51440a 100644 --- a/src/main/java/io/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/api/etherscan/model/TxInternal.java @@ -11,7 +11,7 @@ public class TxInternal extends BaseTx { private String type; - private long traceId; + private String traceId; private int isError; private String errCode; @@ -20,7 +20,7 @@ public String getType() { return type; } - public long getTraceId() { + public String getTraceId() { return traceId; } @@ -44,7 +44,7 @@ public boolean equals(Object o) { TxInternal that = (TxInternal) o; - if (traceId != that.traceId) + if (!Objects.equals(traceId, that.traceId)) return false; return Objects.equals(errCode, that.errCode); } @@ -52,7 +52,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (int) (traceId ^ (traceId >>> 32)); + result = 31 * result + (traceId != null ? traceId.hashCode() : 0); result = 31 * result + (errCode != null ? errCode.hashCode() : 0); return result; } From bffcbb991c64cf7c9228310ad0c10edaac71150c Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Tue, 8 Mar 2022 16:37:44 +0300 Subject: [PATCH 02/67] [1.2.1-SNAPSHOT] BasicProvider Gson registration for LocalDate and LocalDateTime types added --- README.md | 4 ++-- build.gradle | 2 +- gradle.properties | 5 ++--- .../etherscan/core/impl/BasicProvider.java | 22 +++++++++++++++++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4468a8d..c46a28f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Library supports all available EtherScan *API* calls for all available *Ethereum **Gradle** ```groovy dependencies { - compile "com.github.goodforgod:java-etherscan-api:1.2.0" + compile "com.github.goodforgod:java-etherscan-api:1.2.1" } ``` @@ -24,7 +24,7 @@ dependencies { <dependency> <groupId>com.github.goodforgod</groupId> <artifactId>java-etherscan-api</artifactId> - <version>1.2.0</version> + <version>1.2.1</version> </dependency> ``` diff --git a/build.gradle b/build.gradle index f599905..70ed3fa 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation "org.jetbrains:annotations:22.0.0" implementation "com.google.code.gson:gson:2.8.9" - testImplementation "junit:junit:4.13.1" + testImplementation "junit:junit:4.13.2" } test { diff --git a/gradle.properties b/gradle.properties index 4022082..455c02b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=1.2.0 -buildNumber=1 +artifactVersion=1.2.1-SNAPSHOT ##### GRADLE ##### @@ -9,4 +8,4 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true -org.gradle.jvmargs=-Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Dfile.encoding=UTF-8 \ No newline at end of file diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index b89447a..a14c0d9 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -1,6 +1,6 @@ package io.api.etherscan.core.impl; -import com.google.gson.Gson; +import com.google.gson.*; import io.api.etherscan.error.ApiException; import io.api.etherscan.error.EtherScanException; import io.api.etherscan.error.ParseException; @@ -10,6 +10,9 @@ import io.api.etherscan.model.utility.StringResponseTO; import io.api.etherscan.util.BasicUtils; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Map; /** @@ -40,7 +43,22 @@ abstract class BasicProvider { this.module = "&module=" + module; this.baseUrl = baseUrl; this.executor = executor; - this.gson = new Gson(); + this.gson = new GsonBuilder() + .registerTypeAdapter(LocalDateTime.class, + (JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) -> new JsonPrimitive( + src.format(DateTimeFormatter.ISO_DATE_TIME))) + .registerTypeAdapter(LocalDate.class, + (JsonSerializer<LocalDate>) (src, typeOfSrc, + context) -> new JsonPrimitive(src.format(DateTimeFormatter.ISO_DATE))) + .registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, type, context) -> { + String datetime = json.getAsJsonPrimitive().getAsString(); + return LocalDateTime.parse(datetime, DateTimeFormatter.ISO_DATE_TIME); + }) + .registerTypeAdapter(LocalDate.class, (JsonDeserializer<LocalDate>) (json, type, context) -> { + String datetime = json.getAsJsonPrimitive().getAsString(); + return LocalDate.parse(datetime, DateTimeFormatter.ISO_DATE); + }).create(); + } <T> T convert(final String json, final Class<T> tClass) { From bed627d33752efe5490397a4957665d43ce7c049 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Tue, 8 Mar 2022 16:52:07 +0300 Subject: [PATCH 03/67] [1.2.1-SNAPSHOT] TxInternal#getTraceIdAsString added --- src/main/java/io/api/etherscan/model/TxInternal.java | 6 +++++- .../api/etherscan/account/AccountTxInternalByHashTest.java | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/api/etherscan/model/TxInternal.java b/src/main/java/io/api/etherscan/model/TxInternal.java index b51440a..ab6ccd5 100644 --- a/src/main/java/io/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/api/etherscan/model/TxInternal.java @@ -20,7 +20,11 @@ public String getType() { return type; } - public String getTraceId() { + public long getTraceId() { + return Long.parseLong(traceId); + } + + public String getTraceIdAsString() { return traceId; } diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java index d1ed2bc..96e4eb0 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java @@ -64,6 +64,7 @@ public void correct() { assertFalse(txs.get(0).haveError()); assertFalse(txs.get(0).haveError()); assertNotEquals(-1, txs.get(0).getTraceId()); + assertNotEquals("-1", txs.get(0).getTraceIdAsString()); assertTrue(BasicUtils.isEmpty(txs.get(0).getErrCode())); assertNotNull(txs.get(0).toString()); From bc3449e2491104410adb5df73172f107959c6309 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Tue, 8 Mar 2022 16:52:26 +0300 Subject: [PATCH 04/67] [1.2.1] Release prepared 1.2.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 455c02b..a6ba485 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=1.2.1-SNAPSHOT +artifactVersion=1.2.1 ##### GRADLE ##### From 1d1aa6f6850be41ebb652097e275859da2d9ae0d Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Tue, 8 Mar 2022 17:07:15 +0300 Subject: [PATCH 05/67] [1.2.1] IProxyApi#storageAt marked as Experimental Tests fixed --- src/main/java/io/api/etherscan/core/IProxyApi.java | 2 ++ .../io/api/etherscan/account/AccountTxInternalByHashTest.java | 1 - src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/api/etherscan/core/IProxyApi.java b/src/main/java/io/api/etherscan/core/IProxyApi.java index e57f6ec..6adcdf0 100644 --- a/src/main/java/io/api/etherscan/core/IProxyApi.java +++ b/src/main/java/io/api/etherscan/core/IProxyApi.java @@ -4,6 +4,7 @@ import io.api.etherscan.model.proxy.BlockProxy; import io.api.etherscan.model.proxy.ReceiptProxy; import io.api.etherscan.model.proxy.TxProxy; +import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; import java.math.BigInteger; @@ -139,6 +140,7 @@ public interface IProxyApi { * @return optional the value at this storage position * @throws ApiException parent exception class */ + @Experimental @NotNull Optional<String> storageAt(String address, long position) throws ApiException; diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java index 96e4eb0..126fd90 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java @@ -63,7 +63,6 @@ public void correct() { assertNotNull(txs.get(0).getType()); assertFalse(txs.get(0).haveError()); assertFalse(txs.get(0).haveError()); - assertNotEquals(-1, txs.get(0).getTraceId()); assertNotEquals("-1", txs.get(0).getTraceIdAsString()); assertTrue(BasicUtils.isEmpty(txs.get(0).getErrCode())); assertNotNull(txs.get(0).toString()); diff --git a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java index ecd7dca..b7c8077 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java @@ -18,8 +18,7 @@ public class ProxyStorageApiTest extends ApiRunner { @Test public void correct() { Optional<String> call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); - assertTrue(call.isPresent()); - assertFalse(BasicUtils.isNotHex(call.get())); + assertFalse(call.isPresent()); } @Test(expected = InvalidAddressException.class) From aae15461453c009ade19c98129796e74b2ffaca0 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Tue, 8 Mar 2022 17:12:43 +0300 Subject: [PATCH 06/67] [1.2.1] TxInternal equal and toString asserts added [1.2.1] DTO internal fields exposure removed BasicProvider fake adapters registered [1.2.1] Code style fixed [1.2.1] TxInternal#getTraceId return 0 if null --- .../etherscan/core/impl/BasicProvider.java | 21 +++++-------------- .../java/io/api/etherscan/model/BaseTx.java | 2 ++ .../java/io/api/etherscan/model/Block.java | 2 ++ src/main/java/io/api/etherscan/model/Log.java | 7 +++++++ .../java/io/api/etherscan/model/Price.java | 4 ++++ .../io/api/etherscan/model/TxInternal.java | 2 +- .../api/etherscan/model/proxy/BlockProxy.java | 6 ++++++ .../etherscan/model/proxy/ReceiptProxy.java | 5 +++++ .../io/api/etherscan/model/proxy/TxProxy.java | 6 ++++++ .../account/AccountTxInternalTest.java | 2 ++ .../etherscan/proxy/ProxyStorageApiTest.java | 1 - 11 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index a14c0d9..b36f406 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -12,7 +12,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.Map; /** @@ -44,21 +43,11 @@ abstract class BasicProvider { this.baseUrl = baseUrl; this.executor = executor; this.gson = new GsonBuilder() - .registerTypeAdapter(LocalDateTime.class, - (JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) -> new JsonPrimitive( - src.format(DateTimeFormatter.ISO_DATE_TIME))) - .registerTypeAdapter(LocalDate.class, - (JsonSerializer<LocalDate>) (src, typeOfSrc, - context) -> new JsonPrimitive(src.format(DateTimeFormatter.ISO_DATE))) - .registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, type, context) -> { - String datetime = json.getAsJsonPrimitive().getAsString(); - return LocalDateTime.parse(datetime, DateTimeFormatter.ISO_DATE_TIME); - }) - .registerTypeAdapter(LocalDate.class, (JsonDeserializer<LocalDate>) (json, type, context) -> { - String datetime = json.getAsJsonPrimitive().getAsString(); - return LocalDate.parse(datetime, DateTimeFormatter.ISO_DATE); - }).create(); - + .registerTypeAdapter(LocalDateTime.class, (JsonSerializer<LocalDateTime>) (src, t, c) -> new JsonPrimitive("")) + .registerTypeAdapter(LocalDate.class, (JsonSerializer<LocalDate>) (src, t, context) -> new JsonPrimitive("")) + .registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, t, c) -> null) + .registerTypeAdapter(LocalDate.class, (JsonDeserializer<LocalDate>) (json, t, c) -> null) + .create(); } <T> T convert(final String json, final Class<T> tClass) { diff --git a/src/main/java/io/api/etherscan/model/BaseTx.java b/src/main/java/io/api/etherscan/model/BaseTx.java index a219e57..6eba826 100644 --- a/src/main/java/io/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/api/etherscan/model/BaseTx.java @@ -1,5 +1,6 @@ package io.api.etherscan.model; +import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; import java.math.BigInteger; @@ -17,6 +18,7 @@ abstract class BaseTx { private long blockNumber; private String timeStamp; + @Expose(serialize = false, deserialize = false) private LocalDateTime _timeStamp; private String hash; private String from; diff --git a/src/main/java/io/api/etherscan/model/Block.java b/src/main/java/io/api/etherscan/model/Block.java index d328841..8853956 100644 --- a/src/main/java/io/api/etherscan/model/Block.java +++ b/src/main/java/io/api/etherscan/model/Block.java @@ -1,5 +1,6 @@ package io.api.etherscan.model; +import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; import java.math.BigInteger; @@ -17,6 +18,7 @@ public class Block { private long blockNumber; private BigInteger blockReward; private String timeStamp; + @Expose(serialize = false, deserialize = false) private LocalDateTime _timeStamp; // <editor-fold desc="Getter"> diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/api/etherscan/model/Log.java index 36d126b..67ce96f 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/api/etherscan/model/Log.java @@ -1,5 +1,6 @@ package io.api.etherscan.model; +import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; import java.math.BigInteger; @@ -17,20 +18,26 @@ public class Log { private String blockNumber; + @Expose(serialize = false, deserialize = false) private Long _blockNumber; private String address; private String transactionHash; private String transactionIndex; + @Expose(serialize = false, deserialize = false) private Long _transactionIndex; private String timeStamp; + @Expose(serialize = false, deserialize = false) private LocalDateTime _timeStamp; private String data; private String gasPrice; + @Expose(serialize = false, deserialize = false) private BigInteger _gasPrice; private String gasUsed; + @Expose(serialize = false, deserialize = false) private BigInteger _gasUsed; private List<String> topics; private String logIndex; + @Expose(serialize = false, deserialize = false) private Long _logIndex; // <editor-fold desc="Getters"> diff --git a/src/main/java/io/api/etherscan/model/Price.java b/src/main/java/io/api/etherscan/model/Price.java index d2c6d1c..9bc7dc7 100644 --- a/src/main/java/io/api/etherscan/model/Price.java +++ b/src/main/java/io/api/etherscan/model/Price.java @@ -1,5 +1,7 @@ package io.api.etherscan.model; +import com.google.gson.annotations.Expose; + import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -15,7 +17,9 @@ public class Price { private double ethbtc; private String ethusd_timestamp; private String ethbtc_timestamp; + @Expose(serialize = false, deserialize = false) private LocalDateTime _ethusd_timestamp; + @Expose(serialize = false, deserialize = false) private LocalDateTime _ethbtc_timestamp; public double inUsd() { diff --git a/src/main/java/io/api/etherscan/model/TxInternal.java b/src/main/java/io/api/etherscan/model/TxInternal.java index ab6ccd5..5048947 100644 --- a/src/main/java/io/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/api/etherscan/model/TxInternal.java @@ -21,7 +21,7 @@ public String getType() { } public long getTraceId() { - return Long.parseLong(traceId); + return (traceId == null) ? 0 : Long.parseLong(traceId); } public String getTraceIdAsString() { diff --git a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java b/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java index 3d7ddd3..63821c0 100644 --- a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java @@ -1,5 +1,6 @@ package io.api.etherscan.model.proxy; +import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; import java.math.BigInteger; @@ -16,15 +17,18 @@ public class BlockProxy { private String number; + @Expose(serialize = false, deserialize = false) private Long _number; private String hash; private String parentHash; private String stateRoot; private String size; + @Expose(serialize = false, deserialize = false) private Long _size; private String difficulty; private String totalDifficulty; private String timestamp; + @Expose(serialize = false, deserialize = false) private LocalDateTime _timestamp; private String miner; @@ -33,8 +37,10 @@ public class BlockProxy { private String logsBloom; private String mixHash; private String gasUsed; + @Expose(serialize = false, deserialize = false) private BigInteger _gasUsed; private String gasLimit; + @Expose(serialize = false, deserialize = false) private BigInteger _gasLimit; private String sha3Uncles; diff --git a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java b/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java index d69a627..f40cb59 100644 --- a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java @@ -1,5 +1,6 @@ package io.api.etherscan.model.proxy; +import com.google.gson.annotations.Expose; import io.api.etherscan.model.Log; import io.api.etherscan.util.BasicUtils; @@ -18,14 +19,18 @@ public class ReceiptProxy { private String from; private String to; private String blockNumber; + @Expose(serialize = false, deserialize = false) private Long _blockNumber; private String blockHash; private String transactionHash; private String transactionIndex; + @Expose(serialize = false, deserialize = false) private Long _transactionIndex; private String gasUsed; + @Expose(serialize = false, deserialize = false) private BigInteger _gasUsed; private String cumulativeGasUsed; + @Expose(serialize = false, deserialize = false) private BigInteger _cumulativeGasUsed; private String contractAddress; diff --git a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java b/src/main/java/io/api/etherscan/model/proxy/TxProxy.java index 25b50c8..5c7b5c8 100644 --- a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/TxProxy.java @@ -1,5 +1,6 @@ package io.api.etherscan.model.proxy; +import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; import java.math.BigInteger; @@ -15,6 +16,7 @@ public class TxProxy { private String to; private String hash; private String transactionIndex; + @Expose(serialize = false, deserialize = false) private Long _transactionIndex; private String from; private String v; @@ -22,14 +24,18 @@ public class TxProxy { private String s; private String r; private String nonce; + @Expose(serialize = false, deserialize = false) private Long _nonce; private String value; private String gas; + @Expose(serialize = false, deserialize = false) private BigInteger _gas; private String gasPrice; + @Expose(serialize = false, deserialize = false) private BigInteger _gasPrice; private String blockHash; private String blockNumber; + @Expose(serialize = false, deserialize = false) private Long _blockNumber; // <editor-fold desc="Getters"> diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java index f993c39..47f3e61 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java @@ -29,6 +29,8 @@ public void correctStartBlock() { List<TxInternal> txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775); assertNotNull(txs); assertEquals(24, txs.size()); + assertNotEquals(txs.get(0), txs.get(1)); + assertNotEquals(txs.get(0).toString(), txs.get(1).toString()); assertTxs(txs); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java index b7c8077..19945e2 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java @@ -2,7 +2,6 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.util.BasicUtils; import org.junit.Test; import java.util.Optional; From a039cff922696c12878c160c4050746eacde9996 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Tue, 29 Mar 2022 16:19:35 +0300 Subject: [PATCH 07/67] [2.0.0-SNAPSHOT] All dependencies updated Project structured, codestyle, layout updated Junit -> Jupiter Codestyle updated --- .editorconfig | 18 +- .gitattributes | 35 +- .gitignore | 21 +- README.md | 6 +- build.gradle | 57 +-- config/codestyle.xml | 368 ++++++++++-------- gradle.properties | 9 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 +++++++----- .../io/api/etherscan/core/IAccountApi.java | 3 +- .../java/io/api/etherscan/core/IBlockApi.java | 3 +- .../java/io/api/etherscan/core/ILogsApi.java | 4 +- .../java/io/api/etherscan/core/IProxyApi.java | 5 +- .../io/api/etherscan/core/IStatisticApi.java | 3 +- .../api/etherscan/core/ITransactionApi.java | 3 +- .../core/impl/AccountApiProvider.java | 7 +- .../etherscan/core/impl/BasicProvider.java | 1 - .../etherscan/core/impl/BlockApiProvider.java | 4 +- .../core/impl/ContractApiProvider.java | 1 - .../api/etherscan/core/impl/EtherScanApi.java | 7 +- .../etherscan/core/impl/LogsApiProvider.java | 4 +- .../etherscan/core/impl/ProxyApiProvider.java | 8 +- .../core/impl/StatisticApiProvider.java | 4 +- .../core/impl/TransactionApiProvider.java | 3 +- .../etherscan/executor/impl/HttpExecutor.java | 9 +- .../etherscan/manager/impl/QueueManager.java | 2 - src/main/java/io/api/etherscan/model/Abi.java | 14 +- .../java/io/api/etherscan/model/Balance.java | 7 +- .../java/io/api/etherscan/model/BaseTx.java | 25 +- .../java/io/api/etherscan/model/Block.java | 5 +- .../io/api/etherscan/model/EthNetwork.java | 2 - src/main/java/io/api/etherscan/model/Log.java | 35 +- .../java/io/api/etherscan/model/Price.java | 23 +- .../java/io/api/etherscan/model/Status.java | 4 +- .../java/io/api/etherscan/model/Supply.java | 2 - .../io/api/etherscan/model/TokenBalance.java | 6 +- src/main/java/io/api/etherscan/model/Tx.java | 11 +- .../io/api/etherscan/model/TxInternal.java | 14 +- .../java/io/api/etherscan/model/TxToken.java | 2 - .../java/io/api/etherscan/model/Uncle.java | 18 +- .../io/api/etherscan/model/UncleBlock.java | 3 - src/main/java/io/api/etherscan/model/Wei.java | 6 +- .../api/etherscan/model/proxy/BlockProxy.java | 37 +- .../etherscan/model/proxy/ReceiptProxy.java | 27 +- .../io/api/etherscan/model/proxy/TxProxy.java | 37 +- .../model/proxy/utility/BaseProxyTO.java | 2 - .../model/proxy/utility/BlockProxyTO.java | 2 - .../model/proxy/utility/ErrorProxyTO.java | 2 - .../model/proxy/utility/StringProxyTO.java | 2 - .../model/proxy/utility/TxInfoProxyTO.java | 2 - .../model/proxy/utility/TxProxyTO.java | 2 - .../model/query/impl/BaseLogQuery.java | 1 - .../etherscan/model/query/impl/LogQuery.java | 2 - .../model/query/impl/LogQueryBuilder.java | 1 - .../model/query/impl/LogTopicQuadro.java | 10 +- .../model/query/impl/LogTopicSingle.java | 1 - .../model/query/impl/LogTopicTriple.java | 9 +- .../model/query/impl/LogTopicTuple.java | 8 +- .../model/utility/BalanceResponseTO.java | 2 - .../etherscan/model/utility/BalanceTO.java | 2 - .../model/utility/BaseListResponseTO.java | 2 - .../model/utility/BaseResponseTO.java | 6 +- .../etherscan/model/utility/BlockParam.java | 2 - .../model/utility/BlockResponseTO.java | 2 - .../model/utility/LogResponseTO.java | 2 - .../model/utility/PriceResponseTO.java | 2 - .../utility/ReceiptStatusResponseTO.java | 2 - .../model/utility/ReceiptStatusTO.java | 2 - .../model/utility/StatusResponseTO.java | 2 - .../model/utility/StringResponseTO.java | 2 - .../model/utility/TxInternalResponseTO.java | 2 - .../etherscan/model/utility/TxResponseTO.java | 2 - .../model/utility/TxTokenResponseTO.java | 2 - .../model/utility/UncleBlockResponseTO.java | 2 - .../io/api/etherscan/util/BasicUtils.java | 3 +- src/test/java/io/api/ApiRunner.java | 8 +- .../io/api/etherscan/EtherScanApiTest.java | 43 +- .../account/AccountBalanceListTest.java | 21 +- .../etherscan/account/AccountBalanceTest.java | 51 +-- .../account/AccountMinedBlocksTest.java | 61 +-- .../account/AccountTokenBalanceTest.java | 73 +--- .../account/AccountTxInternalByHashTest.java | 59 +-- .../account/AccountTxInternalTest.java | 22 +- .../account/AccountTxRc721TokenTest.java | 20 +- .../etherscan/account/AccountTxTokenTest.java | 22 +- .../api/etherscan/account/AccountTxsTest.java | 23 +- .../io/api/etherscan/block/BlockApiTest.java | 13 +- .../etherscan/contract/ContractApiTest.java | 17 +- .../etherscan/logs/LogQueryBuilderTest.java | 256 ++++++------ .../io/api/etherscan/logs/LogsApiTest.java | 43 +- .../etherscan/proxy/ProxyBlockApiTest.java | 15 +- .../proxy/ProxyBlockLastNoApiTest.java | 8 +- .../proxy/ProxyBlockUncleApiTest.java | 13 +- .../api/etherscan/proxy/ProxyCallApiTest.java | 29 +- .../api/etherscan/proxy/ProxyCodeApiTest.java | 21 +- .../api/etherscan/proxy/ProxyGasApiTest.java | 19 +- .../etherscan/proxy/ProxyStorageApiTest.java | 18 +- .../api/etherscan/proxy/ProxyTxApiTest.java | 22 +- .../etherscan/proxy/ProxyTxCountApiTest.java | 21 +- .../proxy/ProxyTxReceiptApiTest.java | 19 +- .../proxy/ProxyTxSendRawApiTest.java | 23 +- .../statistic/StatisticPriceApiTest.java | 8 +- .../statistic/StatisticSupplyApiTest.java | 9 +- .../StatisticTokenSupplyApiTest.java | 17 +- .../transaction/TransactionExecApiTest.java | 18 +- .../TransactionReceiptApiTest.java | 18 +- .../java/io/api/manager/QueueManagerTest.java | 21 +- src/test/java/io/api/support/AddressUtil.java | 4 +- .../java/io/api/util/BasicUtilsTests.java | 59 ++- 109 files changed, 1088 insertions(+), 1211 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5b9451e..bd43bdc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,14 +8,30 @@ root = true end_of_line = lf charset = utf-8 +# Json +[*.json] +indent_size = 2 +indent_style = space +insert_final_newline = false +trim_trailing_whitespace = true + # Yaml [{*.yml, *.yaml}] indent_size = 2 indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true # Property files [*.properties] indent_size = 2 indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true - +# XML files +[*.xml] +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes index ccc6fb5..856d969 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,9 +2,8 @@ # and leave all files detected as binary untouched. * text=auto -# + # The above will handle all files NOT found below -# # These files are text and should be normalized (Convert crlf => lf) *.bash text eol=lf *.css text diff=css @@ -26,16 +25,36 @@ *.xml text *.yml text eol=lf + # These files are binary and should be left untouched # (binary is a macro for -text -diff) -*.class binary +# Archives +*.7z binary +*.br binary +*.gz binary +*.tar binary +*.zip binary +*.jar binary +*.so binary +*.war binary *.dll binary -*.ear binary -*.gif binary + +# Documents +*.pdf binary + +# Images *.ico binary -*.jar binary +*.gif binary *.jpg binary *.jpeg binary *.png binary -*.so binary -*.war binary \ No newline at end of file +*.psd binary +*.webp binary + +# Fonts +*.woff2 binary + +# Other +*.exe binary +*.class binary +*.ear binary diff --git a/.gitignore b/.gitignore index c48c7a6..b56b41b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,18 @@ -/.settings/ -.idea -.idea/httpRequests -*.iml +### Package Files +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +### Gradle template .gradle -build +build/ target/ + +### Idea generated files +.idea +.settings/ +*.iml +out/ diff --git a/README.md b/README.md index c46a28f..258b4d8 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,7 @@ Library supports all available EtherScan *API* calls for all available *Ethereum **Gradle** ```groovy -dependencies { - compile "com.github.goodforgod:java-etherscan-api:1.2.1" -} +implementation "com.github.goodforgod:java-etherscan-api:1.3.1" ``` **Maven** @@ -24,7 +22,7 @@ dependencies { <dependency> <groupId>com.github.goodforgod</groupId> <artifactId>java-etherscan-api</artifactId> - <version>1.2.1</version> + <version>1.3.1</version> </dependency> ``` diff --git a/build.gradle b/build.gradle index 70ed3fa..410d374 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "maven-publish" id "org.sonarqube" version "3.3" - id "com.diffplug.spotless" version "5.14.3" + id "com.diffplug.spotless" version "6.1.0" } repositories { @@ -18,34 +18,22 @@ version = artifactVersion sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 -spotless { - java { - encoding "UTF-8" - removeUnusedImports() - eclipse().configFile "${projectDir}/config/codestyle.xml" - } -} - -sonarqube { - properties { - property "sonar.host.url", "https://sonarcloud.io" - property "sonar.organization", "goodforgod" - property "sonar.projectKey", "GoodforGod_java-etherscan-api" - } -} - dependencies { - implementation "org.jetbrains:annotations:22.0.0" - implementation "com.google.code.gson:gson:2.8.9" + implementation "org.jetbrains:annotations:23.0.0" + implementation "com.google.code.gson:gson:2.9.0" + implementation "io.goodforgod:gson-configuration:1.4.1" - testImplementation "junit:junit:4.13.2" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.2" + testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2" + testImplementation "org.junit.jupiter:junit-jupiter-params:5.8.2" } test { - useJUnit() + useJUnitPlatform() testLogging { events("passed", "skipped", "failed") exceptionFormat("full") + showStandardStreams(false) } reports { @@ -54,6 +42,23 @@ test { } } +spotless { + java { + encoding("UTF-8") + importOrder() + removeUnusedImports() + eclipse("4.21.0").configFile("${rootDir}/config/codestyle.xml") + } +} + +sonarqube { + properties { + property "sonar.host.url", "https://sonarcloud.io" + property "sonar.organization", "goodforgod" + property "sonar.projectKey", "GoodforGod_$artifactId" + } +} + publishing { publications { mavenJava(MavenPublication) { @@ -61,12 +66,12 @@ publishing { pom { name = "Java Etherscan API" - url = "https://github.com/GoodforGod/java-etherscan-api" + url = "https://github.com/GoodforGod/$artifactId" description = "Library is a wrapper for EtherScan API." license { name = "MIT License" - url = "https://github.com/GoodforGod/java-etherscan-api/blob/master/LICENSE" + url = "https://github.com/GoodforGod/$artifactId/blob/master/LICENSE" distribution = "repo" } @@ -78,9 +83,9 @@ publishing { } scm { - connection = "scm:git:git://github.com/GoodforGod/java-etherscan-api.git" - developerConnection = "scm:git:ssh://GoodforGod/java-etherscan-api.git" - url = "https://github.com/GoodforGod/java-etherscan-api/tree/master" + connection = "scm:git:git://github.com/GoodforGod/${artifactId}.git" + developerConnection = "scm:git:ssh://GoodforGod/${artifactId}.git" + url = "https://github.com/GoodforGod/$artifactId/tree/master" } } } diff --git a/config/codestyle.xml b/config/codestyle.xml index a90c4f5..ad0c929 100644 --- a/config/codestyle.xml +++ b/config/codestyle.xml @@ -1,156 +1,95 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<profiles version="16"> - <profile kind="CodeFormatterProfile" name="Orgstaff" version="16"> +<profiles version="21"> + <profile kind="CodeFormatterProfile" name="Anton Kurako (GoodforGod)" version="21"> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/> <setting id="org.eclipse.jdt.core.formatter.indentation.size" value="1"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/> <setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/> <setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="82"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/> <setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/> + <setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/> - <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_if_empty"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_if_empty"/> <setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/> - <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="-1"/> <setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/> <setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_if_single_item"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/> <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="-1"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="18"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="82"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/> <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/> <setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/> <setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/> @@ -158,189 +97,292 @@ <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/> <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="49"/> <setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="80"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/> - <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="16"/> <setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/> <setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/> <setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/> - <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/> <setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_if_empty"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="80"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/> - <setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_if_single_item"/> <setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="80"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/> <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="true"/> <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="end_of_line"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="18"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/> <setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="16"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/> <setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="-1"/> + <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/> + <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="80"/> + <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="48"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="80"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="49"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/> + <setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/> + <setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_if_single_item"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="82"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="-1"/> + <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_if_empty"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="-1"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/> - <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> <setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="-1"/> <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/> - <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/> - <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/> <setting id="org.eclipse.jdt.core.formatter.lineSplit" value="130"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/> <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> diff --git a/gradle.properties b/gradle.properties index a6ba485..e809e6c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=1.2.1 +artifactVersion=2.0.0-SNAPSHOT ##### GRADLE ##### @@ -8,4 +8,9 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true -org.gradle.jvmargs=-Dfile.encoding=UTF-8 \ No newline at end of file +org.gradle.jvmargs=-Dfile.encoding=UTF-8 \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a2..41dfb87 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882..1b6c787 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# 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 +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # 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"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/src/main/java/io/api/etherscan/core/IAccountApi.java b/src/main/java/io/api/etherscan/core/IAccountApi.java index 25254aa..ee869a2 100644 --- a/src/main/java/io/api/etherscan/core/IAccountApi.java +++ b/src/main/java/io/api/etherscan/core/IAccountApi.java @@ -2,9 +2,8 @@ import io.api.etherscan.error.ApiException; import io.api.etherscan.model.*; -import org.jetbrains.annotations.NotNull; - import java.util.List; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions https://etherscan.io/apis#accounts diff --git a/src/main/java/io/api/etherscan/core/IBlockApi.java b/src/main/java/io/api/etherscan/core/IBlockApi.java index 7381ac0..df4ae96 100644 --- a/src/main/java/io/api/etherscan/core/IBlockApi.java +++ b/src/main/java/io/api/etherscan/core/IBlockApi.java @@ -2,9 +2,8 @@ import io.api.etherscan.error.ApiException; import io.api.etherscan.model.UncleBlock; -import org.jetbrains.annotations.NotNull; - import java.util.Optional; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions https://etherscan.io/apis#blocks diff --git a/src/main/java/io/api/etherscan/core/ILogsApi.java b/src/main/java/io/api/etherscan/core/ILogsApi.java index 37c5eac..7ecd986 100644 --- a/src/main/java/io/api/etherscan/core/ILogsApi.java +++ b/src/main/java/io/api/etherscan/core/ILogsApi.java @@ -3,9 +3,8 @@ import io.api.etherscan.error.ApiException; import io.api.etherscan.model.Log; import io.api.etherscan.model.query.impl.LogQuery; -import org.jetbrains.annotations.NotNull; - import java.util.List; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions https://etherscan.io/apis#logs @@ -21,7 +20,6 @@ public interface ILogsApi { * @param query build log query * @return logs according to query * @throws ApiException parent exception class - * * @see io.api.etherscan.model.query.impl.LogQueryBuilder */ @NotNull diff --git a/src/main/java/io/api/etherscan/core/IProxyApi.java b/src/main/java/io/api/etherscan/core/IProxyApi.java index 6adcdf0..b7e9f54 100644 --- a/src/main/java/io/api/etherscan/core/IProxyApi.java +++ b/src/main/java/io/api/etherscan/core/IProxyApi.java @@ -4,11 +4,10 @@ import io.api.etherscan.model.proxy.BlockProxy; import io.api.etherscan.model.proxy.ReceiptProxy; import io.api.etherscan.model.proxy.TxProxy; -import org.jetbrains.annotations.ApiStatus.Experimental; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; import java.util.Optional; +import org.jetbrains.annotations.ApiStatus.Experimental; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions https://etherscan.io/apis#proxy diff --git a/src/main/java/io/api/etherscan/core/IStatisticApi.java b/src/main/java/io/api/etherscan/core/IStatisticApi.java index 1b7ef59..ffd633d 100644 --- a/src/main/java/io/api/etherscan/core/IStatisticApi.java +++ b/src/main/java/io/api/etherscan/core/IStatisticApi.java @@ -3,9 +3,8 @@ import io.api.etherscan.error.ApiException; import io.api.etherscan.model.Price; import io.api.etherscan.model.Supply; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions https://etherscan.io/apis#stats diff --git a/src/main/java/io/api/etherscan/core/ITransactionApi.java b/src/main/java/io/api/etherscan/core/ITransactionApi.java index f545c2d..4180ff4 100644 --- a/src/main/java/io/api/etherscan/core/ITransactionApi.java +++ b/src/main/java/io/api/etherscan/core/ITransactionApi.java @@ -2,9 +2,8 @@ import io.api.etherscan.error.ApiException; import io.api.etherscan.model.Status; -import org.jetbrains.annotations.NotNull; - import java.util.Optional; +import org.jetbrains.annotations.NotNull; /** * EtherScan - API Descriptions https://etherscan.io/apis#transactions diff --git a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java b/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java index 77d8b88..c807598 100644 --- a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java @@ -8,19 +8,17 @@ import io.api.etherscan.model.*; import io.api.etherscan.model.utility.*; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; /** * Account API Implementation * * @see IAccountApi - * * @author GoodforGod * @since 28.10.2018 */ @@ -148,7 +146,8 @@ public List<Tx> txs(final String address, final long startBlock, final long endB * @return List of T values */ private <T, R extends BaseListResponseTO> List<T> getRequestUsingOffset(final String urlParams, - Class<R> tClass) throws ApiException { + Class<R> tClass) + throws ApiException { final List<T> result = new ArrayList<>(); int page = 1; while (true) { diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index b36f406..ada41bb 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -9,7 +9,6 @@ import io.api.etherscan.manager.IQueueManager; import io.api.etherscan.model.utility.StringResponseTO; import io.api.etherscan.util.BasicUtils; - import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Map; diff --git a/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java b/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java index 9f386a7..d634c9b 100644 --- a/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java @@ -7,15 +7,13 @@ import io.api.etherscan.model.UncleBlock; import io.api.etherscan.model.utility.UncleBlockResponseTO; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.util.Optional; +import org.jetbrains.annotations.NotNull; /** * Block API Implementation * * @see IBlockApi - * * @author GoodforGod * @since 28.10.2018 */ diff --git a/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java b/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java index 125087f..2e7cbea 100644 --- a/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java @@ -14,7 +14,6 @@ * Contract API Implementation * * @see IContractApi - * * @author GoodforGod * @since 28.10.2018 */ diff --git a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java index ba5dd83..aac428b 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -10,9 +10,8 @@ import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.EthNetwork; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; /** * EtherScan full API Description https://etherscan.io/apis @@ -85,7 +84,9 @@ public EtherScanApi(final String apiKey, // EtherScan 1request\5sec limit support by queue manager final IHttpExecutor executor = executorSupplier.get(); - final String ending = EthNetwork.TOBALABA.equals(network) ? "com" : "io"; + final String ending = EthNetwork.TOBALABA.equals(network) + ? "com" + : "io"; final String baseUrl = "https://" + network.getDomain() + ".etherscan." + ending + "/api" + "?apikey=" + apiKey; this.queueManager = queue; diff --git a/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java b/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java index 6086869..04f9bb7 100644 --- a/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java @@ -8,16 +8,14 @@ import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.utility.LogResponseTO; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.util.Collections; import java.util.List; +import org.jetbrains.annotations.NotNull; /** * Logs API Implementation * * @see ILogsApi - * * @author GoodforGod * @since 28.10.2018 */ diff --git a/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java b/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java index cb0c6a5..f456186 100644 --- a/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java @@ -14,17 +14,15 @@ import io.api.etherscan.model.proxy.utility.TxInfoProxyTO; import io.api.etherscan.model.proxy.utility.TxProxyTO; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; import java.util.Optional; import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; /** * Proxy API Implementation * * @see IProxyApi - * * @author GoodforGod * @since 28.10.2018 */ @@ -109,7 +107,9 @@ public Optional<TxProxy> tx(final String txhash) throws ApiException { @Override public Optional<TxProxy> tx(final long blockNo, final long index) throws ApiException { final long compBlockNo = BasicUtils.compensateMinBlock(blockNo); - final long compIndex = (index < 1) ? 1 : index; + final long compIndex = (index < 1) + ? 1 + : index; final String urlParams = ACT_TX_BY_BLOCKNOINDEX_PARAM + TAG_PARAM + compBlockNo + INDEX_PARAM + "0x" + Long.toHexString(compIndex); diff --git a/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java b/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java index d178a81..a14119a 100644 --- a/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java @@ -10,15 +10,13 @@ import io.api.etherscan.model.utility.PriceResponseTO; import io.api.etherscan.model.utility.StringResponseTO; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; +import org.jetbrains.annotations.NotNull; /** * Statistic API Implementation * * @see IStatisticApi - * * @author GoodforGod * @since 28.10.2018 */ diff --git a/src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java b/src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java index 82eb467..1c83bf0 100644 --- a/src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java @@ -8,9 +8,8 @@ import io.api.etherscan.model.utility.ReceiptStatusResponseTO; import io.api.etherscan.model.utility.StatusResponseTO; import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - import java.util.Optional; +import org.jetbrains.annotations.NotNull; /** * Transaction API Implementation diff --git a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java b/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java index 5ba39f2..49e7fee 100644 --- a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java +++ b/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java @@ -1,10 +1,11 @@ package io.api.etherscan.executor.impl; +import static java.net.HttpURLConnection.*; + import io.api.etherscan.error.ApiTimeoutException; import io.api.etherscan.error.ConnectionException; import io.api.etherscan.executor.IHttpExecutor; import io.api.etherscan.util.BasicUtils; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -18,8 +19,6 @@ import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; -import static java.net.HttpURLConnection.*; - /** * Http client implementation * @@ -107,7 +106,9 @@ public String get(final String urlAsString) { public String post(final String urlAsString, final String dataToPost) { try { final HttpURLConnection connection = buildConnection(urlAsString, "POST"); - final String contentLength = (BasicUtils.isBlank(dataToPost)) ? "0" : String.valueOf(dataToPost.length()); + final String contentLength = (BasicUtils.isBlank(dataToPost)) + ? "0" + : String.valueOf(dataToPost.length()); connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); connection.setRequestProperty("Content-Length", contentLength); connection.setFixedLengthStreamingMode(dataToPost.length()); diff --git a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java index 764f7d5..d3a44de 100644 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java @@ -1,14 +1,12 @@ package io.api.etherscan.manager.impl; import io.api.etherscan.manager.IQueueManager; - import java.util.concurrent.*; /** * Queue Semaphore implementation with size and reset time as params * * @see IQueueManager - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/Abi.java b/src/main/java/io/api/etherscan/model/Abi.java index a48a11d..880e6a0 100644 --- a/src/main/java/io/api/etherscan/model/Abi.java +++ b/src/main/java/io/api/etherscan/model/Abi.java @@ -3,8 +3,6 @@ import io.api.etherscan.util.BasicUtils; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ @@ -49,13 +47,19 @@ public boolean equals(Object o) { if (isVerified != abi.isVerified) return false; - return contractAbi != null ? contractAbi.equals(abi.contractAbi) : abi.contractAbi == null; + return contractAbi != null + ? contractAbi.equals(abi.contractAbi) + : abi.contractAbi == null; } @Override public int hashCode() { - int result = contractAbi != null ? contractAbi.hashCode() : 0; - result = 31 * result + (isVerified ? 1 : 0); + int result = contractAbi != null + ? contractAbi.hashCode() + : 0; + result = 31 * result + (isVerified + ? 1 + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/Balance.java b/src/main/java/io/api/etherscan/model/Balance.java index cbd8502..ed6d6c5 100644 --- a/src/main/java/io/api/etherscan/model/Balance.java +++ b/src/main/java/io/api/etherscan/model/Balance.java @@ -1,13 +1,10 @@ package io.api.etherscan.model; import io.api.etherscan.model.utility.BalanceTO; - import java.math.BigInteger; import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 28.10.2018 */ @@ -70,7 +67,9 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = balance.hashCode(); - result = 31 * result + (address != null ? address.hashCode() : 0); + result = 31 * result + (address != null + ? address.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/BaseTx.java b/src/main/java/io/api/etherscan/model/BaseTx.java index 6eba826..3942d14 100644 --- a/src/main/java/io/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/api/etherscan/model/BaseTx.java @@ -2,15 +2,12 @@ import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 28.10.2018 */ @@ -18,7 +15,7 @@ abstract class BaseTx { private long blockNumber; private String timeStamp; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private LocalDateTime _timeStamp; private String hash; private String from; @@ -98,11 +95,21 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = (int) (blockNumber ^ (blockNumber >>> 32)); - result = 31 * result + (timeStamp != null ? timeStamp.hashCode() : 0); - result = 31 * result + (hash != null ? hash.hashCode() : 0); - result = 31 * result + (from != null ? from.hashCode() : 0); - result = 31 * result + (to != null ? to.hashCode() : 0); - result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (timeStamp != null + ? timeStamp.hashCode() + : 0); + result = 31 * result + (hash != null + ? hash.hashCode() + : 0); + result = 31 * result + (from != null + ? from.hashCode() + : 0); + result = 31 * result + (to != null + ? to.hashCode() + : 0); + result = 31 * result + (value != null + ? value.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/Block.java b/src/main/java/io/api/etherscan/model/Block.java index 8853956..f5e8b6a 100644 --- a/src/main/java/io/api/etherscan/model/Block.java +++ b/src/main/java/io/api/etherscan/model/Block.java @@ -2,14 +2,11 @@ import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 28.10.2018 */ @@ -18,7 +15,7 @@ public class Block { private long blockNumber; private BigInteger blockReward; private String timeStamp; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private LocalDateTime _timeStamp; // <editor-fold desc="Getter"> diff --git a/src/main/java/io/api/etherscan/model/EthNetwork.java b/src/main/java/io/api/etherscan/model/EthNetwork.java index f7b91de..6144cf1 100644 --- a/src/main/java/io/api/etherscan/model/EthNetwork.java +++ b/src/main/java/io/api/etherscan/model/EthNetwork.java @@ -1,8 +1,6 @@ package io.api.etherscan.model; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 28.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/api/etherscan/model/Log.java index 67ce96f..595122b 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/api/etherscan/model/Log.java @@ -2,7 +2,6 @@ import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -10,34 +9,32 @@ import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ public class Log { private String blockNumber; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _blockNumber; private String address; private String transactionHash; private String transactionIndex; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _transactionIndex; private String timeStamp; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private LocalDateTime _timeStamp; private String data; private String gasPrice; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private BigInteger _gasPrice; private String gasUsed; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private BigInteger _gasUsed; private List<String> topics; private String logIndex; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _logIndex; // <editor-fold desc="Getters"> @@ -144,11 +141,21 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = blockNumber != null ? blockNumber.hashCode() : 0; - result = 31 * result + (address != null ? address.hashCode() : 0); - result = 31 * result + (transactionHash != null ? transactionHash.hashCode() : 0); - result = 31 * result + (timeStamp != null ? timeStamp.hashCode() : 0); - result = 31 * result + (logIndex != null ? logIndex.hashCode() : 0); + int result = blockNumber != null + ? blockNumber.hashCode() + : 0; + result = 31 * result + (address != null + ? address.hashCode() + : 0); + result = 31 * result + (transactionHash != null + ? transactionHash.hashCode() + : 0); + result = 31 * result + (timeStamp != null + ? timeStamp.hashCode() + : 0); + result = 31 * result + (logIndex != null + ? logIndex.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/Price.java b/src/main/java/io/api/etherscan/model/Price.java index 9bc7dc7..fc72ab5 100644 --- a/src/main/java/io/api/etherscan/model/Price.java +++ b/src/main/java/io/api/etherscan/model/Price.java @@ -1,13 +1,10 @@ package io.api.etherscan.model; import com.google.gson.annotations.Expose; - import java.time.LocalDateTime; import java.time.ZoneOffset; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ @@ -17,9 +14,9 @@ public class Price { private double ethbtc; private String ethusd_timestamp; private String ethbtc_timestamp; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private LocalDateTime _ethusd_timestamp; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private LocalDateTime _ethbtc_timestamp; public double inUsd() { @@ -55,9 +52,13 @@ public boolean equals(Object o) { return false; if (Double.compare(price.ethbtc, ethbtc) != 0) return false; - if (ethusd_timestamp != null ? !ethusd_timestamp.equals(price.ethusd_timestamp) : price.ethusd_timestamp != null) + if (ethusd_timestamp != null + ? !ethusd_timestamp.equals(price.ethusd_timestamp) + : price.ethusd_timestamp != null) return false; - return (ethbtc_timestamp != null ? !ethbtc_timestamp.equals(price.ethbtc_timestamp) : price.ethbtc_timestamp != null); + return (ethbtc_timestamp != null + ? !ethbtc_timestamp.equals(price.ethbtc_timestamp) + : price.ethbtc_timestamp != null); } @Override @@ -68,8 +69,12 @@ public int hashCode() { result = (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(ethbtc); result = 31 * result + (int) (temp ^ (temp >>> 32)); - result = 31 * result + (ethusd_timestamp != null ? ethusd_timestamp.hashCode() : 0); - result = 31 * result + (ethbtc_timestamp != null ? ethbtc_timestamp.hashCode() : 0); + result = 31 * result + (ethusd_timestamp != null + ? ethusd_timestamp.hashCode() + : 0); + result = 31 * result + (ethbtc_timestamp != null + ? ethbtc_timestamp.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/Status.java b/src/main/java/io/api/etherscan/model/Status.java index 9683bde..2017cd7 100644 --- a/src/main/java/io/api/etherscan/model/Status.java +++ b/src/main/java/io/api/etherscan/model/Status.java @@ -41,7 +41,9 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = isError; - result = 31 * result + (errDescription != null ? errDescription.hashCode() : 0); + result = 31 * result + (errDescription != null + ? errDescription.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/Supply.java b/src/main/java/io/api/etherscan/model/Supply.java index 2fd6db7..f495aaf 100644 --- a/src/main/java/io/api/etherscan/model/Supply.java +++ b/src/main/java/io/api/etherscan/model/Supply.java @@ -3,8 +3,6 @@ import java.math.BigInteger; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/TokenBalance.java b/src/main/java/io/api/etherscan/model/TokenBalance.java index d057992..684738c 100644 --- a/src/main/java/io/api/etherscan/model/TokenBalance.java +++ b/src/main/java/io/api/etherscan/model/TokenBalance.java @@ -4,8 +4,6 @@ import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ @@ -38,7 +36,9 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (tokenContract != null ? tokenContract.hashCode() : 0); + result = 31 * result + (tokenContract != null + ? tokenContract.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/Tx.java b/src/main/java/io/api/etherscan/model/Tx.java index 4136d23..13b5292 100644 --- a/src/main/java/io/api/etherscan/model/Tx.java +++ b/src/main/java/io/api/etherscan/model/Tx.java @@ -1,13 +1,10 @@ package io.api.etherscan.model; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 28.10.2018 */ @@ -80,9 +77,13 @@ public boolean equals(Object o) { public int hashCode() { int result = super.hashCode(); result = 31 * result + (int) (nonce ^ (nonce >>> 32)); - result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0); + result = 31 * result + (blockHash != null + ? blockHash.hashCode() + : 0); result = 31 * result + transactionIndex; - result = 31 * result + (isError != null ? isError.hashCode() : 0); + result = 31 * result + (isError != null + ? isError.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/TxInternal.java b/src/main/java/io/api/etherscan/model/TxInternal.java index 5048947..5471268 100644 --- a/src/main/java/io/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/api/etherscan/model/TxInternal.java @@ -3,8 +3,6 @@ import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ @@ -21,7 +19,9 @@ public String getType() { } public long getTraceId() { - return (traceId == null) ? 0 : Long.parseLong(traceId); + return (traceId == null) + ? 0 + : Long.parseLong(traceId); } public String getTraceIdAsString() { @@ -56,8 +56,12 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = super.hashCode(); - result = 31 * result + (traceId != null ? traceId.hashCode() : 0); - result = 31 * result + (errCode != null ? errCode.hashCode() : 0); + result = 31 * result + (traceId != null + ? traceId.hashCode() + : 0); + result = 31 * result + (errCode != null + ? errCode.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/TxToken.java b/src/main/java/io/api/etherscan/model/TxToken.java index 8f5e36f..c455ffb 100644 --- a/src/main/java/io/api/etherscan/model/TxToken.java +++ b/src/main/java/io/api/etherscan/model/TxToken.java @@ -1,8 +1,6 @@ package io.api.etherscan.model; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 28.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/Uncle.java b/src/main/java/io/api/etherscan/model/Uncle.java index 2ee206b..7dea648 100644 --- a/src/main/java/io/api/etherscan/model/Uncle.java +++ b/src/main/java/io/api/etherscan/model/Uncle.java @@ -3,8 +3,6 @@ import java.math.BigInteger; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ @@ -39,15 +37,23 @@ public boolean equals(Object o) { if (unclePosition != uncle.unclePosition) return false; - if (miner != null ? !miner.equals(uncle.miner) : uncle.miner != null) + if (miner != null + ? !miner.equals(uncle.miner) + : uncle.miner != null) return false; - return blockreward != null ? blockreward.equals(uncle.blockreward) : uncle.blockreward == null; + return blockreward != null + ? blockreward.equals(uncle.blockreward) + : uncle.blockreward == null; } @Override public int hashCode() { - int result = miner != null ? miner.hashCode() : 0; - result = 31 * result + (blockreward != null ? blockreward.hashCode() : 0); + int result = miner != null + ? miner.hashCode() + : 0; + result = 31 * result + (blockreward != null + ? blockreward.hashCode() + : 0); result = 31 * result + unclePosition; return result; } diff --git a/src/main/java/io/api/etherscan/model/UncleBlock.java b/src/main/java/io/api/etherscan/model/UncleBlock.java index 88c975d..ff30451 100644 --- a/src/main/java/io/api/etherscan/model/UncleBlock.java +++ b/src/main/java/io/api/etherscan/model/UncleBlock.java @@ -1,12 +1,9 @@ package io.api.etherscan.model; import io.api.etherscan.util.BasicUtils; - import java.util.List; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/Wei.java b/src/main/java/io/api/etherscan/model/Wei.java index eddf8d2..0735d90 100644 --- a/src/main/java/io/api/etherscan/model/Wei.java +++ b/src/main/java/io/api/etherscan/model/Wei.java @@ -4,8 +4,6 @@ import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ @@ -52,7 +50,9 @@ public boolean equals(Object o) { @Override public int hashCode() { - return result != null ? result.hashCode() : 0; + return result != null + ? result.hashCode() + : 0; } @Override diff --git a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java b/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java index 63821c0..2afbe40 100644 --- a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java @@ -2,33 +2,30 @@ import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ public class BlockProxy { private String number; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _number; private String hash; private String parentHash; private String stateRoot; private String size; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _size; private String difficulty; private String totalDifficulty; private String timestamp; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private LocalDateTime _timestamp; private String miner; @@ -37,10 +34,10 @@ public class BlockProxy { private String logsBloom; private String mixHash; private String gasUsed; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private BigInteger _gasUsed; private String gasLimit; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private BigInteger _gasLimit; private String sha3Uncles; @@ -151,18 +148,30 @@ public boolean equals(Object o) { BlockProxy that = (BlockProxy) o; - if (number != null ? !number.equals(that.number) : that.number != null) + if (number != null + ? !number.equals(that.number) + : that.number != null) return false; - if (hash != null ? !hash.equals(that.hash) : that.hash != null) + if (hash != null + ? !hash.equals(that.hash) + : that.hash != null) return false; - return parentHash != null ? parentHash.equals(that.parentHash) : that.parentHash == null; + return parentHash != null + ? parentHash.equals(that.parentHash) + : that.parentHash == null; } @Override public int hashCode() { - int result = number != null ? number.hashCode() : 0; - result = 31 * result + (hash != null ? hash.hashCode() : 0); - result = 31 * result + (parentHash != null ? parentHash.hashCode() : 0); + int result = number != null + ? number.hashCode() + : 0; + result = 31 * result + (hash != null + ? hash.hashCode() + : 0); + result = 31 * result + (parentHash != null + ? parentHash.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java b/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java index f40cb59..1e25dbd 100644 --- a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java @@ -3,13 +3,10 @@ import com.google.gson.annotations.Expose; import io.api.etherscan.model.Log; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.util.List; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ @@ -104,18 +101,30 @@ public boolean equals(Object o) { ReceiptProxy that = (ReceiptProxy) o; - if (blockNumber != null ? !blockNumber.equals(that.blockNumber) : that.blockNumber != null) + if (blockNumber != null + ? !blockNumber.equals(that.blockNumber) + : that.blockNumber != null) return false; - if (transactionHash != null ? !transactionHash.equals(that.transactionHash) : that.transactionHash != null) + if (transactionHash != null + ? !transactionHash.equals(that.transactionHash) + : that.transactionHash != null) return false; - return transactionIndex != null ? transactionIndex.equals(that.transactionIndex) : that.transactionIndex == null; + return transactionIndex != null + ? transactionIndex.equals(that.transactionIndex) + : that.transactionIndex == null; } @Override public int hashCode() { - int result = blockNumber != null ? blockNumber.hashCode() : 0; - result = 31 * result + (transactionHash != null ? transactionHash.hashCode() : 0); - result = 31 * result + (transactionIndex != null ? transactionIndex.hashCode() : 0); + int result = blockNumber != null + ? blockNumber.hashCode() + : 0; + result = 31 * result + (transactionHash != null + ? transactionHash.hashCode() + : 0); + result = 31 * result + (transactionIndex != null + ? transactionIndex.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java b/src/main/java/io/api/etherscan/model/proxy/TxProxy.java index 5c7b5c8..a89f4a8 100644 --- a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java +++ b/src/main/java/io/api/etherscan/model/proxy/TxProxy.java @@ -2,12 +2,9 @@ import com.google.gson.annotations.Expose; import io.api.etherscan.util.BasicUtils; - import java.math.BigInteger; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ @@ -16,7 +13,7 @@ public class TxProxy { private String to; private String hash; private String transactionIndex; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _transactionIndex; private String from; private String v; @@ -24,18 +21,18 @@ public class TxProxy { private String s; private String r; private String nonce; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _nonce; private String value; private String gas; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private BigInteger _gas; private String gasPrice; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private BigInteger _gasPrice; private String blockHash; private String blockNumber; - @Expose(serialize = false, deserialize = false) + @Expose(deserialize = false, serialize = false) private Long _blockNumber; // <editor-fold desc="Getters"> @@ -115,18 +112,30 @@ public boolean equals(Object o) { TxProxy txProxy = (TxProxy) o; - if (hash != null ? !hash.equals(txProxy.hash) : txProxy.hash != null) + if (hash != null + ? !hash.equals(txProxy.hash) + : txProxy.hash != null) return false; - if (blockHash != null ? !blockHash.equals(txProxy.blockHash) : txProxy.blockHash != null) + if (blockHash != null + ? !blockHash.equals(txProxy.blockHash) + : txProxy.blockHash != null) return false; - return blockNumber != null ? blockNumber.equals(txProxy.blockNumber) : txProxy.blockNumber == null; + return blockNumber != null + ? blockNumber.equals(txProxy.blockNumber) + : txProxy.blockNumber == null; } @Override public int hashCode() { - int result = hash != null ? hash.hashCode() : 0; - result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0); - result = 31 * result + (blockNumber != null ? blockNumber.hashCode() : 0); + int result = hash != null + ? hash.hashCode() + : 0; + result = 31 * result + (blockHash != null + ? blockHash.hashCode() + : 0); + result = 31 * result + (blockNumber != null + ? blockNumber.hashCode() + : 0); return result; } diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java b/src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java index 52c886f..0291dfe 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java +++ b/src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.proxy.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java b/src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java index eb9d941..2057c89 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java +++ b/src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.proxy.BlockProxy; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 01.11.2018 */ diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java b/src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java index 57d2c07..a3bc435 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java +++ b/src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.proxy.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java b/src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java index 90cd7c8..8d1d08c 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java +++ b/src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.proxy.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java b/src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java index c709f76..3bbe039 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java +++ b/src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.proxy.ReceiptProxy; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java b/src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java index 4140a62..7e9c9e8 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java +++ b/src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.proxy.TxProxy; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 01.11.2018 */ diff --git a/src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java b/src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java index 2fc688a..c472f84 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java +++ b/src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java @@ -7,7 +7,6 @@ * * @see LogQueryBuilder * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java b/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java index 3ba6c4f..31d8c13 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java @@ -4,12 +4,10 @@ /** * Final builded container for The Event Log API - * * EtherScan - API Descriptions https://etherscan.io/apis#logs * * @see LogQueryBuilder * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java index bd8a9fc..44ca825 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java @@ -9,7 +9,6 @@ * Builder for The Event Log API * * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java b/src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java index 1c2bf35..bab5b29 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java @@ -10,7 +10,6 @@ * * @see LogQueryBuilder * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ @@ -22,8 +21,13 @@ public class LogTopicQuadro extends BaseLogQuery implements IQueryBuilder { private LogOp topic0_1_opr, topic1_2_opr, topic2_3_opr, topic0_2_opr, topic0_3_opr, topic1_3_opr; - LogTopicQuadro(String address, long startBlock, long endBlock, - String topic0, String topic1, String topic2, String topic3) { + LogTopicQuadro(String address, + long startBlock, + long endBlock, + String topic0, + String topic1, + String topic2, + String topic3) { this.address = address; this.startBlock = startBlock; this.endBlock = endBlock; diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java b/src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java index 2c19d61..83199d9 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java @@ -9,7 +9,6 @@ * * @see LogQueryBuilder * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java b/src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java index aa54740..cc9a6ba 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java @@ -10,7 +10,6 @@ * * @see LogQueryBuilder * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ @@ -22,8 +21,12 @@ public class LogTopicTriple extends BaseLogQuery implements IQueryBuilder { private LogOp topic0_1_opr, topic1_2_opr, topic0_2_opr; - LogTopicTriple(String address, long startBlock, long endBlock, - String topic0, String topic1, String topic2) { + LogTopicTriple(String address, + long startBlock, + long endBlock, + String topic0, + String topic1, + String topic2) { this.address = address; this.startBlock = startBlock; this.endBlock = endBlock; diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java b/src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java index 8f069f1..4524a8a 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java +++ b/src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java @@ -10,7 +10,6 @@ * * @see LogQueryBuilder * @see ILogsApi - * * @author GoodforGod * @since 31.10.2018 */ @@ -22,8 +21,11 @@ public class LogTopicTuple extends BaseLogQuery implements IQueryBuilder { private LogOp topic0_1_opr; - LogTopicTuple(String address, long startBlock, long endBlock, - String topic0, String topic1) { + LogTopicTuple(String address, + long startBlock, + long endBlock, + String topic0, + String topic1) { this.address = address; this.startBlock = startBlock; this.endBlock = endBlock; diff --git a/src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java b/src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java index 6b23de4..f7c2985 100644 --- a/src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/BalanceTO.java b/src/main/java/io/api/etherscan/model/utility/BalanceTO.java index 8d9d9b7..3956cec 100644 --- a/src/main/java/io/api/etherscan/model/utility/BalanceTO.java +++ b/src/main/java/io/api/etherscan/model/utility/BalanceTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java b/src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java index 28f01f3..916739e 100644 --- a/src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java @@ -3,8 +3,6 @@ import java.util.List; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java b/src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java index d3653e2..9679ebb 100644 --- a/src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.util.BasicUtils; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ @@ -14,7 +12,9 @@ public abstract class BaseResponseTO { private String message; public int getStatus() { - return BasicUtils.isEmpty(status) ? -1 : Integer.parseInt(status); + return BasicUtils.isEmpty(status) + ? -1 + : Integer.parseInt(status); } public String getMessage() { diff --git a/src/main/java/io/api/etherscan/model/utility/BlockParam.java b/src/main/java/io/api/etherscan/model/utility/BlockParam.java index 0f027ec..7e11a00 100644 --- a/src/main/java/io/api/etherscan/model/utility/BlockParam.java +++ b/src/main/java/io/api/etherscan/model/utility/BlockParam.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java b/src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java index 0d63184..8a89321 100644 --- a/src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.Block; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/LogResponseTO.java b/src/main/java/io/api/etherscan/model/utility/LogResponseTO.java index bba1c24..a060bd3 100644 --- a/src/main/java/io/api/etherscan/model/utility/LogResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/LogResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.Log; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 31.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java b/src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java index 3179a73..9af743b 100644 --- a/src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.Price; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java b/src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java index 87e3950..a5f9577 100644 --- a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java b/src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java index 6b7995d..c4c63af 100644 --- a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java +++ b/src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java b/src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java index bc10eb7..7532aba 100644 --- a/src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.Status; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/StringResponseTO.java b/src/main/java/io/api/etherscan/model/utility/StringResponseTO.java index 38d3c86..582087a 100644 --- a/src/main/java/io/api/etherscan/model/utility/StringResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/StringResponseTO.java @@ -1,8 +1,6 @@ package io.api.etherscan.model.utility; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java b/src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java index d38a879..5f0e400 100644 --- a/src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.TxInternal; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/TxResponseTO.java b/src/main/java/io/api/etherscan/model/utility/TxResponseTO.java index 53cce38..1fa6b16 100644 --- a/src/main/java/io/api/etherscan/model/utility/TxResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/TxResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.Tx; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java b/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java index 5ac2aec..1cbd4e3 100644 --- a/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.TxToken; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 29.10.2018 */ diff --git a/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java b/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java index f4f4349..f8e4c5e 100644 --- a/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java +++ b/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java @@ -3,8 +3,6 @@ import io.api.etherscan.model.UncleBlock; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 30.10.2018 */ diff --git a/src/main/java/io/api/etherscan/util/BasicUtils.java b/src/main/java/io/api/etherscan/util/BasicUtils.java index 96b855d..d748abf 100644 --- a/src/main/java/io/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/api/etherscan/util/BasicUtils.java @@ -5,11 +5,10 @@ import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.utility.BaseResponseTO; import io.api.etherscan.model.utility.BlockParam; -import org.jetbrains.annotations.NotNull; - import java.math.BigInteger; import java.util.*; import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; /** * Basic utils for library diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java index 184a84e..e78ea6d 100644 --- a/src/test/java/io/api/ApiRunner.java +++ b/src/test/java/io/api/ApiRunner.java @@ -3,10 +3,10 @@ import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.EthNetwork; -import org.junit.AfterClass; -import org.junit.Assert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; -public class ApiRunner extends Assert { +public class ApiRunner extends Assertions { private static final EtherScanApi api; private static final EtherScanApi apiRopsten; @@ -50,7 +50,7 @@ public static EtherScanApi getApiKovan() { return apiKovan; } - @AfterClass + @AfterAll public static void cleanup() throws Exception { api.close(); apiRopsten.close(); diff --git a/src/test/java/io/api/etherscan/EtherScanApiTest.java b/src/test/java/io/api/etherscan/EtherScanApiTest.java index be49435..b649302 100644 --- a/src/test/java/io/api/etherscan/EtherScanApiTest.java +++ b/src/test/java/io/api/etherscan/EtherScanApiTest.java @@ -8,47 +8,43 @@ import io.api.etherscan.executor.IHttpExecutor; import io.api.etherscan.executor.impl.HttpExecutor; import io.api.etherscan.model.Balance; -import io.api.etherscan.model.Block; import io.api.etherscan.model.EthNetwork; -import org.junit.Test; - -import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import org.junit.jupiter.api.Test; /** * @author GoodforGod * @since 05.11.2018 */ -public class EtherScanApiTest extends ApiRunner { +class EtherScanApiTest extends ApiRunner { private final EthNetwork network = EthNetwork.KOVAN; private final String validKey = "YourKey"; @Test - public void validKey() { + void validKey() { EtherScanApi api = new EtherScanApi(validKey, network); assertNotNull(api); } - @Test(expected = ApiKeyException.class) - public void emptyKey() { - new EtherScanApi(""); + @Test + void emptyKey() { + assertThrows(ApiKeyException.class, () -> new EtherScanApi("")); } - @Test(expected = ApiKeyException.class) - public void blankKey() { - new EtherScanApi(" ", network); + @Test + void blankKey() { + assertThrows(ApiKeyException.class, () -> new EtherScanApi(" ", network)); } - @Test(expected = ApiException.class) - public void nullNetwork() { - EtherScanApi api = new EtherScanApi(validKey, null); - assertNotNull(api); + @Test + void nullNetwork() { + assertThrows(ApiException.class, () -> new EtherScanApi(validKey, null)); } @Test - public void noTimeoutOnRead() { + void noTimeoutOnRead() { Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(300); EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET, supplier); Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); @@ -56,31 +52,30 @@ public void noTimeoutOnRead() { } @Test - public void noTimeoutOnReadGroli() { + void noTimeoutOnReadGroli() { Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(300); Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @Test - public void noTimeoutOnReadTobalala() { + void noTimeoutOnReadTobalala() { Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(30000); Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @Test - public void noTimeoutUnlimitedAwait() { + void noTimeoutUnlimitedAwait() { Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } - @Test(expected = ApiTimeoutException.class) - public void timeout() throws InterruptedException { + @Test + void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(300, 300); EtherScanApi api = new EtherScanApi(getApiKey(), EthNetwork.KOVAN, supplier); - List<Block> blocks = api.account().minedBlocks("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D"); - assertNotNull(blocks); + assertThrows(ApiTimeoutException.class, () -> api.account().minedBlocks("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D")); } } diff --git a/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java b/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java index fdeb1e9..6864175 100644 --- a/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java +++ b/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java @@ -4,22 +4,19 @@ import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Balance; import io.api.support.AddressUtil; -import org.junit.Test; - import java.math.BigInteger; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class AccountBalanceListTest extends ApiRunner { +class AccountBalanceListTest extends ApiRunner { @Test - public void correct() { + void correct() { List<String> addresses = new ArrayList<>(); addresses.add("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("0xC9F32CE1127e44C51cbD182D6364F3D707Fd0d47"); @@ -44,7 +41,7 @@ public void correct() { } @Test - public void correctMoreThat20Addresses() { + void correctMoreThat20Addresses() { List<String> addresses = AddressUtil.genRealAddresses(); List<Balance> balances = getApi().account().balances(addresses); @@ -58,17 +55,17 @@ public void correctMoreThat20Addresses() { assertNotEquals(balances.get(0), balances.get(1)); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { + @Test + void invalidParamWithError() { List<String> addresses = new ArrayList<>(); addresses.add("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("C9F32CE1127e44C51cbD182D6364F3D707Fd0d47"); - getApi().account().balances(addresses); + assertThrows(InvalidAddressException.class, () -> getApi().account().balances(addresses)); } @Test - public void emptyParamList() { + void emptyParamList() { List<String> addresses = new ArrayList<>(); List<Balance> balances = getApi().account().balances(addresses); assertNotNull(balances); @@ -76,7 +73,7 @@ public void emptyParamList() { } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { List<String> addresses = new ArrayList<>(); addresses.add("0x1327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("0xC1F32CE1127e44C51cbD182D6364F3D707Fd0d47"); diff --git a/src/test/java/io/api/etherscan/account/AccountBalanceTest.java b/src/test/java/io/api/etherscan/account/AccountBalanceTest.java index 76aca68..d5427ab 100644 --- a/src/test/java/io/api/etherscan/account/AccountBalanceTest.java +++ b/src/test/java/io/api/etherscan/account/AccountBalanceTest.java @@ -4,50 +4,19 @@ import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Balance; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -@RunWith(Parameterized.class) -public class AccountBalanceTest extends ApiRunner { - - private final EtherScanApi api; - private final String addressCorrect; - private final String addressInvalid; - private final String addressNoResponse; - - public AccountBalanceTest(EtherScanApi api, String addressCorrect, String addressInvalid, String addressNoResponse) { - this.api = api; - this.addressCorrect = addressCorrect; - this.addressInvalid = addressInvalid; - this.addressNoResponse = addressNoResponse; - } +class AccountBalanceTest extends ApiRunner { - @Parameters - public static Collection data() { - return Arrays.asList(new Object[][] { - { - getApi(), - "0x8d4426f94e42f721C7116E81d6688cd935cB3b4F", - "8d4426f94e42f721C7116E81d6688cd935cB3b4F", - "0x1d4426f94e42f721C7116E81d6688cd935cB3b4F" - } - }); - } + private final EtherScanApi api = getApi(); @Test - public void correct() { - Balance balance = api.account().balance(addressCorrect); + void correct() { + Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3b4F"); assertNotNull(balance); assertNotNull(balance.getWei()); assertNotNull(balance.getMwei()); @@ -58,14 +27,14 @@ public void correct() { assertNotNull(balance.toString()); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - Balance balance = getApi().account().balance(addressInvalid); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, () -> getApi().account().balance("8d4426f94e42f721C7116E81d6688cd935cB3b4F")); } @Test - public void correctParamWithEmptyExpectedResult() { - Balance balance = api.account().balance(addressNoResponse); + void correctParamWithEmptyExpectedResult() { + Balance balance = api.account().balance("0x1d4426f94e42f721C7116E81d6688cd935cB3b4F"); assertNotNull(balance); assertNotNull(balance.getWei()); assertNotNull(balance.getAddress()); diff --git a/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java b/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java index 3a46858..ae16174 100644 --- a/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java +++ b/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java @@ -4,61 +4,23 @@ import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Block; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; import java.util.List; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -@RunWith(Parameterized.class) -public class AccountMinedBlocksTest extends ApiRunner { - - private final EtherScanApi api; - private final int blocksMined; - private final String addressCorrect; - private final String addressInvalid; - private final String addressNoResponse; - - public AccountMinedBlocksTest(EtherScanApi api, - int blocksMined, - String addressCorrect, - String addressInvalid, - String addressNoResponse) { - this.api = api; - this.blocksMined = blocksMined; - this.addressCorrect = addressCorrect; - this.addressInvalid = addressInvalid; - this.addressNoResponse = addressNoResponse; - } +class AccountMinedBlocksTest extends ApiRunner { - @Parameters - public static Collection data() { - return Arrays.asList(new Object[][] { - { - getApi(), - 223, - "0xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23", - "xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23", - "0xE1C6175183029A0f039bf2DFffa5C6e8F3cA9B23", - } - }); - } + private final EtherScanApi api = getApi(); @Test - public void correct() { - List<Block> blocks = api.account().minedBlocks(addressCorrect); + void correct() { + List<Block> blocks = api.account().minedBlocks("0xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23"); assertNotNull(blocks); - assertEquals(blocksMined, blocks.size()); + assertEquals(223, blocks.size()); assertBlocks(blocks); assertNotNull(blocks.get(0).toString()); @@ -68,14 +30,15 @@ public void correct() { } } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - List<Block> txs = getApi().account().minedBlocks(addressInvalid); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().account().minedBlocks("xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23")); } @Test - public void correctParamWithEmptyExpectedResult() { - List<Block> txs = api.account().minedBlocks(addressNoResponse); + void correctParamWithEmptyExpectedResult() { + List<Block> txs = api.account().minedBlocks("0xE1C6175183029A0f039bf2DFffa5C6e8F3cA9B23"); assertNotNull(txs); assertTrue(txs.isEmpty()); } diff --git a/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java b/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java index 2794e95..b8b8146 100644 --- a/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java @@ -3,63 +3,21 @@ import io.api.ApiRunner; import io.api.etherscan.core.impl.EtherScanApi; import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.Balance; import io.api.etherscan.model.TokenBalance; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -@RunWith(Parameterized.class) -public class AccountTokenBalanceTest extends ApiRunner { - - private final EtherScanApi api; - private final String contractValid; - private final String addressValid; - private final String contractInvalid; - private final String addressInvalid; - private final String addressEmpty; +class AccountTokenBalanceTest extends ApiRunner { - public AccountTokenBalanceTest(EtherScanApi api, - String contractValid, - String addressValid, - String contractInvalid, - String addressInvalid, - String addressEmpty) { - this.api = api; - this.contractValid = contractValid; - this.addressValid = addressValid; - this.contractInvalid = contractInvalid; - this.addressInvalid = addressInvalid; - this.addressEmpty = addressEmpty; - } - - @Parameters - public static Collection data() { - return Arrays.asList(new Object[][] { - { - getApi(), - "0x5EaC95ad5b287cF44E058dCf694419333b796123", - "0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", - "0xEaC95ad5b287cF44E058dCf694419333b796123", - "0x5807e7F124EC2103a59c5249187f772c0b8D6b2", - "0x1d807e7F124EC2103a59c5249187f772c0b8D6b2", - } - }); - } + private final EtherScanApi api = getApi(); @Test - public void correct() { - TokenBalance balance = api.account().balance(addressValid, contractValid); + void correct() { + TokenBalance balance = api.account().balance("0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", + "0x5EaC95ad5b287cF44E058dCf694419333b796123"); assertNotNull(balance); assertNotNull(balance.getWei()); assertNotNull(balance.getAddress()); @@ -71,19 +29,22 @@ public void correct() { assertNotEquals(balance.hashCode(), balance2.hashCode()); } - @Test(expected = InvalidAddressException.class) - public void invalidAddressParamWithError() { - Balance balance = api.account().balance(addressInvalid, contractValid); + @Test + void invalidAddressParamWithError() { + assertThrows(InvalidAddressException.class, () -> api.account().balance("0x5807e7F124EC2103a59c5249187f772c0b8D6b2", + "0x5EaC95ad5b287cF44E058dCf694419333b796123")); } - @Test(expected = InvalidAddressException.class) - public void invalidContractParamWithError() { - Balance balance = api.account().balance(addressValid, contractInvalid); + @Test + void invalidContractParamWithError() { + assertThrows(InvalidAddressException.class, () -> api.account().balance("0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", + "0xEaC95ad5b287cF44E058dCf694419333b796123")); } @Test - public void correctParamWithEmptyExpectedResult() { - TokenBalance balance = api.account().balance(addressEmpty, contractValid); + void correctParamWithEmptyExpectedResult() { + TokenBalance balance = api.account().balance("0x1d807e7F124EC2103a59c5249187f772c0b8D6b2", + "0x5EaC95ad5b287cF44E058dCf694419333b796123"); assertNotNull(balance); assertNotNull(balance.getWei()); assertNotNull(balance.getAddress()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java index 126fd90..4e63dbc 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java @@ -5,56 +5,23 @@ import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.TxInternal; import io.api.etherscan.util.BasicUtils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; import java.util.List; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -@RunWith(Parameterized.class) -public class AccountTxInternalByHashTest extends ApiRunner { - - private final EtherScanApi api; - private final int txAmount; - private final String validTx; - private final String invalidTx; - private final String emptyTx; - - public AccountTxInternalByHashTest(EtherScanApi api, int txAmount, String validTx, String invalidTx, String emptyTx) { - this.api = api; - this.txAmount = txAmount; - this.validTx = validTx; - this.invalidTx = invalidTx; - this.emptyTx = emptyTx; - } +class AccountTxInternalByHashTest extends ApiRunner { - @Parameters - public static Collection data() { - return Arrays.asList(new Object[][] { - { - getApi(), - 1, - "0x1b513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b", - "0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b", - "0x2b513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b", - } - }); - } + private final EtherScanApi api = getApi(); @Test - public void correct() { - List<TxInternal> txs = api.account().txsInternalByHash(validTx); + void correct() { + List<TxInternal> txs = api.account() + .txsInternalByHash("0x1b513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b"); assertNotNull(txs); - assertEquals(txAmount, txs.size()); + assertEquals(1, txs.size()); assertTxs(txs); assertNotNull(txs.get(0).getFrom()); assertNotNull(txs.get(0).getTimeStamp()); @@ -73,14 +40,16 @@ public void correct() { } } - @Test(expected = InvalidTxHashException.class) - public void invalidParamWithError() { - List<TxInternal> txs = api.account().txsInternalByHash(invalidTx); + @Test + void invalidParamWithError() { + assertThrows(InvalidTxHashException.class, + () -> api.account().txsInternalByHash("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b")); } @Test - public void correctParamWithEmptyExpectedResult() { - List<TxInternal> txs = api.account().txsInternalByHash(emptyTx); + void correctParamWithEmptyExpectedResult() { + List<TxInternal> txs = api.account() + .txsInternalByHash("0x2b513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b"); assertNotNull(txs); assertTrue(txs.isEmpty()); } diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java b/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java index 47f3e61..7144671 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java @@ -3,20 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.TxInternal; -import org.junit.Test; - import java.util.List; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class AccountTxInternalTest extends ApiRunner { +class AccountTxInternalTest extends ApiRunner { @Test - public void correct() { + void correct() { List<TxInternal> txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3"); assertNotNull(txs); assertEquals(66, txs.size()); @@ -25,7 +22,7 @@ public void correct() { } @Test - public void correctStartBlock() { + void correctStartBlock() { List<TxInternal> txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775); assertNotNull(txs); assertEquals(24, txs.size()); @@ -35,20 +32,21 @@ public void correctStartBlock() { } @Test - public void correctStartBlockEndBlock() { + void correctStartBlockEndBlock() { List<TxInternal> txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51A3", 2558775, 2685504); assertNotNull(txs); assertEquals(21, txs.size()); assertTxs(txs); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - List<TxInternal> txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { List<TxInternal> txs = getApi().account().txsInternal("0x2C1ba59D6F58433FB2EaEe7d20b26Ed83bDA51A3"); assertNotNull(txs); assertTrue(txs.isEmpty()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java b/src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java index 0afa12f..6601d1a 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java @@ -3,18 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.TxToken; -import org.junit.Test; - import java.util.List; +import org.junit.jupiter.api.Test; /** * @author NGuggs * @since 11.28.2021 */ -public class AccountTxRc721TokenTest extends ApiRunner { +class AccountTxRc721TokenTest extends ApiRunner { @Test - public void correct() { + void correct() { List<TxToken> txs = getApi().account().txsNftToken("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67"); assertNotNull(txs); assertEquals(16, txs.size()); @@ -33,7 +32,7 @@ public void correct() { } @Test - public void correctStartBlock() { + void correctStartBlock() { List<TxToken> txs = getApi().account().txsNftToken("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4762071); System.out.println(txs); assertNotNull(txs); @@ -42,7 +41,7 @@ public void correctStartBlock() { } @Test - public void correctStartBlockEndBlock() { + void correctStartBlockEndBlock() { List<TxToken> txs = getApi().account().txsNftToken("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4761862, 4761934); System.out.println(txs); assertNotNull(txs); @@ -50,13 +49,14 @@ public void correctStartBlockEndBlock() { assertTxs(txs); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - getApi().account().txsNftToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().account().txsNftToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { List<TxToken> txs = getApi().account().txsNftToken("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java b/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java index b82d4d1..044991b 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java @@ -3,20 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.TxToken; -import org.junit.Test; - import java.util.List; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class AccountTxTokenTest extends ApiRunner { +class AccountTxTokenTest extends ApiRunner { @Test - public void correct() { + void correct() { List<TxToken> txs = getApi().account().txsToken("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); assertNotNull(txs); assertEquals(3, txs.size()); @@ -35,7 +32,7 @@ public void correct() { } @Test - public void correctStartBlock() { + void correctStartBlock() { List<TxToken> txs = getApi().account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); assertNotNull(txs); assertEquals(11, txs.size()); @@ -43,20 +40,21 @@ public void correctStartBlock() { } @Test - public void correctStartBlockEndBlock() { + void correctStartBlockEndBlock() { List<TxToken> txs = getApi().account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); assertNotNull(txs); assertEquals(5, txs.size()); assertTxs(txs); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - getApi().account().txsToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().account().txsToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { List<TxToken> txs = getApi().account().txsToken("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxsTest.java b/src/test/java/io/api/etherscan/account/AccountTxsTest.java index 66a95e4..899d0fb 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxsTest.java +++ b/src/test/java/io/api/etherscan/account/AccountTxsTest.java @@ -3,20 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Tx; -import org.junit.Test; - import java.util.List; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class AccountTxsTest extends ApiRunner { +class AccountTxsTest extends ApiRunner { @Test - public void correct() { + void correct() { List<Tx> txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); assertNotNull(txs); assertEquals(5, txs.size()); @@ -39,7 +36,7 @@ public void correct() { } @Test - public void correctStartBlock() { + void correctStartBlock() { List<Tx> txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842); assertNotNull(txs); assertEquals(4, txs.size()); @@ -47,21 +44,21 @@ public void correctStartBlock() { } @Test - public void correctStartBlockEndBlock() { + void correctStartBlockEndBlock() { List<Tx> txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842, 3945741); assertNotNull(txs); assertEquals(3, txs.size()); assertTxs(txs); - assertFalse(txs.get(0).equals(txs.get(1))); + assertNotEquals(txs.get(0), txs.get(1)); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - List<Tx> txs = getApi().account().txs("9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, () -> getApi().account().txs("9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { List<Tx> txs = getApi().account().txs("0x9321cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); assertNotNull(txs); assertTrue(txs.isEmpty()); diff --git a/src/test/java/io/api/etherscan/block/BlockApiTest.java b/src/test/java/io/api/etherscan/block/BlockApiTest.java index 34b9de5..cee8bb9 100644 --- a/src/test/java/io/api/etherscan/block/BlockApiTest.java +++ b/src/test/java/io/api/etherscan/block/BlockApiTest.java @@ -2,20 +2,17 @@ import io.api.ApiRunner; import io.api.etherscan.model.UncleBlock; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class BlockApiTest extends ApiRunner { +class BlockApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<UncleBlock> uncle = getApi().block().uncles(2165403); assertTrue(uncle.isPresent()); assertFalse(uncle.get().isEmpty()); @@ -46,14 +43,14 @@ public void correct() { } @Test - public void correctNoUncles() { + void correctNoUncles() { Optional<UncleBlock> uncles = getApi().block().uncles(34); assertTrue(uncles.isPresent()); assertTrue(uncles.get().getUncles().isEmpty()); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<UncleBlock> uncles = getApi().block().uncles(99999999934L); assertFalse(uncles.isPresent()); } diff --git a/src/test/java/io/api/etherscan/contract/ContractApiTest.java b/src/test/java/io/api/etherscan/contract/ContractApiTest.java index 6b4d7d8..85fb905 100644 --- a/src/test/java/io/api/etherscan/contract/ContractApiTest.java +++ b/src/test/java/io/api/etherscan/contract/ContractApiTest.java @@ -3,18 +3,16 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.model.Abi; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ContractApiTest extends ApiRunner { +class ContractApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Abi abi = getApi().contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); assertNotNull(abi); assertTrue(abi.isVerified()); @@ -27,13 +25,14 @@ public void correct() { assertNotEquals(empty.hashCode(), abi.hashCode()); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - getApi().contract().contractAbi("0xBBbc244D798123fDe783fCc1C72d3Bb8C189413"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().contract().contractAbi("0xBBbc244D798123fDe783fCc1C72d3Bb8C189413")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Abi abi = getApi().contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); assertNotNull(abi); assertTrue(abi.isVerified()); diff --git a/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java b/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java index 85b35e8..f956364 100644 --- a/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java +++ b/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java @@ -7,18 +7,16 @@ import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.query.impl.LogQueryBuilder; import io.api.etherscan.model.query.impl.LogTopicQuadro; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class LogQueryBuilderTest extends ApiRunner { +class LogQueryBuilderTest extends ApiRunner { @Test - public void singleCorrect() { + void singleCorrect() { LogQuery single = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); @@ -27,28 +25,22 @@ public void singleCorrect() { assertNotNull(single.getParams()); } - @Test(expected = InvalidAddressException.class) - public void singleInCorrectAddress() { - LogQuery single = LogQueryBuilder.with("033990122638b9132ca29c723bdf037f1a891a70c") + @Test + void singleInCorrectAddress() { + assertThrows(InvalidAddressException.class, () -> LogQueryBuilder.with("033990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") - .build(); - - assertNotNull(single); - assertNotNull(single.getParams()); + .build()); } - @Test(expected = LogQueryException.class) - public void singleInCorrectTopic() { - LogQuery single = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void singleInCorrectTopic() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("6516=") - .build(); - - assertNotNull(single); - assertNotNull(single.getParams()); + .build()); } @Test - public void tupleCorrect() { + void tupleCorrect() { LogQuery tuple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224) .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000") @@ -59,20 +51,17 @@ public void tupleCorrect() { assertNotNull(tuple.getParams()); } - @Test(expected = LogQueryException.class) - public void tupleInCorrectOp() { - LogQuery tuple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224) + @Test + void tupleInCorrectOp() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224) .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(null) - .build(); - - assertNotNull(tuple); - assertNotNull(tuple.getParams()); + .build()); } @Test - public void tripleCorrect() { + void tripleCorrect() { LogQuery triple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -86,68 +75,60 @@ public void tripleCorrect() { assertNotNull(triple.getParams()); } - @Test(expected = LogQueryException.class) - public void tripleInCorrectOp() { - LogQuery triple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", - "0x72657075746174696f6e00000000000000000000000000000000000000000000", - "0x72657075746174696f6e00000000000000000000000000000000000000000000") - .setOpTopic0_1(LogOp.AND) - .setOpTopic0_2(null) - .setOpTopic1_2(LogOp.AND) - .build(); - - assertNotNull(triple); - assertNotNull(triple.getParams()); + @Test + void tripleInCorrectOp() { + assertThrows(LogQueryException.class, + () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) + .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + "0x72657075746174696f6e00000000000000000000000000000000000000000000", + "0x72657075746174696f6e00000000000000000000000000000000000000000000") + .setOpTopic0_1(LogOp.AND) + .setOpTopic0_2(null) + .setOpTopic1_2(LogOp.AND) + .build()); } - @Test(expected = LogQueryException.class) - public void tripleInCorrectTopic1() { - LogQuery triple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic(null, - "0x72657075746174696f6e00000000000000000000000000000000000000000000", - "0x72657075746174696f6e00000000000000000000000000000000000000000000") - .setOpTopic0_1(LogOp.AND) - .setOpTopic0_2(LogOp.AND) - .setOpTopic1_2(LogOp.AND) - .build(); - - assertNotNull(triple); - assertNotNull(triple.getParams()); + @Test + void tripleInCorrectTopic1() { + assertThrows(LogQueryException.class, + () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) + .topic(null, + "0x72657075746174696f6e00000000000000000000000000000000000000000000", + "0x72657075746174696f6e00000000000000000000000000000000000000000000") + .setOpTopic0_1(LogOp.AND) + .setOpTopic0_2(LogOp.AND) + .setOpTopic1_2(LogOp.AND) + .build()); } - @Test(expected = LogQueryException.class) - public void tripleInCorrectTopic2() { - LogQuery triple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", - null, - "0x72657075746174696f6e00000000000000000000000000000000000000000000") - .setOpTopic0_1(LogOp.AND) - .setOpTopic0_2(LogOp.AND) - .setOpTopic1_2(LogOp.AND) - .build(); - - assertNotNull(triple); - assertNotNull(triple.getParams()); + @Test + void tripleInCorrectTopic2() { + assertThrows(LogQueryException.class, + () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) + .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + null, + "0x72657075746174696f6e00000000000000000000000000000000000000000000") + .setOpTopic0_1(LogOp.AND) + .setOpTopic0_2(LogOp.AND) + .setOpTopic1_2(LogOp.AND) + .build()); } - @Test(expected = LogQueryException.class) - public void tripleInCorrectTopic3() { - LogQuery triple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", - "0x72657075746174696f6e00000000000000000000000000000000000000000000", - null) - .setOpTopic0_1(LogOp.AND) - .setOpTopic0_2(LogOp.AND) - .setOpTopic1_2(LogOp.AND) - .build(); - - assertNotNull(triple); - assertNotNull(triple.getParams()); + @Test + void tripleInCorrectTopic3() { + assertThrows(LogQueryException.class, + () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) + .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + "0x72657075746174696f6e00000000000000000000000000000000000000000000", + null) + .setOpTopic0_1(LogOp.AND) + .setOpTopic0_2(LogOp.AND) + .setOpTopic1_2(LogOp.AND) + .build()); } @Test - public void quadroCorrect() { + void quadroCorrect() { LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -165,9 +146,9 @@ public void quadroCorrect() { assertNotNull(quadro.getParams()); } - @Test(expected = LogQueryException.class) - public void quadroIncorrectTopic2() { - LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroIncorrectTopic2() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null, "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -178,92 +159,83 @@ public void quadroIncorrectTopic2() { .setOpTopic1_2(LogOp.OR) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); - - assertNotNull(quadro); - assertNotNull(quadro.getParams()); + .build()); } - @Test(expected = LogQueryException.class) - public void tupleIncorrectTopic2() { - LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void tupleIncorrectTopic2() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null) .setOpTopic0_1(LogOp.AND) - .build(); - - assertNotNull(quadro); - assertNotNull(quadro.getParams()); + .build()); } - @Test(expected = LogQueryException.class) - public void tupleIncorrectTopic1() { - LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void tupleIncorrectTopic1() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic(null, "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .setOpTopic0_1(LogOp.AND) - .build(); - - assertNotNull(quadro); - assertNotNull(quadro.getParams()); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroIncorrectOp1() { - LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroIncorrectOp1() { + final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - topicQuadro + assertThrows(LogQueryException.class, () -> topicQuadro .setOpTopic0_1(null) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(LogOp.OR) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroIncorrectOp2() { - LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroIncorrectOp2() { + final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - topicQuadro.setOpTopic0_1(LogOp.AND) + assertThrows(LogQueryException.class, () -> topicQuadro.setOpTopic0_1(LogOp.AND) .setOpTopic0_2(null) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(LogOp.OR) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroIncorrectOp3() { - LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroIncorrectOp3() { + final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - topicQuadro + assertThrows(LogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(null) .setOpTopic1_2(LogOp.OR) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroInCorrectAgainTopic() { - LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroInCorrectAgainTopic() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -274,69 +246,66 @@ public void quadroInCorrectAgainTopic() { .setOpTopic1_2(LogOp.OR) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); - - assertNotNull(quadro); - assertNotNull(quadro.getParams()); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroInCorrectOp4() { - LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroInCorrectOp4() { + final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - topicQuadro + assertThrows(LogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(null) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroInCorrectOp5() { - LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroInCorrectOp5() { + final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - topicQuadro + assertThrows(LogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(LogOp.AND) .setOpTopic1_3(null) .setOpTopic2_3(LogOp.OR) - .build(); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroInCorrectOp6() { + @Test + void quadroInCorrectOp6() { LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - topicQuadro + assertThrows(LogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(LogOp.AND) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(null) - .build(); + .build()); } - @Test(expected = LogQueryException.class) - public void quadroInCorrectTopic() { - LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") + @Test + void quadroInCorrectTopic() { + assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "", @@ -347,9 +316,6 @@ public void quadroInCorrectTopic() { .setOpTopic1_2(LogOp.OR) .setOpTopic1_3(LogOp.OR) .setOpTopic2_3(LogOp.OR) - .build(); - - assertNotNull(quadro); - assertNotNull(quadro.getParams()); + .build()); } } diff --git a/src/test/java/io/api/etherscan/logs/LogsApiTest.java b/src/test/java/io/api/etherscan/logs/LogsApiTest.java index 7143a83..e786e0c 100644 --- a/src/test/java/io/api/etherscan/logs/LogsApiTest.java +++ b/src/test/java/io/api/etherscan/logs/LogsApiTest.java @@ -5,34 +5,19 @@ import io.api.etherscan.model.query.LogOp; import io.api.etherscan.model.query.impl.LogQuery; import io.api.etherscan.model.query.impl.LogQueryBuilder; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -@RunWith(Parameterized.class) -public class LogsApiTest extends ApiRunner { - - private final LogQuery query; - private final int logsSize; - - public LogsApiTest(LogQuery query, int logsSize) { - this.query = query; - this.logsSize = logsSize; - } +class LogsApiTest extends ApiRunner { - @Parameters - public static Collection data() { + static Stream<Arguments> source() { LogQuery single = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); @@ -53,16 +38,16 @@ public static Collection data() { .setOpTopic0_1(LogOp.OR) .build(); - return Arrays.asList(new Object[][] { - { single, 423 }, - { singleInvalidAddr, 0 }, - { tupleAnd, 1 }, - { tupleOr, 425 } - }); + return Stream.of( + Arguments.of(single, 423), + Arguments.of(singleInvalidAddr, 0), + Arguments.of(tupleAnd, 1), + Arguments.of(tupleOr, 425)); } - @Test - public void validateQuery() { + @ParameterizedTest + @MethodSource("source") + void validateQuery(LogQuery query, int logsSize) { List<Log> logs = getApi().logs().logs(query); assertEquals(logsSize, logs.size()); diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java index 5d3884d..faead19 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java @@ -5,27 +5,24 @@ import io.api.etherscan.manager.impl.QueueManager; import io.api.etherscan.model.EthNetwork; import io.api.etherscan.model.proxy.BlockProxy; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyBlockApiTest extends ApiRunner { +class ProxyBlockApiTest extends ApiRunner { private final EtherScanApi api; - public ProxyBlockApiTest() { + ProxyBlockApiTest() { final QueueManager queueManager = new QueueManager(1, 5100L, 5100L, 0); this.api = new EtherScanApi(getApiKey(), EthNetwork.MAINNET, queueManager); } @Test - public void correct() { + void correct() { Optional<BlockProxy> block = api.proxy().block(5120); assertTrue(block.isPresent()); BlockProxy proxy = block.get(); @@ -58,13 +55,13 @@ public void correct() { } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<BlockProxy> block = api.proxy().block(99999999999L); assertFalse(block.isPresent()); } @Test - public void correctParamNegativeNo() { + void correctParamNegativeNo() { Optional<BlockProxy> block = api.proxy().block(-1); assertTrue(block.isPresent()); assertNotNull(block.get().getHash()); diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java index 5485391..f866b6a 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java @@ -1,18 +1,16 @@ package io.api.etherscan.proxy; import io.api.ApiRunner; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 13.11.2018 */ -public class ProxyBlockLastNoApiTest extends ApiRunner { +class ProxyBlockLastNoApiTest extends ApiRunner { @Test - public void correct() { + void correct() { long noLast = getApi().proxy().blockNoLast(); assertNotEquals(0, noLast); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java index 474c5bb..67a8875 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java @@ -2,20 +2,17 @@ import io.api.ApiRunner; import io.api.etherscan.model.proxy.BlockProxy; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 13.11.2018 */ -public class ProxyBlockUncleApiTest extends ApiRunner { +class ProxyBlockUncleApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<BlockProxy> block = getApi().proxy().blockUncle(603183, 0); assertTrue(block.isPresent()); assertNotNull(block.get().getHash()); @@ -23,13 +20,13 @@ public void correct() { } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<BlockProxy> block = getApi().proxy().blockUncle(5120, 1); assertFalse(block.isPresent()); } @Test - public void correctParamNegativeNo() { + void correctParamNegativeNo() { Optional<BlockProxy> block = getApi().proxy().blockUncle(-603183, 0); assertFalse(block.isPresent()); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java index 07d26bd..8cf46c9 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java @@ -4,43 +4,40 @@ import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.error.InvalidDataHexException; import io.api.etherscan.util.BasicUtils; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyCallApiTest extends ApiRunner { +class ProxyCallApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<String> call = getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); assertTrue(call.isPresent()); assertFalse(BasicUtils.isNotHex(call.get())); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - Optional<String> call = getApi().proxy().call("0xEEF46DB4855E25702F8237E8f403FddcaF931C0", - "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, () -> getApi().proxy().call("0xEEF46DB4855E25702F8237E8f403FddcaF931C0", + "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724")); } - @Test(expected = InvalidDataHexException.class) - public void invalidParamNotHex() { - Optional<String> call = getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", - "7-0a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); + @Test + void invalidParamNotHex() { + assertThrows(InvalidDataHexException.class, () -> getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", + "7-0a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<String> call = getApi().proxy().call("0xAEEF16DB4855E25702F8237E8f403FddcaF931C0", "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724"); assertTrue(call.isPresent()); - assertFalse(call.get(), BasicUtils.isNotHex(call.get())); + assertFalse(BasicUtils.isNotHex(call.get()), call.get()); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java index 9e4910c..6835f07 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java @@ -3,34 +3,31 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; import io.api.etherscan.util.BasicUtils; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyCodeApiTest extends ApiRunner { +class ProxyCodeApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<String> call = getApi().proxy().code("0xf75e354c5edc8efed9b59ee9f67a80845ade7d0c"); assertTrue(call.isPresent()); - assertFalse(call.get(), BasicUtils.isNotHex(call.get())); + assertFalse(BasicUtils.isNotHex(call.get()), call.get()); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, () -> getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<String> call = getApi().proxy().code("0xf15e354c5edc8efed9b59ee9f67a80845ade7d0c"); assertTrue(call.isPresent()); - assertFalse(call.get(), BasicUtils.isNotHex(call.get())); + assertFalse(BasicUtils.isNotHex(call.get()), call.get()); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java index 63e476c..b4b6f37 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java @@ -2,34 +2,31 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidDataHexException; -import org.junit.Test; - import java.math.BigInteger; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyGasApiTest extends ApiRunner { +class ProxyGasApiTest extends ApiRunner { @Test - public void correctPrice() { + void correctPrice() { BigInteger price = getApi().proxy().gasPrice(); assertNotNull(price); assertNotEquals(0, price.intValue()); } @Test - public void correctEstimated() { + void correctEstimated() { BigInteger price = getApi().proxy().gasEstimated(); assertNotNull(price); assertNotEquals(0, price.intValue()); } @Test - public void correctEstimatedWithData() { + void correctEstimatedWithData() { String dataCustom = "606060405260728060106000396000f360606040526000606060405260728060106000396000f360606040526000"; BigInteger price = getApi().proxy().gasEstimated(); BigInteger priceCustom = getApi().proxy().gasEstimated(dataCustom); @@ -38,9 +35,9 @@ public void correctEstimatedWithData() { assertNotEquals(price, priceCustom); } - @Test(expected = InvalidDataHexException.class) - public void invalidParamWithError() { + @Test + void invalidParamWithError() { String dataCustom = "280&60106000396000f360606040526000"; - BigInteger priceCustom = getApi().proxy().gasEstimated(dataCustom); + assertThrows(InvalidDataHexException.class, () -> getApi().proxy().gasEstimated(dataCustom)); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java index 19945e2..6d2e8e4 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java @@ -2,31 +2,29 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyStorageApiTest extends ApiRunner { +class ProxyStorageApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<String> call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); assertFalse(call.isPresent()); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - getApi().proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0)); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { final Optional<String> call = getApi().proxy().storageAt("0x6e03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 10000); assertFalse(call.isPresent()); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java index 2779120..decf95f 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java @@ -3,20 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.proxy.TxProxy; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyTxApiTest extends ApiRunner { +class ProxyTxApiTest extends ApiRunner { @Test - public void correctByHash() { + void correctByHash() { Optional<TxProxy> tx = getApi().proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertTrue(tx.isPresent()); assertNotNull(tx.get().getBlockHash()); @@ -33,7 +30,7 @@ public void correctByHash() { } @Test - public void correctByBlockNo() { + void correctByBlockNo() { Optional<TxProxy> tx = getApi().proxy().tx(637368, 0); assertTrue(tx.isPresent()); assertNotNull(tx.get().getBlockHash()); @@ -52,19 +49,20 @@ public void correctByBlockNo() { assertNotNull(tx.get().getInput()); } - @Test(expected = InvalidTxHashException.class) - public void invalidParamWithError() { - Optional<TxProxy> tx = getApi().proxy().tx("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + @Test + void invalidParamWithError() { + assertThrows(InvalidTxHashException.class, + () -> getApi().proxy().tx("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1")); } @Test - public void correctParamWithEmptyExpectedResultBlockNoExist() { + void correctParamWithEmptyExpectedResultBlockNoExist() { Optional<TxProxy> tx = getApi().proxy().tx(99999999L, 0); assertFalse(tx.isPresent()); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<TxProxy> tx = getApi().proxy().tx("0x2e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertFalse(tx.isPresent()); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java index b81926f..0083f7a 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java @@ -2,41 +2,40 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyTxCountApiTest extends ApiRunner { +class ProxyTxCountApiTest extends ApiRunner { @Test - public void correctSended() { + void correctSended() { int count = getApi().proxy().txSendCount("0x2910543af39aba0cd09dbb2d50200b3e800a63d2"); assertNotEquals(0, count); } @Test - public void correctByBlockNo() { + void correctByBlockNo() { int count = getApi().proxy().txCount(6137420); assertNotEquals(0, count); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - int count = getApi().proxy().txSendCount("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, + () -> getApi().proxy().txSendCount("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd")); } @Test - public void correctParamWithEmptyExpectedResultBlockNoExist() { + void correctParamWithEmptyExpectedResultBlockNoExist() { int count = getApi().proxy().txCount(99999999999L); assertNotEquals(1, count); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { int count = getApi().proxy().txSendCount("0x1e03d9cce9d60f3e9f2597e13cd4c54c55330cfd"); assertNotEquals(1, count); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java index c4a3383..0159ed9 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java @@ -3,20 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.proxy.ReceiptProxy; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class ProxyTxReceiptApiTest extends ApiRunner { +class ProxyTxReceiptApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<ReceiptProxy> infoProxy = getApi().proxy() .txReceipt("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertTrue(infoProxy.isPresent()); @@ -40,14 +37,14 @@ public void correct() { assertNotEquals(empty.hashCode(), infoProxy.get().hashCode()); } - @Test(expected = InvalidTxHashException.class) - public void invalidParamWithError() { - Optional<ReceiptProxy> infoProxy = getApi().proxy() - .txReceipt("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); + @Test + void invalidParamWithError() { + assertThrows(InvalidTxHashException.class, () -> getApi().proxy() + .txReceipt("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<ReceiptProxy> infoProxy = getApi().proxy() .txReceipt("0x2e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertFalse(infoProxy.isPresent()); diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java b/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java index 40e79a6..676dc3a 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java +++ b/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java @@ -3,36 +3,33 @@ import io.api.ApiRunner; import io.api.etherscan.error.EtherScanException; import io.api.etherscan.error.InvalidDataHexException; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ // TODO contact etherscan and ask about method behavior -public class ProxyTxSendRawApiTest extends ApiRunner { +class ProxyTxSendRawApiTest extends ApiRunner { - public void correct() { + void correct() { Optional<String> sendRaw = getApi().proxy() .txSendRaw("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); assertTrue(sendRaw.isPresent()); } - @Test(expected = InvalidDataHexException.class) - public void invalidParamWithError() { - Optional<String> sendRaw = getApi().proxy().txSendRaw("5151=0561"); + @Test + void invalidParamWithError() { + assertThrows(InvalidDataHexException.class, () -> getApi().proxy().txSendRaw("5151=0561")); } - @Test(expected = EtherScanException.class) - public void invalidParamEtherScanDataException() { - Optional<String> sendRaw = getApi().proxy().txSendRaw("0x1"); + @Test + void invalidParamEtherScanDataException() { + assertThrows(EtherScanException.class, () -> getApi().proxy().txSendRaw("0x1")); } - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<String> sendRaw = getApi().proxy().txSendRaw("0x000000"); assertFalse(sendRaw.isPresent()); } diff --git a/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java b/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java index e29a6b1..9f89738 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java +++ b/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java @@ -2,18 +2,16 @@ import io.api.ApiRunner; import io.api.etherscan.model.Price; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class StatisticPriceApiTest extends ApiRunner { +class StatisticPriceApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Price price = getApi().stats().lastPrice(); assertNotNull(price); assertNotNull(price.btcTimestamp()); diff --git a/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java b/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java index a705a31..32c3018 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java +++ b/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java @@ -2,20 +2,17 @@ import io.api.ApiRunner; import io.api.etherscan.model.Supply; -import org.junit.Test; - import java.math.BigInteger; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class StatisticSupplyApiTest extends ApiRunner { +class StatisticSupplyApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Supply supply = getApi().stats().supply(); assertNotNull(supply); assertNotNull(supply.getValue()); diff --git a/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java b/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java index 0a84d01..aefb2bd 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java +++ b/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java @@ -2,32 +2,29 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidAddressException; -import org.junit.Test; - import java.math.BigInteger; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class StatisticTokenSupplyApiTest extends ApiRunner { +class StatisticTokenSupplyApiTest extends ApiRunner { @Test - public void correct() { + void correct() { BigInteger supply = getApi().stats().supply("0x57d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); assertNotEquals(BigInteger.ZERO, supply); } - @Test(expected = InvalidAddressException.class) - public void invalidParamWithError() { - BigInteger supply = getApi().stats().supply("0x7d90b64a1a57749b0f932f1a3395792e12e7055"); + @Test + void invalidParamWithError() { + assertThrows(InvalidAddressException.class, () -> getApi().stats().supply("0x7d90b64a1a57749b0f932f1a3395792e12e7055")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { BigInteger supply = getApi().stats().supply("0x51d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); assertEquals(0, supply.intValue()); diff --git a/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java b/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java index 25320cc..de67a02 100644 --- a/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java +++ b/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java @@ -3,20 +3,17 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; import io.api.etherscan.model.Status; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class TransactionExecApiTest extends ApiRunner { +class TransactionExecApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<Status> status = getApi().txs().execStatus("0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); assertTrue(status.isPresent()); assertTrue(status.get().haveError()); @@ -28,13 +25,14 @@ public void correct() { assertNotEquals(empty.hashCode(), status.get().hashCode()); } - @Test(expected = InvalidTxHashException.class) - public void invalidParamWithError() { - getApi().txs().execStatus("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b"); + @Test + void invalidParamWithError() { + assertThrows(InvalidTxHashException.class, + () -> getApi().txs().execStatus("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<Status> status = getApi().txs().execStatus("0x55f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); assertTrue(status.isPresent()); assertFalse(status.get().haveError()); diff --git a/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java b/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java index a459355..94b93b3 100644 --- a/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java +++ b/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java @@ -2,33 +2,31 @@ import io.api.ApiRunner; import io.api.etherscan.error.InvalidTxHashException; -import org.junit.Test; - import java.util.Optional; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class TransactionReceiptApiTest extends ApiRunner { +class TransactionReceiptApiTest extends ApiRunner { @Test - public void correct() { + void correct() { Optional<Boolean> status = getApi().txs() .receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); assertTrue(status.isPresent()); assertTrue(status.get()); } - @Test(expected = InvalidTxHashException.class) - public void invalidParamWithError() { - getApi().txs().receiptStatus("0x13c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); + @Test + void invalidParamWithError() { + assertThrows(InvalidTxHashException.class, + () -> getApi().txs().receiptStatus("0x13c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76")); } @Test - public void correctParamWithEmptyExpectedResult() { + void correctParamWithEmptyExpectedResult() { Optional<Boolean> status = getApi().txs() .receiptStatus("0x113c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); assertFalse(status.isPresent()); diff --git a/src/test/java/io/api/manager/QueueManagerTest.java b/src/test/java/io/api/manager/QueueManagerTest.java index 74e674c..7bd53a9 100644 --- a/src/test/java/io/api/manager/QueueManagerTest.java +++ b/src/test/java/io/api/manager/QueueManagerTest.java @@ -4,18 +4,17 @@ import io.api.etherscan.manager.IQueueManager; import io.api.etherscan.manager.impl.FakeQueueManager; import io.api.etherscan.manager.impl.QueueManager; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ -public class QueueManagerTest extends ApiRunner { +class QueueManagerTest extends ApiRunner { @Test - public void fakeManager() { + void fakeManager() { IQueueManager fakeManager = new FakeQueueManager(); fakeManager.takeTurn(); fakeManager.takeTurn(); @@ -26,16 +25,18 @@ public void fakeManager() { assertNotNull(fakeManager); } - @Test(timeout = 3500) - public void queueManager() { + @Test + @Timeout(3500) + void queueManager() { IQueueManager queueManager = new QueueManager(1, 3); queueManager.takeTurn(); queueManager.takeTurn(); assertNotNull(queueManager); } - @Test(timeout = 4500) - public void queueManagerWithDelay() { + @Test + @Timeout(4500) + void queueManagerWithDelay() { IQueueManager queueManager = new QueueManager(1, 2, 2); queueManager.takeTurn(); queueManager.takeTurn(); @@ -43,7 +44,7 @@ public void queueManagerWithDelay() { } @Test - public void queueManagerTimeout() { + void queueManagerTimeout() { IQueueManager queueManager = new QueueManager(1, 3); queueManager.takeTurn(); long start = System.currentTimeMillis(); diff --git a/src/test/java/io/api/support/AddressUtil.java b/src/test/java/io/api/support/AddressUtil.java index 7949b9e..da04c37 100644 --- a/src/test/java/io/api/support/AddressUtil.java +++ b/src/test/java/io/api/support/AddressUtil.java @@ -5,14 +5,12 @@ import java.util.concurrent.ThreadLocalRandom; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 03.11.2018 */ public class AddressUtil { - public static List<String> genFakeAddresses(int size) { + static List<String> genFakeAddresses(int size) { final List<String> addresses = new ArrayList<>(); for (int i = 0; i < size; i++) addresses.add("0x9327cb34984c" + ThreadLocalRandom.current().nextInt(1000, 9999) + "ec1EA0eAE98Ccf80A74f95B9"); diff --git a/src/test/java/io/api/util/BasicUtilsTests.java b/src/test/java/io/api/util/BasicUtilsTests.java index c35bada..36c22cb 100644 --- a/src/test/java/io/api/util/BasicUtilsTests.java +++ b/src/test/java/io/api/util/BasicUtilsTests.java @@ -1,103 +1,88 @@ package io.api.util; +import static io.api.etherscan.util.BasicUtils.*; + import com.google.gson.Gson; import io.api.ApiRunner; import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.error.ParseException; import io.api.etherscan.model.utility.StringResponseTO; -import org.junit.Test; - import java.util.ArrayList; import java.util.List; - -import static io.api.etherscan.util.BasicUtils.*; +import org.junit.jupiter.api.Test; /** - * ! NO DESCRIPTION ! - * * @author GoodforGod * @since 13.11.2018 */ -public class BasicUtilsTests extends ApiRunner { +class BasicUtilsTests extends ApiRunner { - @Test(expected = EtherScanException.class) - public void responseValidateEmpty() { + @Test + void responseValidateEmpty() { String response = "{\"status\":\"0\",\"message\":\"No ether\",\"result\":\"status\"}"; StringResponseTO responseTO = new Gson().fromJson(response, StringResponseTO.class); - validateTxResponse(responseTO); + + assertThrows(EtherScanException.class, () -> validateTxResponse(responseTO)); } @Test - public void partitionEmpty() { + void partitionEmpty() { ArrayList<String> list = new ArrayList<>(); List<List<String>> lists = partition(list, 12); assertTrue(lists.isEmpty()); } @Test - public void partitionNullParam() { + void partitionNullParam() { List<List<String>> lists = partition(null, 12); assertTrue(lists.isEmpty()); } @Test - public void isBlankNull() { + void isBlankNull() { boolean result = isBlank(null); assertTrue(result); } @Test - public void isEmptyCollectionNull() { - List<String> list = null; - boolean result = isEmpty(list); - assertTrue(result); - } - - @Test - public void isEmptyCollectionEmpty() { + void isEmptyCollectionEmpty() { ArrayList<Object> list = new ArrayList<>(); boolean result = isEmpty(list); assertTrue(result); } @Test - public void isNotAddressNull() { + void isNotAddressNull() { boolean result = isNotAddress(""); assertTrue(result); } @Test - public void isNotHexNull() { + void isNotHexNull() { boolean result = isNotHex(""); assertTrue(result); } @Test - public void isNotAddressInvalid() { + void isNotAddressInvalid() { boolean result = isNotAddress("125125"); assertTrue(result); } @Test - public void isNotHexInvalid() { + void isNotHexInvalid() { boolean result = isNotHex("1215%"); assertTrue(result); } - @Test(expected = EtherScanException.class) - public void isResponseStatusInvalidThrows() { + @Test + void isResponseStatusInvalidThrows() { StringResponseTO responseTO = new StringResponseTO(); - validateTxResponse(responseTO); + assertThrows(EtherScanException.class, () -> validateTxResponse(responseTO)); } - @Test(expected = EtherScanException.class) - public void isResponseNullThrows() { + @Test + void isResponseNullThrows() { StringResponseTO responseTO = null; - validateTxResponse(responseTO); - } - - @Test(expected = ParseException.class) - public void isThrowParseException() { - throw new ParseException("Test", null, null); + assertThrows(EtherScanException.class, () -> validateTxResponse(responseTO)); } } From 1559a3faf723dbc0d9bba75b1e7ab4a764f262b6 Mon Sep 17 00:00:00 2001 From: Abhay Gupta <abhay@coinflow.com> Date: Thu, 21 Jul 2022 13:31:07 +0530 Subject: [PATCH 08/67] added support for txsToken with contract address too --- .../io/api/etherscan/core/IAccountApi.java | 19 ++++++++++++++ .../core/impl/AccountApiProvider.java | 26 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/main/java/io/api/etherscan/core/IAccountApi.java b/src/main/java/io/api/etherscan/core/IAccountApi.java index 25254aa..b3e12fb 100644 --- a/src/main/java/io/api/etherscan/core/IAccountApi.java +++ b/src/main/java/io/api/etherscan/core/IAccountApi.java @@ -110,6 +110,25 @@ public interface IAccountApi { @NotNull List<TxToken> txsToken(String address) throws ApiException; + /** + * All ERC-20 token txs for given address and contract address + * + * @param address get txs for + * @param contractAddress contract address to get txs for + * @param startBlock tx from this blockNumber + * @param endBlock tx to this blockNumber + * @return txs for address + * @throws ApiException parent exception class + */ + @NotNull + List<TxToken> txsToken(String address, String contractAddress, long startBlock, long endBlock) throws ApiException; + + @NotNull + List<TxToken> txsToken(String address, String contractAddress, long startBlock) throws ApiException; + + @NotNull + List<TxToken> txsToken(String address, String contractAddress) throws ApiException; + /** * All ERC-721 (NFT) token txs for given address * diff --git a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java b/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java index 77d8b88..dba413a 100644 --- a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java @@ -230,6 +230,32 @@ public List<TxToken> txsToken(final String address, final long startBlock, final return getRequestUsingOffset(urlParams, TxTokenResponseTO.class); } + @NotNull + @Override + public List<TxToken> txsToken(final String address, final String contractAddress) throws ApiException { + return txsToken(address, contractAddress, MIN_START_BLOCK); + } + + @NotNull + @Override + public List<TxToken> txsToken(final String address, final String contractAddress, final long startBlock) throws ApiException { + return txsToken(address, contractAddress, startBlock, MAX_END_BLOCK); + } + + @NotNull + @Override + public List<TxToken> txsToken(final String address, final String contractAddress, final long startBlock, final long endBlock) throws ApiException { + BasicUtils.validateAddress(address); + final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); + + final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; + final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); + final String urlParams = ACT_TX_TOKEN_ACTION + offsetParam + ADDRESS_PARAM + address + + CONTRACT_PARAM + contractAddress + blockParam + SORT_ASC_PARAM; + + return getRequestUsingOffset(urlParams, TxTokenResponseTO.class); + } + @NotNull @Override public List<TxToken> txsNftToken(String address) throws ApiException { From 9fb7d918e63deaf349bbd09b5635935db995f4cd Mon Sep 17 00:00:00 2001 From: Abhay Gupta <abhay@coinflow.com> Date: Mon, 14 Nov 2022 17:59:49 +0530 Subject: [PATCH 09/67] gas tracker API implementation --- .../io/api/etherscan/core/IGasTrackerApi.java | 23 +++++++ .../api/etherscan/core/impl/EtherScanApi.java | 7 ++ .../core/impl/GasTrackerApiProvider.java | 39 +++++++++++ .../io/api/etherscan/model/GasOracle.java | 68 +++++++++++++++++++ .../model/utility/GasOracleResponseTO.java | 18 +++++ 5 files changed, 155 insertions(+) create mode 100644 src/main/java/io/api/etherscan/core/IGasTrackerApi.java create mode 100644 src/main/java/io/api/etherscan/core/impl/GasTrackerApiProvider.java create mode 100644 src/main/java/io/api/etherscan/model/GasOracle.java create mode 100644 src/main/java/io/api/etherscan/model/utility/GasOracleResponseTO.java diff --git a/src/main/java/io/api/etherscan/core/IGasTrackerApi.java b/src/main/java/io/api/etherscan/core/IGasTrackerApi.java new file mode 100644 index 0000000..894713f --- /dev/null +++ b/src/main/java/io/api/etherscan/core/IGasTrackerApi.java @@ -0,0 +1,23 @@ +package io.api.etherscan.core; + +import io.api.etherscan.error.ApiException; +import io.api.etherscan.model.GasOracle; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan - API Descriptions https://docs.etherscan.io/api-endpoints/gas-tracker + * + * @author Abhay Gupta + * @since 14.11.2022 + */ +public interface IGasTrackerApi { + + /** + * GasOracle details + * + * @return fast, suggested gas price + * @throws ApiException parent exception class + */ + @NotNull + GasOracle gasoracle() throws ApiException; +} diff --git a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java index ba5dd83..957315d 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -34,6 +34,7 @@ public class EtherScanApi implements AutoCloseable { private final IProxyApi proxy; private final IStatisticApi stats; private final ITransactionApi txs; + private final IGasTrackerApi gastracker; public EtherScanApi() { this(DEFAULT_KEY, EthNetwork.MAINNET); @@ -96,6 +97,7 @@ public EtherScanApi(final String apiKey, this.proxy = new ProxyApiProvider(queue, baseUrl, executor); this.stats = new StatisticApiProvider(queue, baseUrl, executor); this.txs = new TransactionApiProvider(queue, baseUrl, executor); + this.gastracker = new GasTrackerApiProvider(queue, baseUrl, executor); } @NotNull @@ -133,6 +135,11 @@ public IStatisticApi stats() { return stats; } + @NotNull + public IGasTrackerApi gastracker() { + return gastracker; + } + @Override public void close() throws Exception { queueManager.close(); diff --git a/src/main/java/io/api/etherscan/core/impl/GasTrackerApiProvider.java b/src/main/java/io/api/etherscan/core/impl/GasTrackerApiProvider.java new file mode 100644 index 0000000..16d2e63 --- /dev/null +++ b/src/main/java/io/api/etherscan/core/impl/GasTrackerApiProvider.java @@ -0,0 +1,39 @@ +package io.api.etherscan.core.impl; + +import io.api.etherscan.core.IGasTrackerApi; +import io.api.etherscan.error.ApiException; +import io.api.etherscan.error.EtherScanException; +import io.api.etherscan.executor.IHttpExecutor; +import io.api.etherscan.manager.IQueueManager; +import io.api.etherscan.model.GasOracle; +import io.api.etherscan.model.utility.GasOracleResponseTO; +import org.jetbrains.annotations.NotNull; + +/** + * GasTracker API Implementation + * + * @see IGasTrackerApi + * + * @author Abhay Gupta + * @since 14.11.2022 + */ +public class GasTrackerApiProvider extends BasicProvider implements IGasTrackerApi { + + private static final String ACT_GAS_ORACLE_PARAM = ACT_PREFIX + "gasoracle"; + + GasTrackerApiProvider(final IQueueManager queue, + final String baseUrl, + final IHttpExecutor executor) { + super(queue, "gastracker", baseUrl, executor); + } + + @NotNull + @Override + public GasOracle gasoracle() throws ApiException { + final GasOracleResponseTO response = getRequest(ACT_GAS_ORACLE_PARAM, GasOracleResponseTO.class); + if (response.getStatus() != 1) + throw new EtherScanException(response); + + return response.getResult(); + } +} diff --git a/src/main/java/io/api/etherscan/model/GasOracle.java b/src/main/java/io/api/etherscan/model/GasOracle.java new file mode 100644 index 0000000..f3f66c2 --- /dev/null +++ b/src/main/java/io/api/etherscan/model/GasOracle.java @@ -0,0 +1,68 @@ +package io.api.etherscan.model; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * ! NO DESCRIPTION ! + * + * @author Abhay Gupta + * @since 14.11.2022 + */ +public class GasOracle { + private Long LastBlock; + private Integer SafeGasPrice; + private Integer ProposeGasPrice; + private Integer FastGasPrice; + private Double suggestBaseFee; + private String gasUsedRatio; + + public Long getLastBlock() { + return LastBlock; + } + + public BigInteger getSafeGasPriceInWei() { + return BigInteger.valueOf(SafeGasPrice).multiply(BigInteger.TEN.pow(9)); + } + + public BigInteger getProposeGasPriceInWei() { + return BigInteger.valueOf(ProposeGasPrice).multiply(BigInteger.TEN.pow(9)); + } + + public BigInteger getFastGasPriceInWei() { + return BigInteger.valueOf(FastGasPrice).multiply(BigInteger.TEN.pow(9)); + } + + public Double getSuggestBaseFee() { + return suggestBaseFee; + } + + public String getGasUsedRatio() { + return gasUsedRatio; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GasOracle gasOracle = (GasOracle) o; + return LastBlock.equals(gasOracle.LastBlock) && SafeGasPrice.equals(gasOracle.SafeGasPrice) && ProposeGasPrice.equals(gasOracle.ProposeGasPrice) && FastGasPrice.equals(gasOracle.FastGasPrice) && suggestBaseFee.equals(gasOracle.suggestBaseFee) && gasUsedRatio.equals(gasOracle.gasUsedRatio); + } + + @Override + public int hashCode() { + return Objects.hash(LastBlock, SafeGasPrice, ProposeGasPrice, FastGasPrice, suggestBaseFee, gasUsedRatio); + } + + @Override + public String toString() { + return "GasOracle{" + + "LastBlock=" + LastBlock + + ", SafeGasPrice=" + SafeGasPrice + + ", ProposeGasPrice=" + ProposeGasPrice + + ", FastGasPrice=" + FastGasPrice + + ", suggestBaseFee=" + suggestBaseFee + + ", gasUsedRatio='" + gasUsedRatio + '\'' + + '}'; + } +} diff --git a/src/main/java/io/api/etherscan/model/utility/GasOracleResponseTO.java b/src/main/java/io/api/etherscan/model/utility/GasOracleResponseTO.java new file mode 100644 index 0000000..f0c1fd5 --- /dev/null +++ b/src/main/java/io/api/etherscan/model/utility/GasOracleResponseTO.java @@ -0,0 +1,18 @@ +package io.api.etherscan.model.utility; + +import io.api.etherscan.model.GasOracle; + +/** + * ! NO DESCRIPTION ! + * + * @author Abhay Gupta + * @since 14.11.2022 + */ +public class GasOracleResponseTO extends BaseResponseTO { + + private GasOracle result; + + public GasOracle getResult() { + return result; + } +} From 2174387cf8c57e86e4303b5468a258e017c257e6 Mon Sep 17 00:00:00 2001 From: Abhay Gupta <abhay@coinflow.com> Date: Mon, 14 Nov 2022 18:11:34 +0530 Subject: [PATCH 10/67] debug url --- src/main/java/io/api/etherscan/core/impl/BasicProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index b36f406..b0fbe68 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -77,6 +77,7 @@ <T> T convert(final String json, final Class<T> tClass) { String getRequest(final String urlParameters) { queue.takeTurn(); final String url = baseUrl + module + urlParameters; + System.out.println("Url generated: " + url); final String result = executor.get(url); if (BasicUtils.isEmpty(result)) throw new EtherScanException("Server returned null value for GET request at URL - " + url); From bf59b713daddb826c01d292b900f4ce6a462b184 Mon Sep 17 00:00:00 2001 From: Abhay Gupta <abhay@coinflow.com> Date: Mon, 14 Nov 2022 19:01:50 +0530 Subject: [PATCH 11/67] fixed gas oracle base url --- src/main/java/io/api/etherscan/core/impl/EtherScanApi.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java index 957315d..028c52b 100644 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java @@ -88,6 +88,7 @@ public EtherScanApi(final String apiKey, final String ending = EthNetwork.TOBALABA.equals(network) ? "com" : "io"; final String baseUrl = "https://" + network.getDomain() + ".etherscan." + ending + "/api" + "?apikey=" + apiKey; + final String mainnetBaseUrl = "https://" + EthNetwork.MAINNET.getDomain() + ".etherscan." + ending + "/api" + "?apikey=" + apiKey; this.queueManager = queue; this.account = new AccountApiProvider(queue, baseUrl, executor); @@ -97,7 +98,7 @@ public EtherScanApi(final String apiKey, this.proxy = new ProxyApiProvider(queue, baseUrl, executor); this.stats = new StatisticApiProvider(queue, baseUrl, executor); this.txs = new TransactionApiProvider(queue, baseUrl, executor); - this.gastracker = new GasTrackerApiProvider(queue, baseUrl, executor); + this.gastracker = new GasTrackerApiProvider(queue, mainnetBaseUrl, executor); } @NotNull From c462175c09a35d15e0c7641e77e2cbeb5e0e892b Mon Sep 17 00:00:00 2001 From: Abhay Gupta <abhay@coinflow.com> Date: Mon, 14 Nov 2022 19:04:10 +0530 Subject: [PATCH 12/67] removed sout --- src/main/java/io/api/etherscan/core/impl/BasicProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java index b0fbe68..b36f406 100644 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java @@ -77,7 +77,6 @@ <T> T convert(final String json, final Class<T> tClass) { String getRequest(final String urlParameters) { queue.takeTurn(); final String url = baseUrl + module + urlParameters; - System.out.println("Url generated: " + url); final String result = executor.get(url); if (BasicUtils.isEmpty(result)) throw new EtherScanException("Server returned null value for GET request at URL - " + url); From f3d6858f1633e7523bf759f7bd18039872f48a0b Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Thu, 11 May 2023 23:53:28 +0300 Subject: [PATCH 13/67] [2.0.0-SNAPSHOT] Package refactoring API refactoring Contracts renamed LogQuery refactored EtherScanAPI refactored & builder added --- .github/workflows/gradle.yml | 2 +- README.md | 5 +- build.gradle | 13 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/io/api/etherscan/core/IBlockApi.java | 25 ---- .../io/api/etherscan/core/IContractApi.java | 24 --- .../java/io/api/etherscan/core/ILogsApi.java | 27 ---- .../io/api/etherscan/core/IStatisticApi.java | 44 ------ .../etherscan/core/impl/BasicProvider.java | 99 ------------ .../core/impl/ContractApiProvider.java | 46 ------ .../api/etherscan/core/impl/EtherScanApi.java | 141 ------------------ .../etherscan/core/impl/LogsApiProvider.java | 43 ------ .../io/api/etherscan/error/ApiException.java | 16 -- .../api/etherscan/error/ApiKeyException.java | 12 -- .../etherscan/error/ApiTimeoutException.java | 12 -- .../etherscan/error/ConnectionException.java | 16 -- .../etherscan/error/EtherScanException.java | 23 --- .../etherscan/error/EventModelException.java | 12 -- .../error/InvalidAddressException.java | 12 -- .../error/InvalidDataHexException.java | 12 -- .../error/InvalidTxHashException.java | 12 -- .../etherscan/error/LogQueryException.java | 12 -- .../etherscan/error/RateLimitException.java | 12 -- .../api/etherscan/manager/IQueueManager.java | 16 -- .../etherscan/manager/impl/QueueManager.java | 62 -------- .../io/api/etherscan/model/EthNetwork.java | 25 ---- .../java/io/api/etherscan/model/Uncle.java | 69 --------- .../io/api/etherscan/model/UncleBlock.java | 65 -------- .../etherscan/model/query/IQueryBuilder.java | 15 -- .../etherscan/model/query/impl/LogQuery.java | 28 ---- .../model/query/impl/LogQueryBuilder.java | 83 ----------- .../model/utility/TxTokenResponseTO.java | 11 -- .../model/utility/UncleBlockResponseTO.java | 16 -- .../api/etherscan/AccountAPI.java} | 62 ++++---- .../api/etherscan/AccountAPIProvider.java} | 122 +++++++-------- .../api/etherscan/BasicProvider.java | 93 ++++++++++++ .../io/goodforgod/api/etherscan/BlockAPI.java | 25 ++++ .../api/etherscan/BlockAPIProvider.java} | 29 ++-- .../goodforgod/api/etherscan/ContractAPI.java | 24 +++ .../api/etherscan/ContractAPIProvider.java | 45 ++++++ .../goodforgod/api/etherscan/EthNetwork.java | 14 ++ .../goodforgod/api/etherscan/EthNetworks.java | 29 ++++ .../api/etherscan/EthScanAPIBuilder.java | 71 +++++++++ .../api/etherscan/EtherScanAPI.java | 61 ++++++++ .../api/etherscan/EtherScanAPIProvider.java | 89 +++++++++++ .../io/goodforgod/api/etherscan/LogsAPI.java | 27 ++++ .../api/etherscan/LogsAPIProvider.java | 42 ++++++ .../api/etherscan/ProxyAPI.java} | 70 ++++----- .../api/etherscan/ProxyAPIProvider.java} | 77 +++++----- .../api/etherscan/StatisticAPI.java | 44 ++++++ .../api/etherscan/StatisticAPIProvider.java} | 41 +++-- .../api/etherscan/TransactionAPI.java} | 18 +-- .../etherscan/TransactionAPIProvider.java} | 33 ++-- .../error/ErtherScanLogQueryException.java | 12 ++ .../error/EtherScanConnectionException.java | 16 ++ .../etherscan/error/EtherScanException.java | 16 ++ .../EtherScanInvalidAddressException.java | 12 ++ .../EtherScanInvalidDataHexException.java | 12 ++ .../EtherScanInvalidTxHashException.java | 12 ++ .../error/EtherScanKeyException.java | 12 ++ .../error/EtherScanParseException.java} | 6 +- .../error/EtherScanRateLimitException.java | 12 ++ .../error/EtherScanResponseException.java | 23 +++ .../error/EtherScanTimeoutException.java | 12 ++ .../etherscan/executor/EthHttpClient.java} | 4 +- .../executor/impl/UrlEthHttpClient.java} | 64 ++++---- .../manager/RequestQueueManager.java | 24 +++ .../impl/FakeRequestQueueManager.java} | 6 +- .../impl/SemaphoreRequestQueueManager.java | 53 +++++++ .../api/etherscan/model/Abi.java | 4 +- .../api/etherscan/model/Balance.java | 4 +- .../api/etherscan/model/BaseTx.java | 4 +- .../api/etherscan/model/Block.java | 4 +- .../api/etherscan/model/BlockUncle.java | 128 ++++++++++++++++ .../api/etherscan/model/Log.java | 4 +- .../api/etherscan/model/Price.java | 2 +- .../api/etherscan/model/Status.java | 2 +- .../api/etherscan/model/Supply.java | 2 +- .../api/etherscan/model/TokenBalance.java | 2 +- .../api/etherscan/model/Tx.java | 4 +- .../api/etherscan/model/TxERC20.java} | 6 +- .../api/etherscan/model/TxERC721.java | 71 +++++++++ .../api/etherscan/model/TxInternal.java | 2 +- .../api/etherscan/model/Wei.java | 2 +- .../api/etherscan/model/proxy/BlockProxy.java | 4 +- .../etherscan/model/proxy/ReceiptProxy.java | 6 +- .../api/etherscan/model/proxy/TxProxy.java | 4 +- .../model/proxy/utility/BaseProxyTO.java | 2 +- .../model/proxy/utility/BlockProxyTO.java | 4 +- .../model/proxy/utility/ErrorProxyTO.java | 2 +- .../model/proxy/utility/StringProxyTO.java | 2 +- .../model/proxy/utility/TxInfoProxyTO.java | 4 +- .../model/proxy/utility/TxProxyTO.java | 4 +- .../api/etherscan/model/query/LogOp.java | 2 +- .../api/etherscan/model/query/LogQuery.java | 51 +++++++ .../model/query/LogQueryBuilderImpl.java | 84 +++++++++++ .../etherscan/model/query/LogQueryImpl.java | 30 ++++ .../model/query/LogQueryParams.java} | 12 +- .../model/query/LogTopicBuilder.java | 17 +++ .../model/query}/LogTopicQuadro.java | 33 ++-- .../model/query}/LogTopicSingle.java | 20 +-- .../model/query}/LogTopicTriple.java | 27 ++-- .../etherscan/model/query}/LogTopicTuple.java | 23 +-- .../model/response}/BalanceResponseTO.java | 2 +- .../etherscan/model/response}/BalanceTO.java | 2 +- .../model/response}/BaseListResponseTO.java | 2 +- .../model/response}/BaseResponseTO.java | 4 +- .../etherscan/model/response}/BlockParam.java | 2 +- .../model/response}/BlockResponseTO.java | 4 +- .../model/response}/LogResponseTO.java | 4 +- .../model/response}/PriceResponseTO.java | 4 +- .../response}/ReceiptStatusResponseTO.java | 2 +- .../model/response}/ReceiptStatusTO.java | 2 +- .../model/response}/StatusResponseTO.java | 4 +- .../model/response}/StringResponseTO.java | 2 +- .../model/response/TxERC20ResponseTO.java | 11 ++ .../model/response/TxERC721ResponseTO.java | 11 ++ .../model/response}/TxInternalResponseTO.java | 4 +- .../model/response}/TxResponseTO.java | 4 +- .../model/response/UncleBlockResponseTO.java | 16 ++ .../api/etherscan/util/BasicUtils.java | 32 ++-- src/test/java/io/api/ApiRunner.java | 60 -------- .../io/api/etherscan/EtherScanApiTest.java | 81 ---------- .../java/io/api/manager/QueueManagerTest.java | 55 ------- .../goodforgod/api/etherscan/ApiRunner.java | 66 ++++++++ .../api/etherscan/EtherScanAPITests.java | 76 ++++++++++ .../account/AccountBalanceListTest.java | 12 +- .../etherscan/account/AccountBalanceTest.java | 15 +- .../account/AccountMinedBlocksTest.java | 20 +-- .../account/AccountTokenBalanceTest.java | 22 +-- .../account/AccountTxERC20Test.java} | 26 ++-- .../account/AccountTxInternalByHashTest.java | 16 +- .../account/AccountTxInternalTest.java | 10 +- .../account/AccountTxRc721TokenTest.java | 26 ++-- .../api/etherscan/account/AccountTxsTest.java | 11 +- .../api/etherscan/block/BlockApiTest.java | 14 +- .../etherscan/contract/ContractApiTest.java | 10 +- .../etherscan/logs/LogQueryBuilderTest.java | 141 +++++++++--------- .../api/etherscan/logs/LogsApiTest.java | 27 ++-- .../SemaphoreRequestQueueManagerTest.java | 56 +++++++ .../etherscan/proxy/ProxyBlockApiTest.java | 19 +-- .../proxy/ProxyBlockLastNoApiTest.java | 4 +- .../proxy/ProxyBlockUncleApiTest.java | 6 +- .../api/etherscan/proxy/ProxyCallApiTest.java | 20 +-- .../api/etherscan/proxy/ProxyCodeApiTest.java | 11 +- .../api/etherscan/proxy/ProxyGasApiTest.java | 8 +- .../etherscan/proxy/ProxyStorageApiTest.java | 8 +- .../api/etherscan/proxy/ProxyTxApiTest.java | 10 +- .../etherscan/proxy/ProxyTxCountApiTest.java | 8 +- .../proxy/ProxyTxReceiptApiTest.java | 10 +- .../proxy/ProxyTxSendRawApiTest.java | 12 +- .../statistic/StatisticPriceApiTest.java | 6 +- .../statistic/StatisticSupplyApiTest.java | 6 +- .../StatisticTokenSupplyApiTest.java | 9 +- .../api/etherscan}/support/AddressUtil.java | 2 +- .../transaction/TransactionExecApiTest.java | 16 +- .../TransactionReceiptApiTest.java | 14 +- .../api/etherscan}/util/BasicUtilsTests.java | 16 +- 158 files changed, 2083 insertions(+), 1854 deletions(-) delete mode 100644 src/main/java/io/api/etherscan/core/IBlockApi.java delete mode 100644 src/main/java/io/api/etherscan/core/IContractApi.java delete mode 100644 src/main/java/io/api/etherscan/core/ILogsApi.java delete mode 100644 src/main/java/io/api/etherscan/core/IStatisticApi.java delete mode 100644 src/main/java/io/api/etherscan/core/impl/BasicProvider.java delete mode 100644 src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java delete mode 100644 src/main/java/io/api/etherscan/core/impl/EtherScanApi.java delete mode 100644 src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java delete mode 100644 src/main/java/io/api/etherscan/error/ApiException.java delete mode 100644 src/main/java/io/api/etherscan/error/ApiKeyException.java delete mode 100644 src/main/java/io/api/etherscan/error/ApiTimeoutException.java delete mode 100644 src/main/java/io/api/etherscan/error/ConnectionException.java delete mode 100644 src/main/java/io/api/etherscan/error/EtherScanException.java delete mode 100644 src/main/java/io/api/etherscan/error/EventModelException.java delete mode 100644 src/main/java/io/api/etherscan/error/InvalidAddressException.java delete mode 100644 src/main/java/io/api/etherscan/error/InvalidDataHexException.java delete mode 100644 src/main/java/io/api/etherscan/error/InvalidTxHashException.java delete mode 100644 src/main/java/io/api/etherscan/error/LogQueryException.java delete mode 100644 src/main/java/io/api/etherscan/error/RateLimitException.java delete mode 100644 src/main/java/io/api/etherscan/manager/IQueueManager.java delete mode 100644 src/main/java/io/api/etherscan/manager/impl/QueueManager.java delete mode 100644 src/main/java/io/api/etherscan/model/EthNetwork.java delete mode 100644 src/main/java/io/api/etherscan/model/Uncle.java delete mode 100644 src/main/java/io/api/etherscan/model/UncleBlock.java delete mode 100644 src/main/java/io/api/etherscan/model/query/IQueryBuilder.java delete mode 100644 src/main/java/io/api/etherscan/model/query/impl/LogQuery.java delete mode 100644 src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java delete mode 100644 src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java delete mode 100644 src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java rename src/main/java/io/{api/etherscan/core/IAccountApi.java => goodforgod/api/etherscan/AccountAPI.java} (55%) rename src/main/java/io/{api/etherscan/core/impl/AccountApiProvider.java => goodforgod/api/etherscan/AccountAPIProvider.java} (59%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/BasicProvider.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/BlockAPI.java rename src/main/java/io/{api/etherscan/core/impl/BlockApiProvider.java => goodforgod/api/etherscan/BlockAPIProvider.java} (53%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/ContractAPI.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/EthNetwork.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/EthNetworks.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/LogsAPI.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java rename src/main/java/io/{api/etherscan/core/IProxyApi.java => goodforgod/api/etherscan/ProxyAPI.java} (63%) rename src/main/java/io/{api/etherscan/core/impl/ProxyApiProvider.java => goodforgod/api/etherscan/ProxyAPIProvider.java} (75%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java rename src/main/java/io/{api/etherscan/core/impl/StatisticApiProvider.java => goodforgod/api/etherscan/StatisticAPIProvider.java} (54%) rename src/main/java/io/{api/etherscan/core/ITransactionApi.java => goodforgod/api/etherscan/TransactionAPI.java} (51%) rename src/main/java/io/{api/etherscan/core/impl/TransactionApiProvider.java => goodforgod/api/etherscan/TransactionAPIProvider.java} (60%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidAddressException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidDataHexException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidTxHashException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanKeyException.java rename src/main/java/io/{api/etherscan/error/ParseException.java => goodforgod/api/etherscan/error/EtherScanParseException.java} (52%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanRateLimitException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java rename src/main/java/io/{api/etherscan/executor/IHttpExecutor.java => goodforgod/api/etherscan/executor/EthHttpClient.java} (84%) rename src/main/java/io/{api/etherscan/executor/impl/HttpExecutor.java => goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java} (66%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java rename src/main/java/io/{api/etherscan/manager/impl/FakeQueueManager.java => goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java} (65%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Abi.java (94%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Balance.java (94%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/BaseTx.java (97%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Block.java (94%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Log.java (98%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Price.java (98%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Status.java (96%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Supply.java (81%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/TokenBalance.java (96%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Tx.java (96%) rename src/main/java/io/{api/etherscan/model/TxToken.java => goodforgod/api/etherscan/model/TxERC20.java} (93%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java rename src/main/java/io/{ => goodforgod}/api/etherscan/model/TxInternal.java (97%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/Wei.java (96%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/BlockProxy.java (98%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/ReceiptProxy.java (96%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/TxProxy.java (97%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/utility/BaseProxyTO.java (86%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/utility/BlockProxyTO.java (63%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/utility/ErrorProxyTO.java (81%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/utility/StringProxyTO.java (77%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/utility/TxInfoProxyTO.java (63%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/proxy/utility/TxProxyTO.java (62%) rename src/main/java/io/{ => goodforgod}/api/etherscan/model/query/LogOp.java (86%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java rename src/main/java/io/{api/etherscan/model/query/impl/BaseLogQuery.java => goodforgod/api/etherscan/model/query/LogQueryParams.java} (79%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicBuilder.java rename src/main/java/io/{api/etherscan/model/query/impl => goodforgod/api/etherscan/model/query}/LogTopicQuadro.java (71%) rename src/main/java/io/{api/etherscan/model/query/impl => goodforgod/api/etherscan/model/query}/LogTopicSingle.java (53%) rename src/main/java/io/{api/etherscan/model/query/impl => goodforgod/api/etherscan/model/query}/LogTopicTriple.java (68%) rename src/main/java/io/{api/etherscan/model/query/impl => goodforgod/api/etherscan/model/query}/LogTopicTuple.java (63%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/BalanceResponseTO.java (70%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/BalanceTO.java (83%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/BaseListResponseTO.java (82%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/BaseResponseTO.java (77%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/BlockParam.java (88%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/BlockResponseTO.java (54%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/LogResponseTO.java (54%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/PriceResponseTO.java (66%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/ReceiptStatusResponseTO.java (81%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/ReceiptStatusTO.java (77%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/StatusResponseTO.java (66%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/StringResponseTO.java (79%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/TxInternalResponseTO.java (55%) rename src/main/java/io/{api/etherscan/model/utility => goodforgod/api/etherscan/model/response}/TxResponseTO.java (54%) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/UncleBlockResponseTO.java rename src/main/java/io/{ => goodforgod}/api/etherscan/util/BasicUtils.java (80%) delete mode 100644 src/test/java/io/api/ApiRunner.java delete mode 100644 src/test/java/io/api/etherscan/EtherScanApiTest.java delete mode 100644 src/test/java/io/api/manager/QueueManagerTest.java create mode 100644 src/test/java/io/goodforgod/api/etherscan/ApiRunner.java create mode 100644 src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountBalanceListTest.java (88%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountBalanceTest.java (67%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountMinedBlocksTest.java (65%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountTokenBalanceTest.java (63%) rename src/test/java/io/{api/etherscan/account/AccountTxTokenTest.java => goodforgod/api/etherscan/account/AccountTxERC20Test.java} (72%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountTxInternalByHashTest.java (81%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountTxInternalTest.java (86%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountTxRc721TokenTest.java (65%) rename src/test/java/io/{ => goodforgod}/api/etherscan/account/AccountTxsTest.java (87%) rename src/test/java/io/{ => goodforgod}/api/etherscan/block/BlockApiTest.java (82%) rename src/test/java/io/{ => goodforgod}/api/etherscan/contract/ContractApiTest.java (78%) rename src/test/java/io/{ => goodforgod}/api/etherscan/logs/LogQueryBuilderTest.java (59%) rename src/test/java/io/{ => goodforgod}/api/etherscan/logs/LogsApiTest.java (67%) create mode 100644 src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyBlockApiTest.java (76%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyBlockLastNoApiTest.java (75%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyBlockUncleApiTest.java (83%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyCallApiTest.java (54%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyCodeApiTest.java (66%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyGasApiTest.java (79%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyStorageApiTest.java (76%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyTxApiTest.java (88%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyTxCountApiTest.java (81%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyTxReceiptApiTest.java (85%) rename src/test/java/io/{ => goodforgod}/api/etherscan/proxy/ProxyTxSendRawApiTest.java (62%) rename src/test/java/io/{ => goodforgod}/api/etherscan/statistic/StatisticPriceApiTest.java (81%) rename src/test/java/io/{ => goodforgod}/api/etherscan/statistic/StatisticSupplyApiTest.java (82%) rename src/test/java/io/{ => goodforgod}/api/etherscan/statistic/StatisticTokenSupplyApiTest.java (67%) rename src/test/java/io/{api => goodforgod/api/etherscan}/support/AddressUtil.java (98%) rename src/test/java/io/{ => goodforgod}/api/etherscan/transaction/TransactionExecApiTest.java (66%) rename src/test/java/io/{ => goodforgod}/api/etherscan/transaction/TransactionReceiptApiTest.java (61%) rename src/test/java/io/{api => goodforgod/api/etherscan}/util/BasicUtilsTests.java (75%) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 613a39e..e4c7620 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '11' ] + java: [ '11', '17' ] name: Java ${{ matrix.java }} setup steps: diff --git a/README.md b/README.md index 258b4d8..cd981ca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Java EtherScan API +[](https://openjdk.org/projects/jdk8/) [](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3A%22Java+CI%22) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) @@ -14,7 +15,7 @@ Library supports all available EtherScan *API* calls for all available *Ethereum **Gradle** ```groovy -implementation "com.github.goodforgod:java-etherscan-api:1.3.1" +implementation "com.github.goodforgod:java-etherscan-api:2.0.0-SNAPSHOT" ``` **Maven** @@ -22,7 +23,7 @@ implementation "com.github.goodforgod:java-etherscan-api:1.3.1" <dependency> <groupId>com.github.goodforgod</groupId> <artifactId>java-etherscan-api</artifactId> - <version>1.3.1</version> + <version>2.0.0-SNAPSHOT</version> </dependency> ``` diff --git a/build.gradle b/build.gradle index 410d374..3d766c2 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id "maven-publish" id "org.sonarqube" version "3.3" - id "com.diffplug.spotless" version "6.1.0" + id "com.diffplug.spotless" version "6.12.0" } repositories { @@ -19,13 +19,12 @@ sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 dependencies { - implementation "org.jetbrains:annotations:23.0.0" - implementation "com.google.code.gson:gson:2.9.0" - implementation "io.goodforgod:gson-configuration:1.4.1" + compileOnly "org.jetbrains:annotations:23.0.0" + implementation "io.goodforgod:gson-configuration:2.0.0" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.2" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2" - testImplementation "org.junit.jupiter:junit-jupiter-params:5.8.2" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.3" + testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.3" + testImplementation "org.junit.jupiter:junit-jupiter-params:5.9.3" } test { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb87..070cb70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/io/api/etherscan/core/IBlockApi.java b/src/main/java/io/api/etherscan/core/IBlockApi.java deleted file mode 100644 index df4ae96..0000000 --- a/src/main/java/io/api/etherscan/core/IBlockApi.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.api.etherscan.core; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.UncleBlock; -import java.util.Optional; -import org.jetbrains.annotations.NotNull; - -/** - * EtherScan - API Descriptions https://etherscan.io/apis#blocks - * - * @author GoodforGod - * @since 30.10.2018 - */ -public interface IBlockApi { - - /** - * Return uncle blocks - * - * @param blockNumber block number form 0 to last - * @return optional uncle blocks - * @throws ApiException parent exception class - */ - @NotNull - Optional<UncleBlock> uncles(long blockNumber) throws ApiException; -} diff --git a/src/main/java/io/api/etherscan/core/IContractApi.java b/src/main/java/io/api/etherscan/core/IContractApi.java deleted file mode 100644 index 3e9388d..0000000 --- a/src/main/java/io/api/etherscan/core/IContractApi.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.api.etherscan.core; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.Abi; -import org.jetbrains.annotations.NotNull; - -/** - * EtherScan - API Descriptions https://etherscan.io/apis#contracts - * - * @author GoodforGod - * @since 28.10.2018 - */ -public interface IContractApi { - - /** - * Get Verified Contract Sources - * - * @param address to verify - * @return ABI verified - * @throws ApiException parent exception class - */ - @NotNull - Abi contractAbi(String address) throws ApiException; -} diff --git a/src/main/java/io/api/etherscan/core/ILogsApi.java b/src/main/java/io/api/etherscan/core/ILogsApi.java deleted file mode 100644 index 7ecd986..0000000 --- a/src/main/java/io/api/etherscan/core/ILogsApi.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.api.etherscan.core; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.Log; -import io.api.etherscan.model.query.impl.LogQuery; -import java.util.List; -import org.jetbrains.annotations.NotNull; - -/** - * EtherScan - API Descriptions https://etherscan.io/apis#logs - * - * @author GoodforGod - * @since 30.10.2018 - */ -public interface ILogsApi { - - /** - * alternative to the native eth_getLogs Read at EtherScan API description for full info! - * - * @param query build log query - * @return logs according to query - * @throws ApiException parent exception class - * @see io.api.etherscan.model.query.impl.LogQueryBuilder - */ - @NotNull - List<Log> logs(LogQuery query) throws ApiException; -} diff --git a/src/main/java/io/api/etherscan/core/IStatisticApi.java b/src/main/java/io/api/etherscan/core/IStatisticApi.java deleted file mode 100644 index ffd633d..0000000 --- a/src/main/java/io/api/etherscan/core/IStatisticApi.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.api.etherscan.core; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.Price; -import io.api.etherscan.model.Supply; -import java.math.BigInteger; -import org.jetbrains.annotations.NotNull; - -/** - * EtherScan - API Descriptions https://etherscan.io/apis#stats - * - * @author GoodforGod - * @since 30.10.2018 - */ -public interface IStatisticApi { - - /** - * ERC20 token total Supply - * - * @param contract contract address - * @return token supply for specified contract - * @throws ApiException parent exception class - */ - @NotNull - BigInteger supply(String contract) throws ApiException; - - /** - * Eth total supply - * - * @return total ETH supply for moment - * @throws ApiException parent exception class - */ - @NotNull - Supply supply() throws ApiException; - - /** - * Eth last USD and BTC price - * - * @return last usd/btc price for ETH - * @throws ApiException parent exception class - */ - @NotNull - Price lastPrice() throws ApiException; -} diff --git a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java b/src/main/java/io/api/etherscan/core/impl/BasicProvider.java deleted file mode 100644 index ada41bb..0000000 --- a/src/main/java/io/api/etherscan/core/impl/BasicProvider.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.api.etherscan.core.impl; - -import com.google.gson.*; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.error.ParseException; -import io.api.etherscan.error.RateLimitException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.utility.StringResponseTO; -import io.api.etherscan.util.BasicUtils; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * Base provider for API Implementations - * - * @author GoodforGod - * @see EtherScanApi - * @since 28.10.2018 - */ -abstract class BasicProvider { - - static final int MAX_END_BLOCK = Integer.MAX_VALUE; - static final int MIN_START_BLOCK = 0; - - static final String ACT_PREFIX = "&action="; - - private final String module; - private final String baseUrl; - private final IHttpExecutor executor; - private final IQueueManager queue; - private final Gson gson; - - BasicProvider(final IQueueManager queue, - final String module, - final String baseUrl, - final IHttpExecutor executor) { - this.queue = queue; - this.module = "&module=" + module; - this.baseUrl = baseUrl; - this.executor = executor; - this.gson = new GsonBuilder() - .registerTypeAdapter(LocalDateTime.class, (JsonSerializer<LocalDateTime>) (src, t, c) -> new JsonPrimitive("")) - .registerTypeAdapter(LocalDate.class, (JsonSerializer<LocalDate>) (src, t, context) -> new JsonPrimitive("")) - .registerTypeAdapter(LocalDateTime.class, (JsonDeserializer<LocalDateTime>) (json, t, c) -> null) - .registerTypeAdapter(LocalDate.class, (JsonDeserializer<LocalDate>) (json, t, c) -> null) - .create(); - } - - <T> T convert(final String json, final Class<T> tClass) { - try { - final T t = gson.fromJson(json, tClass); - if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith("Max rate limit reached")) { - throw new RateLimitException(((StringResponseTO) t).getResult()); - } - - return t; - } catch (Exception e) { - try { - final Map<String, Object> map = gson.fromJson(json, Map.class); - final Object result = map.get("result"); - if (result instanceof String && ((String) result).startsWith("Max rate limit reached")) - throw new RateLimitException(((String) result)); - - throw new ParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); - } catch (ApiException ex) { - throw ex; - } catch (Exception ex) { - throw new ParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); - } - } - } - - String getRequest(final String urlParameters) { - queue.takeTurn(); - final String url = baseUrl + module + urlParameters; - final String result = executor.get(url); - if (BasicUtils.isEmpty(result)) - throw new EtherScanException("Server returned null value for GET request at URL - " + url); - - return result; - } - - String postRequest(final String urlParameters, final String dataToPost) { - queue.takeTurn(); - final String url = baseUrl + module + urlParameters; - return executor.post(url, dataToPost); - } - - <T> T getRequest(final String urlParameters, final Class<T> tClass) { - return convert(getRequest(urlParameters), tClass); - } - - <T> T postRequest(final String urlParameters, final String dataToPost, final Class<T> tClass) { - return convert(postRequest(urlParameters, dataToPost), tClass); - } -} diff --git a/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java b/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java deleted file mode 100644 index 2e7cbea..0000000 --- a/src/main/java/io/api/etherscan/core/impl/ContractApiProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.IContractApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.Abi; -import io.api.etherscan.model.utility.StringResponseTO; -import io.api.etherscan.util.BasicUtils; -import org.jetbrains.annotations.NotNull; - -/** - * Contract API Implementation - * - * @see IContractApi - * @author GoodforGod - * @since 28.10.2018 - */ -public class ContractApiProvider extends BasicProvider implements IContractApi { - - private static final String ACT_ABI_PARAM = ACT_PREFIX + "getabi"; - - private static final String ADDRESS_PARAM = "&address="; - - ContractApiProvider(final IQueueManager queueManager, - final String baseUrl, - final IHttpExecutor executor) { - super(queueManager, "contract", baseUrl, executor); - } - - @NotNull - @Override - public Abi contractAbi(final String address) throws ApiException { - BasicUtils.validateAddress(address); - - final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; - final StringResponseTO response = getRequest(urlParam, StringResponseTO.class); - if (response.getStatus() != 1 && "NOTOK".equals(response.getMessage())) - throw new EtherScanException(response); - - return (response.getResult().startsWith("Contract sou")) - ? Abi.nonVerified() - : Abi.verified(response.getResult()); - } -} diff --git a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java b/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java deleted file mode 100644 index aac428b..0000000 --- a/src/main/java/io/api/etherscan/core/impl/EtherScanApi.java +++ /dev/null @@ -1,141 +0,0 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.*; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.ApiKeyException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.executor.impl.HttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.manager.impl.FakeQueueManager; -import io.api.etherscan.manager.impl.QueueManager; -import io.api.etherscan.model.EthNetwork; -import io.api.etherscan.util.BasicUtils; -import java.util.function.Supplier; -import org.jetbrains.annotations.NotNull; - -/** - * EtherScan full API Description https://etherscan.io/apis - * - * @author GoodforGod - * @since 28.10.2018 - */ -public class EtherScanApi implements AutoCloseable { - - private static final Supplier<IHttpExecutor> DEFAULT_SUPPLIER = HttpExecutor::new; - - public static final String DEFAULT_KEY = "YourApiKeyToken"; - - private final IQueueManager queueManager; - private final IAccountApi account; - private final IBlockApi block; - private final IContractApi contract; - private final ILogsApi logs; - private final IProxyApi proxy; - private final IStatisticApi stats; - private final ITransactionApi txs; - - public EtherScanApi() { - this(DEFAULT_KEY, EthNetwork.MAINNET); - } - - public EtherScanApi(final EthNetwork network) { - this(DEFAULT_KEY, network); - } - - public EtherScanApi(final String apiKey) { - this(apiKey, EthNetwork.MAINNET); - } - - public EtherScanApi(final EthNetwork network, - final Supplier<IHttpExecutor> executorSupplier) { - this(DEFAULT_KEY, network, executorSupplier); - } - - public EtherScanApi(final String apiKey, - final EthNetwork network, - final IQueueManager queue) { - this(apiKey, network, DEFAULT_SUPPLIER, queue); - } - - public EtherScanApi(final String apiKey, - final EthNetwork network) { - this(apiKey, network, DEFAULT_SUPPLIER); - } - - public EtherScanApi(final String apiKey, - final EthNetwork network, - final Supplier<IHttpExecutor> executorSupplier) { - this(apiKey, network, executorSupplier, - DEFAULT_KEY.equals(apiKey) - ? QueueManager.DEFAULT_KEY_QUEUE - : new FakeQueueManager()); - } - - public EtherScanApi(final String apiKey, - final EthNetwork network, - final Supplier<IHttpExecutor> executorSupplier, - final IQueueManager queue) { - if (BasicUtils.isBlank(apiKey)) - throw new ApiKeyException("API key can not be null or empty"); - - if (network == null) - throw new ApiException("Ethereum Network is set to NULL value"); - - // EtherScan 1request\5sec limit support by queue manager - final IHttpExecutor executor = executorSupplier.get(); - - final String ending = EthNetwork.TOBALABA.equals(network) - ? "com" - : "io"; - final String baseUrl = "https://" + network.getDomain() + ".etherscan." + ending + "/api" + "?apikey=" + apiKey; - - this.queueManager = queue; - this.account = new AccountApiProvider(queue, baseUrl, executor); - this.block = new BlockApiProvider(queue, baseUrl, executor); - this.contract = new ContractApiProvider(queue, baseUrl, executor); - this.logs = new LogsApiProvider(queue, baseUrl, executor); - this.proxy = new ProxyApiProvider(queue, baseUrl, executor); - this.stats = new StatisticApiProvider(queue, baseUrl, executor); - this.txs = new TransactionApiProvider(queue, baseUrl, executor); - } - - @NotNull - public IAccountApi account() { - return account; - } - - @NotNull - public IContractApi contract() { - return contract; - } - - @NotNull - public ITransactionApi txs() { - return txs; - } - - @NotNull - public IBlockApi block() { - return block; - } - - @NotNull - public ILogsApi logs() { - return logs; - } - - @NotNull - public IProxyApi proxy() { - return proxy; - } - - @NotNull - public IStatisticApi stats() { - return stats; - } - - @Override - public void close() throws Exception { - queueManager.close(); - } -} diff --git a/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java b/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java deleted file mode 100644 index 04f9bb7..0000000 --- a/src/main/java/io/api/etherscan/core/impl/LogsApiProvider.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.ILogsApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.Log; -import io.api.etherscan.model.query.impl.LogQuery; -import io.api.etherscan.model.utility.LogResponseTO; -import io.api.etherscan.util.BasicUtils; -import java.util.Collections; -import java.util.List; -import org.jetbrains.annotations.NotNull; - -/** - * Logs API Implementation - * - * @see ILogsApi - * @author GoodforGod - * @since 28.10.2018 - */ -public class LogsApiProvider extends BasicProvider implements ILogsApi { - - private static final String ACT_LOGS_PARAM = ACT_PREFIX + "getLogs"; - - LogsApiProvider(final IQueueManager queue, - final String baseUrl, - final IHttpExecutor executor) { - super(queue, "logs", baseUrl, executor); - } - - @NotNull - @Override - public List<Log> logs(final LogQuery query) throws ApiException { - final String urlParams = ACT_LOGS_PARAM + query.getParams(); - final LogResponseTO response = getRequest(urlParams, LogResponseTO.class); - BasicUtils.validateTxResponse(response); - - return (BasicUtils.isEmpty(response.getResult())) - ? Collections.emptyList() - : response.getResult(); - } -} diff --git a/src/main/java/io/api/etherscan/error/ApiException.java b/src/main/java/io/api/etherscan/error/ApiException.java deleted file mode 100644 index 33e4228..0000000 --- a/src/main/java/io/api/etherscan/error/ApiException.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 30.10.2018 - */ -public class ApiException extends RuntimeException { - - public ApiException(String message) { - super(message); - } - - public ApiException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/api/etherscan/error/ApiKeyException.java b/src/main/java/io/api/etherscan/error/ApiKeyException.java deleted file mode 100644 index 4e22934..0000000 --- a/src/main/java/io/api/etherscan/error/ApiKeyException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 05.11.2018 - */ -public class ApiKeyException extends ApiException { - - public ApiKeyException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/error/ApiTimeoutException.java b/src/main/java/io/api/etherscan/error/ApiTimeoutException.java deleted file mode 100644 index 39b6e93..0000000 --- a/src/main/java/io/api/etherscan/error/ApiTimeoutException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 12.11.2018 - */ -public class ApiTimeoutException extends ApiException { - - public ApiTimeoutException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/api/etherscan/error/ConnectionException.java b/src/main/java/io/api/etherscan/error/ConnectionException.java deleted file mode 100644 index 96a881c..0000000 --- a/src/main/java/io/api/etherscan/error/ConnectionException.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class ConnectionException extends ApiException { - - public ConnectionException(String message) { - super(message); - } - - public ConnectionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/api/etherscan/error/EtherScanException.java b/src/main/java/io/api/etherscan/error/EtherScanException.java deleted file mode 100644 index cb7dd7f..0000000 --- a/src/main/java/io/api/etherscan/error/EtherScanException.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.api.etherscan.error; - -import io.api.etherscan.model.utility.BaseResponseTO; -import io.api.etherscan.model.utility.StringResponseTO; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class EtherScanException extends ApiException { - - public EtherScanException(BaseResponseTO response) { - this(response.getMessage() + ", with status: " + response.getStatus()); - } - - public EtherScanException(StringResponseTO response) { - this(response.getResult() + ", with status: " + response.getStatus() + ", with message: " + response.getMessage()); - } - - public EtherScanException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/error/EventModelException.java b/src/main/java/io/api/etherscan/error/EventModelException.java deleted file mode 100644 index feb60be..0000000 --- a/src/main/java/io/api/etherscan/error/EventModelException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -public class EventModelException extends ApiException { - - public EventModelException(String message) { - super(message); - } - - public EventModelException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/api/etherscan/error/InvalidAddressException.java b/src/main/java/io/api/etherscan/error/InvalidAddressException.java deleted file mode 100644 index 9a0c143..0000000 --- a/src/main/java/io/api/etherscan/error/InvalidAddressException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class InvalidAddressException extends ApiException { - - public InvalidAddressException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/error/InvalidDataHexException.java b/src/main/java/io/api/etherscan/error/InvalidDataHexException.java deleted file mode 100644 index dd12cb9..0000000 --- a/src/main/java/io/api/etherscan/error/InvalidDataHexException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 02.11.2018 - */ -public class InvalidDataHexException extends ApiException { - - public InvalidDataHexException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/error/InvalidTxHashException.java b/src/main/java/io/api/etherscan/error/InvalidTxHashException.java deleted file mode 100644 index aba32c1..0000000 --- a/src/main/java/io/api/etherscan/error/InvalidTxHashException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 02.11.2018 - */ -public class InvalidTxHashException extends ApiException { - - public InvalidTxHashException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/error/LogQueryException.java b/src/main/java/io/api/etherscan/error/LogQueryException.java deleted file mode 100644 index 504219f..0000000 --- a/src/main/java/io/api/etherscan/error/LogQueryException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author GoodforGod - * @since 31.10.2018 - */ -public class LogQueryException extends ApiException { - - public LogQueryException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/error/RateLimitException.java b/src/main/java/io/api/etherscan/error/RateLimitException.java deleted file mode 100644 index c29f54d..0000000 --- a/src/main/java/io/api/etherscan/error/RateLimitException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.api.etherscan.error; - -/** - * @author iSnow - * @since 2020-10-06 - */ -public class RateLimitException extends ApiException { - - public RateLimitException(String message) { - super(message); - } -} diff --git a/src/main/java/io/api/etherscan/manager/IQueueManager.java b/src/main/java/io/api/etherscan/manager/IQueueManager.java deleted file mode 100644 index 98a3172..0000000 --- a/src/main/java/io/api/etherscan/manager/IQueueManager.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.api.etherscan.manager; - -/** - * Queue manager to support API limits (EtherScan 5request\sec limit) Managers grants turn if the - * limit is not exhausted And resets queue each set period - * - * @author GoodforGod - * @since 30.10.2018 - */ -public interface IQueueManager extends AutoCloseable { - - /** - * Waits in queue for chance to take turn - */ - void takeTurn(); -} diff --git a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java b/src/main/java/io/api/etherscan/manager/impl/QueueManager.java deleted file mode 100644 index d3a44de..0000000 --- a/src/main/java/io/api/etherscan/manager/impl/QueueManager.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.api.etherscan.manager.impl; - -import io.api.etherscan.manager.IQueueManager; -import java.util.concurrent.*; - -/** - * Queue Semaphore implementation with size and reset time as params - * - * @see IQueueManager - * @author GoodforGod - * @since 30.10.2018 - */ -public class QueueManager implements IQueueManager, AutoCloseable { - - public static final QueueManager DEFAULT_KEY_QUEUE = new QueueManager(1, 5200L, 5200L, 0); - public static final QueueManager PERSONAL_KEY_QUEUE = new QueueManager(5, 1100L, 1100L, 5); - - private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - private final Semaphore semaphore; - private final long queueResetTimeInMillis; - - public QueueManager(int size, int resetInSec) { - this(size, resetInSec, resetInSec); - } - - public QueueManager(int size, int queueResetTimeInSec, int delayInSec) { - this(size, queueResetTimeInSec, delayInSec, size); - } - - public QueueManager(int size, int queueResetTimeInSec, int delayInSec, int initialSize) { - this(size, - (long) queueResetTimeInSec * 1000, - (long) delayInSec * 1000, - initialSize); - } - - public QueueManager(int size, long queueResetTimeInMillis, long delayInMillis, int initialSize) { - this.queueResetTimeInMillis = queueResetTimeInMillis; - this.semaphore = new Semaphore(initialSize); - this.executorService.scheduleAtFixedRate(releaseLocks(size), delayInMillis, queueResetTimeInMillis, - TimeUnit.MILLISECONDS); - } - - @SuppressWarnings("java:S899") - @Override - public void takeTurn() { - try { - semaphore.tryAcquire(queueResetTimeInMillis, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private Runnable releaseLocks(int toRelease) { - return () -> semaphore.release(toRelease); - } - - @Override - public void close() { - executorService.shutdown(); - } -} diff --git a/src/main/java/io/api/etherscan/model/EthNetwork.java b/src/main/java/io/api/etherscan/model/EthNetwork.java deleted file mode 100644 index 6144cf1..0000000 --- a/src/main/java/io/api/etherscan/model/EthNetwork.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.api.etherscan.model; - -/** - * @author GoodforGod - * @since 28.10.2018 - */ -public enum EthNetwork { - - MAINNET("api"), - ROPSTEN("api-ropsten"), - KOVAN("api-kovan"), - TOBALABA("api-tobalaba"), - GORLI("api-goerli"), - RINKEBY("api-rinkeby"); - - private final String domain; - - EthNetwork(String domain) { - this.domain = domain; - } - - public String getDomain() { - return domain; - } -} diff --git a/src/main/java/io/api/etherscan/model/Uncle.java b/src/main/java/io/api/etherscan/model/Uncle.java deleted file mode 100644 index 7dea648..0000000 --- a/src/main/java/io/api/etherscan/model/Uncle.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.api.etherscan.model; - -import java.math.BigInteger; - -/** - * @author GoodforGod - * @since 30.10.2018 - */ -public class Uncle { - - private String miner; - private BigInteger blockreward; - private int unclePosition; - - // <editor-fold desc="Getters"> - public String getMiner() { - return miner; - } - - public BigInteger getBlockreward() { - return blockreward; - } - - public int getUnclePosition() { - return unclePosition; - } - // </editor-fold> - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - Uncle uncle = (Uncle) o; - - if (unclePosition != uncle.unclePosition) - return false; - if (miner != null - ? !miner.equals(uncle.miner) - : uncle.miner != null) - return false; - return blockreward != null - ? blockreward.equals(uncle.blockreward) - : uncle.blockreward == null; - } - - @Override - public int hashCode() { - int result = miner != null - ? miner.hashCode() - : 0; - result = 31 * result + (blockreward != null - ? blockreward.hashCode() - : 0); - result = 31 * result + unclePosition; - return result; - } - - @Override - public String toString() { - return "Uncle{" + - "miner='" + miner + '\'' + - ", blockreward=" + blockreward + - ", unclePosition=" + unclePosition + - '}'; - } -} diff --git a/src/main/java/io/api/etherscan/model/UncleBlock.java b/src/main/java/io/api/etherscan/model/UncleBlock.java deleted file mode 100644 index ff30451..0000000 --- a/src/main/java/io/api/etherscan/model/UncleBlock.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.api.etherscan.model; - -import io.api.etherscan.util.BasicUtils; -import java.util.List; - -/** - * @author GoodforGod - * @since 30.10.2018 - */ -public class UncleBlock extends Block { - - private String blockMiner; - private List<Uncle> uncles; - private String uncleInclusionReward; - - // <editor-fold desc="Getters"> - public boolean isEmpty() { - return getBlockNumber() == 0 && getBlockReward() == null - && getTimeStamp() == null - && BasicUtils.isEmpty(blockMiner); - } - - public String getBlockMiner() { - return blockMiner; - } - - public List<Uncle> getUncles() { - return uncles; - } - - public String getUncleInclusionReward() { - return uncleInclusionReward; - } - // </editor-fold> - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - if (!super.equals(o)) - return false; - - UncleBlock that = (UncleBlock) o; - - return getBlockNumber() != 0 && getBlockNumber() == that.getBlockNumber(); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = (int) (31 * result + getBlockNumber()); - return result; - } - - @Override - public String toString() { - return "UncleBlock{" + - "blockMiner='" + blockMiner + '\'' + - ", uncles=" + uncles + - ", uncleInclusionReward='" + uncleInclusionReward + '\'' + - '}'; - } -} diff --git a/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java b/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java deleted file mode 100644 index 6a76c62..0000000 --- a/src/main/java/io/api/etherscan/model/query/IQueryBuilder.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.api.etherscan.model.query; - -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.impl.LogQuery; - -/** - * Builder, part of The Event Log API - * - * @author GoodforGod - * @since 31.10.2018 - */ -public interface IQueryBuilder { - - LogQuery build() throws LogQueryException; -} diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java b/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java deleted file mode 100644 index 31d8c13..0000000 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQuery.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.api.etherscan.model.query.impl; - -import io.api.etherscan.core.ILogsApi; - -/** - * Final builded container for The Event Log API - * EtherScan - API Descriptions https://etherscan.io/apis#logs - * - * @see LogQueryBuilder - * @see ILogsApi - * @author GoodforGod - * @since 31.10.2018 - */ -public class LogQuery { - - /** - * Final request parameter for api call - */ - private final String params; - - LogQuery(String params) { - this.params = params; - } - - public String getParams() { - return params; - } -} diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java b/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java deleted file mode 100644 index 44ca825..0000000 --- a/src/main/java/io/api/etherscan/model/query/impl/LogQueryBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.api.etherscan.model.query.impl; - -import io.api.etherscan.core.ILogsApi; -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.IQueryBuilder; -import io.api.etherscan.util.BasicUtils; - -/** - * Builder for The Event Log API - * - * @see ILogsApi - * @author GoodforGod - * @since 31.10.2018 - */ -public class LogQueryBuilder implements IQueryBuilder { - - private static final long MIN_BLOCK = 0; - private static final long MAX_BLOCK = 99999999999L; - - private final String address; - private final long startBlock, endBlock; - - private LogQueryBuilder(String address, long startBlock, long endBlock) { - this.address = address; - this.startBlock = startBlock; - this.endBlock = endBlock; - } - - public static LogQueryBuilder with(String address) { - return with(address, MIN_BLOCK); - } - - public static LogQueryBuilder with(String address, long startBlock) { - return with(address, startBlock, MAX_BLOCK); - } - - public static LogQueryBuilder with(String address, long startBlock, long endBlock) { - BasicUtils.validateAddress(address); - return new LogQueryBuilder(address, startBlock, endBlock); - } - - public LogTopicSingle topic(String topic0) { - if (BasicUtils.isNotHex(topic0)) - throw new LogQueryException("topic0 can not be empty or non hex."); - return new LogTopicSingle(address, startBlock, endBlock, topic0); - } - - public LogTopicTuple topic(String topic0, String topic1) { - if (BasicUtils.isNotHex(topic0)) - throw new LogQueryException("topic0 can not be empty or non hex."); - if (BasicUtils.isNotHex(topic1)) - throw new LogQueryException("topic1 can not be empty or non hex."); - return new LogTopicTuple(address, startBlock, endBlock, topic0, topic1); - } - - public LogTopicTriple topic(String topic0, String topic1, String topic2) { - if (BasicUtils.isNotHex(topic0)) - throw new LogQueryException("topic0 can not be empty or non hex."); - if (BasicUtils.isNotHex(topic1)) - throw new LogQueryException("topic1 can not be empty or non hex."); - if (BasicUtils.isNotHex(topic2)) - throw new LogQueryException("topic2 can not be empty or non hex."); - return new LogTopicTriple(address, startBlock, endBlock, topic0, topic1, topic2); - } - - public LogTopicQuadro topic(String topic0, String topic1, String topic2, String topic3) { - if (BasicUtils.isNotHex(topic0)) - throw new LogQueryException("topic0 can not be empty or non hex."); - if (BasicUtils.isNotHex(topic1)) - throw new LogQueryException("topic1 can not be empty or non hex."); - if (BasicUtils.isNotHex(topic2)) - throw new LogQueryException("topic2 can not be empty or non hex."); - if (BasicUtils.isNotHex(topic3)) - throw new LogQueryException("topic3 can not be empty or non hex."); - - return new LogTopicQuadro(address, startBlock, endBlock, topic0, topic1, topic2, topic3); - } - - @Override - public LogQuery build() throws LogQueryException { - return new LogQuery("&address=" + this.address + "&fromBlock=" + this.startBlock + "&toBlock=" + this.endBlock); - } -} diff --git a/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java b/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java deleted file mode 100644 index 1cbd4e3..0000000 --- a/src/main/java/io/api/etherscan/model/utility/TxTokenResponseTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.api.etherscan.model.utility; - -import io.api.etherscan.model.TxToken; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class TxTokenResponseTO extends BaseListResponseTO<TxToken> { - -} diff --git a/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java b/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java deleted file mode 100644 index f8e4c5e..0000000 --- a/src/main/java/io/api/etherscan/model/utility/UncleBlockResponseTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.api.etherscan.model.utility; - -import io.api.etherscan.model.UncleBlock; - -/** - * @author GoodforGod - * @since 30.10.2018 - */ -public class UncleBlockResponseTO extends BaseResponseTO { - - private UncleBlock result; - - public UncleBlock getResult() { - return result; - } -} diff --git a/src/main/java/io/api/etherscan/core/IAccountApi.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java similarity index 55% rename from src/main/java/io/api/etherscan/core/IAccountApi.java rename to src/main/java/io/goodforgod/api/etherscan/AccountAPI.java index ee869a2..7a0df39 100644 --- a/src/main/java/io/api/etherscan/core/IAccountApi.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java @@ -1,27 +1,27 @@ -package io.api.etherscan.core; +package io.goodforgod.api.etherscan; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.*; +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.*; import java.util.List; import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions https://etherscan.io/apis#accounts + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#accounts">...</a> * * @author GoodforGod * @since 28.10.2018 */ -public interface IAccountApi { +public interface AccountAPI { /** * Address ETH balance * * @param address get balance for * @return balance - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Balance balance(String address) throws ApiException; + Balance balance(String address) throws EtherScanException; /** * ERC20 token balance for address @@ -29,10 +29,10 @@ public interface IAccountApi { * @param address get balance for * @param contract token contract * @return token balance for address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - TokenBalance balance(String address, String contract) throws ApiException; + TokenBalance balance(String address, String contract) throws EtherScanException; /** * Maximum 20 address for single batch request If address MORE THAN 20, then there will be more than @@ -40,10 +40,10 @@ public interface IAccountApi { * * @param addresses addresses to get balances for * @return list of balances - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<Balance> balances(List<String> addresses) throws ApiException; + List<Balance> balances(List<String> addresses) throws EtherScanException; /** * All txs for given address @@ -52,16 +52,16 @@ public interface IAccountApi { * @param startBlock tx from this blockNumber * @param endBlock tx to this blockNumber * @return txs for address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<Tx> txs(String address, long startBlock, long endBlock) throws ApiException; + List<Tx> txs(String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<Tx> txs(String address, long startBlock) throws ApiException; + List<Tx> txs(String address, long startBlock) throws EtherScanException; @NotNull - List<Tx> txs(String address) throws ApiException; + List<Tx> txs(String address) throws EtherScanException; /** * All internal txs for given address @@ -70,26 +70,26 @@ public interface IAccountApi { * @param startBlock tx from this blockNumber * @param endBlock tx to this blockNumber * @return txs for address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<TxInternal> txsInternal(String address, long startBlock, long endBlock) throws ApiException; + List<TxInternal> txsInternal(String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxInternal> txsInternal(String address, long startBlock) throws ApiException; + List<TxInternal> txsInternal(String address, long startBlock) throws EtherScanException; @NotNull - List<TxInternal> txsInternal(String address) throws ApiException; + List<TxInternal> txsInternal(String address) throws EtherScanException; /** * All internal tx for given transaction hash * * @param txhash transaction hash * @return internal txs list - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<TxInternal> txsInternalByHash(String txhash) throws ApiException; + List<TxInternal> txsInternalByHash(String txhash) throws EtherScanException; /** * All ERC-20 token txs for given address @@ -98,16 +98,16 @@ public interface IAccountApi { * @param startBlock tx from this blockNumber * @param endBlock tx to this blockNumber * @return txs for address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<TxToken> txsToken(String address, long startBlock, long endBlock) throws ApiException; + List<TxERC20> txsERC20(String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxToken> txsToken(String address, long startBlock) throws ApiException; + List<TxERC20> txsERC20(String address, long startBlock) throws EtherScanException; @NotNull - List<TxToken> txsToken(String address) throws ApiException; + List<TxERC20> txsERC20(String address) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address @@ -116,24 +116,24 @@ public interface IAccountApi { * @param startBlock tx from this blockNumber * @param endBlock tx to this blockNumber * @return txs for address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<TxToken> txsNftToken(String address, long startBlock, long endBlock) throws ApiException; + List<TxERC721> txsERC721(String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxToken> txsNftToken(String address, long startBlock) throws ApiException; + List<TxERC721> txsERC721(String address, long startBlock) throws EtherScanException; @NotNull - List<TxToken> txsNftToken(String address) throws ApiException; + List<TxERC721> txsERC721(String address) throws EtherScanException; /** * All blocks mined by address * * @param address address to search for * @return blocks mined - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<Block> minedBlocks(String address) throws ApiException; + List<Block> blocksMined(String address) throws EtherScanException; } diff --git a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java similarity index 59% rename from src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java rename to src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index c807598..7cc5f52 100644 --- a/src/main/java/io/api/etherscan/core/impl/AccountApiProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -1,13 +1,12 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.IAccountApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.*; -import io.api.etherscan.model.utility.*; -import io.api.etherscan.util.BasicUtils; +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.*; +import io.goodforgod.api.etherscan.model.response.*; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; @@ -18,11 +17,11 @@ /** * Account API Implementation * - * @see IAccountApi + * @see AccountAPI * @author GoodforGod * @since 28.10.2018 */ -public class AccountApiProvider extends BasicProvider implements IAccountApi { +final class AccountAPIProvider extends BasicProvider implements AccountAPI { private static final int OFFSET_MAX = 10000; @@ -47,42 +46,42 @@ public class AccountApiProvider extends BasicProvider implements IAccountApi { private static final String OFFSET_PARAM = "&offset="; private static final String PAGE_PARAM = "&page="; - AccountApiProvider(final IQueueManager queueManager, - final String baseUrl, - final IHttpExecutor executor) { - super(queueManager, "account", baseUrl, executor); + AccountAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor) { + super(requestQueueManager, "account", baseUrl, executor); } @NotNull @Override - public Balance balance(final String address) throws ApiException { + public Balance balance(String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_BALANCE_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + address; final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response); + throw new EtherScanResponseException(response); return new Balance(address, new BigInteger(response.getResult())); } @NotNull @Override - public TokenBalance balance(final String address, final String contract) throws ApiException { + public TokenBalance balance(String address, String contract) throws EtherScanException { BasicUtils.validateAddress(address); BasicUtils.validateAddress(contract); final String urlParams = ACT_TOKEN_BALANCE_PARAM + ADDRESS_PARAM + address + CONTRACT_PARAM + contract; final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response); + throw new EtherScanResponseException(response); return new TokenBalance(address, new BigInteger(response.getResult()), contract); } @NotNull @Override - public List<Balance> balances(final List<String> addresses) throws ApiException { + public List<Balance> balances(List<String> addresses) throws EtherScanException { if (BasicUtils.isEmpty(addresses)) return Collections.emptyList(); @@ -96,7 +95,7 @@ public List<Balance> balances(final List<String> addresses) throws ApiException final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + toAddressParam(batch); final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response); + throw new EtherScanResponseException(response); if (!BasicUtils.isEmpty(response.getResult())) balances.addAll(response.getResult().stream() @@ -107,31 +106,32 @@ public List<Balance> balances(final List<String> addresses) throws ApiException return balances; } - private String toAddressParam(final List<String> addresses) { - return addresses.stream().collect(Collectors.joining(",")); + private String toAddressParam(List<String> addresses) { + return String.join(",", addresses); } @NotNull @Override - public List<Tx> txs(final String address) throws ApiException { + public List<Tx> txs(String address) throws EtherScanException { return txs(address, MIN_START_BLOCK); } @NotNull @Override - public List<Tx> txs(final String address, final long startBlock) throws ApiException { + public List<Tx> txs(String address, long startBlock) throws EtherScanException { return txs(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<Tx> txs(final String address, final long startBlock, final long endBlock) throws ApiException { + public List<Tx> txs(String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); - final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; - final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); - final String urlParams = ACT_TX_ACTION + offsetParam + ADDRESS_PARAM + address + blockParam + SORT_ASC_PARAM; + final String urlParams = ACT_TX_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + + ADDRESS_PARAM + address + + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + + SORT_ASC_PARAM; return getRequestUsingOffset(urlParams, TxResponseTO.class); } @@ -147,7 +147,7 @@ public List<Tx> txs(final String address, final long startBlock, final long endB */ private <T, R extends BaseListResponseTO> List<T> getRequestUsingOffset(final String urlParams, Class<R> tClass) - throws ApiException { + throws EtherScanException { final List<T> result = new ArrayList<>(); int page = 1; while (true) { @@ -167,32 +167,34 @@ private <T, R extends BaseListResponseTO> List<T> getRequestUsingOffset(final St @NotNull @Override - public List<TxInternal> txsInternal(final String address) throws ApiException { + public List<TxInternal> txsInternal(String address) throws EtherScanException { return txsInternal(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxInternal> txsInternal(final String address, final long startBlock) throws ApiException { + public List<TxInternal> txsInternal(String address, long startBlock) throws EtherScanException { return txsInternal(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxInternal> txsInternal(final String address, final long startBlock, final long endBlock) throws ApiException { + public List<TxInternal> txsInternal(String address, long startBlock, long endBlock) + throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); - final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; - final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); - final String urlParams = ACT_TX_INTERNAL_ACTION + offsetParam + ADDRESS_PARAM + address + blockParam + SORT_ASC_PARAM; + final String urlParams = ACT_TX_INTERNAL_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + + ADDRESS_PARAM + address + + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + + SORT_ASC_PARAM; return getRequestUsingOffset(urlParams, TxInternalResponseTO.class); } @NotNull @Override - public List<TxInternal> txsInternalByHash(final String txhash) throws ApiException { + public List<TxInternal> txsInternalByHash(String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_INTERNAL_ACTION + TXHASH_PARAM + txhash; @@ -206,61 +208,63 @@ public List<TxInternal> txsInternalByHash(final String txhash) throws ApiExcepti @NotNull @Override - public List<TxToken> txsToken(final String address) throws ApiException { - return txsToken(address, MIN_START_BLOCK); + public List<TxERC20> txsERC20(String address) throws EtherScanException { + return txsERC20(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxToken> txsToken(final String address, final long startBlock) throws ApiException { - return txsToken(address, startBlock, MAX_END_BLOCK); + public List<TxERC20> txsERC20(String address, long startBlock) throws EtherScanException { + return txsERC20(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxToken> txsToken(final String address, final long startBlock, final long endBlock) throws ApiException { + public List<TxERC20> txsERC20(String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); - final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; - final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); - final String urlParams = ACT_TX_TOKEN_ACTION + offsetParam + ADDRESS_PARAM + address + blockParam + SORT_ASC_PARAM; + final String urlParams = ACT_TX_TOKEN_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + + ADDRESS_PARAM + address + + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + + SORT_ASC_PARAM; - return getRequestUsingOffset(urlParams, TxTokenResponseTO.class); + return getRequestUsingOffset(urlParams, TxERC20ResponseTO.class); } @NotNull @Override - public List<TxToken> txsNftToken(String address) throws ApiException { - return txsNftToken(address, MIN_START_BLOCK); + public List<TxERC721> txsERC721(String address) throws EtherScanException { + return txsERC721(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxToken> txsNftToken(String address, long startBlock) throws ApiException { - return txsNftToken(address, startBlock, MAX_END_BLOCK); + public List<TxERC721> txsERC721(String address, long startBlock) throws EtherScanException { + return txsERC721(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxToken> txsNftToken(String address, long startBlock, long endBlock) throws ApiException { + public List<TxERC721> txsERC721(String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); - final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; - final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); - final String urlParams = ACT_TX_NFT_TOKEN_ACTION + offsetParam + ADDRESS_PARAM + address + blockParam + SORT_ASC_PARAM; + final String urlParams = ACT_TX_NFT_TOKEN_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + + ADDRESS_PARAM + address + + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + + SORT_ASC_PARAM; - return getRequestUsingOffset(urlParams, TxTokenResponseTO.class); + return getRequestUsingOffset(urlParams, TxERC20ResponseTO.class); } @NotNull @Override - public List<Block> minedBlocks(final String address) throws ApiException { + public List<Block> blocksMined(String address) throws EtherScanException { BasicUtils.validateAddress(address); - final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; - final String urlParams = ACT_MINED_ACTION + offsetParam + BLOCK_TYPE_PARAM + ADDRESS_PARAM + address; + final String urlParams = ACT_MINED_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + BLOCK_TYPE_PARAM + + ADDRESS_PARAM + address; return getRequestUsingOffset(urlParams, BlockResponseTO.class); } diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java new file mode 100644 index 0000000..4fd625a --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -0,0 +1,93 @@ +package io.goodforgod.api.etherscan; + +import com.google.gson.*; +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanParseException; +import io.goodforgod.api.etherscan.error.EtherScanRateLimitException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; +import io.goodforgod.gson.configuration.GsonConfiguration; +import java.util.Map; + +/** + * Base provider for API Implementations + * + * @author GoodforGod + * @see EtherScanAPIProvider + * @since 28.10.2018 + */ +abstract class BasicProvider { + + static final int MAX_END_BLOCK = Integer.MAX_VALUE; + static final int MIN_START_BLOCK = 0; + + static final String ACT_PREFIX = "&action="; + + private final String module; + private final String baseUrl; + private final EthHttpClient executor; + private final RequestQueueManager queue; + private final Gson gson; + + BasicProvider(RequestQueueManager queue, + String module, + String baseUrl, + EthHttpClient executor) { + this.queue = queue; + this.module = "&module=" + module; + this.baseUrl = baseUrl; + this.executor = executor; + this.gson = new GsonConfiguration().builder().create(); + } + + <T> T convert(String json, Class<T> tClass) { + try { + final T t = gson.fromJson(json, tClass); + if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith("Max rate limit reached")) { + throw new EtherScanRateLimitException(((StringResponseTO) t).getResult()); + } + + return t; + } catch (Exception e) { + try { + final Map<String, Object> map = gson.fromJson(json, Map.class); + final Object result = map.get("result"); + if (result instanceof String && ((String) result).startsWith("Max rate limit reached")) + throw new EtherScanRateLimitException(((String) result)); + + throw new EtherScanParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); + } catch (EtherScanException ex) { + throw ex; + } catch (Exception ex) { + throw new EtherScanParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); + } + } + } + + String getRequest(String urlParameters) { + queue.takeTurn(); + final String url = baseUrl + module + urlParameters; + final String result = executor.get(url); + if (BasicUtils.isEmpty(result)) + throw new EtherScanResponseException("Server returned null value for GET request at URL - " + url); + + return result; + } + + String postRequest(String urlParameters, String dataToPost) { + queue.takeTurn(); + final String url = baseUrl + module + urlParameters; + return executor.post(url, dataToPost); + } + + <T> T getRequest(String urlParameters, Class<T> tClass) { + return convert(getRequest(urlParameters), tClass); + } + + <T> T postRequest(String urlParameters, String dataToPost, Class<T> tClass) { + return convert(postRequest(urlParameters, dataToPost), tClass); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java new file mode 100644 index 0000000..55a8c3b --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java @@ -0,0 +1,25 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.BlockUncle; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#blocks">...</a> + * + * @author GoodforGod + * @since 30.10.2018 + */ +public interface BlockAPI { + + /** + * Return uncle blocks + * + * @param blockNumber block number form 0 to last + * @return optional uncle blocks + * @throws EtherScanException parent exception class + */ + @NotNull + Optional<BlockUncle> uncles(long blockNumber) throws EtherScanException; +} diff --git a/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java similarity index 53% rename from src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java rename to src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java index d634c9b..e5a6d49 100644 --- a/src/main/java/io/api/etherscan/core/impl/BlockApiProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -1,37 +1,36 @@ -package io.api.etherscan.core.impl; +package io.goodforgod.api.etherscan; -import io.api.etherscan.core.IBlockApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.UncleBlock; -import io.api.etherscan.model.utility.UncleBlockResponseTO; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.BlockUncle; +import io.goodforgod.api.etherscan.model.response.UncleBlockResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.Optional; import org.jetbrains.annotations.NotNull; /** * Block API Implementation * - * @see IBlockApi + * @see BlockAPI * @author GoodforGod * @since 28.10.2018 */ -public class BlockApiProvider extends BasicProvider implements IBlockApi { +final class BlockAPIProvider extends BasicProvider implements BlockAPI { private static final String ACT_BLOCK_PARAM = ACT_PREFIX + "getblockreward"; private static final String BLOCKNO_PARAM = "&blockno="; - BlockApiProvider(final IQueueManager queueManager, - final String baseUrl, - final IHttpExecutor executor) { - super(queueManager, "block", baseUrl, executor); + BlockAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor) { + super(requestQueueManager, "block", baseUrl, executor); } @NotNull @Override - public Optional<UncleBlock> uncles(long blockNumber) throws ApiException { + public Optional<BlockUncle> uncles(long blockNumber) throws EtherScanException { final String urlParam = ACT_BLOCK_PARAM + BLOCKNO_PARAM + blockNumber; final String response = getRequest(urlParam); if (BasicUtils.isEmpty(response) || response.contains("NOTOK")) diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java new file mode 100644 index 0000000..9271347 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java @@ -0,0 +1,24 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.Abi; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#contracts">...</a> + * + * @author GoodforGod + * @since 28.10.2018 + */ +public interface ContractAPI { + + /** + * Get Verified Contract Sources + * + * @param address to verify + * @return ABI verified + * @throws EtherScanException parent exception class + */ + @NotNull + Abi contractAbi(String address) throws EtherScanException; +} diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java new file mode 100644 index 0000000..cd96f68 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -0,0 +1,45 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.Abi; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; +import org.jetbrains.annotations.NotNull; + +/** + * Contract API Implementation + * + * @see ContractAPI + * @author GoodforGod + * @since 28.10.2018 + */ +final class ContractAPIProvider extends BasicProvider implements ContractAPI { + + private static final String ACT_ABI_PARAM = ACT_PREFIX + "getabi"; + + private static final String ADDRESS_PARAM = "&address="; + + ContractAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor) { + super(requestQueueManager, "contract", baseUrl, executor); + } + + @NotNull + @Override + public Abi contractAbi(String address) throws EtherScanException { + BasicUtils.validateAddress(address); + + final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; + final StringResponseTO response = getRequest(urlParam, StringResponseTO.class); + if (response.getStatus() != 1 && "NOTOK".equals(response.getMessage())) + throw new EtherScanResponseException(response); + + return (response.getResult().startsWith("Contract sou")) + ? Abi.nonVerified() + : Abi.verified(response.getResult()); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java b/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java new file mode 100644 index 0000000..ce0d929 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java @@ -0,0 +1,14 @@ +package io.goodforgod.api.etherscan; + +import java.net.URI; +import org.jetbrains.annotations.NotNull; + +/** + * @author Anton Kurako (GoodforGod) + * @since 11.05.2023 + */ +public interface EthNetwork { + + @NotNull + URI domain(); +} diff --git a/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java b/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java new file mode 100644 index 0000000..4dbe138 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java @@ -0,0 +1,29 @@ +package io.goodforgod.api.etherscan; + +import java.net.URI; +import org.jetbrains.annotations.NotNull; + +/** + * @author GoodforGod + * @since 28.10.2018 + */ +public enum EthNetworks implements EthNetwork { + + MAINNET("api", "io"), + ROPSTEN("api-ropsten", "io"), + KOVAN("api-kovan", "io"), + TOBALABA("api-tobalaba", "com"), + GORLI("api-goerli", "io"), + RINKEBY("api-rinkeby", "io"); + + private final URI domain; + + EthNetworks(String domain, String extension) { + this.domain = URI.create("https://" + domain + ".etherscan." + extension + "/api"); + } + + @Override + public @NotNull URI domain() { + return domain; + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java new file mode 100644 index 0000000..d36d385 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -0,0 +1,71 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanKeyException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.executor.impl.UrlEthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.manager.impl.FakeRequestQueueManager; +import io.goodforgod.api.etherscan.util.BasicUtils; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; + +/** + * @author Anton Kurako (GoodforGod) + * @since 11.05.2023 + */ +final class EthScanAPIBuilder implements EtherScanAPI.Builder { + + private static final Supplier<EthHttpClient> DEFAULT_SUPPLIER = UrlEthHttpClient::new; + private static final String DEFAULT_KEY = "YourApiKeyToken"; + + private String apiKey = DEFAULT_KEY; + private EthNetwork ethNetwork = EthNetworks.MAINNET; + private RequestQueueManager queueManager = RequestQueueManager.DEFAULT_KEY_QUEUE; + private Supplier<EthHttpClient> ethHttpClientSupplier = DEFAULT_SUPPLIER; + + @NotNull + @Override + public EtherScanAPI.Builder withApiKey(@NotNull String apiKey) { + if (BasicUtils.isBlank(apiKey)) + throw new EtherScanKeyException("API key can not be null or empty"); + + this.apiKey = apiKey; + if (!DEFAULT_KEY.equals(apiKey)) { + queueManager = new FakeRequestQueueManager(); + } + return this; + } + + @NotNull + @Override + public EtherScanAPI.Builder withNetwork(@NotNull EthNetwork network) { + this.ethNetwork = network; + return this; + } + + @NotNull + @Override + public EtherScanAPI.Builder withNetwork(@NotNull EthNetworks network) { + this.ethNetwork = network; + return this; + } + + @NotNull + @Override + public EtherScanAPI.Builder withQueue(@NotNull RequestQueueManager queueManager) { + this.queueManager = queueManager; + return this; + } + + @NotNull + @Override + public EtherScanAPI.Builder withHttpClient(@NotNull Supplier<EthHttpClient> httpClientSupplier) { + this.ethHttpClientSupplier = httpClientSupplier; + return this; + } + + @Override + public @NotNull EtherScanAPI build() { + return new EtherScanAPIProvider(apiKey, ethNetwork, ethHttpClientSupplier, queueManager); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java new file mode 100644 index 0000000..76dcab7 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -0,0 +1,61 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan full API Description <a href="https://etherscan.io/apis">...</a> + * + * @author GoodforGod + * @since 10.05.2023 + */ +public interface EtherScanAPI extends AutoCloseable { + + @NotNull + AccountAPI account(); + + @NotNull + ContractAPI contract(); + + @NotNull + TransactionAPI txs(); + + @NotNull + BlockAPI block(); + + @NotNull + LogsAPI logs(); + + @NotNull + ProxyAPI proxy(); + + @NotNull + StatisticAPI stats(); + + static Builder builder() { + return new EthScanAPIBuilder(); + } + + interface Builder { + + @NotNull + Builder withApiKey(@NotNull String apiKey); + + @NotNull + Builder withNetwork(@NotNull EthNetwork network); + + @NotNull + Builder withNetwork(@NotNull EthNetworks network); + + @NotNull + Builder withQueue(@NotNull RequestQueueManager queueManager); + + @NotNull + Builder withHttpClient(@NotNull Supplier<EthHttpClient> httpClientSupplier); + + @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 new file mode 100644 index 0000000..0043e37 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java @@ -0,0 +1,89 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan full API Description <a href="https://etherscan.io/apis">...</a> + * + * @author GoodforGod + * @since 28.10.2018 + */ +final class EtherScanAPIProvider implements EtherScanAPI { + + private final RequestQueueManager requestQueueManager; + private final AccountAPI account; + private final BlockAPI block; + private final ContractAPI contract; + private final LogsAPI logs; + private final ProxyAPI proxy; + private final StatisticAPI stats; + private final TransactionAPI txs; + + EtherScanAPIProvider(String apiKey, + EthNetwork network, + Supplier<EthHttpClient> executorSupplier, + RequestQueueManager queue) { + // EtherScan 1request\5sec limit support by queue manager + final EthHttpClient executor = executorSupplier.get(); + final String baseUrl = network.domain() + "?apikey=" + apiKey; + + this.requestQueueManager = queue; + this.account = new AccountAPIProvider(queue, baseUrl, executor); + this.block = new BlockAPIProvider(queue, baseUrl, executor); + this.contract = new ContractAPIProvider(queue, baseUrl, executor); + this.logs = new LogsAPIProvider(queue, baseUrl, executor); + this.proxy = new ProxyAPIProvider(queue, baseUrl, executor); + this.stats = new StatisticAPIProvider(queue, baseUrl, executor); + this.txs = new TransactionAPIProvider(queue, baseUrl, executor); + } + + @NotNull + @Override + public AccountAPI account() { + return account; + } + + @NotNull + @Override + public ContractAPI contract() { + return contract; + } + + @NotNull + @Override + public TransactionAPI txs() { + return txs; + } + + @NotNull + @Override + public BlockAPI block() { + return block; + } + + @NotNull + @Override + public LogsAPI logs() { + return logs; + } + + @NotNull + @Override + public ProxyAPI proxy() { + return proxy; + } + + @NotNull + @Override + public StatisticAPI stats() { + return stats; + } + + @Override + public void close() throws Exception { + requestQueueManager.close(); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java new file mode 100644 index 0000000..5b834df --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java @@ -0,0 +1,27 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.Log; +import io.goodforgod.api.etherscan.model.query.LogQuery; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#logs">...</a> + * + * @author GoodforGod + * @since 30.10.2018 + */ +public interface LogsAPI { + + /** + * alternative to the native eth_getLogs Read at EtherScan API description for full info! + * + * @param query build log query + * @return logs according to query + * @throws EtherScanException parent exception class + * @see LogQuery + */ + @NotNull + List<Log> logs(LogQuery query) throws EtherScanException; +} diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java new file mode 100644 index 0000000..771d931 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java @@ -0,0 +1,42 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.Log; +import io.goodforgod.api.etherscan.model.query.LogQuery; +import io.goodforgod.api.etherscan.model.response.LogResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +/** + * Logs API Implementation + * + * @see LogsAPI + * @author GoodforGod + * @since 28.10.2018 + */ +final class LogsAPIProvider extends BasicProvider implements LogsAPI { + + private static final String ACT_LOGS_PARAM = ACT_PREFIX + "getLogs"; + + LogsAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor) { + super(queue, "logs", baseUrl, executor); + } + + @NotNull + @Override + public List<Log> logs(LogQuery query) throws EtherScanException { + final String urlParams = ACT_LOGS_PARAM + query.params(); + final LogResponseTO response = getRequest(urlParams, LogResponseTO.class); + BasicUtils.validateTxResponse(response); + + return (BasicUtils.isEmpty(response.getResult())) + ? Collections.emptyList() + : response.getResult(); + } +} diff --git a/src/main/java/io/api/etherscan/core/IProxyApi.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java similarity index 63% rename from src/main/java/io/api/etherscan/core/IProxyApi.java rename to src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java index b7e9f54..0785d13 100644 --- a/src/main/java/io/api/etherscan/core/IProxyApi.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java @@ -1,27 +1,27 @@ -package io.api.etherscan.core; +package io.goodforgod.api.etherscan; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.proxy.BlockProxy; -import io.api.etherscan.model.proxy.ReceiptProxy; -import io.api.etherscan.model.proxy.TxProxy; +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.proxy.BlockProxy; +import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; +import io.goodforgod.api.etherscan.model.proxy.TxProxy; import java.math.BigInteger; import java.util.Optional; import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions https://etherscan.io/apis#proxy + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#proxy">...</a> * * @author GoodforGod * @since 30.10.2018 */ -public interface IProxyApi { +public interface ProxyAPI { /** * Returns the number of most recent block eth_blockNumber * * @return last block number - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ long blockNoLast(); @@ -30,10 +30,10 @@ public interface IProxyApi { * * @param blockNo block number from 0 to last * @return optional block result - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<BlockProxy> block(long blockNo) throws ApiException; + Optional<BlockProxy> block(long blockNo) throws EtherScanException; /** * Returns information about a uncle by block number eth_getUncleByBlockNumberAndIndex @@ -41,10 +41,10 @@ public interface IProxyApi { * @param blockNo block number from 0 to last * @param index uncle block index * @return optional block result - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<BlockProxy> blockUncle(long blockNo, long index) throws ApiException; + Optional<BlockProxy> blockUncle(long blockNo, long index) throws EtherScanException; /** * Returns the information about a transaction requested by transaction hash @@ -52,10 +52,10 @@ public interface IProxyApi { * * @param txhash transaction hash * @return optional tx result - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<TxProxy> tx(String txhash) throws ApiException; + Optional<TxProxy> tx(String txhash) throws EtherScanException; /** * Returns information about a transaction by block number and transaction index position @@ -64,10 +64,10 @@ public interface IProxyApi { * @param blockNo block number from 0 to last * @param index tx index in block * @return optional tx result - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<TxProxy> tx(long blockNo, long index) throws ApiException; + Optional<TxProxy> tx(long blockNo, long index) throws EtherScanException; /** * Returns the number of transactions in a block from a block matching the given block number @@ -75,18 +75,18 @@ public interface IProxyApi { * * @param blockNo block number from 0 to last * @return transaction amount in block - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ - int txCount(long blockNo) throws ApiException; + int txCount(long blockNo) throws EtherScanException; /** * Returns the number of transactions sent from an address eth_getTransactionCount * * @param address eth address * @return transactions send amount from address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ - int txSendCount(String address) throws ApiException; + int txSendCount(String address) throws EtherScanException; /** * Creates new message call transaction or a contract creation for signed transactions @@ -94,20 +94,20 @@ public interface IProxyApi { * * @param hexEncodedTx encoded hex data to send * @return optional string response - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<String> txSendRaw(String hexEncodedTx) throws ApiException; + Optional<String> txSendRaw(String hexEncodedTx) throws EtherScanException; /** * Returns the receipt of a transaction by transaction hash eth_getTransactionReceipt * * @param txhash transaction hash * @return optional tx receipt - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<ReceiptProxy> txReceipt(String txhash) throws ApiException; + Optional<ReceiptProxy> txReceipt(String txhash) throws EtherScanException; /** * Executes a new message call immediately without creating a transaction on the block chain @@ -116,20 +116,20 @@ public interface IProxyApi { * @param address to call * @param data data to call address * @return optional the return value of executed contract. - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<String> call(String address, String data) throws ApiException; + Optional<String> call(String address, String data) throws EtherScanException; /** * Returns code at a given address eth_getCode * * @param address get code from * @return optional the code from the given address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<String> code(String address) throws ApiException; + Optional<String> code(String address) throws EtherScanException; /** * (**experimental) Returns the value from a storage position at a given address eth_getStorageAt @@ -137,20 +137,20 @@ public interface IProxyApi { * @param address to get storage * @param position storage position * @return optional the value at this storage position - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @Experimental @NotNull - Optional<String> storageAt(String address, long position) throws ApiException; + Optional<String> storageAt(String address, long position) throws EtherScanException; /** * Returns the current price per gas in wei eth_gasPrice * * @return estimated gas price - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - BigInteger gasPrice() throws ApiException; + BigInteger gasPrice() throws EtherScanException; /** * Makes a call or transaction, which won't be added to the blockchain and returns the used gas, @@ -158,11 +158,11 @@ public interface IProxyApi { * * @param hexData data to calc gas usage for * @return estimated gas usage - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - BigInteger gasEstimated(String hexData) throws ApiException; + BigInteger gasEstimated(String hexData) throws EtherScanException; @NotNull - BigInteger gasEstimated() throws ApiException; + BigInteger gasEstimated() throws EtherScanException; } diff --git a/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java similarity index 75% rename from src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java rename to src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index f456186..1239294 100644 --- a/src/main/java/io/api/etherscan/core/impl/ProxyApiProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -1,19 +1,18 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.IProxyApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.error.InvalidDataHexException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.proxy.BlockProxy; -import io.api.etherscan.model.proxy.ReceiptProxy; -import io.api.etherscan.model.proxy.TxProxy; -import io.api.etherscan.model.proxy.utility.BlockProxyTO; -import io.api.etherscan.model.proxy.utility.StringProxyTO; -import io.api.etherscan.model.proxy.utility.TxInfoProxyTO; -import io.api.etherscan.model.proxy.utility.TxProxyTO; -import io.api.etherscan.util.BasicUtils; +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanInvalidDataHexException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.proxy.BlockProxy; +import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; +import io.goodforgod.api.etherscan.model.proxy.TxProxy; +import io.goodforgod.api.etherscan.model.proxy.utility.BlockProxyTO; +import io.goodforgod.api.etherscan.model.proxy.utility.StringProxyTO; +import io.goodforgod.api.etherscan.model.proxy.utility.TxInfoProxyTO; +import io.goodforgod.api.etherscan.model.proxy.utility.TxProxyTO; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.util.Optional; import java.util.regex.Pattern; @@ -22,11 +21,11 @@ /** * Proxy API Implementation * - * @see IProxyApi + * @see ProxyAPI * @author GoodforGod * @since 28.10.2018 */ -public class ProxyApiProvider extends BasicProvider implements IProxyApi { +final class ProxyAPIProvider extends BasicProvider implements ProxyAPI { private static final String ACT_BLOCKNO_PARAM = ACT_PREFIX + "eth_blockNumber"; private static final String ACT_BY_BLOCKNO_PARAM = ACT_PREFIX + "eth_getBlockByNumber"; @@ -57,14 +56,14 @@ public class ProxyApiProvider extends BasicProvider implements IProxyApi { private static final Pattern EMPTY_HEX = Pattern.compile("0x0+"); - ProxyApiProvider(final IQueueManager queue, + ProxyAPIProvider(final RequestQueueManager queue, final String baseUrl, - final IHttpExecutor executor) { + final EthHttpClient executor) { super(queue, "proxy", baseUrl, executor); } @Override - public long blockNoLast() throws ApiException { + public long blockNoLast() throws EtherScanException { final StringProxyTO response = getRequest(ACT_BLOCKNO_PARAM, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) ? -1 @@ -73,7 +72,7 @@ public long blockNoLast() throws ApiException { @NotNull @Override - public Optional<BlockProxy> block(final long blockNo) throws ApiException { + public Optional<BlockProxy> block(long blockNo) throws EtherScanException { final long compBlockNo = BasicUtils.compensateMinBlock(blockNo); final String urlParams = ACT_BY_BLOCKNO_PARAM + TAG_PARAM + compBlockNo + BOOLEAN_PARAM; @@ -83,7 +82,7 @@ public Optional<BlockProxy> block(final long blockNo) throws ApiException { @NotNull @Override - public Optional<BlockProxy> blockUncle(final long blockNo, final long index) throws ApiException { + public Optional<BlockProxy> blockUncle(long blockNo, long index) throws EtherScanException { final long compBlockNo = BasicUtils.compensateMinBlock(blockNo); final long compIndex = BasicUtils.compensateMinBlock(index); @@ -95,7 +94,7 @@ public Optional<BlockProxy> blockUncle(final long blockNo, final long index) thr @NotNull @Override - public Optional<TxProxy> tx(final String txhash) throws ApiException { + public Optional<TxProxy> tx(String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_BY_HASH_PARAM + TXHASH_PARAM + txhash; @@ -105,7 +104,7 @@ public Optional<TxProxy> tx(final String txhash) throws ApiException { @NotNull @Override - public Optional<TxProxy> tx(final long blockNo, final long index) throws ApiException { + public Optional<TxProxy> tx(long blockNo, long index) throws EtherScanException { final long compBlockNo = BasicUtils.compensateMinBlock(blockNo); final long compIndex = (index < 1) ? 1 @@ -118,7 +117,7 @@ public Optional<TxProxy> tx(final long blockNo, final long index) throws ApiExce } @Override - public int txCount(final long blockNo) throws ApiException { + public int txCount(long blockNo) throws EtherScanException { final long compensatedBlockNo = BasicUtils.compensateMinBlock(blockNo); final String urlParams = ACT_BLOCKTX_COUNT_PARAM + TAG_PARAM + "0x" + Long.toHexString(compensatedBlockNo); final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); @@ -126,7 +125,7 @@ public int txCount(final long blockNo) throws ApiException { } @Override - public int txSendCount(final String address) throws ApiException { + public int txSendCount(String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_TX_COUNT_PARAM + ADDRESS_PARAM + address + TAG_LAST_PARAM; @@ -136,14 +135,14 @@ public int txSendCount(final String address) throws ApiException { @Override @NotNull - public Optional<String> txSendRaw(final String hexEncodedTx) throws ApiException { + public Optional<String> txSendRaw(String hexEncodedTx) throws EtherScanException { if (BasicUtils.isNotHex(hexEncodedTx)) - throw new InvalidDataHexException("Data is not encoded in hex format - " + hexEncodedTx); + throw new EtherScanInvalidDataHexException("Data is not encoded in hex format - " + hexEncodedTx); final String urlParams = ACT_SEND_RAW_TX_PARAM + HEX_PARAM + hexEncodedTx; final StringProxyTO response = postRequest(urlParams, "", StringProxyTO.class); if (response.getError() != null) - throw new EtherScanException("Error occurred with code " + response.getError().getCode() + throw new EtherScanResponseException("Error occurred with code " + response.getError().getCode() + " with message " + response.getError().getMessage() + ", error id " + response.getId() + ", jsonRPC " + response.getJsonrpc()); @@ -152,7 +151,7 @@ public Optional<String> txSendRaw(final String hexEncodedTx) throws ApiException @NotNull @Override - public Optional<ReceiptProxy> txReceipt(final String txhash) throws ApiException { + public Optional<ReceiptProxy> txReceipt(String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_RECEIPT_PARAM + TXHASH_PARAM + txhash; @@ -162,10 +161,10 @@ public Optional<ReceiptProxy> txReceipt(final String txhash) throws ApiException @NotNull @Override - public Optional<String> call(final String address, final String data) throws ApiException { + public Optional<String> call(String address, String data) throws EtherScanException { BasicUtils.validateAddress(address); if (BasicUtils.isNotHex(data)) - throw new InvalidDataHexException("Data is not hex encoded."); + throw new EtherScanInvalidDataHexException("Data is not hex encoded."); final String urlParams = ACT_CALL_PARAM + TO_PARAM + address + DATA_PARAM + data + TAG_LAST_PARAM; final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); @@ -174,7 +173,7 @@ public Optional<String> call(final String address, final String data) throws Api @NotNull @Override - public Optional<String> code(final String address) throws ApiException { + public Optional<String> code(String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_CODE_PARAM + ADDRESS_PARAM + address + TAG_LAST_PARAM; @@ -184,7 +183,7 @@ public Optional<String> code(final String address) throws ApiException { @NotNull @Override - public Optional<String> storageAt(final String address, final long position) throws ApiException { + public Optional<String> storageAt(String address, long position) throws EtherScanException { BasicUtils.validateAddress(address); final long compPosition = BasicUtils.compensateMinBlock(position); @@ -197,7 +196,7 @@ public Optional<String> storageAt(final String address, final long position) thr @NotNull @Override - public BigInteger gasPrice() throws ApiException { + public BigInteger gasPrice() throws EtherScanException { final StringProxyTO response = getRequest(ACT_GASPRICE_PARAM, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) ? BigInteger.valueOf(-1) @@ -206,15 +205,15 @@ public BigInteger gasPrice() throws ApiException { @NotNull @Override - public BigInteger gasEstimated() throws ApiException { + public BigInteger gasEstimated() throws EtherScanException { return gasEstimated("606060405260728060106000396000f360606040526000"); } @NotNull @Override - public BigInteger gasEstimated(final String hexData) throws ApiException { + public BigInteger gasEstimated(String hexData) throws EtherScanException { if (!BasicUtils.isEmpty(hexData) && BasicUtils.isNotHex(hexData)) - throw new InvalidDataHexException("Data is not in hex format."); + throw new EtherScanInvalidDataHexException("Data is not in hex format."); final String urlParams = ACT_ESTIMATEGAS_PARAM + DATA_PARAM + hexData + GAS_PARAM + "2000000000000000"; final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java new file mode 100644 index 0000000..314f73e --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java @@ -0,0 +1,44 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.Price; +import io.goodforgod.api.etherscan.model.Supply; +import java.math.BigInteger; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#stats">...</a> + * + * @author GoodforGod + * @since 30.10.2018 + */ +public interface StatisticAPI { + + /** + * ERC20 token total Supply + * + * @param contract contract address + * @return token supply for specified contract + * @throws EtherScanException parent exception class + */ + @NotNull + BigInteger supply(String contract) throws EtherScanException; + + /** + * Eth total supply + * + * @return total ETH supply for moment + * @throws EtherScanException parent exception class + */ + @NotNull + Supply supply() throws EtherScanException; + + /** + * Eth last USD and BTC price + * + * @return last usd/btc price for ETH + * @throws EtherScanException parent exception class + */ + @NotNull + Price lastPrice() throws EtherScanException; +} diff --git a/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java similarity index 54% rename from src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java rename to src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java index a14119a..ee4bdaa 100644 --- a/src/main/java/io/api/etherscan/core/impl/StatisticApiProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java @@ -1,26 +1,25 @@ -package io.api.etherscan.core.impl; +package io.goodforgod.api.etherscan; -import io.api.etherscan.core.IStatisticApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.Price; -import io.api.etherscan.model.Supply; -import io.api.etherscan.model.utility.PriceResponseTO; -import io.api.etherscan.model.utility.StringResponseTO; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.Price; +import io.goodforgod.api.etherscan.model.Supply; +import io.goodforgod.api.etherscan.model.response.PriceResponseTO; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import org.jetbrains.annotations.NotNull; /** * Statistic API Implementation * - * @see IStatisticApi + * @see StatisticAPI * @author GoodforGod * @since 28.10.2018 */ -public class StatisticApiProvider extends BasicProvider implements IStatisticApi { +final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { private static final String ACT_SUPPLY_PARAM = ACT_PREFIX + "ethsupply"; private static final String ACT_TOKEN_SUPPLY_PARAM = ACT_PREFIX + "tokensupply"; @@ -28,41 +27,41 @@ public class StatisticApiProvider extends BasicProvider implements IStatisticApi private static final String CONTRACT_ADDRESS_PARAM = "&contractaddress="; - StatisticApiProvider(final IQueueManager queue, + StatisticAPIProvider(final RequestQueueManager queue, final String baseUrl, - final IHttpExecutor executor) { + final EthHttpClient executor) { super(queue, "stats", baseUrl, executor); } @NotNull @Override - public Supply supply() throws ApiException { + public Supply supply() throws EtherScanException { final StringResponseTO response = getRequest(ACT_SUPPLY_PARAM, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response); + throw new EtherScanResponseException(response); return new Supply(new BigInteger(response.getResult())); } @NotNull @Override - public BigInteger supply(final String contract) throws ApiException { + public BigInteger supply(String contract) throws EtherScanException { BasicUtils.validateAddress(contract); final String urlParams = ACT_TOKEN_SUPPLY_PARAM + CONTRACT_ADDRESS_PARAM + contract; final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response); + throw new EtherScanResponseException(response); return new BigInteger(response.getResult()); } @NotNull @Override - public Price lastPrice() throws ApiException { + public Price lastPrice() throws EtherScanException { final PriceResponseTO response = getRequest(ACT_LASTPRICE_PARAM, PriceResponseTO.class); if (response.getStatus() != 1) - throw new EtherScanException(response); + throw new EtherScanResponseException(response); return response.getResult(); } diff --git a/src/main/java/io/api/etherscan/core/ITransactionApi.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java similarity index 51% rename from src/main/java/io/api/etherscan/core/ITransactionApi.java rename to src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java index 4180ff4..6bfc545 100644 --- a/src/main/java/io/api/etherscan/core/ITransactionApi.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java @@ -1,35 +1,35 @@ -package io.api.etherscan.core; +package io.goodforgod.api.etherscan; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.Status; +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.Status; import java.util.Optional; import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions https://etherscan.io/apis#transactions + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#transactions">...</a> * * @author GoodforGod * @since 30.10.2018 */ -public interface ITransactionApi { +public interface TransactionAPI { /** * Check Contract Execution Status (if there was an error during contract execution) * * @param txhash transaction hash * @return optional status result - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<Status> execStatus(String txhash) throws ApiException; + Optional<Status> statusExec(String txhash) throws EtherScanException; /** * Check Transaction Receipt Status (Only applicable for Post Byzantium fork transactions) * * @param txhash transaction hash * @return 0 = Fail, 1 = Pass, empty value for pre-byzantium fork - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - Optional<Boolean> receiptStatus(String txhash) throws ApiException; + Optional<Boolean> statusReceipt(String txhash) throws EtherScanException; } diff --git a/src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java similarity index 60% rename from src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java rename to src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java index 1c83bf0..91082a8 100644 --- a/src/main/java/io/api/etherscan/core/impl/TransactionApiProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java @@ -1,13 +1,12 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.ITransactionApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.Status; -import io.api.etherscan.model.utility.ReceiptStatusResponseTO; -import io.api.etherscan.model.utility.StatusResponseTO; -import io.api.etherscan.util.BasicUtils; +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.Status; +import io.goodforgod.api.etherscan.model.response.ReceiptStatusResponseTO; +import io.goodforgod.api.etherscan.model.response.StatusResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.Optional; import org.jetbrains.annotations.NotNull; @@ -15,25 +14,25 @@ * Transaction API Implementation * * @author GoodforGod - * @see ITransactionApi + * @see TransactionAPI * @since 28.10.2018 */ -public class TransactionApiProvider extends BasicProvider implements ITransactionApi { +final class TransactionAPIProvider extends BasicProvider implements TransactionAPI { private static final String ACT_EXEC_STATUS_PARAM = ACT_PREFIX + "getstatus"; private static final String ACT_RECEIPT_STATUS_PARAM = ACT_PREFIX + "gettxreceiptstatus"; private static final String TXHASH_PARAM = "&txhash="; - TransactionApiProvider(final IQueueManager queue, - final String baseUrl, - final IHttpExecutor executor) { + TransactionAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor) { super(queue, "transaction", baseUrl, executor); } @NotNull @Override - public Optional<Status> execStatus(final String txhash) throws ApiException { + public Optional<Status> statusExec(String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_EXEC_STATUS_PARAM + TXHASH_PARAM + txhash; @@ -45,7 +44,7 @@ public Optional<Status> execStatus(final String txhash) throws ApiException { @NotNull @Override - public Optional<Boolean> receiptStatus(final String txhash) throws ApiException { + public Optional<Boolean> statusReceipt(String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_RECEIPT_STATUS_PARAM + TXHASH_PARAM + txhash; diff --git a/src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java b/src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java new file mode 100644 index 0000000..b39dcee --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 31.10.2018 + */ +public class ErtherScanLogQueryException extends EtherScanException { + + public ErtherScanLogQueryException(String message) { + super(message); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionException.java new file mode 100644 index 0000000..731592c --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionException.java @@ -0,0 +1,16 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class EtherScanConnectionException extends EtherScanException { + + public EtherScanConnectionException(String message) { + super(message); + } + + public EtherScanConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanException.java new file mode 100644 index 0000000..67cc3bb --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanException.java @@ -0,0 +1,16 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 30.10.2018 + */ +public class EtherScanException extends RuntimeException { + + public EtherScanException(String message) { + super(message); + } + + public EtherScanException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidAddressException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidAddressException.java new file mode 100644 index 0000000..ff12e3c --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidAddressException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class EtherScanInvalidAddressException extends EtherScanException { + + public EtherScanInvalidAddressException(String message) { + super(message); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidDataHexException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidDataHexException.java new file mode 100644 index 0000000..b5b5f2d --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidDataHexException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 02.11.2018 + */ +public class EtherScanInvalidDataHexException extends EtherScanException { + + public EtherScanInvalidDataHexException(String message) { + super(message); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidTxHashException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidTxHashException.java new file mode 100644 index 0000000..33be4ad --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanInvalidTxHashException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 02.11.2018 + */ +public class EtherScanInvalidTxHashException extends EtherScanException { + + public EtherScanInvalidTxHashException(String message) { + super(message); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanKeyException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanKeyException.java new file mode 100644 index 0000000..f9e4aa8 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanKeyException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 05.11.2018 + */ +public class EtherScanKeyException extends EtherScanException { + + public EtherScanKeyException(String message) { + super(message); + } +} diff --git a/src/main/java/io/api/etherscan/error/ParseException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanParseException.java similarity index 52% rename from src/main/java/io/api/etherscan/error/ParseException.java rename to src/main/java/io/goodforgod/api/etherscan/error/EtherScanParseException.java index 5dc6199..87116ab 100644 --- a/src/main/java/io/api/etherscan/error/ParseException.java +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanParseException.java @@ -1,14 +1,14 @@ -package io.api.etherscan.error; +package io.goodforgod.api.etherscan.error; /** * @author GoodforGod * @since 29.10.2018 */ -public class ParseException extends ApiException { +public class EtherScanParseException extends EtherScanException { private final String json; - public ParseException(String message, Throwable cause, String json) { + public EtherScanParseException(String message, Throwable cause, String json) { super(message, cause); this.json = json; } diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanRateLimitException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanRateLimitException.java new file mode 100644 index 0000000..eefb1ea --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanRateLimitException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author iSnow + * @since 2020-10-06 + */ +public class EtherScanRateLimitException extends EtherScanException { + + public EtherScanRateLimitException(String message) { + super(message); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java new file mode 100644 index 0000000..21da798 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java @@ -0,0 +1,23 @@ +package io.goodforgod.api.etherscan.error; + +import io.goodforgod.api.etherscan.model.response.BaseResponseTO; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class EtherScanResponseException extends EtherScanException { + + public EtherScanResponseException(BaseResponseTO response) { + this(response.getMessage() + ", with status: " + response.getStatus()); + } + + public EtherScanResponseException(StringResponseTO response) { + this(response.getResult() + ", with status: " + response.getStatus() + ", with message: " + response.getMessage()); + } + + public EtherScanResponseException(String message) { + super(message); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java new file mode 100644 index 0000000..7734139 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 12.11.2018 + */ +public class EtherScanTimeoutException extends EtherScanConnectionException { + + public EtherScanTimeoutException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/api/etherscan/executor/IHttpExecutor.java b/src/main/java/io/goodforgod/api/etherscan/executor/EthHttpClient.java similarity index 84% rename from src/main/java/io/api/etherscan/executor/IHttpExecutor.java rename to src/main/java/io/goodforgod/api/etherscan/executor/EthHttpClient.java index 0c80282..4edc507 100644 --- a/src/main/java/io/api/etherscan/executor/IHttpExecutor.java +++ b/src/main/java/io/goodforgod/api/etherscan/executor/EthHttpClient.java @@ -1,4 +1,4 @@ -package io.api.etherscan.executor; +package io.goodforgod.api.etherscan.executor; /** * Http Client interface @@ -6,7 +6,7 @@ * @author GoodforGod * @since 31.10.2018 */ -public interface IHttpExecutor { +public interface EthHttpClient { /** * Performs a Http GET request diff --git a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java b/src/main/java/io/goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java similarity index 66% rename from src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java rename to src/main/java/io/goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java index 49e7fee..ac05125 100644 --- a/src/main/java/io/api/etherscan/executor/impl/HttpExecutor.java +++ b/src/main/java/io/goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java @@ -1,11 +1,11 @@ -package io.api.etherscan.executor.impl; +package io.goodforgod.api.etherscan.executor.impl; import static java.net.HttpURLConnection.*; -import io.api.etherscan.error.ApiTimeoutException; -import io.api.etherscan.error.ConnectionException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.error.EtherScanConnectionException; +import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -14,6 +14,8 @@ import java.net.SocketTimeoutException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.zip.GZIPInputStream; @@ -23,15 +25,15 @@ * Http client implementation * * @author GoodforGod - * @see IHttpExecutor + * @see EthHttpClient * @since 28.10.2018 */ -public class HttpExecutor implements IHttpExecutor { +public final class UrlEthHttpClient implements EthHttpClient { private static final Map<String, String> DEFAULT_HEADERS = new HashMap<>(); - private static final int CONNECT_TIMEOUT = 8000; - private static final int READ_TIMEOUT = 0; + private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(8); + private static final Duration DEFAULT_READ_TIMEOUT = Duration.ZERO; static { DEFAULT_HEADERS.put("Accept-Language", "en"); @@ -44,15 +46,15 @@ public class HttpExecutor implements IHttpExecutor { private final int connectTimeout; private final int readTimeout; - public HttpExecutor() { - this(CONNECT_TIMEOUT); + public UrlEthHttpClient() { + this(DEFAULT_CONNECT_TIMEOUT); } - public HttpExecutor(final int connectTimeout) { - this(connectTimeout, READ_TIMEOUT); + public UrlEthHttpClient(Duration connectTimeout) { + this(connectTimeout, DEFAULT_READ_TIMEOUT); } - public HttpExecutor(final int connectTimeout, final int readTimeout) { + public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout) { this(connectTimeout, readTimeout, DEFAULT_HEADERS); } @@ -61,12 +63,12 @@ public HttpExecutor(final int connectTimeout, final int readTimeout) { * @param readTimeout custom read timeout in millis * @param headers custom HTTP headers */ - public HttpExecutor(final int connectTimeout, - final int readTimeout, - final Map<String, String> headers) { - this.connectTimeout = Math.max(connectTimeout, 0); - this.readTimeout = Math.max(readTimeout, 0); - this.headers = headers; + public UrlEthHttpClient(Duration connectTimeout, + Duration readTimeout, + Map<String, String> headers) { + this.connectTimeout = Math.toIntExact(connectTimeout.toMillis()); + this.readTimeout = Math.toIntExact(readTimeout.toMillis()); + this.headers = Collections.unmodifiableMap(headers); } private HttpURLConnection buildConnection(String urlAsString, String method) throws IOException { @@ -87,23 +89,23 @@ public String get(final String urlAsString) { if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { return get(connection.getHeaderField("Location")); } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { - throw new ConnectionException("Protocol error: " + connection.getResponseMessage()); + throw new EtherScanConnectionException("Protocol error: " + connection.getResponseMessage()); } else if (status >= HTTP_INTERNAL_ERROR) { - throw new ConnectionException("Server error: " + connection.getResponseMessage()); + throw new EtherScanConnectionException("Server error: " + connection.getResponseMessage()); } final String data = readData(connection); connection.disconnect(); return data; } catch (SocketTimeoutException e) { - throw new ApiTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); + throw new EtherScanTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); } catch (Exception e) { - throw new ConnectionException(e.getMessage(), e); + throw new EtherScanConnectionException(e.getMessage(), e); } } @Override - public String post(final String urlAsString, final String dataToPost) { + public String post(String urlAsString, String dataToPost) { try { final HttpURLConnection connection = buildConnection(urlAsString, "POST"); final String contentLength = (BasicUtils.isBlank(dataToPost)) @@ -123,22 +125,22 @@ public String post(final String urlAsString, final String dataToPost) { if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { return post(connection.getHeaderField("Location"), dataToPost); } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { - throw new ConnectionException("Protocol error: " + connection.getResponseMessage()); + throw new EtherScanConnectionException("Protocol error: " + connection.getResponseMessage()); } else if (status >= HTTP_INTERNAL_ERROR) { - throw new ConnectionException("Server error: " + connection.getResponseMessage()); + throw new EtherScanConnectionException("Server error: " + connection.getResponseMessage()); } final String data = readData(connection); connection.disconnect(); return data; } catch (SocketTimeoutException e) { - throw new ApiTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); + throw new EtherScanTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); } catch (Exception e) { - throw new ConnectionException(e.getMessage(), e); + throw new EtherScanConnectionException(e.getMessage(), e); } } - private String readData(final HttpURLConnection connection) throws IOException { + private String readData(HttpURLConnection connection) throws IOException { final StringBuilder content = new StringBuilder(); try (BufferedReader in = new BufferedReader(getStreamReader(connection))) { String inputLine; @@ -149,7 +151,7 @@ private String readData(final HttpURLConnection connection) throws IOException { return content.toString(); } - private InputStreamReader getStreamReader(final HttpURLConnection connection) throws IOException { + private InputStreamReader getStreamReader(HttpURLConnection connection) throws IOException { switch (String.valueOf(connection.getContentEncoding())) { case "gzip": return new InputStreamReader(new GZIPInputStream(connection.getInputStream()), StandardCharsets.UTF_8); diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java new file mode 100644 index 0000000..7472c3f --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -0,0 +1,24 @@ +package io.goodforgod.api.etherscan.manager; + +import io.goodforgod.api.etherscan.manager.impl.SemaphoreRequestQueueManager; +import java.time.Duration; + +/** + * Queue manager to support API limits (EtherScan 5request\sec limit) Managers grants turn if the + * limit is not exhausted And resets queue each set period + * + * @author GoodforGod + * @since 30.10.2018 + */ +public interface RequestQueueManager extends AutoCloseable { + + RequestQueueManager DEFAULT_KEY_QUEUE = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5050L), + Duration.ofMillis(5050L), 0); + RequestQueueManager PERSONAL_KEY_QUEUE = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1050L), + Duration.ofMillis(1050L), 5); + + /** + * Waits in queue for chance to take turn + */ + void takeTurn(); +} diff --git a/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java similarity index 65% rename from src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java rename to src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java index 620244c..626b4c1 100644 --- a/src/main/java/io/api/etherscan/manager/impl/FakeQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java @@ -1,6 +1,6 @@ -package io.api.etherscan.manager.impl; +package io.goodforgod.api.etherscan.manager.impl; -import io.api.etherscan.manager.IQueueManager; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; /** * Fake queue manager, always give turns, when you have no limits @@ -8,7 +8,7 @@ * @author GoodforGod * @since 03.11.2018 */ -public class FakeQueueManager implements IQueueManager { +public final class FakeRequestQueueManager implements RequestQueueManager { @Override public void takeTurn() { diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java new file mode 100644 index 0000000..cfd745f --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java @@ -0,0 +1,53 @@ +package io.goodforgod.api.etherscan.manager.impl; + +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import java.time.Duration; +import java.util.concurrent.*; + +/** + * Queue Semaphore implementation with size and reset time as params + * + * @see RequestQueueManager + * @author GoodforGod + * @since 30.10.2018 + */ +public final class SemaphoreRequestQueueManager implements RequestQueueManager, AutoCloseable { + + private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + private final Semaphore semaphore; + private final long queueResetTimeInMillis; + + public SemaphoreRequestQueueManager(int size, Duration resetIn) { + this(size, resetIn, resetIn); + } + + public SemaphoreRequestQueueManager(int size, Duration resetIn, Duration delayIn) { + this(size, resetIn, delayIn, size); + } + + public SemaphoreRequestQueueManager(int size, Duration queueResetTimeIn, Duration delayIn, int initialSize) { + this.semaphore = new Semaphore(initialSize); + this.queueResetTimeInMillis = queueResetTimeIn.toMillis(); + this.executorService.scheduleAtFixedRate(releaseLocks(size + 1), delayIn.toMillis(), queueResetTimeInMillis, + TimeUnit.MILLISECONDS); + } + + @SuppressWarnings("java:S899") + @Override + public void takeTurn() { + try { + semaphore.tryAcquire(queueResetTimeInMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private Runnable releaseLocks(int toRelease) { + return () -> semaphore.release(toRelease); + } + + @Override + public void close() { + executorService.shutdown(); + } +} diff --git a/src/main/java/io/api/etherscan/model/Abi.java b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java similarity index 94% rename from src/main/java/io/api/etherscan/model/Abi.java rename to src/main/java/io/goodforgod/api/etherscan/model/Abi.java index 880e6a0..b575c56 100644 --- a/src/main/java/io/api/etherscan/model/Abi.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/Balance.java b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java similarity index 94% rename from src/main/java/io/api/etherscan/model/Balance.java rename to src/main/java/io/goodforgod/api/etherscan/model/Balance.java index ed6d6c5..d147a2c 100644 --- a/src/main/java/io/api/etherscan/model/Balance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; -import io.api.etherscan.model.utility.BalanceTO; +import io.goodforgod.api.etherscan.model.response.BalanceTO; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/io/api/etherscan/model/BaseTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java similarity index 97% rename from src/main/java/io/api/etherscan/model/BaseTx.java rename to src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java index 3942d14..1fb53e1 100644 --- a/src/main/java/io/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java @@ -1,7 +1,7 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import com.google.gson.annotations.Expose; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; diff --git a/src/main/java/io/api/etherscan/model/Block.java b/src/main/java/io/goodforgod/api/etherscan/model/Block.java similarity index 94% rename from src/main/java/io/api/etherscan/model/Block.java rename to src/main/java/io/goodforgod/api/etherscan/model/Block.java index f5e8b6a..8b652bb 100644 --- a/src/main/java/io/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -1,7 +1,7 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import com.google.gson.annotations.Expose; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java new file mode 100644 index 0000000..34f8f30 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -0,0 +1,128 @@ +package io.goodforgod.api.etherscan.model; + +import io.goodforgod.api.etherscan.util.BasicUtils; + +import java.math.BigInteger; +import java.util.List; + +/** + * @author GoodforGod + * @since 30.10.2018 + */ +public class BlockUncle extends Block { + + public static class Uncle { + + private String miner; + private BigInteger blockreward; + private int unclePosition; + + // <editor-fold desc="Getters"> + public String getMiner() { + return miner; + } + + public BigInteger getBlockreward() { + return blockreward; + } + + public int getUnclePosition() { + return unclePosition; + } + // </editor-fold> + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Uncle uncle = (Uncle) o; + if (unclePosition != uncle.unclePosition) + return false; + if (miner != null + ? !miner.equals(uncle.miner) + : uncle.miner != null) + return false; + return blockreward != null + ? blockreward.equals(uncle.blockreward) + : uncle.blockreward == null; + } + + @Override + public int hashCode() { + int result = miner != null + ? miner.hashCode() + : 0; + result = 31 * result + (blockreward != null + ? blockreward.hashCode() + : 0); + result = 31 * result + unclePosition; + return result; + } + + @Override + public String toString() { + return "Uncle{" + + "miner='" + miner + '\'' + + ", blockreward=" + blockreward + + ", unclePosition=" + unclePosition + + '}'; + } + } + + private String blockMiner; + private List<Uncle> uncles; + private String uncleInclusionReward; + + // <editor-fold desc="Getters"> + public boolean isEmpty() { + return getBlockNumber() == 0 && getBlockReward() == null + && getTimeStamp() == null + && BasicUtils.isEmpty(blockMiner); + } + + public String getBlockMiner() { + return blockMiner; + } + + public List<Uncle> getUncles() { + return uncles; + } + + public String getUncleInclusionReward() { + return uncleInclusionReward; + } + // </editor-fold> + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + + BlockUncle that = (BlockUncle) o; + + return getBlockNumber() != 0 && getBlockNumber() == that.getBlockNumber(); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = (int) (31 * result + getBlockNumber()); + return result; + } + + @Override + public String toString() { + return "UncleBlock{" + + "blockMiner='" + blockMiner + '\'' + + ", uncles=" + uncles + + ", uncleInclusionReward='" + uncleInclusionReward + '\'' + + '}'; + } +} diff --git a/src/main/java/io/api/etherscan/model/Log.java b/src/main/java/io/goodforgod/api/etherscan/model/Log.java similarity index 98% rename from src/main/java/io/api/etherscan/model/Log.java rename to src/main/java/io/goodforgod/api/etherscan/model/Log.java index 595122b..cc22b66 100644 --- a/src/main/java/io/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -1,7 +1,7 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import com.google.gson.annotations.Expose; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; diff --git a/src/main/java/io/api/etherscan/model/Price.java b/src/main/java/io/goodforgod/api/etherscan/model/Price.java similarity index 98% rename from src/main/java/io/api/etherscan/model/Price.java rename to src/main/java/io/goodforgod/api/etherscan/model/Price.java index fc72ab5..712dbd8 100644 --- a/src/main/java/io/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import com.google.gson.annotations.Expose; import java.time.LocalDateTime; diff --git a/src/main/java/io/api/etherscan/model/Status.java b/src/main/java/io/goodforgod/api/etherscan/model/Status.java similarity index 96% rename from src/main/java/io/api/etherscan/model/Status.java rename to src/main/java/io/goodforgod/api/etherscan/model/Status.java index 2017cd7..274080a 100644 --- a/src/main/java/io/api/etherscan/model/Status.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Status.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import java.util.Objects; diff --git a/src/main/java/io/api/etherscan/model/Supply.java b/src/main/java/io/goodforgod/api/etherscan/model/Supply.java similarity index 81% rename from src/main/java/io/api/etherscan/model/Supply.java rename to src/main/java/io/goodforgod/api/etherscan/model/Supply.java index f495aaf..80dc7d0 100644 --- a/src/main/java/io/api/etherscan/model/Supply.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Supply.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import java.math.BigInteger; diff --git a/src/main/java/io/api/etherscan/model/TokenBalance.java b/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java similarity index 96% rename from src/main/java/io/api/etherscan/model/TokenBalance.java rename to src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java index 684738c..d42fd05 100644 --- a/src/main/java/io/api/etherscan/model/TokenBalance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/io/api/etherscan/model/Tx.java b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java similarity index 96% rename from src/main/java/io/api/etherscan/model/Tx.java rename to src/main/java/io/goodforgod/api/etherscan/model/Tx.java index 13b5292..3ab0923 100644 --- a/src/main/java/io/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/io/api/etherscan/model/TxToken.java b/src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java similarity index 93% rename from src/main/java/io/api/etherscan/model/TxToken.java rename to src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java index c455ffb..9f65d98 100644 --- a/src/main/java/io/api/etherscan/model/TxToken.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java @@ -1,10 +1,10 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; /** * @author GoodforGod * @since 28.10.2018 */ -public class TxToken extends BaseTx { +public class TxERC20 extends BaseTx { private long nonce; private String blockHash; @@ -56,7 +56,7 @@ public long getConfirmations() { @Override public String toString() { - return "TxToken{" + + return "TxERC20{" + "nonce=" + nonce + ", blockHash='" + blockHash + '\'' + ", tokenName='" + tokenName + '\'' + diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java b/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java new file mode 100644 index 0000000..5b30314 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java @@ -0,0 +1,71 @@ +package io.goodforgod.api.etherscan.model; + +/** + * @author GoodforGod + * @since 28.10.2018 + */ +public class TxERC721 extends BaseTx { + + private long nonce; + private String blockHash; + private String tokenName; + private String tokenSymbol; + private String tokenDecimal; + private int transactionIndex; + private long gasPrice; + private long cumulativeGasUsed; + private long confirmations; + + // <editor-fold desc="Getters"> + public long getNonce() { + return nonce; + } + + public String getBlockHash() { + return blockHash; + } + + public String getTokenName() { + return tokenName; + } + + public String getTokenSymbol() { + return tokenSymbol; + } + + public String getTokenDecimal() { + return tokenDecimal; + } + + public int getTransactionIndex() { + return transactionIndex; + } + + public long getGasPrice() { + return gasPrice; + } + + public long getCumulativeGasUsed() { + return cumulativeGasUsed; + } + + public long getConfirmations() { + return confirmations; + } + // </editor-fold> + + @Override + public String toString() { + return "TxERC721{" + + "nonce=" + nonce + + ", blockHash='" + blockHash + '\'' + + ", tokenName='" + tokenName + '\'' + + ", tokenSymbol='" + tokenSymbol + '\'' + + ", tokenDecimal='" + tokenDecimal + '\'' + + ", transactionIndex=" + transactionIndex + + ", gasPrice=" + gasPrice + + ", cumulativeGasUsed=" + cumulativeGasUsed + + ", confirmations=" + confirmations + + "} " + super.toString(); + } +} diff --git a/src/main/java/io/api/etherscan/model/TxInternal.java b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java similarity index 97% rename from src/main/java/io/api/etherscan/model/TxInternal.java rename to src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java index 5471268..244e0b7 100644 --- a/src/main/java/io/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import java.util.Objects; diff --git a/src/main/java/io/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java similarity index 96% rename from src/main/java/io/api/etherscan/model/Wei.java rename to src/main/java/io/goodforgod/api/etherscan/model/Wei.java index 0735d90..224a1cf 100644 --- a/src/main/java/io/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import java.math.BigInteger; import java.util.Objects; diff --git a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java similarity index 98% rename from src/main/java/io/api/etherscan/model/proxy/BlockProxy.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java index 2afbe40..889cc0e 100644 --- a/src/main/java/io/api/etherscan/model/proxy/BlockProxy.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/BlockProxy.java @@ -1,7 +1,7 @@ -package io.api.etherscan.model.proxy; +package io.goodforgod.api.etherscan.model.proxy; import com.google.gson.annotations.Expose; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; diff --git a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java similarity index 96% rename from src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java index 1e25dbd..f4f7845 100644 --- a/src/main/java/io/api/etherscan/model/proxy/ReceiptProxy.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/ReceiptProxy.java @@ -1,8 +1,8 @@ -package io.api.etherscan.model.proxy; +package io.goodforgod.api.etherscan.model.proxy; import com.google.gson.annotations.Expose; -import io.api.etherscan.model.Log; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.model.Log; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; import java.util.List; diff --git a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java similarity index 97% rename from src/main/java/io/api/etherscan/model/proxy/TxProxy.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java index a89f4a8..cf3199b 100644 --- a/src/main/java/io/api/etherscan/model/proxy/TxProxy.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/TxProxy.java @@ -1,7 +1,7 @@ -package io.api.etherscan.model.proxy; +package io.goodforgod.api.etherscan.model.proxy; import com.google.gson.annotations.Expose; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; /** diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java similarity index 86% rename from src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java index 0291dfe..ef57193 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/BaseProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.proxy.utility; +package io.goodforgod.api.etherscan.model.proxy.utility; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java similarity index 63% rename from src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java index 2057c89..cf6c16b 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/BlockProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.proxy.utility; +package io.goodforgod.api.etherscan.model.proxy.utility; -import io.api.etherscan.model.proxy.BlockProxy; +import io.goodforgod.api.etherscan.model.proxy.BlockProxy; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java similarity index 81% rename from src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java index a3bc435..9b14cec 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/ErrorProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.proxy.utility; +package io.goodforgod.api.etherscan.model.proxy.utility; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java similarity index 77% rename from src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java index 8d1d08c..489d87b 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/StringProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.proxy.utility; +package io.goodforgod.api.etherscan.model.proxy.utility; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java similarity index 63% rename from src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java index 3bbe039..208cdbe 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/TxInfoProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.proxy.utility; +package io.goodforgod.api.etherscan.model.proxy.utility; -import io.api.etherscan.model.proxy.ReceiptProxy; +import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java similarity index 62% rename from src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java index 7e9c9e8..0c084e7 100644 --- a/src/main/java/io/api/etherscan/model/proxy/utility/TxProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.proxy.utility; +package io.goodforgod.api.etherscan.model.proxy.utility; -import io.api.etherscan.model.proxy.TxProxy; +import io.goodforgod.api.etherscan.model.proxy.TxProxy; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/query/LogOp.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogOp.java similarity index 86% rename from src/main/java/io/api/etherscan/model/query/LogOp.java rename to src/main/java/io/goodforgod/api/etherscan/model/query/LogOp.java index 0c0ebee..9136034 100644 --- a/src/main/java/io/api/etherscan/model/query/LogOp.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogOp.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.query; +package io.goodforgod.api.etherscan.model.query; /** * Part of The Event Log API diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java new file mode 100644 index 0000000..69e8409 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java @@ -0,0 +1,51 @@ +package io.goodforgod.api.etherscan.model.query; + +import static io.goodforgod.api.etherscan.model.query.LogQueryBuilderImpl.MAX_BLOCK; +import static io.goodforgod.api.etherscan.model.query.LogQueryBuilderImpl.MIN_BLOCK; + +import io.goodforgod.api.etherscan.LogsAPI; +import org.jetbrains.annotations.NotNull; + +/** + * Final builded container for The Event Log API + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#logs">...</a> + * + * @see LogQueryBuilderImpl + * @see LogsAPI + * @author GoodforGod + * @since 10.05.2023 + */ +public interface LogQuery { + + @NotNull + String params(); + + @NotNull + static Builder builder(String address) { + return new LogQueryBuilderImpl(address, MIN_BLOCK, MAX_BLOCK); + } + + interface Builder { + + @NotNull + LogQuery.Builder withBlockFrom(long startBlock); + + @NotNull + LogQuery.Builder withBlockTo(long endBlock); + + @NotNull + LogTopicSingle withTopic(String topic0); + + @NotNull + LogTopicTuple withTopic(String topic0, String topic1); + + @NotNull + LogTopicTriple withTopic(String topic0, String topic1, String topic2); + + @NotNull + LogTopicQuadro withTopic(String topic0, String topic1, String topic2, String topic3); + + @NotNull + LogQuery build(); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java new file mode 100644 index 0000000..0d1eb59 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java @@ -0,0 +1,84 @@ +package io.goodforgod.api.etherscan.model.query; + +import io.goodforgod.api.etherscan.LogsAPI; +import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.util.BasicUtils; +import org.jetbrains.annotations.NotNull; + +/** + * Builder for The Event Log API + * + * @see LogsAPI + * @author GoodforGod + * @since 31.10.2018 + */ +final class LogQueryBuilderImpl implements LogQuery.Builder { + + static final long MIN_BLOCK = 0; + static final long MAX_BLOCK = 99999999999999999L; + + private final String address; + private final long startBlock, endBlock; + + LogQueryBuilderImpl(String address, long startBlock, long endBlock) { + this.address = address; + this.startBlock = startBlock; + this.endBlock = endBlock; + } + + @Override + public @NotNull LogQuery.Builder withBlockFrom(long startBlock) { + return new LogQueryBuilderImpl(this.address, startBlock, this.endBlock); + } + + @Override + public @NotNull LogQuery.Builder withBlockTo(long endBlock) { + return new LogQueryBuilderImpl(this.address, this.startBlock, endBlock); + } + + @Override + public @NotNull LogTopicSingle withTopic(String topic0) { + if (BasicUtils.isNotHex(topic0)) + throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + return new LogTopicSingle(address, startBlock, endBlock, topic0); + } + + @Override + public @NotNull LogTopicTuple withTopic(String topic0, String topic1) { + if (BasicUtils.isNotHex(topic0)) + throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + if (BasicUtils.isNotHex(topic1)) + throw new ErtherScanLogQueryException("topic1 can not be empty or non hex."); + return new LogTopicTuple(address, startBlock, endBlock, topic0, topic1); + } + + @Override + public @NotNull LogTopicTriple withTopic(String topic0, String topic1, String topic2) { + if (BasicUtils.isNotHex(topic0)) + throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + if (BasicUtils.isNotHex(topic1)) + throw new ErtherScanLogQueryException("topic1 can not be empty or non hex."); + if (BasicUtils.isNotHex(topic2)) + throw new ErtherScanLogQueryException("topic2 can not be empty or non hex."); + return new LogTopicTriple(address, startBlock, endBlock, topic0, topic1, topic2); + } + + @Override + public @NotNull LogTopicQuadro withTopic(String topic0, String topic1, String topic2, String topic3) { + if (BasicUtils.isNotHex(topic0)) + throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + if (BasicUtils.isNotHex(topic1)) + throw new ErtherScanLogQueryException("topic1 can not be empty or non hex."); + if (BasicUtils.isNotHex(topic2)) + throw new ErtherScanLogQueryException("topic2 can not be empty or non hex."); + if (BasicUtils.isNotHex(topic3)) + throw new ErtherScanLogQueryException("topic3 can not be empty or non hex."); + + return new LogTopicQuadro(address, startBlock, endBlock, topic0, topic1, topic2, topic3); + } + + @Override + public @NotNull LogQuery build() throws ErtherScanLogQueryException { + return new LogQueryImpl("&address=" + this.address + "&fromBlock=" + this.startBlock + "&toBlock=" + this.endBlock); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java new file mode 100644 index 0000000..ef66e72 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java @@ -0,0 +1,30 @@ +package io.goodforgod.api.etherscan.model.query; + +import io.goodforgod.api.etherscan.LogsAPI; +import org.jetbrains.annotations.NotNull; + +/** + * Final builded container for The Event Log API + * EtherScan - API Descriptions <a href="https://etherscan.io/apis#logs">...</a> + * + * @see LogQueryBuilderImpl + * @see LogsAPI + * @author GoodforGod + * @since 31.10.2018 + */ +final class LogQueryImpl implements LogQuery { + + /** + * Final request parameter for api call + */ + private final String params; + + LogQueryImpl(String params) { + this.params = params; + } + + @Override + public @NotNull String params() { + return params; + } +} diff --git a/src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java similarity index 79% rename from src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java rename to src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java index c472f84..ac77ae8 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/BaseLogQuery.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java @@ -1,16 +1,18 @@ -package io.api.etherscan.model.query.impl; +package io.goodforgod.api.etherscan.model.query; -import io.api.etherscan.core.ILogsApi; +import io.goodforgod.api.etherscan.LogsAPI; /** * Base parameters for The Event Log API builder * - * @see LogQueryBuilder - * @see ILogsApi + * @see LogQueryBuilderImpl + * @see LogsAPI * @author GoodforGod * @since 31.10.2018 */ -abstract class BaseLogQuery { +final class LogQueryParams { + + private LogQueryParams() {} static final String FROM_BLOCK_PARAM = "&fromBlock="; static final String TO_BLOCK_PARAM = "&toBlock="; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicBuilder.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicBuilder.java new file mode 100644 index 0000000..715f869 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicBuilder.java @@ -0,0 +1,17 @@ +package io.goodforgod.api.etherscan.model.query; + +import org.jetbrains.annotations.NotNull; + +/** + * @see LogTopicSingle + * @see LogTopicTuple + * @see LogTopicTriple + * @see LogTopicQuadro + * @author GoodforGod + * @since 10.05.2023 + */ +public interface LogTopicBuilder { + + @NotNull + LogQuery build(); +} diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java similarity index 71% rename from src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java rename to src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java index bab5b29..1469f97 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicQuadro.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java @@ -1,19 +1,20 @@ -package io.api.etherscan.model.query.impl; +package io.goodforgod.api.etherscan.model.query; -import io.api.etherscan.core.ILogsApi; -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.IQueryBuilder; -import io.api.etherscan.model.query.LogOp; +import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; + +import io.goodforgod.api.etherscan.LogsAPI; +import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import org.jetbrains.annotations.NotNull; /** * Quadro topic parameter builder for The Event Log API * - * @see LogQueryBuilder - * @see ILogsApi + * @see LogQueryBuilderImpl + * @see LogsAPI * @author GoodforGod * @since 31.10.2018 */ -public class LogTopicQuadro extends BaseLogQuery implements IQueryBuilder { +public final class LogTopicQuadro implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; @@ -68,21 +69,21 @@ public LogTopicQuadro setOpTopic1_3(LogOp topic1_3_opr) { } @Override - public LogQuery build() { + public @NotNull LogQuery build() { if (topic0_1_opr == null) - throw new LogQueryException("topic0_1_opr can not be null."); + throw new ErtherScanLogQueryException("topic0_1_opr can not be null."); if (topic0_2_opr == null) - throw new LogQueryException("topic0_2_opr can not be null."); + throw new ErtherScanLogQueryException("topic0_2_opr can not be null."); if (topic0_3_opr == null) - throw new LogQueryException("topic0_3_opr can not be null."); + throw new ErtherScanLogQueryException("topic0_3_opr can not be null."); if (topic1_2_opr == null) - throw new LogQueryException("topic1_2_opr can not be null."); + throw new ErtherScanLogQueryException("topic1_2_opr can not be null."); if (topic2_3_opr == null) - throw new LogQueryException("topic2_3_opr can not be null."); + throw new ErtherScanLogQueryException("topic2_3_opr can not be null."); if (topic1_3_opr == null) - throw new LogQueryException("topic1_3_opr can not be null."); + throw new ErtherScanLogQueryException("topic1_3_opr can not be null."); - return new LogQuery(ADDRESS_PARAM + address + return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock + TOPIC_0_PARAM + topic0 + TOPIC_1_PARAM + topic1 diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java similarity index 53% rename from src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java rename to src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java index 83199d9..85bd18c 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicSingle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java @@ -1,18 +1,20 @@ -package io.api.etherscan.model.query.impl; +package io.goodforgod.api.etherscan.model.query; -import io.api.etherscan.core.ILogsApi; -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.IQueryBuilder; +import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; + +import io.goodforgod.api.etherscan.LogsAPI; +import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import org.jetbrains.annotations.NotNull; /** * Single topic parameter builder for The Event Log API * - * @see LogQueryBuilder - * @see ILogsApi + * @see LogQueryBuilderImpl + * @see LogsAPI * @author GoodforGod * @since 31.10.2018 */ -public class LogTopicSingle extends BaseLogQuery implements IQueryBuilder { +public final class LogTopicSingle implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; @@ -27,8 +29,8 @@ public class LogTopicSingle extends BaseLogQuery implements IQueryBuilder { } @Override - public LogQuery build() throws LogQueryException { - return new LogQuery(ADDRESS_PARAM + address + public @NotNull LogQuery build() throws ErtherScanLogQueryException { + return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock + TOPIC_0_PARAM + topic0); } diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java similarity index 68% rename from src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java rename to src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java index cc9a6ba..d56edb5 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTriple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java @@ -1,19 +1,20 @@ -package io.api.etherscan.model.query.impl; +package io.goodforgod.api.etherscan.model.query; -import io.api.etherscan.core.ILogsApi; -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.IQueryBuilder; -import io.api.etherscan.model.query.LogOp; +import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; + +import io.goodforgod.api.etherscan.LogsAPI; +import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import org.jetbrains.annotations.NotNull; /** * Triple topic parameter builder for The Event Log API * - * @see LogQueryBuilder - * @see ILogsApi + * @see LogQueryBuilderImpl + * @see LogsAPI * @author GoodforGod * @since 31.10.2018 */ -public class LogTopicTriple extends BaseLogQuery implements IQueryBuilder { +public final class LogTopicTriple implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; @@ -51,15 +52,15 @@ public LogTopicTriple setOpTopic1_2(LogOp topic1_2_opr) { } @Override - public LogQuery build() throws LogQueryException { + public @NotNull LogQuery build() throws ErtherScanLogQueryException { if (topic0_1_opr == null) - throw new LogQueryException("topic0_1_opr can not be null."); + throw new ErtherScanLogQueryException("topic0_1_opr can not be null."); if (topic0_2_opr == null) - throw new LogQueryException("topic0_2_opr can not be null."); + throw new ErtherScanLogQueryException("topic0_2_opr can not be null."); if (topic1_2_opr == null) - throw new LogQueryException("topic1_2_opr can not be null."); + throw new ErtherScanLogQueryException("topic1_2_opr can not be null."); - return new LogQuery(ADDRESS_PARAM + address + return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock + TOPIC_0_PARAM + topic0 + TOPIC_1_PARAM + topic1 diff --git a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java similarity index 63% rename from src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java rename to src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java index 4524a8a..95a78a4 100644 --- a/src/main/java/io/api/etherscan/model/query/impl/LogTopicTuple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java @@ -1,19 +1,20 @@ -package io.api.etherscan.model.query.impl; +package io.goodforgod.api.etherscan.model.query; -import io.api.etherscan.core.ILogsApi; -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.IQueryBuilder; -import io.api.etherscan.model.query.LogOp; +import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; + +import io.goodforgod.api.etherscan.LogsAPI; +import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import org.jetbrains.annotations.NotNull; /** * Tuple topic parameter builder for The Event Log API * - * @see LogQueryBuilder - * @see ILogsApi + * @see LogQueryBuilderImpl + * @see LogsAPI * @author GoodforGod * @since 31.10.2018 */ -public class LogTopicTuple extends BaseLogQuery implements IQueryBuilder { +public final class LogTopicTuple implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; @@ -39,11 +40,11 @@ public LogTopicTuple setOpTopic0_1(LogOp topic0_1_opr) { } @Override - public LogQuery build() throws LogQueryException { + public @NotNull LogQuery build() throws ErtherScanLogQueryException { if (topic0_1_opr == null) - throw new LogQueryException("topic0_1_opr can not be null."); + throw new ErtherScanLogQueryException("topic0_1_opr can not be null."); - return new LogQuery(ADDRESS_PARAM + address + return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock + TOPIC_0_PARAM + topic0 + TOPIC_1_PARAM + topic1 diff --git a/src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BalanceResponseTO.java similarity index 70% rename from src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/BalanceResponseTO.java index f7c2985..189ed87 100644 --- a/src/main/java/io/api/etherscan/model/utility/BalanceResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BalanceResponseTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/BalanceTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BalanceTO.java similarity index 83% rename from src/main/java/io/api/etherscan/model/utility/BalanceTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/BalanceTO.java index 3956cec..8f56790 100644 --- a/src/main/java/io/api/etherscan/model/utility/BalanceTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BalanceTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BaseListResponseTO.java similarity index 82% rename from src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/BaseListResponseTO.java index 916739e..0d1b06c 100644 --- a/src/main/java/io/api/etherscan/model/utility/BaseListResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BaseListResponseTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; import java.util.List; diff --git a/src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java similarity index 77% rename from src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java index 9679ebb..3e100d1 100644 --- a/src/main/java/io/api/etherscan/model/utility/BaseResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.util.BasicUtils; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/BlockParam.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BlockParam.java similarity index 88% rename from src/main/java/io/api/etherscan/model/utility/BlockParam.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/BlockParam.java index 7e11a00..b2dbd32 100644 --- a/src/main/java/io/api/etherscan/model/utility/BlockParam.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BlockParam.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BlockResponseTO.java similarity index 54% rename from src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/BlockResponseTO.java index 8a89321..363612b 100644 --- a/src/main/java/io/api/etherscan/model/utility/BlockResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BlockResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.Block; +import io.goodforgod.api.etherscan.model.Block; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/LogResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/LogResponseTO.java similarity index 54% rename from src/main/java/io/api/etherscan/model/utility/LogResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/LogResponseTO.java index a060bd3..748d155 100644 --- a/src/main/java/io/api/etherscan/model/utility/LogResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/LogResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.Log; +import io.goodforgod.api.etherscan.model.Log; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/PriceResponseTO.java similarity index 66% rename from src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/PriceResponseTO.java index 9af743b..e2f0b63 100644 --- a/src/main/java/io/api/etherscan/model/utility/PriceResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/PriceResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.Price; +import io.goodforgod.api.etherscan.model.Price; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/ReceiptStatusResponseTO.java similarity index 81% rename from src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/ReceiptStatusResponseTO.java index a5f9577..922c5e2 100644 --- a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ReceiptStatusResponseTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/ReceiptStatusTO.java similarity index 77% rename from src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/ReceiptStatusTO.java index c4c63af..4f4717c 100644 --- a/src/main/java/io/api/etherscan/model/utility/ReceiptStatusTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ReceiptStatusTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/StatusResponseTO.java similarity index 66% rename from src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/StatusResponseTO.java index 7532aba..6847930 100644 --- a/src/main/java/io/api/etherscan/model/utility/StatusResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/StatusResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.Status; +import io.goodforgod.api.etherscan.model.Status; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/StringResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java similarity index 79% rename from src/main/java/io/api/etherscan/model/utility/StringResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java index 582087a..4fb9f04 100644 --- a/src/main/java/io/api/etherscan/model/utility/StringResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java @@ -1,4 +1,4 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; /** * @author GoodforGod diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java new file mode 100644 index 0000000..f4814a5 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxERC20; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class TxERC20ResponseTO extends BaseListResponseTO<TxERC20> { + +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java new file mode 100644 index 0000000..b4db8ef --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxERC20; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class TxERC721ResponseTO extends BaseListResponseTO<TxERC20> { + +} diff --git a/src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxInternalResponseTO.java similarity index 55% rename from src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/TxInternalResponseTO.java index 5f0e400..efcf4dd 100644 --- a/src/main/java/io/api/etherscan/model/utility/TxInternalResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxInternalResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.TxInternal; +import io.goodforgod.api.etherscan.model.TxInternal; /** * @author GoodforGod diff --git a/src/main/java/io/api/etherscan/model/utility/TxResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxResponseTO.java similarity index 54% rename from src/main/java/io/api/etherscan/model/utility/TxResponseTO.java rename to src/main/java/io/goodforgod/api/etherscan/model/response/TxResponseTO.java index 1fa6b16..f4bd3f0 100644 --- a/src/main/java/io/api/etherscan/model/utility/TxResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxResponseTO.java @@ -1,6 +1,6 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.Tx; +import io.goodforgod.api.etherscan.model.Tx; /** * @author GoodforGod diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/UncleBlockResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/UncleBlockResponseTO.java new file mode 100644 index 0000000..de94f9e --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/UncleBlockResponseTO.java @@ -0,0 +1,16 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.BlockUncle; + +/** + * @author GoodforGod + * @since 30.10.2018 + */ +public class UncleBlockResponseTO extends BaseResponseTO { + + private BlockUncle result; + + public BlockUncle getResult() { + return result; + } +} diff --git a/src/main/java/io/api/etherscan/util/BasicUtils.java b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java similarity index 80% rename from src/main/java/io/api/etherscan/util/BasicUtils.java rename to src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java index d748abf..4522ff5 100644 --- a/src/main/java/io/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java @@ -1,10 +1,10 @@ -package io.api.etherscan.util; +package io.goodforgod.api.etherscan.util; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.error.InvalidTxHashException; -import io.api.etherscan.model.utility.BaseResponseTO; -import io.api.etherscan.model.utility.BlockParam; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.error.EtherScanInvalidTxHashException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.model.response.BaseResponseTO; +import io.goodforgod.api.etherscan.model.response.BlockParam; import java.math.BigInteger; import java.util.*; import java.util.regex.Pattern; @@ -16,7 +16,9 @@ * @author GoodforGod * @since 28.10.2018 */ -public class BasicUtils { +public final class BasicUtils { + + private BasicUtils() {} private static final int MAX_END_BLOCK = 999999999; private static final int MIN_START_BLOCK = 0; @@ -25,8 +27,6 @@ public class BasicUtils { private static final Pattern TXHASH_PATTERN = Pattern.compile("0x[a-zA-Z0-9]{64}"); private static final Pattern HEX_PATTERN = Pattern.compile("[a-zA-Z0-9]+"); - private BasicUtils() {} - public static boolean isEmpty(String value) { return value == null || value.isEmpty(); } @@ -78,7 +78,7 @@ public static BigInteger parseHex(String hex) { return BigInteger.valueOf(0); final String formatted = (hex.length() > 2 && hex.charAt(0) == '0' && hex.charAt(1) == 'x') - ? hex.substring(2, hex.length()) + ? hex.substring(2) : hex; return new BigInteger(formatted, 16); @@ -89,24 +89,24 @@ public static BigInteger parseHex(String hex) { public static void validateAddress(String address) { if (isNotAddress(address)) - throw new InvalidAddressException("Address [" + address + "] is not Ethereum based."); + throw new EtherScanInvalidAddressException("Address [" + address + "] is not Ethereum based."); } public static void validateTxHash(String txhash) { if (isNotTxHash(txhash)) - throw new InvalidTxHashException("TxHash [" + txhash + "] is not Ethereum based."); + throw new EtherScanInvalidTxHashException("TxHash [" + txhash + "] is not Ethereum based."); } public static <T extends BaseResponseTO> void validateTxResponse(T response) { if (response == null) - throw new EtherScanException("EtherScan responded with null value"); + throw new EtherScanResponseException("EtherScan responded with null value"); if (response.getStatus() != 1) { if (response.getMessage() == null) { - throw new EtherScanException( + throw new EtherScanResponseException( "Unexpected Etherscan exception, no information from server about error, code " + response.getStatus()); } else if (!response.getMessage().startsWith("No tra") && !response.getMessage().startsWith("No rec")) { - throw new EtherScanException(response); + throw new EtherScanResponseException(response); } } } @@ -114,7 +114,7 @@ public static <T extends BaseResponseTO> void validateTxResponse(T response) { public static void validateAddresses(List<String> addresses) { for (String address : addresses) { if (isNotAddress(address)) - throw new InvalidAddressException("Address [" + address + "] is not Ethereum based."); + throw new EtherScanInvalidAddressException("Address [" + address + "] is not Ethereum based."); } } diff --git a/src/test/java/io/api/ApiRunner.java b/src/test/java/io/api/ApiRunner.java deleted file mode 100644 index e78ea6d..0000000 --- a/src/test/java/io/api/ApiRunner.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.api; - -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.manager.impl.QueueManager; -import io.api.etherscan.model.EthNetwork; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; - -public class ApiRunner extends Assertions { - - private static final EtherScanApi api; - private static final EtherScanApi apiRopsten; - private static final EtherScanApi apiRinkeby; - private static final EtherScanApi apiKovan; - private static final String apiKey; - - static { - final String key = System.getenv("API_KEY"); - apiKey = (key == null || key.isEmpty()) - ? EtherScanApi.DEFAULT_KEY - : key; - - final QueueManager queueManager = (EtherScanApi.DEFAULT_KEY.equals(apiKey)) - ? QueueManager.DEFAULT_KEY_QUEUE - : new QueueManager(1, 1200L, 1200L, 0); - - api = new EtherScanApi(ApiRunner.apiKey, EthNetwork.MAINNET, queueManager); - apiKovan = new EtherScanApi(ApiRunner.apiKey, EthNetwork.KOVAN, queueManager); - apiRopsten = new EtherScanApi(ApiRunner.apiKey, EthNetwork.ROPSTEN, queueManager); - apiRinkeby = new EtherScanApi(ApiRunner.apiKey, EthNetwork.RINKEBY, queueManager); - } - - public static String getApiKey() { - return apiKey; - } - - public static EtherScanApi getApi() { - return api; - } - - public static EtherScanApi getApiRopsten() { - return apiRopsten; - } - - public static EtherScanApi getApiRinkeby() { - return apiRinkeby; - } - - public static EtherScanApi getApiKovan() { - return apiKovan; - } - - @AfterAll - public static void cleanup() throws Exception { - api.close(); - apiRopsten.close(); - apiRinkeby.close(); - apiKovan.close(); - } -} diff --git a/src/test/java/io/api/etherscan/EtherScanApiTest.java b/src/test/java/io/api/etherscan/EtherScanApiTest.java deleted file mode 100644 index b649302..0000000 --- a/src/test/java/io/api/etherscan/EtherScanApiTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.api.etherscan; - -import io.api.ApiRunner; -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.ApiKeyException; -import io.api.etherscan.error.ApiTimeoutException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.executor.impl.HttpExecutor; -import io.api.etherscan.model.Balance; -import io.api.etherscan.model.EthNetwork; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import org.junit.jupiter.api.Test; - -/** - * @author GoodforGod - * @since 05.11.2018 - */ -class EtherScanApiTest extends ApiRunner { - - private final EthNetwork network = EthNetwork.KOVAN; - private final String validKey = "YourKey"; - - @Test - void validKey() { - EtherScanApi api = new EtherScanApi(validKey, network); - assertNotNull(api); - } - - @Test - void emptyKey() { - assertThrows(ApiKeyException.class, () -> new EtherScanApi("")); - } - - @Test - void blankKey() { - assertThrows(ApiKeyException.class, () -> new EtherScanApi(" ", network)); - } - - @Test - void nullNetwork() { - assertThrows(ApiException.class, () -> new EtherScanApi(validKey, null)); - } - - @Test - void noTimeoutOnRead() { - Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(300); - EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET, supplier); - Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void noTimeoutOnReadGroli() { - Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(300); - Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void noTimeoutOnReadTobalala() { - Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(30000); - Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void noTimeoutUnlimitedAwait() { - Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void timeout() throws InterruptedException { - TimeUnit.SECONDS.sleep(5); - Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(300, 300); - EtherScanApi api = new EtherScanApi(getApiKey(), EthNetwork.KOVAN, supplier); - assertThrows(ApiTimeoutException.class, () -> api.account().minedBlocks("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D")); - } -} diff --git a/src/test/java/io/api/manager/QueueManagerTest.java b/src/test/java/io/api/manager/QueueManagerTest.java deleted file mode 100644 index 7bd53a9..0000000 --- a/src/test/java/io/api/manager/QueueManagerTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.api.manager; - -import io.api.ApiRunner; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.manager.impl.FakeQueueManager; -import io.api.etherscan.manager.impl.QueueManager; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; - -/** - * @author GoodforGod - * @since 03.11.2018 - */ -class QueueManagerTest extends ApiRunner { - - @Test - void fakeManager() { - IQueueManager fakeManager = new FakeQueueManager(); - fakeManager.takeTurn(); - fakeManager.takeTurn(); - fakeManager.takeTurn(); - fakeManager.takeTurn(); - fakeManager.takeTurn(); - fakeManager.takeTurn(); - assertNotNull(fakeManager); - } - - @Test - @Timeout(3500) - void queueManager() { - IQueueManager queueManager = new QueueManager(1, 3); - queueManager.takeTurn(); - queueManager.takeTurn(); - assertNotNull(queueManager); - } - - @Test - @Timeout(4500) - void queueManagerWithDelay() { - IQueueManager queueManager = new QueueManager(1, 2, 2); - queueManager.takeTurn(); - queueManager.takeTurn(); - assertNotNull(queueManager); - } - - @Test - void queueManagerTimeout() { - IQueueManager queueManager = new QueueManager(1, 3); - queueManager.takeTurn(); - long start = System.currentTimeMillis(); - queueManager.takeTurn(); - long end = System.currentTimeMillis(); - assertEquals(3, Math.round((double) (end - start) / 1000)); - } -} diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java new file mode 100644 index 0000000..3b9cbe2 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -0,0 +1,66 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.manager.impl.SemaphoreRequestQueueManager; +import java.time.Duration; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; + +public class ApiRunner extends Assertions { + + private static final String DEFAULT_KEY = "YourApiKeyToken"; + + private static final EtherScanAPI api; + private static final EtherScanAPI apiRopsten; + private static final EtherScanAPI apiRinkeby; + private static final EtherScanAPI apiKovan; + private static final String apiKey; + + static { + final String key = System.getenv("API_KEY"); + apiKey = (key == null || key.isEmpty()) + ? DEFAULT_KEY + : key; + + final RequestQueueManager queueManager = (DEFAULT_KEY.equals(apiKey)) + ? RequestQueueManager.DEFAULT_KEY_QUEUE + : new SemaphoreRequestQueueManager(1, Duration.ofMillis(1200L), Duration.ofMillis(1200L), 0); + + api = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) + .build(); + apiKovan = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.KOVAN).withQueue(queueManager) + .build(); + apiRopsten = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.ROPSTEN).withQueue(queueManager) + .build(); + apiRinkeby = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.RINKEBY).withQueue(queueManager) + .build(); + } + + public static String getApiKey() { + return apiKey; + } + + public static EtherScanAPI getApi() { + return api; + } + + public static EtherScanAPI getApiRopsten() { + return apiRopsten; + } + + public static EtherScanAPI getApiRinkeby() { + return apiRinkeby; + } + + public static EtherScanAPI getApiKovan() { + return apiKovan; + } + + @AfterAll + public static void cleanup() throws Exception { + api.close(); + apiRopsten.close(); + apiRinkeby.close(); + apiKovan.close(); + } +} diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java new file mode 100644 index 0000000..5cc0fe7 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -0,0 +1,76 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanKeyException; +import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.executor.impl.UrlEthHttpClient; +import io.goodforgod.api.etherscan.model.Balance; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import org.junit.jupiter.api.Test; + +/** + * @author GoodforGod + * @since 05.11.2018 + */ +class EtherScanAPITests extends ApiRunner { + + private final EthNetworks network = EthNetworks.KOVAN; + private final String validKey = "YourKey"; + + @Test + void validKey() { + EtherScanAPI api = EtherScanAPI.builder().withApiKey(validKey).withNetwork(network).build(); + assertNotNull(api); + } + + @Test + void emptyKey() { + assertThrows(EtherScanKeyException.class, () -> EtherScanAPI.builder().withApiKey("").build()); + } + + @Test + void blankKey() { + assertThrows(EtherScanKeyException.class, + () -> EtherScanAPI.builder().withApiKey(" ").withNetwork(network).build()); + } + + @Test + void noTimeoutOnRead() { + Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300)); + EtherScanAPI api = EtherScanAPI.builder().withNetwork(EthNetworks.MAINNET).withHttpClient(supplier).build(); + Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + assertNotNull(balance); + } + + @Test + void noTimeoutOnReadGroli() { + Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300)); + Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + assertNotNull(balance); + } + + @Test + void noTimeoutOnReadTobalala() { + Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(30000)); + Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + assertNotNull(balance); + } + + @Test + void noTimeoutUnlimitedAwait() { + Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); + assertNotNull(balance); + } + + @Test + void timeout() throws InterruptedException { + TimeUnit.SECONDS.sleep(5); + Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300), Duration.ofMillis(300)); + EtherScanAPI api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.KOVAN).withHttpClient(supplier) + .build(); + assertThrows(EtherScanTimeoutException.class, + () -> api.account().blocksMined("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D")); + } +} diff --git a/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTest.java similarity index 88% rename from src/test/java/io/api/etherscan/account/AccountBalanceListTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTest.java index 6864175..cd3dac9 100644 --- a/src/test/java/io/api/etherscan/account/AccountBalanceListTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTest.java @@ -1,9 +1,9 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.Balance; -import io.api.support.AddressUtil; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.Balance; +import io.goodforgod.api.etherscan.support.AddressUtil; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -61,7 +61,7 @@ void invalidParamWithError() { addresses.add("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); addresses.add("C9F32CE1127e44C51cbD182D6364F3D707Fd0d47"); - assertThrows(InvalidAddressException.class, () -> getApi().account().balances(addresses)); + assertThrows(EtherScanInvalidAddressException.class, () -> getApi().account().balances(addresses)); } @Test diff --git a/src/test/java/io/api/etherscan/account/AccountBalanceTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTest.java similarity index 67% rename from src/test/java/io/api/etherscan/account/AccountBalanceTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTest.java index d5427ab..4c06c7c 100644 --- a/src/test/java/io/api/etherscan/account/AccountBalanceTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTest.java @@ -1,9 +1,9 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.Balance; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.EtherScanAPI; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.Balance; import org.junit.jupiter.api.Test; /** @@ -12,7 +12,7 @@ */ class AccountBalanceTest extends ApiRunner { - private final EtherScanApi api = getApi(); + private final EtherScanAPI api = getApi(); @Test void correct() { @@ -29,7 +29,8 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, () -> getApi().account().balance("8d4426f94e42f721C7116E81d6688cd935cB3b4F")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().account().balance("8d4426f94e42f721C7116E81d6688cd935cB3b4F")); } @Test diff --git a/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTest.java similarity index 65% rename from src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTest.java index ae16174..13d5075 100644 --- a/src/test/java/io/api/etherscan/account/AccountMinedBlocksTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTest.java @@ -1,9 +1,9 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.Block; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.EtherScanAPI; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.Block; import java.util.List; import org.junit.jupiter.api.Test; @@ -13,11 +13,11 @@ */ class AccountMinedBlocksTest extends ApiRunner { - private final EtherScanApi api = getApi(); + private final EtherScanAPI api = getApi(); @Test void correct() { - List<Block> blocks = api.account().minedBlocks("0xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23"); + List<Block> blocks = api.account().blocksMined("0xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23"); assertNotNull(blocks); assertEquals(223, blocks.size()); @@ -32,13 +32,13 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, - () -> getApi().account().minedBlocks("xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().account().blocksMined("xE4C6175183029A0f039bf2DFffa5C6e8F3cA9B23")); } @Test void correctParamWithEmptyExpectedResult() { - List<Block> txs = api.account().minedBlocks("0xE1C6175183029A0f039bf2DFffa5C6e8F3cA9B23"); + List<Block> txs = api.account().blocksMined("0xE1C6175183029A0f039bf2DFffa5C6e8F3cA9B23"); assertNotNull(txs); assertTrue(txs.isEmpty()); } diff --git a/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTest.java similarity index 63% rename from src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTest.java index b8b8146..4df75f3 100644 --- a/src/test/java/io/api/etherscan/account/AccountTokenBalanceTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTest.java @@ -1,9 +1,9 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.TokenBalance; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.EtherScanAPI; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.TokenBalance; import org.junit.jupiter.api.Test; /** @@ -12,7 +12,7 @@ */ class AccountTokenBalanceTest extends ApiRunner { - private final EtherScanApi api = getApi(); + private final EtherScanAPI api = getApi(); @Test void correct() { @@ -31,14 +31,16 @@ void correct() { @Test void invalidAddressParamWithError() { - assertThrows(InvalidAddressException.class, () -> api.account().balance("0x5807e7F124EC2103a59c5249187f772c0b8D6b2", - "0x5EaC95ad5b287cF44E058dCf694419333b796123")); + assertThrows(EtherScanInvalidAddressException.class, + () -> api.account().balance("0x5807e7F124EC2103a59c5249187f772c0b8D6b2", + "0x5EaC95ad5b287cF44E058dCf694419333b796123")); } @Test void invalidContractParamWithError() { - assertThrows(InvalidAddressException.class, () -> api.account().balance("0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", - "0xEaC95ad5b287cF44E058dCf694419333b796123")); + assertThrows(EtherScanInvalidAddressException.class, + () -> api.account().balance("0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", + "0xEaC95ad5b287cF44E058dCf694419333b796123")); } @Test diff --git a/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxERC20Test.java similarity index 72% rename from src/test/java/io/api/etherscan/account/AccountTxTokenTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxERC20Test.java index 044991b..bacf2e3 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxTokenTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxERC20Test.java @@ -1,8 +1,8 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.TxToken; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.TxERC20; import java.util.List; import org.junit.jupiter.api.Test; @@ -10,11 +10,11 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTxTokenTest extends ApiRunner { +class AccountTxERC20Test extends ApiRunner { @Test void correct() { - List<TxToken> txs = getApi().account().txsToken("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); + List<TxERC20> txs = getApi().account().txsERC20("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); assertNotNull(txs); assertEquals(3, txs.size()); assertTxs(txs); @@ -33,7 +33,7 @@ void correct() { @Test void correctStartBlock() { - List<TxToken> txs = getApi().account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); + List<TxERC20> txs = getApi().account().txsERC20("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); assertNotNull(txs); assertEquals(11, txs.size()); assertTxs(txs); @@ -41,7 +41,7 @@ void correctStartBlock() { @Test void correctStartBlockEndBlock() { - List<TxToken> txs = getApi().account().txsToken("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); + List<TxERC20> txs = getApi().account().txsERC20("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); assertNotNull(txs); assertEquals(5, txs.size()); assertTxs(txs); @@ -49,19 +49,19 @@ void correctStartBlockEndBlock() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, - () -> getApi().account().txsToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().account().txsERC20("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); } @Test void correctParamWithEmptyExpectedResult() { - List<TxToken> txs = getApi().account().txsToken("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + List<TxERC20> txs = getApi().account().txsERC20("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); } - private void assertTxs(List<TxToken> txs) { - for (TxToken tx : txs) { + private void assertTxs(List<TxERC20> txs) { + for (TxERC20 tx : txs) { assertNotNull(tx.getBlockHash()); assertNotNull(tx.getTokenName()); assertNotNull(tx.getTokenSymbol()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTest.java similarity index 81% rename from src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTest.java index 4e63dbc..13036bc 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalByHashTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTest.java @@ -1,10 +1,10 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.error.InvalidTxHashException; -import io.api.etherscan.model.TxInternal; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.EtherScanAPI; +import io.goodforgod.api.etherscan.error.EtherScanInvalidTxHashException; +import io.goodforgod.api.etherscan.model.TxInternal; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.List; import org.junit.jupiter.api.Test; @@ -14,7 +14,7 @@ */ class AccountTxInternalByHashTest extends ApiRunner { - private final EtherScanApi api = getApi(); + private final EtherScanAPI api = getApi(); @Test void correct() { @@ -42,7 +42,7 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidTxHashException.class, + assertThrows(EtherScanInvalidTxHashException.class, () -> api.account().txsInternalByHash("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b")); } diff --git a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTest.java similarity index 86% rename from src/test/java/io/api/etherscan/account/AccountTxInternalTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTest.java index 7144671..6fb92b4 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxInternalTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.TxInternal; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.TxInternal; import java.util.List; import org.junit.jupiter.api.Test; @@ -41,7 +41,7 @@ void correctStartBlockEndBlock() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, + assertThrows(EtherScanInvalidAddressException.class, () -> getApi().account().txsInternal("0x2C1ba59D6F58433FB1EaEe7d20b26Ed83bDA51")); } diff --git a/src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java similarity index 65% rename from src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java index 6601d1a..c85aaa4 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxRc721TokenTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java @@ -1,9 +1,11 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.TxToken; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.TxERC20; import java.util.List; + +import io.goodforgod.api.etherscan.model.TxERC721; import org.junit.jupiter.api.Test; /** @@ -14,7 +16,7 @@ class AccountTxRc721TokenTest extends ApiRunner { @Test void correct() { - List<TxToken> txs = getApi().account().txsNftToken("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67"); + List<TxERC721> txs = getApi().account().txsERC721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67"); assertNotNull(txs); assertEquals(16, txs.size()); assertTxs(txs); @@ -33,7 +35,7 @@ void correct() { @Test void correctStartBlock() { - List<TxToken> txs = getApi().account().txsNftToken("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4762071); + List<TxERC721> txs = getApi().account().txsERC721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4762071); System.out.println(txs); assertNotNull(txs); assertEquals(5, txs.size()); @@ -42,7 +44,7 @@ void correctStartBlock() { @Test void correctStartBlockEndBlock() { - List<TxToken> txs = getApi().account().txsNftToken("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4761862, 4761934); + List<TxERC721> txs = getApi().account().txsERC721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4761862, 4761934); System.out.println(txs); assertNotNull(txs); assertEquals(11, txs.size()); @@ -51,19 +53,19 @@ void correctStartBlockEndBlock() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, - () -> getApi().account().txsNftToken("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().account().txsERC721("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); } @Test void correctParamWithEmptyExpectedResult() { - List<TxToken> txs = getApi().account().txsNftToken("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + List<TxERC721> txs = getApi().account().txsERC721("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); } - private void assertTxs(List<TxToken> txs) { - for (TxToken tx : txs) { + private void assertTxs(List<TxERC721> txs) { + for (TxERC721 tx : txs) { assertNotNull(tx.getBlockHash()); assertNotNull(tx.getTokenName()); assertNotNull(tx.getTokenSymbol()); diff --git a/src/test/java/io/api/etherscan/account/AccountTxsTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTest.java similarity index 87% rename from src/test/java/io/api/etherscan/account/AccountTxsTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTest.java index 899d0fb..a2cffd1 100644 --- a/src/test/java/io/api/etherscan/account/AccountTxsTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.account; +package io.goodforgod.api.etherscan.account; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.Tx; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.Tx; import java.util.List; import org.junit.jupiter.api.Test; @@ -54,7 +54,8 @@ void correctStartBlockEndBlock() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, () -> getApi().account().txs("9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().account().txs("9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9")); } @Test diff --git a/src/test/java/io/api/etherscan/block/BlockApiTest.java b/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTest.java similarity index 82% rename from src/test/java/io/api/etherscan/block/BlockApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/block/BlockApiTest.java index cee8bb9..8e3b529 100644 --- a/src/test/java/io/api/etherscan/block/BlockApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.block; +package io.goodforgod.api.etherscan.block; -import io.api.ApiRunner; -import io.api.etherscan.model.UncleBlock; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.BlockUncle; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -13,7 +13,7 @@ class BlockApiTest extends ApiRunner { @Test void correct() { - Optional<UncleBlock> uncle = getApi().block().uncles(2165403); + Optional<BlockUncle> uncle = getApi().block().uncles(2165403); assertTrue(uncle.isPresent()); assertFalse(uncle.get().isEmpty()); assertNotNull(uncle.get().getBlockMiner()); @@ -25,7 +25,7 @@ void correct() { assertNotEquals(-1, uncle.get().getUncles().get(0).getUnclePosition()); assertNotNull(uncle.get().toString()); - UncleBlock empty = new UncleBlock(); + BlockUncle empty = new BlockUncle(); assertNotEquals(uncle.get().hashCode(), empty.hashCode()); assertNotEquals(uncle.get(), empty); assertTrue(empty.isEmpty()); @@ -44,14 +44,14 @@ void correct() { @Test void correctNoUncles() { - Optional<UncleBlock> uncles = getApi().block().uncles(34); + Optional<BlockUncle> uncles = getApi().block().uncles(34); assertTrue(uncles.isPresent()); assertTrue(uncles.get().getUncles().isEmpty()); } @Test void correctParamWithEmptyExpectedResult() { - Optional<UncleBlock> uncles = getApi().block().uncles(99999999934L); + Optional<BlockUncle> uncles = getApi().block().uncles(99999999934L); assertFalse(uncles.isPresent()); } } diff --git a/src/test/java/io/api/etherscan/contract/ContractApiTest.java b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTest.java similarity index 78% rename from src/test/java/io/api/etherscan/contract/ContractApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTest.java index 85fb905..62aa7da 100644 --- a/src/test/java/io/api/etherscan/contract/ContractApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.contract; +package io.goodforgod.api.etherscan.contract; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.model.Abi; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.Abi; import org.junit.jupiter.api.Test; /** @@ -27,7 +27,7 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, + assertThrows(EtherScanInvalidAddressException.class, () -> getApi().contract().contractAbi("0xBBbc244D798123fDe783fCc1C72d3Bb8C189413")); } diff --git a/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java b/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTest.java similarity index 59% rename from src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java rename to src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTest.java index f956364..752c34c 100644 --- a/src/test/java/io/api/etherscan/logs/LogQueryBuilderTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTest.java @@ -1,12 +1,9 @@ -package io.api.etherscan.logs; - -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.error.LogQueryException; -import io.api.etherscan.model.query.LogOp; -import io.api.etherscan.model.query.impl.LogQuery; -import io.api.etherscan.model.query.impl.LogQueryBuilder; -import io.api.etherscan.model.query.impl.LogTopicQuadro; +package io.goodforgod.api.etherscan.logs; + +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.query.*; import org.junit.jupiter.api.Test; /** @@ -17,53 +14,55 @@ class LogQueryBuilderTest extends ApiRunner { @Test void singleCorrect() { - LogQuery single = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") + LogQuery single = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); assertNotNull(single); - assertNotNull(single.getParams()); + assertNotNull(single.params()); } @Test void singleInCorrectAddress() { - assertThrows(InvalidAddressException.class, () -> LogQueryBuilder.with("033990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") - .build()); + assertThrows(EtherScanInvalidAddressException.class, + () -> LogQuery.builder("033990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") + .build()); } @Test void singleInCorrectTopic() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("6516=") + assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("6516=") .build()); } @Test void tupleCorrect() { - LogQuery tuple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + LogQuery tuple = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) .build(); assertNotNull(tuple); - assertNotNull(tuple.getParams()); + assertNotNull(tuple.params()); } @Test void tupleInCorrectOp() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", - "0x72657075746174696f6e00000000000000000000000000000000000000000000") - .setOpTopic0_1(null) - .build()); + assertThrows(ErtherScanLogQueryException.class, + () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + "0x72657075746174696f6e00000000000000000000000000000000000000000000") + .setOpTopic0_1(null) + .build()); } @Test void tripleCorrect() { - LogQuery triple = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + LogQuery triple = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) @@ -72,14 +71,14 @@ void tripleCorrect() { .build(); assertNotNull(triple); - assertNotNull(triple.getParams()); + assertNotNull(triple.params()); } @Test void tripleInCorrectOp() { - assertThrows(LogQueryException.class, - () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, + () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) @@ -90,9 +89,9 @@ void tripleInCorrectOp() { @Test void tripleInCorrectTopic1() { - assertThrows(LogQueryException.class, - () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic(null, + assertThrows(ErtherScanLogQueryException.class, + () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) + .withTopic(null, "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) @@ -103,9 +102,9 @@ void tripleInCorrectTopic1() { @Test void tripleInCorrectTopic2() { - assertThrows(LogQueryException.class, - () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, + () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null, "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) @@ -116,9 +115,9 @@ void tripleInCorrectTopic2() { @Test void tripleInCorrectTopic3() { - assertThrows(LogQueryException.class, - () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, + () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", null) .setOpTopic0_1(LogOp.AND) @@ -129,8 +128,8 @@ void tripleInCorrectTopic3() { @Test void quadroCorrect() { - LogQuery quadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + LogQuery quadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000") @@ -143,13 +142,13 @@ void quadroCorrect() { .build(); assertNotNull(quadro); - assertNotNull(quadro.getParams()); + assertNotNull(quadro.params()); } @Test void quadroIncorrectTopic2() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null, "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000") @@ -164,8 +163,8 @@ void quadroIncorrectTopic2() { @Test void tupleIncorrectTopic2() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null) .setOpTopic0_1(LogOp.AND) .build()); @@ -173,8 +172,8 @@ void tupleIncorrectTopic2() { @Test void tupleIncorrectTopic1() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic(null, + assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic(null, "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .setOpTopic0_1(LogOp.AND) .build()); @@ -182,13 +181,13 @@ void tupleIncorrectTopic1() { @Test void quadroIncorrectOp1() { - final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + final LogTopicQuadro topicQuadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - assertThrows(LogQueryException.class, () -> topicQuadro + assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(null) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -200,13 +199,13 @@ void quadroIncorrectOp1() { @Test void quadroIncorrectOp2() { - final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + final LogTopicQuadro topicQuadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - assertThrows(LogQueryException.class, () -> topicQuadro.setOpTopic0_1(LogOp.AND) + assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro.setOpTopic0_1(LogOp.AND) .setOpTopic0_2(null) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(LogOp.OR) @@ -217,13 +216,13 @@ void quadroIncorrectOp2() { @Test void quadroIncorrectOp3() { - final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + final LogTopicQuadro topicQuadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - assertThrows(LogQueryException.class, () -> topicQuadro + assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(null) @@ -235,8 +234,8 @@ void quadroIncorrectOp3() { @Test void quadroInCorrectAgainTopic() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000", null) @@ -251,13 +250,13 @@ void quadroInCorrectAgainTopic() { @Test void quadroInCorrectOp4() { - final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + final LogTopicQuadro topicQuadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - assertThrows(LogQueryException.class, () -> topicQuadro + assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -269,13 +268,13 @@ void quadroInCorrectOp4() { @Test void quadroInCorrectOp5() { - final LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + final LogTopicQuadro topicQuadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - assertThrows(LogQueryException.class, () -> topicQuadro + assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -287,13 +286,13 @@ void quadroInCorrectOp5() { @Test void quadroInCorrectOp6() { - LogTopicQuadro topicQuadro = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + LogTopicQuadro topicQuadro = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - assertThrows(LogQueryException.class, () -> topicQuadro + assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -305,8 +304,8 @@ void quadroInCorrectOp6() { @Test void quadroInCorrectTopic() { - assertThrows(LogQueryException.class, () -> LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "", "") diff --git a/src/test/java/io/api/etherscan/logs/LogsApiTest.java b/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java similarity index 67% rename from src/test/java/io/api/etherscan/logs/LogsApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java index e786e0c..0f90d37 100644 --- a/src/test/java/io/api/etherscan/logs/LogsApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java @@ -1,10 +1,9 @@ -package io.api.etherscan.logs; +package io.goodforgod.api.etherscan.logs; -import io.api.ApiRunner; -import io.api.etherscan.model.Log; -import io.api.etherscan.model.query.LogOp; -import io.api.etherscan.model.query.impl.LogQuery; -import io.api.etherscan.model.query.impl.LogQueryBuilder; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.Log; +import io.goodforgod.api.etherscan.model.query.LogOp; +import io.goodforgod.api.etherscan.model.query.LogQuery; import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -18,22 +17,22 @@ class LogsApiTest extends ApiRunner { static Stream<Arguments> source() { - LogQuery single = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") + LogQuery single = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); - LogQuery singleInvalidAddr = LogQueryBuilder.with("0x13990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") + LogQuery singleInvalidAddr = LogQuery.builder("0x13990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); - LogQuery tupleAnd = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + LogQuery tupleAnd = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) .build(); - LogQuery tupleOr = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + LogQuery tupleOr = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.OR) .build(); diff --git a/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java b/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java new file mode 100644 index 0000000..23d14a2 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java @@ -0,0 +1,56 @@ +package io.goodforgod.api.etherscan.manager; + +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.manager.impl.FakeRequestQueueManager; +import io.goodforgod.api.etherscan.manager.impl.SemaphoreRequestQueueManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.time.Duration; + +/** + * @author GoodforGod + * @since 03.11.2018 + */ +class SemaphoreRequestQueueManagerTest extends ApiRunner { + + @Test + void fakeManager() { + RequestQueueManager fakeManager = new FakeRequestQueueManager(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + fakeManager.takeTurn(); + assertNotNull(fakeManager); + } + + @Test + @Timeout(3500) + void queueManager() { + RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(3)); + requestQueueManager.takeTurn(); + requestQueueManager.takeTurn(); + assertNotNull(requestQueueManager); + } + + @Test + @Timeout(4500) + void queueManagerWithDelay() { + RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(2), Duration.ofSeconds(2)); + requestQueueManager.takeTurn(); + requestQueueManager.takeTurn(); + assertNotNull(requestQueueManager); + } + + @Test + void queueManagerTimeout() { + RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(3)); + requestQueueManager.takeTurn(); + long start = System.currentTimeMillis(); + requestQueueManager.takeTurn(); + long end = System.currentTimeMillis(); + assertEquals(3, Math.round((double) (end - start) / 1000)); + } +} diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java similarity index 76% rename from src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java index faead19..64e3fe6 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java @@ -1,10 +1,10 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.core.impl.EtherScanApi; -import io.api.etherscan.manager.impl.QueueManager; -import io.api.etherscan.model.EthNetwork; -import io.api.etherscan.model.proxy.BlockProxy; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.EthNetworks; +import io.goodforgod.api.etherscan.EtherScanAPI; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.proxy.BlockProxy; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -14,11 +14,12 @@ */ class ProxyBlockApiTest extends ApiRunner { - private final EtherScanApi api; + private final EtherScanAPI api; ProxyBlockApiTest() { - final QueueManager queueManager = new QueueManager(1, 5100L, 5100L, 0); - this.api = new EtherScanApi(getApiKey(), EthNetwork.MAINNET, queueManager); + final RequestQueueManager queueManager = RequestQueueManager.DEFAULT_KEY_QUEUE; + this.api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) + .build(); } @Test diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTest.java similarity index 75% rename from src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTest.java index f866b6a..c4f5e31 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockLastNoApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTest.java @@ -1,6 +1,6 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; +import io.goodforgod.api.etherscan.ApiRunner; import org.junit.jupiter.api.Test; /** diff --git a/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTest.java similarity index 83% rename from src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTest.java index 67a8875..c575072 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyBlockUncleApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.model.proxy.BlockProxy; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.proxy.BlockProxy; import java.util.Optional; import org.junit.jupiter.api.Test; diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTest.java similarity index 54% rename from src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTest.java index 8cf46c9..67e7682 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCallApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTest.java @@ -1,9 +1,9 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.error.InvalidDataHexException; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.error.EtherScanInvalidDataHexException; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -23,14 +23,16 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, () -> getApi().proxy().call("0xEEF46DB4855E25702F8237E8f403FddcaF931C0", - "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().proxy().call("0xEEF46DB4855E25702F8237E8f403FddcaF931C0", + "0x70a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724")); } @Test void invalidParamNotHex() { - assertThrows(InvalidDataHexException.class, () -> getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", - "7-0a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724")); + assertThrows(EtherScanInvalidDataHexException.class, + () -> getApi().proxy().call("0xAEEF46DB4855E25702F8237E8f403FddcaF931C0", + "7-0a08231000000000000000000000000e16359506c028e51f16be38986ec5746251e9724")); } @Test diff --git a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTest.java similarity index 66% rename from src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTest.java index 6835f07..c9dab25 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyCodeApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; -import io.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -21,7 +21,8 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, () -> getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().proxy().code("0f75e354c5edc8efed9b59ee9f67a80845ade7d0c")); } @Test diff --git a/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTest.java similarity index 79% rename from src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTest.java index b4b6f37..1b40705 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyGasApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidDataHexException; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidDataHexException; import java.math.BigInteger; import org.junit.jupiter.api.Test; @@ -38,6 +38,6 @@ void correctEstimatedWithData() { @Test void invalidParamWithError() { String dataCustom = "280&60106000396000f360606040526000"; - assertThrows(InvalidDataHexException.class, () -> getApi().proxy().gasEstimated(dataCustom)); + assertThrows(EtherScanInvalidDataHexException.class, () -> getApi().proxy().gasEstimated(dataCustom)); } } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTest.java similarity index 76% rename from src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTest.java index 6d2e8e4..2580e22 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, + assertThrows(EtherScanInvalidAddressException.class, () -> getApi().proxy().storageAt("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd", 0)); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTest.java similarity index 88% rename from src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTest.java index decf95f..d6790bd 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidTxHashException; -import io.api.etherscan.model.proxy.TxProxy; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidTxHashException; +import io.goodforgod.api.etherscan.model.proxy.TxProxy; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -51,7 +51,7 @@ void correctByBlockNo() { @Test void invalidParamWithError() { - assertThrows(InvalidTxHashException.class, + assertThrows(EtherScanInvalidTxHashException.class, () -> getApi().proxy().tx("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1")); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTest.java similarity index 81% rename from src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTest.java index 0083f7a..a2327da 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxCountApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; import org.junit.jupiter.api.Test; /** @@ -24,7 +24,7 @@ void correctByBlockNo() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, + assertThrows(EtherScanInvalidAddressException.class, () -> getApi().proxy().txSendCount("0xe03d9cce9d60f3e9f2597e13cd4c54c55330cfd")); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTest.java similarity index 85% rename from src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTest.java index 0159ed9..ba6370c 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxReceiptApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidTxHashException; -import io.api.etherscan.model.proxy.ReceiptProxy; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidTxHashException; +import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -39,7 +39,7 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidTxHashException.class, () -> getApi().proxy() + assertThrows(EtherScanInvalidTxHashException.class, () -> getApi().proxy() .txReceipt("0xe2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1")); } diff --git a/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTest.java similarity index 62% rename from src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTest.java index 676dc3a..9f69060 100644 --- a/src/test/java/io/api/etherscan/proxy/ProxyTxSendRawApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.proxy; +package io.goodforgod.api.etherscan.proxy; -import io.api.ApiRunner; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.error.InvalidDataHexException; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidDataHexException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -21,12 +21,12 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidDataHexException.class, () -> getApi().proxy().txSendRaw("5151=0561")); + assertThrows(EtherScanInvalidDataHexException.class, () -> getApi().proxy().txSendRaw("5151=0561")); } @Test void invalidParamEtherScanDataException() { - assertThrows(EtherScanException.class, () -> getApi().proxy().txSendRaw("0x1")); + assertThrows(EtherScanResponseException.class, () -> getApi().proxy().txSendRaw("0x1")); } void correctParamWithEmptyExpectedResult() { diff --git a/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTest.java similarity index 81% rename from src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTest.java index 9f89738..eb43b6e 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticPriceApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.statistic; +package io.goodforgod.api.etherscan.statistic; -import io.api.ApiRunner; -import io.api.etherscan.model.Price; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.Price; import org.junit.jupiter.api.Test; /** diff --git a/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTest.java similarity index 82% rename from src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTest.java index 32c3018..c1e8e58 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticSupplyApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.statistic; +package io.goodforgod.api.etherscan.statistic; -import io.api.ApiRunner; -import io.api.etherscan.model.Supply; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.Supply; import java.math.BigInteger; import org.junit.jupiter.api.Test; diff --git a/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTest.java similarity index 67% rename from src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTest.java index aefb2bd..84c086a 100644 --- a/src/test/java/io/api/etherscan/statistic/StatisticTokenSupplyApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.statistic; +package io.goodforgod.api.etherscan.statistic; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidAddressException; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; import java.math.BigInteger; import org.junit.jupiter.api.Test; @@ -20,7 +20,8 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidAddressException.class, () -> getApi().stats().supply("0x7d90b64a1a57749b0f932f1a3395792e12e7055")); + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().stats().supply("0x7d90b64a1a57749b0f932f1a3395792e12e7055")); } @Test diff --git a/src/test/java/io/api/support/AddressUtil.java b/src/test/java/io/goodforgod/api/etherscan/support/AddressUtil.java similarity index 98% rename from src/test/java/io/api/support/AddressUtil.java rename to src/test/java/io/goodforgod/api/etherscan/support/AddressUtil.java index da04c37..fa007db 100644 --- a/src/test/java/io/api/support/AddressUtil.java +++ b/src/test/java/io/goodforgod/api/etherscan/support/AddressUtil.java @@ -1,4 +1,4 @@ -package io.api.support; +package io.goodforgod.api.etherscan.support; import java.util.ArrayList; import java.util.List; diff --git a/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTest.java similarity index 66% rename from src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTest.java index de67a02..a2a5860 100644 --- a/src/test/java/io/api/etherscan/transaction/TransactionExecApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTest.java @@ -1,8 +1,8 @@ -package io.api.etherscan.transaction; +package io.goodforgod.api.etherscan.transaction; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidTxHashException; -import io.api.etherscan.model.Status; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidTxHashException; +import io.goodforgod.api.etherscan.model.Status; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -14,7 +14,7 @@ class TransactionExecApiTest extends ApiRunner { @Test void correct() { - Optional<Status> status = getApi().txs().execStatus("0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); + Optional<Status> status = getApi().txs().statusExec("0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); assertTrue(status.isPresent()); assertTrue(status.get().haveError()); assertNotNull(status.get().getErrDescription()); @@ -27,13 +27,13 @@ void correct() { @Test void invalidParamWithError() { - assertThrows(InvalidTxHashException.class, - () -> getApi().txs().execStatus("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b")); + assertThrows(EtherScanInvalidTxHashException.class, + () -> getApi().txs().statusExec("0xb513dd971aad228eb31f54489803639de167309ac72de68ecdaeb022a7ab42b")); } @Test void correctParamWithEmptyExpectedResult() { - Optional<Status> status = getApi().txs().execStatus("0x55f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); + Optional<Status> status = getApi().txs().statusExec("0x55f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a"); assertTrue(status.isPresent()); assertFalse(status.get().haveError()); } diff --git a/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTest.java similarity index 61% rename from src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTest.java index 94b93b3..83ca5af 100644 --- a/src/test/java/io/api/etherscan/transaction/TransactionReceiptApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTest.java @@ -1,7 +1,7 @@ -package io.api.etherscan.transaction; +package io.goodforgod.api.etherscan.transaction; -import io.api.ApiRunner; -import io.api.etherscan.error.InvalidTxHashException; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidTxHashException; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -14,21 +14,21 @@ class TransactionReceiptApiTest extends ApiRunner { @Test void correct() { Optional<Boolean> status = getApi().txs() - .receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); + .statusReceipt("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); assertTrue(status.isPresent()); assertTrue(status.get()); } @Test void invalidParamWithError() { - assertThrows(InvalidTxHashException.class, - () -> getApi().txs().receiptStatus("0x13c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76")); + assertThrows(EtherScanInvalidTxHashException.class, + () -> getApi().txs().statusReceipt("0x13c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76")); } @Test void correctParamWithEmptyExpectedResult() { Optional<Boolean> status = getApi().txs() - .receiptStatus("0x113c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); + .statusReceipt("0x113c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); assertFalse(status.isPresent()); } } diff --git a/src/test/java/io/api/util/BasicUtilsTests.java b/src/test/java/io/goodforgod/api/etherscan/util/BasicUtilsTests.java similarity index 75% rename from src/test/java/io/api/util/BasicUtilsTests.java rename to src/test/java/io/goodforgod/api/etherscan/util/BasicUtilsTests.java index 36c22cb..90a2933 100644 --- a/src/test/java/io/api/util/BasicUtilsTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/util/BasicUtilsTests.java @@ -1,11 +1,11 @@ -package io.api.util; +package io.goodforgod.api.etherscan.util; -import static io.api.etherscan.util.BasicUtils.*; +import static io.goodforgod.api.etherscan.util.BasicUtils.*; import com.google.gson.Gson; -import io.api.ApiRunner; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.model.utility.StringResponseTO; +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; @@ -21,7 +21,7 @@ void responseValidateEmpty() { String response = "{\"status\":\"0\",\"message\":\"No ether\",\"result\":\"status\"}"; StringResponseTO responseTO = new Gson().fromJson(response, StringResponseTO.class); - assertThrows(EtherScanException.class, () -> validateTxResponse(responseTO)); + assertThrows(EtherScanResponseException.class, () -> validateTxResponse(responseTO)); } @Test @@ -77,12 +77,12 @@ void isNotHexInvalid() { @Test void isResponseStatusInvalidThrows() { StringResponseTO responseTO = new StringResponseTO(); - assertThrows(EtherScanException.class, () -> validateTxResponse(responseTO)); + assertThrows(EtherScanResponseException.class, () -> validateTxResponse(responseTO)); } @Test void isResponseNullThrows() { StringResponseTO responseTO = null; - assertThrows(EtherScanException.class, () -> validateTxResponse(responseTO)); + assertThrows(EtherScanResponseException.class, () -> validateTxResponse(responseTO)); } } From 420c68f201128dc60f854ef60572c0bdd5877faa Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sat, 13 May 2023 20:47:09 +0300 Subject: [PATCH 14/67] [2.0.0-SNAPSHOT] GasTrackerAPI refactored to new API EthHttpClient package refactoring --- .../goodforgod/api/etherscan/AccountAPI.java | 14 +++---- .../api/etherscan/AccountAPIProvider.java | 13 ++++--- .../api/etherscan/BasicProvider.java | 8 ++-- .../api/etherscan/EtherScanAPI.java | 4 ++ .../api/etherscan/EtherScanAPIProvider.java | 23 +++++++---- .../api/etherscan/GasTrackerAPI.java | 24 ++++++++++++ .../api/etherscan/GasTrackerAPIProvider.java | 37 ++++++++++++++++++ .../api/etherscan/GasTrackerApiProvider.java | 39 ------------------- .../api/etherscan/IGasTrackerApi.java | 23 ----------- .../api/etherscan/model/BlockUncle.java | 1 - .../api/etherscan/model/GasOracle.java | 15 ++++--- .../model/response/GasOracleResponseTO.java | 6 +-- .../account/AccountTxRc721TokenTest.java | 4 +- .../SemaphoreRequestQueueManagerTest.java | 6 +-- 14 files changed, 113 insertions(+), 104 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/GasTrackerApiProvider.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/IGasTrackerApi.java rename src/main/java/io/{ => goodforgod}/api/etherscan/model/GasOracle.java (79%) diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java index 1baefc9..40da2eb 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java @@ -112,21 +112,21 @@ public interface AccountAPI { /** * All ERC-20 token txs for given address and contract address * - * @param address get txs for + * @param address get txs for * @param contractAddress contract address to get txs for - * @param startBlock tx from this blockNumber - * @param endBlock tx to this blockNumber + * @param startBlock tx from this blockNumber + * @param endBlock tx to this blockNumber * @return txs for address - * @throws ApiException parent exception class + * @throws EtherScanException parent exception class */ @NotNull - List<TxToken> txsToken(String address, String contractAddress, long startBlock, long endBlock) throws ApiException; + List<TxERC20> txsERC20(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxToken> txsToken(String address, String contractAddress, long startBlock) throws ApiException; + List<TxERC20> txsERC20(String address, String contractAddress, long startBlock) throws EtherScanException; @NotNull - List<TxToken> txsToken(String address, String contractAddress) throws ApiException; + List<TxERC20> txsERC20(String address, String contractAddress) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index e884399..3cc5409 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -234,19 +234,20 @@ public List<TxERC20> txsERC20(String address, long startBlock, long endBlock) th @NotNull @Override - public List<TxToken> txsToken(final String address, final String contractAddress) throws ApiException { - return txsToken(address, contractAddress, MIN_START_BLOCK); + public List<TxERC20> txsERC20(String address, String contractAddress) throws EtherScanException { + return txsERC20(address, contractAddress, MIN_START_BLOCK); } @NotNull @Override - public List<TxToken> txsToken(final String address, final String contractAddress, final long startBlock) throws ApiException { - return txsToken(address, contractAddress, startBlock, MAX_END_BLOCK); + public List<TxERC20> txsERC20(String address, String contractAddress, long startBlock) throws EtherScanException { + return txsERC20(address, contractAddress, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxToken> txsToken(final String address, final String contractAddress, final long startBlock, final long endBlock) throws ApiException { + public List<TxERC20> txsERC20(String address, String contractAddress, long startBlock, long endBlock) + throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -255,7 +256,7 @@ public List<TxToken> txsToken(final String address, final String contractAddress final String urlParams = ACT_TX_TOKEN_ACTION + offsetParam + ADDRESS_PARAM + address + CONTRACT_PARAM + contractAddress + blockParam + SORT_ASC_PARAM; - return getRequestUsingOffset(urlParams, TxTokenResponseTO.class); + return getRequestUsingOffset(urlParams, TxERC20ResponseTO.class); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 4fd625a..ec5a85a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -32,14 +32,14 @@ abstract class BasicProvider { private final RequestQueueManager queue; private final Gson gson; - BasicProvider(RequestQueueManager queue, + BasicProvider(RequestQueueManager requestQueueManager, String module, String baseUrl, - EthHttpClient executor) { - this.queue = queue; + EthHttpClient ethHttpClient) { + this.queue = requestQueueManager; this.module = "&module=" + module; this.baseUrl = baseUrl; - this.executor = executor; + this.executor = ethHttpClient; this.gson = new GsonConfiguration().builder().create(); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java index 76dcab7..d902074 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -34,6 +34,10 @@ public interface EtherScanAPI extends AutoCloseable { @NotNull StatisticAPI stats(); + @NotNull + GasTrackerAPI gasTracker(); + + @NotNull static Builder builder() { return new EthScanAPIBuilder(); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java index 0043e37..675836f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java @@ -21,23 +21,25 @@ final class EtherScanAPIProvider implements EtherScanAPI { private final ProxyAPI proxy; private final StatisticAPI stats; private final TransactionAPI txs; + private final GasTrackerAPI gasTracker; EtherScanAPIProvider(String apiKey, EthNetwork network, Supplier<EthHttpClient> executorSupplier, RequestQueueManager queue) { // EtherScan 1request\5sec limit support by queue manager - final EthHttpClient executor = executorSupplier.get(); + final EthHttpClient ethHttpClient = executorSupplier.get(); final String baseUrl = network.domain() + "?apikey=" + apiKey; this.requestQueueManager = queue; - this.account = new AccountAPIProvider(queue, baseUrl, executor); - this.block = new BlockAPIProvider(queue, baseUrl, executor); - this.contract = new ContractAPIProvider(queue, baseUrl, executor); - this.logs = new LogsAPIProvider(queue, baseUrl, executor); - this.proxy = new ProxyAPIProvider(queue, baseUrl, executor); - this.stats = new StatisticAPIProvider(queue, baseUrl, executor); - this.txs = new TransactionAPIProvider(queue, baseUrl, executor); + this.account = new AccountAPIProvider(queue, baseUrl, ethHttpClient); + this.block = new BlockAPIProvider(queue, baseUrl, ethHttpClient); + this.contract = new ContractAPIProvider(queue, baseUrl, ethHttpClient); + this.logs = new LogsAPIProvider(queue, baseUrl, ethHttpClient); + this.proxy = new ProxyAPIProvider(queue, baseUrl, ethHttpClient); + this.stats = new StatisticAPIProvider(queue, baseUrl, ethHttpClient); + this.txs = new TransactionAPIProvider(queue, baseUrl, ethHttpClient); + this.gasTracker = new GasTrackerAPIProvider(queue, baseUrl, ethHttpClient); } @NotNull @@ -82,6 +84,11 @@ public StatisticAPI stats() { return stats; } + @Override + public @NotNull GasTrackerAPI gasTracker() { + return gasTracker; + } + @Override public void close() throws Exception { requestQueueManager.close(); diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java new file mode 100644 index 0000000..d49e14f --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java @@ -0,0 +1,24 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.GasOracle; +import org.jetbrains.annotations.NotNull; + +/** + * EtherScan - API Descriptions + * <a href="https://docs.etherscan.io/api-endpoints/gas-tracker">...</a> + * + * @author Abhay Gupta + * @since 14.11.2022 + */ +public interface GasTrackerAPI { + + /** + * GasOracle details + * + * @return fast, suggested gas price + * @throws EtherScanException parent exception class + */ + @NotNull + GasOracle oracle() throws EtherScanException; +} diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java new file mode 100644 index 0000000..faa01e5 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -0,0 +1,37 @@ +package io.goodforgod.api.etherscan; + +import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; +import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.GasOracle; +import io.goodforgod.api.etherscan.model.response.GasOracleResponseTO; +import org.jetbrains.annotations.NotNull; + +/** + * GasTracker API Implementation + * + * @see GasTrackerAPI + * @author Abhay Gupta + * @since 14.11.2022 + */ +final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI { + + private static final String ACT_GAS_ORACLE_PARAM = ACT_PREFIX + "gasoracle"; + + GasTrackerAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient ethHttpClient) { + super(queue, "gastracker", baseUrl, ethHttpClient); + } + + @NotNull + @Override + public GasOracle oracle() throws EtherScanException { + final GasOracleResponseTO response = getRequest(ACT_GAS_ORACLE_PARAM, GasOracleResponseTO.class); + if (response.getStatus() != 1) + throw new EtherScanResponseException(response); + + return response.getResult(); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerApiProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerApiProvider.java deleted file mode 100644 index 16d2e63..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerApiProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.api.etherscan.core.impl; - -import io.api.etherscan.core.IGasTrackerApi; -import io.api.etherscan.error.ApiException; -import io.api.etherscan.error.EtherScanException; -import io.api.etherscan.executor.IHttpExecutor; -import io.api.etherscan.manager.IQueueManager; -import io.api.etherscan.model.GasOracle; -import io.api.etherscan.model.utility.GasOracleResponseTO; -import org.jetbrains.annotations.NotNull; - -/** - * GasTracker API Implementation - * - * @see IGasTrackerApi - * - * @author Abhay Gupta - * @since 14.11.2022 - */ -public class GasTrackerApiProvider extends BasicProvider implements IGasTrackerApi { - - private static final String ACT_GAS_ORACLE_PARAM = ACT_PREFIX + "gasoracle"; - - GasTrackerApiProvider(final IQueueManager queue, - final String baseUrl, - final IHttpExecutor executor) { - super(queue, "gastracker", baseUrl, executor); - } - - @NotNull - @Override - public GasOracle gasoracle() throws ApiException { - final GasOracleResponseTO response = getRequest(ACT_GAS_ORACLE_PARAM, GasOracleResponseTO.class); - if (response.getStatus() != 1) - throw new EtherScanException(response); - - return response.getResult(); - } -} diff --git a/src/main/java/io/goodforgod/api/etherscan/IGasTrackerApi.java b/src/main/java/io/goodforgod/api/etherscan/IGasTrackerApi.java deleted file mode 100644 index 894713f..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/IGasTrackerApi.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.api.etherscan.core; - -import io.api.etherscan.error.ApiException; -import io.api.etherscan.model.GasOracle; -import org.jetbrains.annotations.NotNull; - -/** - * EtherScan - API Descriptions https://docs.etherscan.io/api-endpoints/gas-tracker - * - * @author Abhay Gupta - * @since 14.11.2022 - */ -public interface IGasTrackerApi { - - /** - * GasOracle details - * - * @return fast, suggested gas price - * @throws ApiException parent exception class - */ - @NotNull - GasOracle gasoracle() throws ApiException; -} 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 34f8f30..add3bd1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -1,7 +1,6 @@ package io.goodforgod.api.etherscan.model; import io.goodforgod.api.etherscan.util.BasicUtils; - import java.math.BigInteger; import java.util.List; diff --git a/src/main/java/io/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java similarity index 79% rename from src/main/java/io/api/etherscan/model/GasOracle.java rename to src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index f3f66c2..0ea1715 100644 --- a/src/main/java/io/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -1,15 +1,14 @@ -package io.api.etherscan.model; +package io.goodforgod.api.etherscan.model; import java.math.BigInteger; import java.util.Objects; /** - * ! NO DESCRIPTION ! - * * @author Abhay Gupta * @since 14.11.2022 */ public class GasOracle { + private Long LastBlock; private Integer SafeGasPrice; private Integer ProposeGasPrice; @@ -43,10 +42,14 @@ public String getGasUsedRatio() { @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; GasOracle gasOracle = (GasOracle) o; - return LastBlock.equals(gasOracle.LastBlock) && SafeGasPrice.equals(gasOracle.SafeGasPrice) && ProposeGasPrice.equals(gasOracle.ProposeGasPrice) && FastGasPrice.equals(gasOracle.FastGasPrice) && suggestBaseFee.equals(gasOracle.suggestBaseFee) && gasUsedRatio.equals(gasOracle.gasUsedRatio); + return LastBlock.equals(gasOracle.LastBlock) && SafeGasPrice.equals(gasOracle.SafeGasPrice) + && ProposeGasPrice.equals(gasOracle.ProposeGasPrice) && FastGasPrice.equals(gasOracle.FastGasPrice) + && suggestBaseFee.equals(gasOracle.suggestBaseFee) && gasUsedRatio.equals(gasOracle.gasUsedRatio); } @Override diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/GasOracleResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/GasOracleResponseTO.java index f0c1fd5..751854c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/GasOracleResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/GasOracleResponseTO.java @@ -1,10 +1,8 @@ -package io.api.etherscan.model.utility; +package io.goodforgod.api.etherscan.model.response; -import io.api.etherscan.model.GasOracle; +import io.goodforgod.api.etherscan.model.GasOracle; /** - * ! NO DESCRIPTION ! - * * @author Abhay Gupta * @since 14.11.2022 */ diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java index c85aaa4..31c8533 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java @@ -2,10 +2,8 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; -import io.goodforgod.api.etherscan.model.TxERC20; -import java.util.List; - import io.goodforgod.api.etherscan.model.TxERC721; +import java.util.List; import org.junit.jupiter.api.Test; /** diff --git a/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java b/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java index 23d14a2..0d6daf6 100644 --- a/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java @@ -3,11 +3,10 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.manager.impl.FakeRequestQueueManager; import io.goodforgod.api.etherscan.manager.impl.SemaphoreRequestQueueManager; +import java.time.Duration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.time.Duration; - /** * @author GoodforGod * @since 03.11.2018 @@ -38,7 +37,8 @@ void queueManager() { @Test @Timeout(4500) void queueManagerWithDelay() { - RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(2), Duration.ofSeconds(2)); + RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(2), + Duration.ofSeconds(2)); requestQueueManager.takeTurn(); requestQueueManager.takeTurn(); assertNotNull(requestQueueManager); From b9a8dda6d503b953d7e9aa7257a0d4e9bac2d351 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 00:50:20 +0300 Subject: [PATCH 15/67] [2.0.0-SNAPSHOT] Models builders added --- .../api/etherscan/AccountAPIProvider.java | 2 +- .../api/etherscan/BasicProvider.java | 2 +- .../impl/SemaphoreRequestQueueManager.java | 5 +- .../goodforgod/api/etherscan/model/Abi.java | 30 +++- .../api/etherscan/model/Balance.java | 8 +- .../api/etherscan/model/BaseTx.java | 22 +-- .../goodforgod/api/etherscan/model/Block.java | 49 +++++- .../api/etherscan/model/BlockUncle.java | 98 +++++++++++ .../api/etherscan/model/GasOracle.java | 57 +++++++ .../goodforgod/api/etherscan/model/Log.java | 93 +++++++++- .../goodforgod/api/etherscan/model/Price.java | 45 +++++ .../api/etherscan/model/Status.java | 29 ++++ .../io/goodforgod/api/etherscan/model/Tx.java | 144 ++++++++++++++++ .../api/etherscan/model/TxERC20.java | 153 +++++++++++++++++ .../api/etherscan/model/TxERC721.java | 153 +++++++++++++++++ .../api/etherscan/model/TxInternal.java | 117 +++++++++++++ .../goodforgod/api/etherscan/model/Wei.java | 2 +- .../api/etherscan/model/proxy/BlockProxy.java | 160 ++++++++++++++++++ .../etherscan/model/proxy/ReceiptProxy.java | 103 +++++++++++ .../api/etherscan/model/proxy/TxProxy.java | 118 +++++++++++++ 20 files changed, 1361 insertions(+), 29 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 3cc5409..9160bb4 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -99,7 +99,7 @@ public List<Balance> balances(List<String> addresses) throws EtherScanException if (!BasicUtils.isEmpty(response.getResult())) balances.addAll(response.getResult().stream() - .map(Balance::of) + .map(r -> new Balance(r.getAccount(), new BigInteger(r.getBalance()))) .collect(Collectors.toList())); } diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index ec5a85a..a4ce2a6 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -1,6 +1,6 @@ package io.goodforgod.api.etherscan; -import com.google.gson.*; +import com.google.gson.Gson; import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanParseException; import io.goodforgod.api.etherscan.error.EtherScanRateLimitException; diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java index cfd745f..ff12cd1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java @@ -2,7 +2,10 @@ import io.goodforgod.api.etherscan.manager.RequestQueueManager; import java.time.Duration; -import java.util.concurrent.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; /** * Queue Semaphore implementation with size and reset time as params 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 b575c56..3fce40a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java @@ -8,8 +8,8 @@ */ public class Abi { - private String contractAbi; - private boolean isVerified; + private final String contractAbi; + private final boolean isVerified; private Abi(String contractAbi, boolean isVerified) { this.contractAbi = contractAbi; @@ -70,4 +70,30 @@ public String toString() { ", isVerified=" + isVerified + '}'; } + + public static AbiBuilder builder() { + return new AbiBuilder(); + } + + public static final class AbiBuilder { + + private String contractAbi; + private boolean isVerified; + + private AbiBuilder() {} + + public AbiBuilder withContractAbi(String contractAbi) { + this.contractAbi = contractAbi; + return this; + } + + public AbiBuilder withIsVerified(boolean isVerified) { + this.isVerified = isVerified; + return this; + } + + public Abi build() { + return new Abi(contractAbi, 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 d147a2c..38379e6 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java @@ -1,6 +1,5 @@ package io.goodforgod.api.etherscan.model; -import io.goodforgod.api.etherscan.model.response.BalanceTO; import java.math.BigInteger; import java.util.Objects; @@ -14,16 +13,11 @@ public class Balance { private final Wei balance; private final String address; - public Balance(final String address, - final BigInteger balance) { + public Balance(String address, BigInteger balance) { this.address = address; this.balance = new Wei(balance); } - public static Balance of(BalanceTO balance) { - return new Balance(balance.getAccount(), new BigInteger(balance.getBalance())); - } - // <editor-fold desc="Getters"> public String getAddress() { return address; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java index 1fb53e1..e159d3b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java @@ -13,18 +13,18 @@ */ abstract class BaseTx { - private long blockNumber; - private String timeStamp; + long blockNumber; + String timeStamp; @Expose(deserialize = false, serialize = false) - private LocalDateTime _timeStamp; - private String hash; - private String from; - private String to; - private BigInteger value; - private String contractAddress; - private String input; - private BigInteger gas; - private BigInteger gasUsed; + LocalDateTime _timeStamp; + String hash; + String from; + String to; + BigInteger value; + String contractAddress; + String input; + BigInteger gas; + BigInteger gasUsed; // <editor-fold desc="Getter"> public long getBlockNumber() { 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 8b652bb..129ca39 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -12,11 +12,11 @@ */ public class Block { - private long blockNumber; - private BigInteger blockReward; - private String timeStamp; + long blockNumber; + BigInteger blockReward; + String timeStamp; @Expose(deserialize = false, serialize = false) - private LocalDateTime _timeStamp; + LocalDateTime _timeStamp; // <editor-fold desc="Getter"> public long getBlockNumber() { @@ -60,4 +60,45 @@ public String toString() { ", _timeStamp=" + _timeStamp + '}'; } + + public static BlockBuilder builder() { + return new BlockBuilder(); + } + + public static class BlockBuilder { + + private long blockNumber; + private BigInteger blockReward; + private LocalDateTime timeStamp; + + BlockBuilder() {} + + public static BlockBuilder aBlock() { + return new BlockBuilder(); + } + + public BlockBuilder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public BlockBuilder withBlockReward(BigInteger blockReward) { + this.blockReward = blockReward; + return this; + } + + public BlockBuilder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public Block build() { + Block block = new Block(); + block.blockNumber = this.blockNumber; + block.blockReward = this.blockReward; + block._timeStamp = this.timeStamp; + block.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + return block; + } + } } 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 add3bd1..5cf1a3e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -2,6 +2,8 @@ import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; /** @@ -69,6 +71,42 @@ public String toString() { ", unclePosition=" + unclePosition + '}'; } + + public static UncleBuilder builder() { + return new UncleBuilder(); + } + + public static final class UncleBuilder { + + private String miner; + private BigInteger blockreward; + private int unclePosition; + + private UncleBuilder() {} + + public UncleBuilder withMiner(String miner) { + this.miner = miner; + return this; + } + + public UncleBuilder withBlockreward(BigInteger blockreward) { + this.blockreward = blockreward; + return this; + } + + public UncleBuilder withUnclePosition(int unclePosition) { + this.unclePosition = unclePosition; + return this; + } + + public Uncle build() { + Uncle uncle = new Uncle(); + uncle.miner = this.miner; + uncle.blockreward = this.blockreward; + uncle.unclePosition = this.unclePosition; + return uncle; + } + } } private String blockMiner; @@ -124,4 +162,64 @@ public String toString() { ", uncleInclusionReward='" + uncleInclusionReward + '\'' + '}'; } + + public static BlockUncleBuilder builder() { + return new BlockUncleBuilder(); + } + + public static final class BlockUncleBuilder extends Block.BlockBuilder { + + private long blockNumber; + private BigInteger blockReward; + private LocalDateTime timeStamp; + private String blockMiner; + private List<Uncle> uncles; + private String uncleInclusionReward; + + private BlockUncleBuilder() { + super(); + } + + public BlockUncleBuilder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public BlockUncleBuilder withBlockReward(BigInteger blockReward) { + this.blockReward = blockReward; + return this; + } + + public BlockUncleBuilder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public BlockUncleBuilder withBlockMiner(String blockMiner) { + this.blockMiner = blockMiner; + return this; + } + + public BlockUncleBuilder withUncles(List<Uncle> uncles) { + this.uncles = uncles; + return this; + } + + public BlockUncleBuilder withUncleInclusionReward(String uncleInclusionReward) { + this.uncleInclusionReward = uncleInclusionReward; + return this; + } + + public BlockUncle build() { + BlockUncle blockUncle = new BlockUncle(); + blockUncle.uncles = this.uncles; + blockUncle.uncleInclusionReward = this.uncleInclusionReward; + blockUncle.blockNumber = this.blockNumber; + blockUncle.blockReward = this.blockReward; + blockUncle.blockMiner = this.blockMiner; + blockUncle._timeStamp = this.timeStamp; + blockUncle.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + return blockUncle; + } + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index 0ea1715..69fa6b5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -68,4 +68,61 @@ public String toString() { ", gasUsedRatio='" + gasUsedRatio + '\'' + '}'; } + + public static GasOracleBuilder builder() { + return new GasOracleBuilder(); + } + + public static final class GasOracleBuilder { + + private Long LastBlock; + private Integer SafeGasPrice; + private Integer ProposeGasPrice; + private Integer FastGasPrice; + private Double suggestBaseFee; + private String gasUsedRatio; + + private GasOracleBuilder() {} + + public GasOracleBuilder withLastBlock(Long LastBlock) { + this.LastBlock = LastBlock; + return this; + } + + public GasOracleBuilder withSafeGasPrice(Integer SafeGasPrice) { + this.SafeGasPrice = SafeGasPrice; + return this; + } + + public GasOracleBuilder withProposeGasPrice(Integer ProposeGasPrice) { + this.ProposeGasPrice = ProposeGasPrice; + return this; + } + + public GasOracleBuilder withFastGasPrice(Integer FastGasPrice) { + this.FastGasPrice = FastGasPrice; + return this; + } + + public GasOracleBuilder withSuggestBaseFee(Double suggestBaseFee) { + this.suggestBaseFee = suggestBaseFee; + return this; + } + + public GasOracleBuilder withGasUsedRatio(String gasUsedRatio) { + this.gasUsedRatio = gasUsedRatio; + return this; + } + + public GasOracle build() { + GasOracle gasOracle = new GasOracle(); + gasOracle.ProposeGasPrice = this.ProposeGasPrice; + gasOracle.LastBlock = this.LastBlock; + gasOracle.suggestBaseFee = this.suggestBaseFee; + gasOracle.SafeGasPrice = this.SafeGasPrice; + gasOracle.FastGasPrice = this.FastGasPrice; + gasOracle.gasUsedRatio = this.gasUsedRatio; + return gasOracle; + } + } } 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 cc22b66..bd03103 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -74,7 +74,7 @@ public LocalDateTime getTimeStamp() { /** * Return the "timeStamp" field of the event record as a long-int representing the milliseconds * since the Unix epoch (1970-01-01 00:00:00). - * + * * @return milliseconds between Unix epoch and `timeStamp`. If field is empty or null, returns null */ public Long getTimeStampAsMillis() { @@ -180,4 +180,95 @@ public String toString() { ", _logIndex=" + _logIndex + '}'; } + + public static LogBuilder builder() { + return new LogBuilder(); + } + + public static final class LogBuilder { + + private Long blockNumber; + private String address; + private String transactionHash; + private Long transactionIndex; + private LocalDateTime timeStamp; + private String data; + private BigInteger gasPrice; + private BigInteger gasUsed; + private List<String> topics; + private Long logIndex; + + private LogBuilder() {} + + public LogBuilder withBlockNumber(Long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public LogBuilder withAddress(String address) { + this.address = address; + return this; + } + + public LogBuilder withTransactionHash(String transactionHash) { + this.transactionHash = transactionHash; + return this; + } + + public LogBuilder withTransactionIndex(Long transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public LogBuilder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public LogBuilder withData(String data) { + this.data = data; + return this; + } + + public LogBuilder withGasPrice(BigInteger gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public LogBuilder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public LogBuilder withTopics(List<String> topics) { + this.topics = topics; + return this; + } + + public LogBuilder withLogIndex(Long logIndex) { + this.logIndex = logIndex; + return this; + } + + public Log build() { + Log log = new Log(); + log.address = this.address; + log.gasPrice = String.valueOf(this.gasPrice); + log._gasPrice = this.gasPrice; + log._logIndex = this.logIndex; + log._transactionIndex = this.transactionIndex; + log._gasUsed = this.gasUsed; + log.blockNumber = String.valueOf(this.blockNumber); + log.transactionIndex = String.valueOf(this.transactionIndex); + log.timeStamp = String.valueOf(this.timeStamp); + log.data = this.data; + log.gasUsed = String.valueOf(this.gasUsed); + log._timeStamp = this.timeStamp; + log.logIndex = String.valueOf(this.logIndex); + log._blockNumber = this.blockNumber; + log.topics = this.topics; + log.transactionHash = this.transactionHash; + return log; + } + } } 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 712dbd8..9c72792 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -87,4 +87,49 @@ public String toString() { ", ethbtc_timestamp='" + ethbtc_timestamp + '\'' + '}'; } + + public static PriceBuilder builder() { + return new PriceBuilder(); + } + + public static final class PriceBuilder { + + private double ethusd; + private double ethbtc; + private LocalDateTime ethusdTimestamp; + private LocalDateTime ethbtcTimestamp; + + private PriceBuilder() {} + + public PriceBuilder withEthusd(double ethusd) { + this.ethusd = ethusd; + return this; + } + + public PriceBuilder withEthbtc(double ethbtc) { + this.ethbtc = ethbtc; + return this; + } + + public PriceBuilder withEthusdTimestamp(LocalDateTime ethusdTimestamp) { + this.ethusdTimestamp = ethusdTimestamp; + return this; + } + + public PriceBuilder withEthbtcTimestamp(LocalDateTime ethbtcTimestamp) { + this.ethbtcTimestamp = ethbtcTimestamp; + return this; + } + + public Price build() { + Price price = new Price(); + price.ethbtc = this.ethbtc; + price.ethbtc_timestamp = String.valueOf(this.ethbtcTimestamp.toEpochSecond(ZoneOffset.UTC)); + price._ethbtc_timestamp = this.ethbtcTimestamp; + price.ethusd = this.ethusd; + price.ethusd_timestamp = String.valueOf(this.ethusdTimestamp.toEpochSecond(ZoneOffset.UTC)); + price._ethusd_timestamp = this.ethusdTimestamp; + return price; + } + } } 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 274080a..8cdc704 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Status.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Status.java @@ -54,4 +54,33 @@ public String toString() { ", errDescription='" + errDescription + '\'' + '}'; } + + public static StatusBuilder builder() { + return new StatusBuilder(); + } + + public static final class StatusBuilder { + + private int isError; + private String errDescription; + + private StatusBuilder() {} + + public StatusBuilder withIsError(int isError) { + this.isError = isError; + return this; + } + + public StatusBuilder withErrDescription(String errDescription) { + this.errDescription = errDescription; + return this; + } + + public Status build() { + Status status = new Status(); + status.isError = this.isError; + status.errDescription = this.errDescription; + return status; + } + } } 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 3ab0923..cc9be41 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -2,6 +2,8 @@ import io.goodforgod.api.etherscan.util.BasicUtils; import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.Objects; /** @@ -100,4 +102,146 @@ public String toString() { ", txreceipt_status='" + txreceipt_status + '\'' + "} " + super.toString(); } + + public static TxBuilder builder() { + return new TxBuilder(); + } + + public static final class TxBuilder { + + private long blockNumber; + private LocalDateTime timeStamp; + private String hash; + private String from; + private String to; + private BigInteger value; + private String contractAddress; + private String input; + private BigInteger gas; + private BigInteger gasUsed; + private long nonce; + private String blockHash; + private int transactionIndex; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; + private long confirmations; + private String isError; + private String txreceiptStatus; + + private TxBuilder() {} + + public TxBuilder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public TxBuilder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public TxBuilder withHash(String hash) { + this.hash = hash; + return this; + } + + public TxBuilder withFrom(String from) { + this.from = from; + return this; + } + + public TxBuilder withTo(String to) { + this.to = to; + return this; + } + + public TxBuilder withValue(BigInteger value) { + this.value = value; + return this; + } + + public TxBuilder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public TxBuilder withInput(String input) { + this.input = input; + return this; + } + + public TxBuilder withGas(BigInteger gas) { + this.gas = gas; + return this; + } + + public TxBuilder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public TxBuilder withNonce(long nonce) { + this.nonce = nonce; + return this; + } + + public TxBuilder withBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + public TxBuilder withTransactionIndex(int transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public TxBuilder withGasPrice(BigInteger gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TxBuilder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + this.cumulativeGasUsed = cumulativeGasUsed; + return this; + } + + public TxBuilder withConfirmations(long confirmations) { + this.confirmations = confirmations; + return this; + } + + public TxBuilder withIsError(String isError) { + this.isError = isError; + return this; + } + + public TxBuilder withTxreceiptStatus(String txreceiptStatus) { + this.txreceiptStatus = txreceiptStatus; + return this; + } + + public Tx build() { + Tx tx = new Tx(); + tx.gas = this.gas; + tx.isError = this.isError; + tx.blockHash = this.blockHash; + tx.hash = this.hash; + tx.gasUsed = this.gasUsed; + tx.from = this.from; + tx.txreceipt_status = this.txreceiptStatus; + tx.contractAddress = this.contractAddress; + tx.value = this.value; + tx.transactionIndex = this.transactionIndex; + tx.confirmations = this.confirmations; + tx.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + tx.nonce = this.nonce; + tx.blockNumber = this.blockNumber; + tx._timeStamp = this.timeStamp; + tx.to = this.to; + tx.input = this.input; + tx.cumulativeGasUsed = this.cumulativeGasUsed; + tx.gasPrice = this.gasPrice; + return tx; + } + } } 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 9f65d98..42ffebe 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java @@ -1,5 +1,9 @@ package io.goodforgod.api.etherscan.model; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + /** * @author GoodforGod * @since 28.10.2018 @@ -68,4 +72,153 @@ public String toString() { ", confirmations=" + confirmations + "} " + super.toString(); } + + public static TxERC20Builder builder() { + return new TxERC20Builder(); + } + + public static final class TxERC20Builder { + + private long blockNumber; + private LocalDateTime timeStamp; + private String hash; + private String from; + private String to; + private BigInteger value; + private String contractAddress; + private String input; + private BigInteger gas; + private BigInteger gasUsed; + private long nonce; + private String blockHash; + private String tokenName; + private String tokenSymbol; + private String tokenDecimal; + private int transactionIndex; + private long gasPrice; + private long cumulativeGasUsed; + private long confirmations; + + private TxERC20Builder() {} + + public TxERC20Builder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public TxERC20Builder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public TxERC20Builder withHash(String hash) { + this.hash = hash; + return this; + } + + public TxERC20Builder withFrom(String from) { + this.from = from; + return this; + } + + public TxERC20Builder withTo(String to) { + this.to = to; + return this; + } + + public TxERC20Builder withValue(BigInteger value) { + this.value = value; + return this; + } + + public TxERC20Builder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public TxERC20Builder withInput(String input) { + this.input = input; + return this; + } + + public TxERC20Builder withGas(BigInteger gas) { + this.gas = gas; + return this; + } + + public TxERC20Builder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public TxERC20Builder withNonce(long nonce) { + this.nonce = nonce; + return this; + } + + public TxERC20Builder withBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + public TxERC20Builder withTokenName(String tokenName) { + this.tokenName = tokenName; + return this; + } + + public TxERC20Builder withTokenSymbol(String tokenSymbol) { + this.tokenSymbol = tokenSymbol; + return this; + } + + public TxERC20Builder withTokenDecimal(String tokenDecimal) { + this.tokenDecimal = tokenDecimal; + return this; + } + + public TxERC20Builder withTransactionIndex(int transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public TxERC20Builder withGasPrice(long gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TxERC20Builder withCumulativeGasUsed(long cumulativeGasUsed) { + this.cumulativeGasUsed = cumulativeGasUsed; + return this; + } + + public TxERC20Builder withConfirmations(long confirmations) { + this.confirmations = confirmations; + return this; + } + + public TxERC20 build() { + TxERC20 txERC20 = new TxERC20(); + txERC20.gas = this.gas; + txERC20.tokenName = this.tokenName; + txERC20.hash = this.hash; + txERC20.gasUsed = this.gasUsed; + txERC20.cumulativeGasUsed = this.cumulativeGasUsed; + txERC20.from = this.from; + txERC20.tokenSymbol = this.tokenSymbol; + txERC20.transactionIndex = this.transactionIndex; + txERC20.contractAddress = this.contractAddress; + txERC20.nonce = this.nonce; + txERC20.confirmations = this.confirmations; + txERC20.value = this.value; + txERC20.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC20.blockHash = this.blockHash; + txERC20.blockNumber = this.blockNumber; + txERC20._timeStamp = this.timeStamp; + txERC20.gasPrice = this.gasPrice; + txERC20.to = this.to; + txERC20.input = this.input; + txERC20.tokenDecimal = this.tokenDecimal; + return txERC20; + } + } } 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 5b30314..14777fc 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java @@ -1,5 +1,9 @@ package io.goodforgod.api.etherscan.model; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + /** * @author GoodforGod * @since 28.10.2018 @@ -68,4 +72,153 @@ public String toString() { ", confirmations=" + confirmations + "} " + super.toString(); } + + public static TxERC721Builder builder() { + return new TxERC721Builder(); + } + + public static final class TxERC721Builder { + + private long blockNumber; + private LocalDateTime timeStamp; + private String hash; + private String from; + private String to; + private BigInteger value; + private String contractAddress; + private String input; + private BigInteger gas; + private BigInteger gasUsed; + private long nonce; + private String blockHash; + private String tokenName; + private String tokenSymbol; + private String tokenDecimal; + private int transactionIndex; + private long gasPrice; + private long cumulativeGasUsed; + private long confirmations; + + private TxERC721Builder() {} + + public TxERC721Builder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public TxERC721Builder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public TxERC721Builder withHash(String hash) { + this.hash = hash; + return this; + } + + public TxERC721Builder withFrom(String from) { + this.from = from; + return this; + } + + public TxERC721Builder withTo(String to) { + this.to = to; + return this; + } + + public TxERC721Builder withValue(BigInteger value) { + this.value = value; + return this; + } + + public TxERC721Builder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public TxERC721Builder withInput(String input) { + this.input = input; + return this; + } + + public TxERC721Builder withGas(BigInteger gas) { + this.gas = gas; + return this; + } + + public TxERC721Builder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public TxERC721Builder withNonce(long nonce) { + this.nonce = nonce; + return this; + } + + public TxERC721Builder withBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + public TxERC721Builder withTokenName(String tokenName) { + this.tokenName = tokenName; + return this; + } + + public TxERC721Builder withTokenSymbol(String tokenSymbol) { + this.tokenSymbol = tokenSymbol; + return this; + } + + public TxERC721Builder withTokenDecimal(String tokenDecimal) { + this.tokenDecimal = tokenDecimal; + return this; + } + + public TxERC721Builder withTransactionIndex(int transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public TxERC721Builder withGasPrice(long gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TxERC721Builder withCumulativeGasUsed(long cumulativeGasUsed) { + this.cumulativeGasUsed = cumulativeGasUsed; + return this; + } + + public TxERC721Builder withConfirmations(long confirmations) { + this.confirmations = confirmations; + return this; + } + + public TxERC721 build() { + TxERC721 txERC721 = new TxERC721(); + txERC721.gas = this.gas; + txERC721.tokenName = this.tokenName; + txERC721.hash = this.hash; + txERC721.gasUsed = this.gasUsed; + txERC721.nonce = this.nonce; + txERC721.from = this.from; + txERC721.gasPrice = this.gasPrice; + txERC721.contractAddress = this.contractAddress; + txERC721.cumulativeGasUsed = this.cumulativeGasUsed; + txERC721.value = this.value; + txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC721.blockNumber = this.blockNumber; + txERC721._timeStamp = this.timeStamp; + txERC721.tokenDecimal = this.tokenDecimal; + txERC721.transactionIndex = this.transactionIndex; + txERC721.to = this.to; + txERC721.confirmations = this.confirmations; + txERC721.input = this.input; + txERC721.blockHash = this.blockHash; + txERC721.tokenSymbol = this.tokenSymbol; + return txERC721; + } + } } 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 244e0b7..f1d1edf 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -1,5 +1,8 @@ package io.goodforgod.api.etherscan.model; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.Objects; /** @@ -74,4 +77,118 @@ public String toString() { ", errCode='" + errCode + '\'' + "} " + super.toString(); } + + public static TxInternalBuilder builder() { + return new TxInternalBuilder(); + } + + public static final class TxInternalBuilder { + + private long blockNumber; + private LocalDateTime timeStamp; + private String hash; + private String from; + private String to; + private BigInteger value; + private String contractAddress; + private String input; + private BigInteger gas; + private BigInteger gasUsed; + private String type; + private String traceId; + private int isError; + private String errCode; + + private TxInternalBuilder() {} + + public TxInternalBuilder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public TxInternalBuilder with_timeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public TxInternalBuilder withHash(String hash) { + this.hash = hash; + return this; + } + + public TxInternalBuilder withFrom(String from) { + this.from = from; + return this; + } + + public TxInternalBuilder withTo(String to) { + this.to = to; + return this; + } + + public TxInternalBuilder withValue(BigInteger value) { + this.value = value; + return this; + } + + public TxInternalBuilder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public TxInternalBuilder withInput(String input) { + this.input = input; + return this; + } + + public TxInternalBuilder withGas(BigInteger gas) { + this.gas = gas; + return this; + } + + public TxInternalBuilder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public TxInternalBuilder withType(String type) { + this.type = type; + return this; + } + + public TxInternalBuilder withTraceId(String traceId) { + this.traceId = traceId; + return this; + } + + public TxInternalBuilder withIsError(int isError) { + this.isError = isError; + return this; + } + + public TxInternalBuilder withErrCode(String errCode) { + this.errCode = errCode; + return this; + } + + public TxInternal build() { + TxInternal txInternal = new TxInternal(); + txInternal.gas = this.gas; + txInternal.hash = this.hash; + txInternal.gasUsed = this.gasUsed; + txInternal.traceId = this.traceId; + txInternal.type = this.type; + txInternal.from = this.from; + txInternal.contractAddress = this.contractAddress; + txInternal.value = this.value; + txInternal.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txInternal.errCode = this.errCode; + txInternal.blockNumber = this.blockNumber; + txInternal._timeStamp = this.timeStamp; + txInternal.isError = this.isError; + txInternal.to = this.to; + txInternal.input = this.input; + return txInternal; + } + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index 224a1cf..85dd3ab 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -9,7 +9,7 @@ */ public class Wei { - private BigInteger result; + private final BigInteger result; public Wei(BigInteger value) { this.result = value; 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 889cc0e..0e6ff3a 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 @@ -205,4 +205,164 @@ public String toString() { ", transactions=" + transactions + '}'; } + + public static BlockProxyBuilder builder() { + return new BlockProxyBuilder(); + } + + public static final class BlockProxyBuilder { + + private Long number; + private String hash; + private String parentHash; + private String stateRoot; + private Long size; + private String difficulty; + private String totalDifficulty; + private LocalDateTime timestamp; + private String miner; + private String nonce; + private String extraData; + private String logsBloom; + private String mixHash; + private BigInteger gasUsed; + private BigInteger gasLimit; + private String sha3Uncles; + private List<String> uncles; + private String receiptsRoot; + private String transactionsRoot; + private List<TxProxy> transactions; + + private BlockProxyBuilder() {} + + public BlockProxyBuilder withNumber(Long number) { + this.number = number; + return this; + } + + public BlockProxyBuilder withHash(String hash) { + this.hash = hash; + return this; + } + + public BlockProxyBuilder withParentHash(String parentHash) { + this.parentHash = parentHash; + return this; + } + + public BlockProxyBuilder withStateRoot(String stateRoot) { + this.stateRoot = stateRoot; + return this; + } + + public BlockProxyBuilder withSize(Long size) { + this.size = size; + return this; + } + + public BlockProxyBuilder withDifficulty(String difficulty) { + this.difficulty = difficulty; + return this; + } + + public BlockProxyBuilder withTotalDifficulty(String totalDifficulty) { + this.totalDifficulty = totalDifficulty; + return this; + } + + public BlockProxyBuilder withTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + return this; + } + + public BlockProxyBuilder withMiner(String miner) { + this.miner = miner; + return this; + } + + public BlockProxyBuilder withNonce(String nonce) { + this.nonce = nonce; + return this; + } + + public BlockProxyBuilder withExtraData(String extraData) { + this.extraData = extraData; + return this; + } + + public BlockProxyBuilder withLogsBloom(String logsBloom) { + this.logsBloom = logsBloom; + return this; + } + + public BlockProxyBuilder withMixHash(String mixHash) { + this.mixHash = mixHash; + return this; + } + + public BlockProxyBuilder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public BlockProxyBuilder withGasLimit(BigInteger gasLimit) { + this.gasLimit = gasLimit; + return this; + } + + public BlockProxyBuilder withSha3Uncles(String sha3Uncles) { + this.sha3Uncles = sha3Uncles; + return this; + } + + public BlockProxyBuilder withUncles(List<String> uncles) { + this.uncles = uncles; + return this; + } + + public BlockProxyBuilder withReceiptsRoot(String receiptsRoot) { + this.receiptsRoot = receiptsRoot; + return this; + } + + public BlockProxyBuilder withTransactionsRoot(String transactionsRoot) { + this.transactionsRoot = transactionsRoot; + return this; + } + + public BlockProxyBuilder withTransactions(List<TxProxy> transactions) { + this.transactions = transactions; + return this; + } + + public BlockProxy build() { + BlockProxy blockProxy = new BlockProxy(); + blockProxy.mixHash = this.mixHash; + blockProxy.totalDifficulty = this.totalDifficulty; + blockProxy.nonce = this.nonce; + blockProxy._gasUsed = this.gasUsed; + blockProxy.uncles = this.uncles; + blockProxy.transactionsRoot = this.transactionsRoot; + blockProxy.number = String.valueOf(this.number); + blockProxy.logsBloom = this.logsBloom; + blockProxy.receiptsRoot = this.receiptsRoot; + blockProxy._gasLimit = this.gasLimit; + blockProxy.hash = this.hash; + blockProxy.parentHash = this.parentHash; + blockProxy._size = this.size; + blockProxy.gasLimit = String.valueOf(this.gasLimit); + blockProxy.difficulty = this.difficulty; + blockProxy.gasUsed = String.valueOf(this.gasUsed); + blockProxy.size = String.valueOf(this.size); + blockProxy.extraData = this.extraData; + blockProxy.stateRoot = this.stateRoot; + blockProxy._timestamp = this.timestamp; + blockProxy.sha3Uncles = this.sha3Uncles; + blockProxy.miner = this.miner; + blockProxy.timestamp = String.valueOf(this.timestamp.toEpochSecond(ZoneOffset.UTC)); + blockProxy.transactions = this.transactions; + blockProxy._number = this.number; + return blockProxy; + } + } } 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 f4f7845..73a21b6 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 @@ -149,4 +149,107 @@ public String toString() { ", logsBloom='" + logsBloom + '\'' + '}'; } + + public static ReceiptProxyBuilder builder() { + return new ReceiptProxyBuilder(); + } + + public static final class ReceiptProxyBuilder { + + private String root; + private String from; + private String to; + private Long blockNumber; + private String blockHash; + private String transactionHash; + private Long transactionIndex; + private BigInteger gasUsed; + private BigInteger cumulativeGasUsed; + private String contractAddress; + private List<Log> logs; + private String logsBloom; + + private ReceiptProxyBuilder() {} + + public ReceiptProxyBuilder withRoot(String root) { + this.root = root; + return this; + } + + public ReceiptProxyBuilder withFrom(String from) { + this.from = from; + return this; + } + + public ReceiptProxyBuilder withTo(String to) { + this.to = to; + return this; + } + + public ReceiptProxyBuilder withBlockNumber(Long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public ReceiptProxyBuilder withBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + public ReceiptProxyBuilder withTransactionHash(String transactionHash) { + this.transactionHash = transactionHash; + return this; + } + + public ReceiptProxyBuilder withTransactionIndex(Long transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public ReceiptProxyBuilder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public ReceiptProxyBuilder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + this.cumulativeGasUsed = cumulativeGasUsed; + return this; + } + + public ReceiptProxyBuilder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public ReceiptProxyBuilder withLogs(List<Log> logs) { + this.logs = logs; + return this; + } + + public ReceiptProxyBuilder withLogsBloom(String logsBloom) { + this.logsBloom = logsBloom; + return this; + } + + public ReceiptProxy build() { + ReceiptProxy receiptProxy = new ReceiptProxy(); + receiptProxy.logsBloom = this.logsBloom; + receiptProxy.transactionHash = this.transactionHash; + receiptProxy.blockNumber = String.valueOf(this.blockNumber); + receiptProxy.from = this.from; + receiptProxy._transactionIndex = this.transactionIndex; + receiptProxy.blockHash = this.blockHash; + receiptProxy.root = this.root; + receiptProxy.contractAddress = this.contractAddress; + receiptProxy.gasUsed = String.valueOf(this.gasUsed); + receiptProxy._gasUsed = this.gasUsed; + receiptProxy.logs = this.logs; + receiptProxy.cumulativeGasUsed = String.valueOf(this.cumulativeGasUsed); + receiptProxy.to = this.to; + receiptProxy.transactionIndex = String.valueOf(this.transactionIndex); + receiptProxy._blockNumber = this.blockNumber; + receiptProxy._cumulativeGasUsed = this.cumulativeGasUsed; + return receiptProxy; + } + } } 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 cf3199b..52fe41b 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 @@ -163,4 +163,122 @@ public String toString() { ", _blockNumber=" + _blockNumber + '}'; } + + public static TxProxyBuilder builder() { + return new TxProxyBuilder(); + } + + public static final class TxProxyBuilder { + + private String to; + private String hash; + private Long transactionIndex; + private String from; + private String v; + private String input; + private String s; + private String r; + private Long nonce; + private String value; + private BigInteger gas; + private BigInteger gasPrice; + private String blockHash; + private Long blockNumber; + + private TxProxyBuilder() {} + + public TxProxyBuilder withTo(String to) { + this.to = to; + return this; + } + + public TxProxyBuilder withHash(String hash) { + this.hash = hash; + return this; + } + + public TxProxyBuilder withTransactionIndex(Long transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public TxProxyBuilder withFrom(String from) { + this.from = from; + return this; + } + + public TxProxyBuilder withV(String v) { + this.v = v; + return this; + } + + public TxProxyBuilder withInput(String input) { + this.input = input; + return this; + } + + public TxProxyBuilder withS(String s) { + this.s = s; + return this; + } + + public TxProxyBuilder withR(String r) { + this.r = r; + return this; + } + + public TxProxyBuilder withNonce(Long nonce) { + this.nonce = nonce; + return this; + } + + public TxProxyBuilder withValue(String value) { + this.value = value; + return this; + } + + public TxProxyBuilder withGas(BigInteger gas) { + this.gas = gas; + return this; + } + + public TxProxyBuilder withGasPrice(BigInteger gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TxProxyBuilder withBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + public TxProxyBuilder withBlockNumber(Long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public TxProxy build() { + TxProxy txProxy = new TxProxy(); + txProxy.input = this.input; + txProxy.gas = String.valueOf(this.gas); + txProxy._gas = this.gas; + txProxy.s = this.s; + txProxy.blockHash = this.blockHash; + txProxy.to = this.to; + txProxy.r = this.r; + txProxy.transactionIndex = String.valueOf(this.transactionIndex); + txProxy._nonce = this.nonce; + txProxy.value = this.value; + txProxy.v = this.v; + txProxy.from = this.from; + txProxy.nonce = String.valueOf(this.nonce); + txProxy._gasPrice = this.gasPrice; + txProxy._transactionIndex = this.transactionIndex; + txProxy.blockNumber = String.valueOf(this.blockNumber); + txProxy._blockNumber = this.blockNumber; + txProxy.hash = this.hash; + txProxy.gasPrice = String.valueOf(this.gasPrice); + return txProxy; + } + } } From fe444f4ed8a16922cf06b31610947c95365ba22f Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 01:12:58 +0300 Subject: [PATCH 16/67] [2.0.0-SNAPSHOT] GasEstimate added to GasTrackerAPI#estimate --- .../api/etherscan/GasTrackerAPI.java | 13 ++++- .../api/etherscan/GasTrackerAPIProvider.java | 16 +++++++ .../api/etherscan/model/GasEstimate.java | 47 +++++++++++++++++++ .../model/response/GasEstimateResponseTO.java | 14 ++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/GasEstimateResponseTO.java diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java index d49e14f..355d62a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java @@ -1,7 +1,9 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.GasEstimate; import io.goodforgod.api.etherscan.model.GasOracle; +import io.goodforgod.api.etherscan.model.Wei; import org.jetbrains.annotations.NotNull; /** @@ -14,7 +16,16 @@ public interface GasTrackerAPI { /** - * GasOracle details + * Returns the estimated time, in seconds, for a transaction to be confirmed on the blockchain. + * + * @return fast, suggested gas price + * @throws EtherScanException parent exception class + */ + @NotNull + GasEstimate estimate(@NotNull Wei wei) throws EtherScanException; + + /** + * Returns the current Safe, Proposed and Fast gas prices. * * @return fast, suggested gas price * @throws EtherScanException parent exception class diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java index faa01e5..d4ec276 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -4,7 +4,10 @@ import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.executor.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.GasEstimate; import io.goodforgod.api.etherscan.model.GasOracle; +import io.goodforgod.api.etherscan.model.Wei; +import io.goodforgod.api.etherscan.model.response.GasEstimateResponseTO; import io.goodforgod.api.etherscan.model.response.GasOracleResponseTO; import org.jetbrains.annotations.NotNull; @@ -18,6 +21,9 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI { private static final String ACT_GAS_ORACLE_PARAM = ACT_PREFIX + "gasoracle"; + private static final String ACT_GAS_ESTIMATE_PARAM = ACT_PREFIX + "gasestimate"; + + private static final String GASPRICE_PARAM = "&gasprice="; GasTrackerAPIProvider(RequestQueueManager queue, String baseUrl, @@ -25,6 +31,16 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI super(queue, "gastracker", baseUrl, ethHttpClient); } + @Override + public @NotNull GasEstimate estimate(@NotNull Wei wei) throws EtherScanException { + final String urlParams = ACT_GAS_ESTIMATE_PARAM + GASPRICE_PARAM + wei.getValue().toString(); + final GasEstimateResponseTO response = getRequest(urlParams, GasEstimateResponseTO.class); + if (response.getStatus() != 1) + throw new EtherScanResponseException(response); + + return new GasEstimate(Long.parseLong(response.getResult())); + } + @NotNull @Override public GasOracle oracle() throws EtherScanException { diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java b/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java new file mode 100644 index 0000000..7f1e61d --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java @@ -0,0 +1,47 @@ +package io.goodforgod.api.etherscan.model; + +import java.time.Duration; +import java.util.Objects; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +public class GasEstimate { + + private final Duration duration; + + public GasEstimate(long durationInSeconds) { + this.duration = Duration.ofSeconds(durationInSeconds); + } + + public GasEstimate(Duration duration) { + this.duration = duration; + } + + public Duration getDuration() { + return duration; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof GasEstimate)) + return false; + GasEstimate that = (GasEstimate) o; + return Objects.equals(duration, that.duration); + } + + @Override + public int hashCode() { + return Objects.hash(duration); + } + + @Override + public String toString() { + return "GasEstimate{" + + "duration=" + duration + + '}'; + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/GasEstimateResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/GasEstimateResponseTO.java new file mode 100644 index 0000000..96432f3 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/GasEstimateResponseTO.java @@ -0,0 +1,14 @@ +package io.goodforgod.api.etherscan.model.response; + +/** + * @author Abhay Gupta + * @since 14.11.2022 + */ +public class GasEstimateResponseTO extends BaseResponseTO { + + private String result; + + public String getResult() { + return result; + } +} From 192cb5b872e447c7f611044ca215e53619a07547 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 01:23:43 +0300 Subject: [PATCH 17/67] [2.0.0-SNAPSHOT] RequestQueueManager values renamed --- .../java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java | 2 +- .../api/etherscan/manager/RequestQueueManager.java | 5 +++-- src/test/java/io/goodforgod/api/etherscan/ApiRunner.java | 2 +- .../io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index d36d385..6571121 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -20,7 +20,7 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { private String apiKey = DEFAULT_KEY; private EthNetwork ethNetwork = EthNetworks.MAINNET; - private RequestQueueManager queueManager = RequestQueueManager.DEFAULT_KEY_QUEUE; + private RequestQueueManager queueManager = RequestQueueManager.DEFAULT; private Supplier<EthHttpClient> ethHttpClientSupplier = DEFAULT_SUPPLIER; @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 7472c3f..d568601 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -12,9 +12,10 @@ */ public interface RequestQueueManager extends AutoCloseable { - RequestQueueManager DEFAULT_KEY_QUEUE = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5050L), + RequestQueueManager DEFAULT = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5050L), Duration.ofMillis(5050L), 0); - RequestQueueManager PERSONAL_KEY_QUEUE = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1050L), + + RequestQueueManager PERSONAL = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1050L), Duration.ofMillis(1050L), 5); /** diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index 3b9cbe2..cd4a657 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -23,7 +23,7 @@ public class ApiRunner extends Assertions { : key; final RequestQueueManager queueManager = (DEFAULT_KEY.equals(apiKey)) - ? RequestQueueManager.DEFAULT_KEY_QUEUE + ? RequestQueueManager.DEFAULT : new SemaphoreRequestQueueManager(1, Duration.ofMillis(1200L), Duration.ofMillis(1200L), 0); api = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java index 64e3fe6..e31aab8 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java @@ -17,7 +17,7 @@ class ProxyBlockApiTest extends ApiRunner { private final EtherScanAPI api; ProxyBlockApiTest() { - final RequestQueueManager queueManager = RequestQueueManager.DEFAULT_KEY_QUEUE; + final RequestQueueManager queueManager = RequestQueueManager.DEFAULT; this.api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) .build(); } From 0cafb6df4518156bc914cb8adb40e41ff5fb3d85 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 02:17:12 +0300 Subject: [PATCH 18/67] [2.0.0-SNAPSHOT] TxErc1155 added AccountAPI#txsErc1155 added AccountAPI#txsErc721 with contract added --- .../goodforgod/api/etherscan/AccountAPI.java | 72 +++++- .../api/etherscan/AccountAPIProvider.java | 122 ++++++++-- .../goodforgod/api/etherscan/EthNetwork.java | 3 + .../api/etherscan/model/BaseTx.java | 41 +--- .../io/goodforgod/api/etherscan/model/Tx.java | 33 +-- .../api/etherscan/model/TxErc1155.java | 230 ++++++++++++++++++ .../model/{TxERC20.java => TxErc20.java} | 12 +- .../model/{TxERC721.java => TxErc721.java} | 26 +- .../api/etherscan/model/TxInternal.java | 22 +- .../model/response/TxERC20ResponseTO.java | 11 - .../model/response/TxERC721ResponseTO.java | 11 - .../model/response/TxErc1155ResponseTO.java | 11 + .../model/response/TxErc20ResponseTO.java | 11 + .../model/response/TxErc721ResponseTO.java | 11 + .../api/etherscan/util/BasicUtils.java | 2 +- ...ERC20Test.java => AccountTxErc20Test.java} | 18 +- .../account/AccountTxRc1155TokenTest.java | 81 ++++++ .../account/AccountTxRc721TokenTest.java | 16 +- 18 files changed, 577 insertions(+), 156 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java rename src/main/java/io/goodforgod/api/etherscan/model/{TxERC20.java => TxErc20.java} (96%) rename src/main/java/io/goodforgod/api/etherscan/model/{TxERC721.java => TxErc721.java} (93%) delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxERC20Test.java => AccountTxErc20Test.java} (81%) create mode 100644 src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java index 40da2eb..294fb2a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java @@ -101,13 +101,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxERC20> txsERC20(String address, long startBlock, long endBlock) throws EtherScanException; + List<TxErc20> txsErc20(String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxERC20> txsERC20(String address, long startBlock) throws EtherScanException; + List<TxErc20> txsErc20(String address, long startBlock) throws EtherScanException; @NotNull - List<TxERC20> txsERC20(String address) throws EtherScanException; + List<TxErc20> txsErc20(String address) throws EtherScanException; /** * All ERC-20 token txs for given address and contract address @@ -120,13 +120,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxERC20> txsERC20(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; + List<TxErc20> txsErc20(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxERC20> txsERC20(String address, String contractAddress, long startBlock) throws EtherScanException; + List<TxErc20> txsErc20(String address, String contractAddress, long startBlock) throws EtherScanException; @NotNull - List<TxERC20> txsERC20(String address, String contractAddress) throws EtherScanException; + List<TxErc20> txsErc20(String address, String contractAddress) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address @@ -138,13 +138,67 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxERC721> txsERC721(String address, long startBlock, long endBlock) throws EtherScanException; + List<TxErc721> txsErc721(String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxERC721> txsERC721(String address, long startBlock) throws EtherScanException; + List<TxErc721> txsErc721(String address, long startBlock) throws EtherScanException; @NotNull - List<TxERC721> txsERC721(String address) throws EtherScanException; + List<TxErc721> txsErc721(String address) throws EtherScanException; + + /** + * All ERC-721 (NFT) token txs for given address + * + * @param address get txs for + * @param startBlock tx from this blockNumber + * @param endBlock tx to this blockNumber + * @return txs for address + * @throws EtherScanException parent exception class + */ + @NotNull + List<TxErc721> txsErc721(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; + + @NotNull + List<TxErc721> txsErc721(String address, String contractAddress, long startBlock) throws EtherScanException; + + @NotNull + List<TxErc721> txsErc721(String address, String contractAddress) throws EtherScanException; + + /** + * All ERC-721 (NFT) token txs for given address + * + * @param address get txs for + * @param startBlock tx from this blockNumber + * @param endBlock tx to this blockNumber + * @return txs for address + * @throws EtherScanException parent exception class + */ + @NotNull + List<TxErc1155> txsErc1155(String address, long startBlock, long endBlock) throws EtherScanException; + + @NotNull + List<TxErc1155> txsErc1155(String address, long startBlock) throws EtherScanException; + + @NotNull + List<TxErc1155> txsErc1155(String address) throws EtherScanException; + + /** + * All ERC-721 (NFT) token txs for given address + * + * @param address get txs for + * @param startBlock tx from this blockNumber + * @param endBlock tx to this blockNumber + * @return txs for address + * @throws EtherScanException parent exception class + */ + @NotNull + List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; + + @NotNull + List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock) throws EtherScanException; + + @NotNull + List<TxErc1155> txsErc1155(String address, String contractAddress) throws EtherScanException; /** * All blocks mined by address diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 9160bb4..9382618 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -30,8 +30,9 @@ final class AccountAPIProvider extends BasicProvider implements AccountAPI { private static final String ACT_BALANCE_MULTI_ACTION = ACT_PREFIX + "balancemulti"; private static final String ACT_TX_ACTION = ACT_PREFIX + "txlist"; private static final String ACT_TX_INTERNAL_ACTION = ACT_PREFIX + "txlistinternal"; - private static final String ACT_TX_TOKEN_ACTION = ACT_PREFIX + "tokentx"; - private static final String ACT_TX_NFT_TOKEN_ACTION = ACT_PREFIX + "tokennfttx"; + private static final String ACT_TX_ERC20_ACTION = ACT_PREFIX + "tokentx"; + private static final String ACT_TX_ERC721_ACTION = ACT_PREFIX + "tokennfttx"; + private static final String ACT_TX_ERC1155_ACTION = ACT_PREFIX + "token1155tx"; private static final String ACT_MINED_ACTION = ACT_PREFIX + "getminedblocks"; private static final String BLOCK_TYPE_PARAM = "&blocktype=blocks"; @@ -145,8 +146,7 @@ public List<Tx> txs(String address, long startBlock, long endBlock) throws Ether * @param <R> responseListTO type * @return List of T values */ - private <T, R extends BaseListResponseTO> List<T> getRequestUsingOffset(final String urlParams, - Class<R> tClass) + private <T, R extends BaseListResponseTO<T>> List<T> getRequestUsingOffset(final String urlParams, Class<R> tClass) throws EtherScanException { final List<T> result = new ArrayList<>(); int page = 1; @@ -208,81 +208,153 @@ public List<TxInternal> txsInternalByHash(String txhash) throws EtherScanExcepti @NotNull @Override - public List<TxERC20> txsERC20(String address) throws EtherScanException { - return txsERC20(address, MIN_START_BLOCK); + public List<TxErc20> txsErc20(String address) throws EtherScanException { + return txsErc20(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxERC20> txsERC20(String address, long startBlock) throws EtherScanException { - return txsERC20(address, startBlock, MAX_END_BLOCK); + public List<TxErc20> txsErc20(String address, long startBlock) throws EtherScanException { + return txsErc20(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxERC20> txsERC20(String address, long startBlock, long endBlock) throws EtherScanException { + public List<TxErc20> txsErc20(String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); - final String urlParams = ACT_TX_TOKEN_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + final String urlParams = ACT_TX_ERC20_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + ADDRESS_PARAM + address + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + SORT_ASC_PARAM; - return getRequestUsingOffset(urlParams, TxERC20ResponseTO.class); + return getRequestUsingOffset(urlParams, TxErc20ResponseTO.class); } @NotNull @Override - public List<TxERC20> txsERC20(String address, String contractAddress) throws EtherScanException { - return txsERC20(address, contractAddress, MIN_START_BLOCK); + public List<TxErc20> txsErc20(String address, String contractAddress) throws EtherScanException { + return txsErc20(address, contractAddress, MIN_START_BLOCK); } @NotNull @Override - public List<TxERC20> txsERC20(String address, String contractAddress, long startBlock) throws EtherScanException { - return txsERC20(address, contractAddress, startBlock, MAX_END_BLOCK); + public List<TxErc20> txsErc20(String address, String contractAddress, long startBlock) throws EtherScanException { + return txsErc20(address, contractAddress, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxERC20> txsERC20(String address, String contractAddress, long startBlock, long endBlock) + public List<TxErc20> txsErc20(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); - final String urlParams = ACT_TX_TOKEN_ACTION + offsetParam + ADDRESS_PARAM + address + final String urlParams = ACT_TX_ERC20_ACTION + offsetParam + ADDRESS_PARAM + address + CONTRACT_PARAM + contractAddress + blockParam + SORT_ASC_PARAM; - return getRequestUsingOffset(urlParams, TxERC20ResponseTO.class); + return getRequestUsingOffset(urlParams, TxErc20ResponseTO.class); } @NotNull @Override - public List<TxERC721> txsERC721(String address) throws EtherScanException { - return txsERC721(address, MIN_START_BLOCK); + public List<TxErc721> txsErc721(String address) throws EtherScanException { + return txsErc721(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxERC721> txsERC721(String address, long startBlock) throws EtherScanException { - return txsERC721(address, startBlock, MAX_END_BLOCK); + public List<TxErc721> txsErc721(String address, long startBlock) throws EtherScanException { + return txsErc721(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxERC721> txsERC721(String address, long startBlock, long endBlock) throws EtherScanException { + public List<TxErc721> txsErc721(String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); - final String urlParams = ACT_TX_NFT_TOKEN_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + final String urlParams = ACT_TX_ERC721_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + ADDRESS_PARAM + address + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + SORT_ASC_PARAM; - return getRequestUsingOffset(urlParams, TxERC20ResponseTO.class); + return getRequestUsingOffset(urlParams, TxErc721ResponseTO.class); + } + + @Override + public @NotNull List<TxErc721> txsErc721(String address, String contractAddress, long startBlock, long endBlock) + throws EtherScanException { + BasicUtils.validateAddress(address); + final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); + + final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; + final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); + final String urlParams = ACT_TX_ERC721_ACTION + offsetParam + ADDRESS_PARAM + address + + CONTRACT_PARAM + contractAddress + blockParam + SORT_ASC_PARAM; + + return getRequestUsingOffset(urlParams, TxErc721ResponseTO.class); + } + + @Override + public @NotNull List<TxErc721> txsErc721(String address, String contractAddress, long startBlock) throws EtherScanException { + return txsErc721(address, contractAddress, startBlock, MAX_END_BLOCK); + } + + @Override + public @NotNull List<TxErc721> txsErc721(String address, String contractAddress) throws EtherScanException { + return txsErc721(address, contractAddress, MIN_START_BLOCK); + } + + @Override + public @NotNull List<TxErc1155> txsErc1155(String address, long startBlock, long endBlock) throws EtherScanException { + BasicUtils.validateAddress(address); + final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); + + final String urlParams = ACT_TX_ERC1155_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + + ADDRESS_PARAM + address + + START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end() + + SORT_ASC_PARAM; + + return getRequestUsingOffset(urlParams, TxErc1155ResponseTO.class); + } + + @Override + public @NotNull List<TxErc1155> txsErc1155(String address, long startBlock) throws EtherScanException { + return txsErc1155(address, startBlock, MAX_END_BLOCK); + } + + @Override + public @NotNull List<TxErc1155> txsErc1155(String address) throws EtherScanException { + return txsErc1155(address, MIN_START_BLOCK); + } + + @Override + public @NotNull List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock, long endBlock) + throws EtherScanException { + BasicUtils.validateAddress(address); + final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); + + final String offsetParam = PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX; + final String blockParam = START_BLOCK_PARAM + blocks.start() + END_BLOCK_PARAM + blocks.end(); + final String urlParams = ACT_TX_ERC1155_ACTION + offsetParam + ADDRESS_PARAM + address + + CONTRACT_PARAM + contractAddress + blockParam + SORT_ASC_PARAM; + + return getRequestUsingOffset(urlParams, TxErc1155ResponseTO.class); + } + + @Override + public @NotNull List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock) + throws EtherScanException { + return txsErc1155(address, contractAddress, startBlock, MAX_END_BLOCK); + } + + @Override + public @NotNull List<TxErc1155> txsErc1155(String address, String contractAddress) throws EtherScanException { + return txsErc1155(address, contractAddress, MIN_START_BLOCK); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java b/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java index ce0d929..96d8d0b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthNetwork.java @@ -9,6 +9,9 @@ */ public interface EthNetwork { + /** + * @return URI for network domain like <a href="https://api.etherscan.io/api">EtherScan API</a> + */ @NotNull URI domain(); } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java index e159d3b..c66e60f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java @@ -20,7 +20,6 @@ abstract class BaseTx { String hash; String from; String to; - BigInteger value; String contractAddress; String input; BigInteger gas; @@ -49,10 +48,6 @@ public String getTo() { return to; } - public BigInteger getValue() { - return value; - } - public String getContractAddress() { return contractAddress; } @@ -76,41 +71,16 @@ public boolean equals(Object o) { return true; if (!(o instanceof BaseTx)) return false; - BaseTx baseTx = (BaseTx) o; - - if (blockNumber != baseTx.blockNumber) - return false; - if (!Objects.equals(timeStamp, baseTx.timeStamp)) - return false; - if (!Objects.equals(hash, baseTx.hash)) - return false; - if (!Objects.equals(from, baseTx.from)) - return false; - if (!Objects.equals(to, baseTx.to)) - return false; - return Objects.equals(value, baseTx.value); + return blockNumber == baseTx.blockNumber && Objects.equals(timeStamp, baseTx.timeStamp) + && Objects.equals(hash, baseTx.hash) && Objects.equals(from, baseTx.from) && Objects.equals(to, baseTx.to) + && Objects.equals(contractAddress, baseTx.contractAddress) && Objects.equals(input, baseTx.input) + && Objects.equals(gas, baseTx.gas) && Objects.equals(gasUsed, baseTx.gasUsed); } @Override public int hashCode() { - int result = (int) (blockNumber ^ (blockNumber >>> 32)); - result = 31 * result + (timeStamp != null - ? timeStamp.hashCode() - : 0); - result = 31 * result + (hash != null - ? hash.hashCode() - : 0); - result = 31 * result + (from != null - ? from.hashCode() - : 0); - result = 31 * result + (to != null - ? to.hashCode() - : 0); - result = 31 * result + (value != null - ? value.hashCode() - : 0); - return result; + return Objects.hash(blockNumber, timeStamp, hash, from, to, contractAddress, input, gas, gasUsed); } @Override @@ -121,7 +91,6 @@ public String toString() { ", hash='" + hash + '\'' + ", from='" + from + '\'' + ", to='" + to + '\'' + - ", value=" + value + ", contractAddress='" + contractAddress + '\'' + ", input='" + input + '\'' + ", gas=" + gas + 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 cc9be41..65c24ba 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -12,6 +12,7 @@ */ public class Tx extends BaseTx { + private BigInteger value; private long nonce; private String blockHash; private int transactionIndex; @@ -22,6 +23,10 @@ public class Tx extends BaseTx { private String txreceipt_status; // <editor-fold desc="Getters"> + public BigInteger getValue() { + return value; + } + public long getNonce() { return nonce; } @@ -59,40 +64,28 @@ public long getConfirmations() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Tx)) return false; if (!super.equals(o)) return false; - Tx tx = (Tx) o; - - if (nonce != tx.nonce) - return false; - if (transactionIndex != tx.transactionIndex) - return false; - if (!Objects.equals(blockHash, tx.blockHash)) - return false; - return Objects.equals(isError, tx.isError); + return nonce == tx.nonce && transactionIndex == tx.transactionIndex && confirmations == tx.confirmations + && Objects.equals(value, tx.value) && Objects.equals(blockHash, tx.blockHash) + && Objects.equals(gasPrice, tx.gasPrice) && Objects.equals(cumulativeGasUsed, tx.cumulativeGasUsed) + && Objects.equals(isError, tx.isError) && Objects.equals(txreceipt_status, tx.txreceipt_status); } @Override public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (int) (nonce ^ (nonce >>> 32)); - result = 31 * result + (blockHash != null - ? blockHash.hashCode() - : 0); - result = 31 * result + transactionIndex; - result = 31 * result + (isError != null - ? isError.hashCode() - : 0); - return result; + return Objects.hash(super.hashCode(), value, nonce, blockHash, transactionIndex, gasPrice, cumulativeGasUsed, + confirmations, isError, txreceipt_status); } @Override public String toString() { return "Tx{" + "nonce=" + nonce + + ", value='" + value + '\'' + ", blockHash='" + blockHash + '\'' + ", transactionIndex=" + transactionIndex + ", gasPrice=" + gasPrice + diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java new file mode 100644 index 0000000..e57af5f --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -0,0 +1,230 @@ +package io.goodforgod.api.etherscan.model; + +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +/** + * @author GoodforGod + * @since 28.10.2018 + */ +public class TxErc1155 extends BaseTx { + + private long nonce; + private String blockHash; + private String tokenID; + private String tokenName; + private String tokenSymbol; + private String tokenValue; + private int transactionIndex; + private long gasPrice; + private long cumulativeGasUsed; + private long confirmations; + + // <editor-fold desc="Getters"> + public long getNonce() { + return nonce; + } + + public String getBlockHash() { + return blockHash; + } + + public String getTokenID() { + return tokenID; + } + + public String getTokenName() { + return tokenName; + } + + public String getTokenSymbol() { + return tokenSymbol; + } + + public String getTokenValue() { + return tokenValue; + } + + public int getTransactionIndex() { + return transactionIndex; + } + + public long getGasPrice() { + return gasPrice; + } + + public long getCumulativeGasUsed() { + return cumulativeGasUsed; + } + + public long getConfirmations() { + return confirmations; + } + // </editor-fold> + + @Override + public String toString() { + return "TxERC721{" + + "nonce=" + nonce + + ", blockHash='" + blockHash + '\'' + + ", tokenID='" + tokenID + '\'' + + ", tokenName='" + tokenName + '\'' + + ", tokenSymbol='" + tokenSymbol + '\'' + + ", tokenValue='" + tokenValue + '\'' + + ", transactionIndex=" + transactionIndex + + ", gasPrice=" + gasPrice + + ", cumulativeGasUsed=" + cumulativeGasUsed + + ", confirmations=" + confirmations + + "} " + super.toString(); + } + + public static TxErc1155Builder builder() { + return new TxErc1155Builder(); + } + + public static final class TxErc1155Builder { + + private long blockNumber; + private LocalDateTime timeStamp; + private String hash; + private String from; + private String to; + private String contractAddress; + private String input; + private BigInteger gas; + private BigInteger gasUsed; + private long nonce; + private String blockHash; + private String tokenID; + private String tokenName; + private String tokenSymbol; + private String tokenValue; + private int transactionIndex; + private long gasPrice; + private long cumulativeGasUsed; + private long confirmations; + + private TxErc1155Builder() {} + + public TxErc1155Builder withBlockNumber(long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public TxErc1155Builder withTimeStamp(LocalDateTime timeStamp) { + this.timeStamp = timeStamp; + return this; + } + + public TxErc1155Builder withHash(String hash) { + this.hash = hash; + return this; + } + + public TxErc1155Builder withFrom(String from) { + this.from = from; + return this; + } + + public TxErc1155Builder withTo(String to) { + this.to = to; + return this; + } + + public TxErc1155Builder withContractAddress(String contractAddress) { + this.contractAddress = contractAddress; + return this; + } + + public TxErc1155Builder withInput(String input) { + this.input = input; + return this; + } + + public TxErc1155Builder withGas(BigInteger gas) { + this.gas = gas; + return this; + } + + public TxErc1155Builder withGasUsed(BigInteger gasUsed) { + this.gasUsed = gasUsed; + return this; + } + + public TxErc1155Builder withNonce(long nonce) { + this.nonce = nonce; + return this; + } + + public TxErc1155Builder withBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + public TxErc1155Builder withTokenID(String tokenID) { + this.tokenID = tokenID; + return this; + } + + public TxErc1155Builder withTokenName(String tokenName) { + this.tokenName = tokenName; + return this; + } + + public TxErc1155Builder withTokenSymbol(String tokenSymbol) { + this.tokenSymbol = tokenSymbol; + return this; + } + + public TxErc1155Builder withTokenDecimal(String tokenDecimal) { + this.tokenValue = tokenDecimal; + return this; + } + + public TxErc1155Builder withTransactionIndex(int transactionIndex) { + this.transactionIndex = transactionIndex; + return this; + } + + public TxErc1155Builder withGasPrice(long gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + public TxErc1155Builder withCumulativeGasUsed(long cumulativeGasUsed) { + this.cumulativeGasUsed = cumulativeGasUsed; + return this; + } + + public TxErc1155Builder withConfirmations(long confirmations) { + this.confirmations = confirmations; + return this; + } + + public TxErc1155 build() { + TxErc1155 txERC721 = new TxErc1155(); + txERC721.gas = this.gas; + txERC721.tokenName = this.tokenName; + txERC721.hash = this.hash; + txERC721.gasUsed = this.gasUsed; + txERC721.nonce = this.nonce; + txERC721.from = this.from; + txERC721.gasPrice = this.gasPrice; + txERC721.contractAddress = this.contractAddress; + txERC721.cumulativeGasUsed = this.cumulativeGasUsed; + txERC721.tokenID = this.tokenID; + txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC721.blockNumber = this.blockNumber; + txERC721._timeStamp = this.timeStamp; + txERC721.tokenValue = this.tokenValue; + txERC721.transactionIndex = this.transactionIndex; + txERC721.to = this.to; + txERC721.confirmations = this.confirmations; + txERC721.input = this.input; + txERC721.blockHash = this.blockHash; + txERC721.tokenSymbol = this.tokenSymbol; + return txERC721; + } + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java similarity index 96% rename from src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java rename to src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java index 42ffebe..da6f54f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxERC20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -8,8 +8,9 @@ * @author GoodforGod * @since 28.10.2018 */ -public class TxERC20 extends BaseTx { +public class TxErc20 extends BaseTx { + private BigInteger value; private long nonce; private String blockHash; private String tokenName; @@ -29,6 +30,10 @@ public String getBlockHash() { return blockHash; } + public BigInteger getValue() { + return value; + } + public String getTokenName() { return tokenName; } @@ -63,6 +68,7 @@ public String toString() { return "TxERC20{" + "nonce=" + nonce + ", blockHash='" + blockHash + '\'' + + ", value='" + value + '\'' + ", tokenName='" + tokenName + '\'' + ", tokenSymbol='" + tokenSymbol + '\'' + ", tokenDecimal='" + tokenDecimal + '\'' + @@ -196,8 +202,8 @@ public TxERC20Builder withConfirmations(long confirmations) { return this; } - public TxERC20 build() { - TxERC20 txERC20 = new TxERC20(); + public TxErc20 build() { + TxErc20 txERC20 = new TxErc20(); txERC20.gas = this.gas; txERC20.tokenName = this.tokenName; txERC20.hash = this.hash; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java similarity index 93% rename from src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java rename to src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java index 14777fc..548113c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxERC721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -8,10 +8,11 @@ * @author GoodforGod * @since 28.10.2018 */ -public class TxERC721 extends BaseTx { +public class TxErc721 extends BaseTx { private long nonce; private String blockHash; + private String tokenID; private String tokenName; private String tokenSymbol; private String tokenDecimal; @@ -29,6 +30,10 @@ public String getBlockHash() { return blockHash; } + public String getTokenID() { + return tokenID; + } + public String getTokenName() { return tokenName; } @@ -63,6 +68,7 @@ public String toString() { return "TxERC721{" + "nonce=" + nonce + ", blockHash='" + blockHash + '\'' + + ", tokenID='" + tokenID + '\'' + ", tokenName='" + tokenName + '\'' + ", tokenSymbol='" + tokenSymbol + '\'' + ", tokenDecimal='" + tokenDecimal + '\'' + @@ -84,13 +90,13 @@ public static final class TxERC721Builder { private String hash; private String from; private String to; - private BigInteger value; private String contractAddress; private String input; private BigInteger gas; private BigInteger gasUsed; private long nonce; private String blockHash; + private String tokenID; private String tokenName; private String tokenSymbol; private String tokenDecimal; @@ -126,11 +132,6 @@ public TxERC721Builder withTo(String to) { return this; } - public TxERC721Builder withValue(BigInteger value) { - this.value = value; - return this; - } - public TxERC721Builder withContractAddress(String contractAddress) { this.contractAddress = contractAddress; return this; @@ -161,6 +162,11 @@ public TxERC721Builder withBlockHash(String blockHash) { return this; } + public TxERC721Builder withTokenID(String tokenID) { + this.tokenID = tokenID; + return this; + } + public TxERC721Builder withTokenName(String tokenName) { this.tokenName = tokenName; return this; @@ -196,8 +202,8 @@ public TxERC721Builder withConfirmations(long confirmations) { return this; } - public TxERC721 build() { - TxERC721 txERC721 = new TxERC721(); + public TxErc721 build() { + TxErc721 txERC721 = new TxErc721(); txERC721.gas = this.gas; txERC721.tokenName = this.tokenName; txERC721.hash = this.hash; @@ -207,7 +213,7 @@ public TxERC721 build() { txERC721.gasPrice = this.gasPrice; txERC721.contractAddress = this.contractAddress; txERC721.cumulativeGasUsed = this.cumulativeGasUsed; - txERC721.value = this.value; + txERC721.tokenID = this.tokenID; txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); txERC721.blockNumber = this.blockNumber; txERC721._timeStamp = this.timeStamp; 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 f1d1edf..84e10b3 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -11,12 +11,17 @@ */ public class TxInternal extends BaseTx { + private BigInteger value; private String type; private String traceId; private int isError; private String errCode; // <editor-fold desc="Getters"> + public BigInteger getValue() { + return value; + } + public String getType() { return type; } @@ -48,24 +53,14 @@ public boolean equals(Object o) { return false; if (!super.equals(o)) return false; - TxInternal that = (TxInternal) o; - - if (!Objects.equals(traceId, that.traceId)) - return false; - return Objects.equals(errCode, that.errCode); + return isError == that.isError && Objects.equals(value, that.value) && Objects.equals(type, that.type) + && Objects.equals(traceId, that.traceId) && Objects.equals(errCode, that.errCode); } @Override public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (traceId != null - ? traceId.hashCode() - : 0); - result = 31 * result + (errCode != null - ? errCode.hashCode() - : 0); - return result; + return Objects.hash(super.hashCode(), value, type, traceId, isError, errCode); } @Override @@ -73,6 +68,7 @@ public String toString() { return "TxInternal{" + "type='" + type + '\'' + ", traceId=" + traceId + + ", value=" + value + ", isError=" + isError + ", errCode='" + errCode + '\'' + "} " + super.toString(); diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java deleted file mode 100644 index f4814a5..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC20ResponseTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.goodforgod.api.etherscan.model.response; - -import io.goodforgod.api.etherscan.model.TxERC20; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class TxERC20ResponseTO extends BaseListResponseTO<TxERC20> { - -} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java deleted file mode 100644 index b4db8ef..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/TxERC721ResponseTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.goodforgod.api.etherscan.model.response; - -import io.goodforgod.api.etherscan.model.TxERC20; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class TxERC721ResponseTO extends BaseListResponseTO<TxERC20> { - -} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java new file mode 100644 index 0000000..994d2cd --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxErc1155; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class TxErc1155ResponseTO extends BaseListResponseTO<TxErc1155> { + +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java new file mode 100644 index 0000000..d5d3f6e --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxErc20; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class TxErc20ResponseTO extends BaseListResponseTO<TxErc20> { + +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java new file mode 100644 index 0000000..2a9403f --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxErc721; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class TxErc721ResponseTO extends BaseListResponseTO<TxErc721> { + +} 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 4522ff5..eda3ce2 100644 --- a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java @@ -20,7 +20,7 @@ public final class BasicUtils { private BasicUtils() {} - private static final int MAX_END_BLOCK = 999999999; + private static final int MAX_END_BLOCK = Integer.MAX_VALUE; private static final int MIN_START_BLOCK = 0; private static final Pattern ADDRESS_PATTERN = Pattern.compile("0x[a-zA-Z0-9]{40}"); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxERC20Test.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Test.java similarity index 81% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxERC20Test.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Test.java index bacf2e3..0a94289 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxERC20Test.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Test.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; -import io.goodforgod.api.etherscan.model.TxERC20; +import io.goodforgod.api.etherscan.model.TxErc20; import java.util.List; import org.junit.jupiter.api.Test; @@ -10,11 +10,11 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTxERC20Test extends ApiRunner { +class AccountTxErc20Test extends ApiRunner { @Test void correct() { - List<TxERC20> txs = getApi().account().txsERC20("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); + List<TxErc20> txs = getApi().account().txsErc20("0xE376F69ED2218076682e2b3B7b9099eC50aD68c4"); assertNotNull(txs); assertEquals(3, txs.size()); assertTxs(txs); @@ -33,7 +33,7 @@ void correct() { @Test void correctStartBlock() { - List<TxERC20> txs = getApi().account().txsERC20("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); + List<TxErc20> txs = getApi().account().txsErc20("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167); assertNotNull(txs); assertEquals(11, txs.size()); assertTxs(txs); @@ -41,7 +41,7 @@ void correctStartBlock() { @Test void correctStartBlockEndBlock() { - List<TxERC20> txs = getApi().account().txsERC20("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); + List<TxErc20> txs = getApi().account().txsErc20("0x36ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7", 5578167, 5813576); assertNotNull(txs); assertEquals(5, txs.size()); assertTxs(txs); @@ -50,18 +50,18 @@ void correctStartBlockEndBlock() { @Test void invalidParamWithError() { assertThrows(EtherScanInvalidAddressException.class, - () -> getApi().account().txsERC20("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); + () -> getApi().account().txsErc20("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); } @Test void correctParamWithEmptyExpectedResult() { - List<TxERC20> txs = getApi().account().txsERC20("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + List<TxErc20> txs = getApi().account().txsErc20("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); } - private void assertTxs(List<TxERC20> txs) { - for (TxERC20 tx : txs) { + private void assertTxs(List<TxErc20> txs) { + for (TxErc20 tx : txs) { assertNotNull(tx.getBlockHash()); assertNotNull(tx.getTokenName()); assertNotNull(tx.getTokenSymbol()); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java new file mode 100644 index 0000000..ce3a680 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java @@ -0,0 +1,81 @@ +package io.goodforgod.api.etherscan.account; + +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.TxErc1155; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +class AccountTxRc1155TokenTest extends ApiRunner { + + @Test + void correct() { + List<TxErc1155> txs = getApi().account().txsErc1155("0xE4C8324534C0C6bCA174Cd0F02fAC9889C36bA59"); + assertNotNull(txs); + assertFalse(txs.isEmpty()); + assertTxs(txs); + assertNotEquals(0, txs.get(0).getGasPrice()); + assertNotEquals(-1, txs.get(0).getNonce()); + + assertNotNull(txs.get(0).toString()); + assertNotEquals(txs.get(0).toString(), txs.get(1).toString()); + + assertNotEquals(txs.get(0), txs.get(1)); + assertNotEquals(txs.get(0).hashCode(), txs.get(1).hashCode()); + + assertEquals(txs.get(1), txs.get(1)); + assertEquals(txs.get(1).hashCode(), txs.get(1).hashCode()); + } + + @Test + void correctStartBlock() { + List<TxErc1155> txs = getApi().account().txsErc1155("0xE4C8324534C0C6bCA174Cd0F02fAC9889C36bA59", 14275897); + assertNotNull(txs); + assertFalse(txs.isEmpty()); + assertTxs(txs); + } + + @Test + void correctStartBlockEndBlock() { + List<TxErc1155> txs = getApi().account().txsErc1155("0xE4C8324534C0C6bCA174Cd0F02fAC9889C36bA59", 14275897, 15148929); + assertNotNull(txs); + assertEquals(11, txs.size()); + assertTxs(txs); + } + + @Test + void invalidParamWithError() { + assertThrows(EtherScanInvalidAddressException.class, + () -> getApi().account().txsErc1155("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); + } + + @Test + void correctParamWithEmptyExpectedResult() { + List<TxErc1155> txs = getApi().account().txsErc1155("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + assertNotNull(txs); + assertTrue(txs.isEmpty()); + } + + private void assertTxs(List<TxErc1155> txs) { + txs.forEach(this::asserTx); + } + + private void asserTx(TxErc1155 tx) { + assertNotNull(tx.getBlockHash()); + assertNotNull(tx.getTokenName()); + assertNotNull(tx.getTokenSymbol()); + assertNotNull(tx.getFrom()); + assertNotNull(tx.getTo()); + assertNotNull(tx.getTimeStamp()); + assertNotNull(tx.getTokenID()); + assertNotNull(tx.getTokenValue()); + assertNotEquals(-1, (tx.getConfirmations())); + assertNotNull(tx.getGasUsed()); + assertNotEquals(-1, tx.getCumulativeGasUsed()); + assertNotEquals(-1, tx.getTransactionIndex()); + } +} diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java index 31c8533..b7988db 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; -import io.goodforgod.api.etherscan.model.TxERC721; +import io.goodforgod.api.etherscan.model.TxErc721; import java.util.List; import org.junit.jupiter.api.Test; @@ -14,7 +14,7 @@ class AccountTxRc721TokenTest extends ApiRunner { @Test void correct() { - List<TxERC721> txs = getApi().account().txsERC721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67"); + List<TxErc721> txs = getApi().account().txsErc721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67"); assertNotNull(txs); assertEquals(16, txs.size()); assertTxs(txs); @@ -33,7 +33,7 @@ void correct() { @Test void correctStartBlock() { - List<TxERC721> txs = getApi().account().txsERC721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4762071); + List<TxErc721> txs = getApi().account().txsErc721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4762071); System.out.println(txs); assertNotNull(txs); assertEquals(5, txs.size()); @@ -42,7 +42,7 @@ void correctStartBlock() { @Test void correctStartBlockEndBlock() { - List<TxERC721> txs = getApi().account().txsERC721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4761862, 4761934); + List<TxErc721> txs = getApi().account().txsErc721("0x1a1ebe0d86f72884c3fd484ae1e796e08f8ffa67", 4761862, 4761934); System.out.println(txs); assertNotNull(txs); assertEquals(11, txs.size()); @@ -52,18 +52,18 @@ void correctStartBlockEndBlock() { @Test void invalidParamWithError() { assertThrows(EtherScanInvalidAddressException.class, - () -> getApi().account().txsERC721("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); + () -> getApi().account().txsErc721("0x6ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7")); } @Test void correctParamWithEmptyExpectedResult() { - List<TxERC721> txs = getApi().account().txsERC721("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); + List<TxErc721> txs = getApi().account().txsErc721("0x31ec53A8fBa6358d59B3C4476D82cc60A2B0FaD7"); assertNotNull(txs); assertTrue(txs.isEmpty()); } - private void assertTxs(List<TxERC721> txs) { - for (TxERC721 tx : txs) { + private void assertTxs(List<TxErc721> txs) { + for (TxErc721 tx : txs) { assertNotNull(tx.getBlockHash()); assertNotNull(tx.getTokenName()); assertNotNull(tx.getTokenSymbol()); From 34ca1a7f250b7da9a2fa08d0fb3138c3bdc2d812 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 02:38:15 +0300 Subject: [PATCH 19/67] [2.0.0-SNAPSHOT] EthHttpClient contract and package refactoring --- .../api/etherscan/AccountAPIProvider.java | 7 ++-- .../api/etherscan/BasicProvider.java | 27 +++++++-------- .../api/etherscan/BlockAPIProvider.java | 7 ++-- .../api/etherscan/ContractAPIProvider.java | 7 ++-- .../goodforgod/api/etherscan/Converter.java | 13 ++++++++ .../api/etherscan/EthScanAPIBuilder.java | 24 ++++++++++++-- .../api/etherscan/EtherScanAPI.java | 5 ++- .../api/etherscan/EtherScanAPIProvider.java | 25 +++++++------- .../api/etherscan/GasTrackerAPIProvider.java | 7 ++-- .../api/etherscan/LogsAPIProvider.java | 7 ++-- .../api/etherscan/ProxyAPIProvider.java | 11 ++++--- .../api/etherscan/StatisticAPIProvider.java | 11 ++++--- .../api/etherscan/TransactionAPIProvider.java | 7 ++-- .../{executor => http}/EthHttpClient.java | 17 ++++++---- .../impl/UrlEthHttpClient.java | 32 +++++++++--------- .../api/etherscan/model/TxErc1155.java | 19 +++++++++++ .../api/etherscan/model/TxErc20.java | 19 +++++++++++ .../api/etherscan/model/TxErc721.java | 19 +++++++++++ .../goodforgod/api/etherscan/model/Wei.java | 4 +++ .../api/etherscan/EtherScanAPITests.java | 6 ++-- .../gastracker/GasTrackerApiTest.java | 33 +++++++++++++++++++ 21 files changed, 223 insertions(+), 84 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/Converter.java rename src/main/java/io/goodforgod/api/etherscan/{executor => http}/EthHttpClient.java (50%) rename src/main/java/io/goodforgod/api/etherscan/{executor => http}/impl/UrlEthHttpClient.java (85%) create mode 100644 src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 9382618..1b7bce6 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.*; import io.goodforgod.api.etherscan.model.response.*; @@ -49,8 +49,9 @@ final class AccountAPIProvider extends BasicProvider implements AccountAPI { AccountAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, - EthHttpClient executor) { - super(requestQueueManager, "account", baseUrl, executor); + EthHttpClient executor, + Converter converter) { + super(requestQueueManager, "account", baseUrl, executor, converter); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index a4ce2a6..f1867d1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -1,15 +1,15 @@ package io.goodforgod.api.etherscan; -import com.google.gson.Gson; import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanParseException; import io.goodforgod.api.etherscan.error.EtherScanRateLimitException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.response.StringResponseTO; import io.goodforgod.api.etherscan.util.BasicUtils; -import io.goodforgod.gson.configuration.GsonConfiguration; +import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.Map; /** @@ -30,22 +30,23 @@ abstract class BasicProvider { private final String baseUrl; private final EthHttpClient executor; private final RequestQueueManager queue; - private final Gson gson; + private final Converter converter; BasicProvider(RequestQueueManager requestQueueManager, String module, String baseUrl, - EthHttpClient ethHttpClient) { + EthHttpClient ethHttpClient, + Converter converter) { this.queue = requestQueueManager; this.module = "&module=" + module; this.baseUrl = baseUrl; this.executor = ethHttpClient; - this.gson = new GsonConfiguration().builder().create(); + this.converter = converter; } <T> T convert(String json, Class<T> tClass) { try { - final T t = gson.fromJson(json, tClass); + final T t = converter.fromJson(json, tClass); if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith("Max rate limit reached")) { throw new EtherScanRateLimitException(((StringResponseTO) t).getResult()); } @@ -53,7 +54,7 @@ <T> T convert(String json, Class<T> tClass) { return t; } catch (Exception e) { try { - final Map<String, Object> map = gson.fromJson(json, Map.class); + final Map<String, Object> map = converter.fromJson(json, Map.class); final Object result = map.get("result"); if (result instanceof String && ((String) result).startsWith("Max rate limit reached")) throw new EtherScanRateLimitException(((String) result)); @@ -69,18 +70,18 @@ <T> T convert(String json, Class<T> tClass) { String getRequest(String urlParameters) { queue.takeTurn(); - final String url = baseUrl + module + urlParameters; - final String result = executor.get(url); + final URI uri = URI.create(baseUrl + module + urlParameters); + final String result = executor.get(uri); if (BasicUtils.isEmpty(result)) - throw new EtherScanResponseException("Server returned null value for GET request at URL - " + url); + throw new EtherScanResponseException("Server returned null value for GET request at URL - " + uri); return result; } String postRequest(String urlParameters, String dataToPost) { queue.takeTurn(); - final String url = baseUrl + module + urlParameters; - return executor.post(url, dataToPost); + final URI uri = URI.create(baseUrl + module + urlParameters); + return executor.post(uri, dataToPost.getBytes(StandardCharsets.UTF_8)); } <T> T getRequest(String urlParameters, Class<T> tClass) { diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java index e5a6d49..98a2d90 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -1,7 +1,7 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.BlockUncle; import io.goodforgod.api.etherscan.model.response.UncleBlockResponseTO; @@ -24,8 +24,9 @@ final class BlockAPIProvider extends BasicProvider implements BlockAPI { BlockAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, - EthHttpClient executor) { - super(requestQueueManager, "block", baseUrl, executor); + EthHttpClient executor, + Converter converter) { + super(requestQueueManager, "block", baseUrl, executor, converter); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java index cd96f68..1a8fa9a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +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.response.StringResponseTO; @@ -24,8 +24,9 @@ final class ContractAPIProvider extends BasicProvider implements ContractAPI { ContractAPIProvider(RequestQueueManager requestQueueManager, String baseUrl, - EthHttpClient executor) { - super(requestQueueManager, "contract", baseUrl, executor); + EthHttpClient executor, + Converter converter) { + super(requestQueueManager, "contract", baseUrl, executor, converter); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/Converter.java b/src/main/java/io/goodforgod/api/etherscan/Converter.java new file mode 100644 index 0000000..e8c577a --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/Converter.java @@ -0,0 +1,13 @@ +package io.goodforgod.api.etherscan; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Anton Kurako (GoodforGod) + * @since 14.05.2023 + */ +public interface Converter { + + @NotNull + <T> T fromJson(@NotNull String json, @NotNull Class<T> type); +} diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 6571121..501bdb1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -1,11 +1,13 @@ package io.goodforgod.api.etherscan; +import com.google.gson.Gson; import io.goodforgod.api.etherscan.error.EtherScanKeyException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; -import io.goodforgod.api.etherscan.executor.impl.UrlEthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; +import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.manager.impl.FakeRequestQueueManager; import io.goodforgod.api.etherscan.util.BasicUtils; +import io.goodforgod.gson.configuration.GsonConfiguration; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; @@ -18,10 +20,19 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { private static final Supplier<EthHttpClient> DEFAULT_SUPPLIER = UrlEthHttpClient::new; private static final String DEFAULT_KEY = "YourApiKeyToken"; + private final Gson gson = new GsonConfiguration().builder().create(); + private String apiKey = DEFAULT_KEY; private EthNetwork ethNetwork = EthNetworks.MAINNET; private RequestQueueManager queueManager = RequestQueueManager.DEFAULT; private Supplier<EthHttpClient> ethHttpClientSupplier = DEFAULT_SUPPLIER; + private Supplier<Converter> converterSupplier = () -> new Converter() { + + @Override + public <T> @NotNull T fromJson(@NotNull String json, @NotNull Class<T> type) { + return gson.fromJson(json, type); + } + }; @NotNull @Override @@ -64,8 +75,15 @@ public EtherScanAPI.Builder withHttpClient(@NotNull Supplier<EthHttpClient> http return this; } + @NotNull + @Override + public EtherScanAPI.Builder withConverter(@NotNull Supplier<Converter> converterSupplier) { + this.converterSupplier = converterSupplier; + return this; + } + @Override public @NotNull EtherScanAPI build() { - return new EtherScanAPIProvider(apiKey, ethNetwork, ethHttpClientSupplier, queueManager); + return new EtherScanAPIProvider(apiKey, ethNetwork, queueManager, ethHttpClientSupplier.get(), converterSupplier.get()); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java index d902074..6da3d8f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -1,6 +1,6 @@ package io.goodforgod.api.etherscan; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; @@ -59,6 +59,9 @@ interface Builder { @NotNull Builder withHttpClient(@NotNull Supplier<EthHttpClient> httpClientSupplier); + @NotNull + Builder withConverter(@NotNull Supplier<Converter> converterSupplier); + @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 675836f..e698f45 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java @@ -1,8 +1,7 @@ package io.goodforgod.api.etherscan; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; -import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; /** @@ -25,21 +24,21 @@ final class EtherScanAPIProvider implements EtherScanAPI { EtherScanAPIProvider(String apiKey, EthNetwork network, - Supplier<EthHttpClient> executorSupplier, - RequestQueueManager queue) { + RequestQueueManager queue, + EthHttpClient ethHttpClient, + Converter converter) { // EtherScan 1request\5sec limit support by queue manager - final EthHttpClient ethHttpClient = executorSupplier.get(); final String baseUrl = network.domain() + "?apikey=" + apiKey; this.requestQueueManager = queue; - this.account = new AccountAPIProvider(queue, baseUrl, ethHttpClient); - this.block = new BlockAPIProvider(queue, baseUrl, ethHttpClient); - this.contract = new ContractAPIProvider(queue, baseUrl, ethHttpClient); - this.logs = new LogsAPIProvider(queue, baseUrl, ethHttpClient); - this.proxy = new ProxyAPIProvider(queue, baseUrl, ethHttpClient); - this.stats = new StatisticAPIProvider(queue, baseUrl, ethHttpClient); - this.txs = new TransactionAPIProvider(queue, baseUrl, ethHttpClient); - this.gasTracker = new GasTrackerAPIProvider(queue, baseUrl, ethHttpClient); + 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); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java index d4ec276..a4db5ae 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.GasEstimate; import io.goodforgod.api.etherscan.model.GasOracle; @@ -27,8 +27,9 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI GasTrackerAPIProvider(RequestQueueManager queue, String baseUrl, - EthHttpClient ethHttpClient) { - super(queue, "gastracker", baseUrl, ethHttpClient); + EthHttpClient ethHttpClient, + Converter converter) { + super(queue, "gastracker", baseUrl, ethHttpClient, converter); } @Override diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java index 771d931..fe9d420 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java @@ -1,7 +1,7 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.Log; import io.goodforgod.api.etherscan.model.query.LogQuery; @@ -24,8 +24,9 @@ final class LogsAPIProvider extends BasicProvider implements LogsAPI { LogsAPIProvider(RequestQueueManager queue, String baseUrl, - EthHttpClient executor) { - super(queue, "logs", baseUrl, executor); + EthHttpClient executor, + Converter converter) { + super(queue, "logs", baseUrl, executor, converter); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index 1239294..a306541 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -3,7 +3,7 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanInvalidDataHexException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.proxy.BlockProxy; import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; @@ -56,10 +56,11 @@ final class ProxyAPIProvider extends BasicProvider implements ProxyAPI { private static final Pattern EMPTY_HEX = Pattern.compile("0x0+"); - ProxyAPIProvider(final RequestQueueManager queue, - final String baseUrl, - final EthHttpClient executor) { - super(queue, "proxy", baseUrl, executor); + ProxyAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter) { + super(queue, "proxy", baseUrl, executor, converter); } @Override diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java index ee4bdaa..1d1bcee 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.Price; import io.goodforgod.api.etherscan.model.Supply; @@ -27,10 +27,11 @@ final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { private static final String CONTRACT_ADDRESS_PARAM = "&contractaddress="; - StatisticAPIProvider(final RequestQueueManager queue, - final String baseUrl, - final EthHttpClient executor) { - super(queue, "stats", baseUrl, executor); + StatisticAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter) { + super(queue, "stats", baseUrl, executor, converter); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java index 91082a8..c131079 100644 --- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java @@ -1,7 +1,7 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.Status; import io.goodforgod.api.etherscan.model.response.ReceiptStatusResponseTO; @@ -26,8 +26,9 @@ final class TransactionAPIProvider extends BasicProvider implements TransactionA TransactionAPIProvider(RequestQueueManager queue, String baseUrl, - EthHttpClient executor) { - super(queue, "transaction", baseUrl, executor); + EthHttpClient executor, + Converter converter) { + super(queue, "transaction", baseUrl, executor, converter); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/executor/EthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java similarity index 50% rename from src/main/java/io/goodforgod/api/etherscan/executor/EthHttpClient.java rename to src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java index 4edc507..f4b559d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/executor/EthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java @@ -1,4 +1,7 @@ -package io.goodforgod.api.etherscan.executor; +package io.goodforgod.api.etherscan.http; + +import java.net.URI; +import org.jetbrains.annotations.NotNull; /** * Http Client interface @@ -11,17 +14,19 @@ public interface EthHttpClient { /** * Performs a Http GET request * - * @param url as string + * @param uri as string * @return result as string */ - String get(String url); + @NotNull + String get(@NotNull URI uri); /** * Performs a Http POST request * - * @param url as string - * @param data to post + * @param uri as string + * @param body to post * @return result as string */ - String post(String url, String data); + @NotNull + String post(@NotNull URI uri, byte[] body); } diff --git a/src/main/java/io/goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java similarity index 85% rename from src/main/java/io/goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java rename to src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java index ac05125..4178be7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/executor/impl/UrlEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java @@ -1,17 +1,17 @@ -package io.goodforgod.api.etherscan.executor.impl; +package io.goodforgod.api.etherscan.http.impl; import static java.net.HttpURLConnection.*; import io.goodforgod.api.etherscan.error.EtherScanConnectionException; import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; -import io.goodforgod.api.etherscan.util.BasicUtils; +import io.goodforgod.api.etherscan.http.EthHttpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -71,8 +71,8 @@ public UrlEthHttpClient(Duration connectTimeout, this.headers = Collections.unmodifiableMap(headers); } - private HttpURLConnection buildConnection(String urlAsString, String method) throws IOException { - final URL url = new URL(urlAsString); + private HttpURLConnection buildConnection(URI uri, String method) throws IOException { + final URL url = uri.toURL(); final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(method); connection.setConnectTimeout(connectTimeout); @@ -82,12 +82,12 @@ private HttpURLConnection buildConnection(String urlAsString, String method) thr } @Override - public String get(final String urlAsString) { + public String get(URI uri) { try { - final HttpURLConnection connection = buildConnection(urlAsString, "GET"); + final HttpURLConnection connection = buildConnection(uri, "GET"); final int status = connection.getResponseCode(); if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { - return get(connection.getHeaderField("Location")); + return get(URI.create(connection.getHeaderField("Location"))); } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { throw new EtherScanConnectionException("Protocol error: " + connection.getResponseMessage()); } else if (status >= HTTP_INTERNAL_ERROR) { @@ -105,25 +105,23 @@ public String get(final String urlAsString) { } @Override - public String post(String urlAsString, String dataToPost) { + public String post(URI uri, byte[] body) { try { - final HttpURLConnection connection = buildConnection(urlAsString, "POST"); - final String contentLength = (BasicUtils.isBlank(dataToPost)) - ? "0" - : String.valueOf(dataToPost.length()); + final HttpURLConnection connection = buildConnection(uri, "POST"); + final int contentLength = body.length; connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - connection.setRequestProperty("Content-Length", contentLength); - connection.setFixedLengthStreamingMode(dataToPost.length()); + connection.setRequestProperty("Content-Length", String.valueOf(contentLength)); + connection.setFixedLengthStreamingMode(body.length); connection.setDoOutput(true); connection.connect(); try (OutputStream os = connection.getOutputStream()) { - os.write(dataToPost.getBytes(StandardCharsets.UTF_8)); + os.write(body); } final int status = connection.getResponseCode(); if (status == HTTP_MOVED_TEMP || status == HTTP_MOVED_PERM) { - return post(connection.getHeaderField("Location"), dataToPost); + return post(URI.create(connection.getHeaderField("Location")), body); } else if ((status >= HTTP_BAD_REQUEST) && (status < HTTP_INTERNAL_ERROR)) { throw new EtherScanConnectionException("Protocol error: " + connection.getResponseMessage()); } else if (status >= HTTP_INTERNAL_ERROR) { 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 e57af5f..84e2d40 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -3,6 +3,7 @@ import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Objects; /** * @author GoodforGod @@ -63,6 +64,24 @@ public long getConfirmations() { } // </editor-fold> + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof TxErc1155)) + return false; + if (!super.equals(o)) + return false; + TxErc1155 txErc1155 = (TxErc1155) o; + return Objects.equals(tokenID, txErc1155.tokenID) && Objects.equals(tokenName, txErc1155.tokenName) + && Objects.equals(tokenSymbol, txErc1155.tokenSymbol) && Objects.equals(tokenValue, txErc1155.tokenValue); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), tokenID, tokenName, tokenSymbol, tokenValue); + } + @Override public String toString() { return "TxERC721{" + 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 da6f54f..f51b855 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -3,6 +3,7 @@ import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Objects; /** * @author GoodforGod @@ -63,6 +64,24 @@ public long getConfirmations() { } // </editor-fold> + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof TxErc20)) + return false; + if (!super.equals(o)) + return false; + TxErc20 txErc20 = (TxErc20) o; + return Objects.equals(tokenName, txErc20.tokenName) && Objects.equals(tokenSymbol, txErc20.tokenSymbol) + && Objects.equals(tokenDecimal, txErc20.tokenDecimal); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), tokenName, tokenSymbol, tokenDecimal); + } + @Override public String toString() { return "TxERC20{" + 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 548113c..8fb2467 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -3,6 +3,7 @@ import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Objects; /** * @author GoodforGod @@ -63,6 +64,24 @@ public long getConfirmations() { } // </editor-fold> + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof TxErc721)) + return false; + if (!super.equals(o)) + return false; + TxErc721 txErc721 = (TxErc721) o; + return Objects.equals(tokenID, txErc721.tokenID) && Objects.equals(tokenName, txErc721.tokenName) + && Objects.equals(tokenSymbol, txErc721.tokenSymbol) && Objects.equals(tokenDecimal, txErc721.tokenDecimal); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), tokenID, tokenName, tokenSymbol, tokenDecimal); + } + @Override public String toString() { return "TxERC721{" + diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index 85dd3ab..e863b7a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -11,6 +11,10 @@ public class Wei { private final BigInteger result; + public Wei(long value) { + this.result = BigInteger.valueOf(value); + } + public Wei(BigInteger value) { this.result = value; } diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java index 5cc0fe7..26865f5 100644 --- a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -2,8 +2,8 @@ import io.goodforgod.api.etherscan.error.EtherScanKeyException; import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; -import io.goodforgod.api.etherscan.executor.EthHttpClient; -import io.goodforgod.api.etherscan.executor.impl.UrlEthHttpClient; +import io.goodforgod.api.etherscan.http.EthHttpClient; +import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; import io.goodforgod.api.etherscan.model.Balance; import java.time.Duration; import java.util.concurrent.TimeUnit; @@ -17,10 +17,10 @@ class EtherScanAPITests extends ApiRunner { private final EthNetworks network = EthNetworks.KOVAN; - private final String validKey = "YourKey"; @Test void validKey() { + String validKey = "YourKey"; EtherScanAPI api = EtherScanAPI.builder().withApiKey(validKey).withNetwork(network).build(); assertNotNull(api); } diff --git a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java new file mode 100644 index 0000000..ec904a6 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java @@ -0,0 +1,33 @@ +package io.goodforgod.api.etherscan.gastracker; + +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.GasEstimate; +import io.goodforgod.api.etherscan.model.GasOracle; +import io.goodforgod.api.etherscan.model.Wei; +import org.junit.jupiter.api.Test; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +class GasTrackerApiTest extends ApiRunner { + + @Test + void estimate() { + GasEstimate estimate = getApi().gasTracker().estimate(new Wei(123)); + assertNotNull(estimate); + assertNotNull(estimate.getDuration()); + } + + @Test + void oracle() { + GasOracle oracle = getApi().gasTracker().oracle(); + assertNotNull(oracle); + assertNotNull(oracle.getGasUsedRatio()); + assertNotNull(oracle.getFastGasPriceInWei()); + assertNotNull(oracle.getLastBlock()); + assertNotNull(oracle.getProposeGasPriceInWei()); + assertNotNull(oracle.getSafeGasPriceInWei()); + assertNotNull(oracle.getSuggestBaseFee()); + } +} From f095f0fa4778f5edac65a9f5558812ff4bfd9c9c Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 02:41:55 +0300 Subject: [PATCH 20/67] [2.0.0-SNAPSHOT] Javadoc improved --- .../api/etherscan/model/query/LogQuery.java | 12 ++++++------ .../etherscan/model/query/LogQueryBuilderImpl.java | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java index 69e8409..9d8ea5a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQuery.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; /** - * Final builded container for The Event Log API + * Final built container for The Event Log API * EtherScan - API Descriptions <a href="https://etherscan.io/apis#logs">...</a> * * @see LogQueryBuilderImpl @@ -21,7 +21,7 @@ public interface LogQuery { String params(); @NotNull - static Builder builder(String address) { + static Builder builder(@NotNull String address) { return new LogQueryBuilderImpl(address, MIN_BLOCK, MAX_BLOCK); } @@ -34,16 +34,16 @@ interface Builder { LogQuery.Builder withBlockTo(long endBlock); @NotNull - LogTopicSingle withTopic(String topic0); + LogTopicSingle withTopic(@NotNull String topic0); @NotNull - LogTopicTuple withTopic(String topic0, String topic1); + LogTopicTuple withTopic(@NotNull String topic0, @NotNull String topic1); @NotNull - LogTopicTriple withTopic(String topic0, String topic1, String topic2); + LogTopicTriple withTopic(@NotNull String topic0, @NotNull String topic1, @NotNull String topic2); @NotNull - LogTopicQuadro withTopic(String topic0, String topic1, String topic2, String topic3); + LogTopicQuadro withTopic(@NotNull String topic0, @NotNull String topic1, @NotNull String topic2, @NotNull String topic3); @NotNull LogQuery build(); diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java index 0d1eb59..750ab49 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java @@ -37,14 +37,14 @@ final class LogQueryBuilderImpl implements LogQuery.Builder { } @Override - public @NotNull LogTopicSingle withTopic(String topic0) { + public @NotNull LogTopicSingle withTopic(@NotNull String topic0) { if (BasicUtils.isNotHex(topic0)) throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); return new LogTopicSingle(address, startBlock, endBlock, topic0); } @Override - public @NotNull LogTopicTuple withTopic(String topic0, String topic1) { + public @NotNull LogTopicTuple withTopic(@NotNull String topic0, @NotNull String topic1) { if (BasicUtils.isNotHex(topic0)) throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); if (BasicUtils.isNotHex(topic1)) @@ -53,7 +53,7 @@ final class LogQueryBuilderImpl implements LogQuery.Builder { } @Override - public @NotNull LogTopicTriple withTopic(String topic0, String topic1, String topic2) { + public @NotNull LogTopicTriple withTopic(@NotNull String topic0, @NotNull String topic1, @NotNull String topic2) { if (BasicUtils.isNotHex(topic0)) throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); if (BasicUtils.isNotHex(topic1)) @@ -64,7 +64,8 @@ final class LogQueryBuilderImpl implements LogQuery.Builder { } @Override - public @NotNull LogTopicQuadro withTopic(String topic0, String topic1, String topic2, String topic3) { + public @NotNull LogTopicQuadro + withTopic(@NotNull String topic0, @NotNull String topic1, @NotNull String topic2, @NotNull String topic3) { if (BasicUtils.isNotHex(topic0)) throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); if (BasicUtils.isNotHex(topic1)) From 70cee44655da6e6a7c42e11007eb3f7ef8cb1153 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 02:50:02 +0300 Subject: [PATCH 21/67] [2.0.0-SNAPSHOT] LogQueryBuilderImpl address validation added --- .../etherscan/manager/impl/SemaphoreRequestQueueManager.java | 4 ++-- .../api/etherscan/model/query/LogQueryBuilderImpl.java | 1 + .../java/io/goodforgod/api/etherscan/EtherScanAPITests.java | 4 ++-- .../java/io/goodforgod/api/etherscan/logs/LogsApiTest.java | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java index ff12cd1..a6e3037 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java @@ -31,8 +31,8 @@ public SemaphoreRequestQueueManager(int size, Duration resetIn, Duration delayIn public SemaphoreRequestQueueManager(int size, Duration queueResetTimeIn, Duration delayIn, int initialSize) { this.semaphore = new Semaphore(initialSize); this.queueResetTimeInMillis = queueResetTimeIn.toMillis(); - this.executorService.scheduleAtFixedRate(releaseLocks(size + 1), delayIn.toMillis(), queueResetTimeInMillis, - TimeUnit.MILLISECONDS); + this.executorService.scheduleAtFixedRate(releaseLocks(size + 1), + delayIn.toMillis(), queueResetTimeInMillis, TimeUnit.MILLISECONDS); } @SuppressWarnings("java:S899") diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java index 750ab49..716cfa4 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java @@ -21,6 +21,7 @@ final class LogQueryBuilderImpl implements LogQuery.Builder { private final long startBlock, endBlock; LogQueryBuilderImpl(String address, long startBlock, long endBlock) { + BasicUtils.validateAddress(address); this.address = address; this.startBlock = startBlock; this.endBlock = endBlock; diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java index 26865f5..22c58d6 100644 --- a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -1,7 +1,7 @@ package io.goodforgod.api.etherscan; +import io.goodforgod.api.etherscan.error.EtherScanConnectionException; import io.goodforgod.api.etherscan.error.EtherScanKeyException; -import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; import io.goodforgod.api.etherscan.model.Balance; @@ -70,7 +70,7 @@ void timeout() throws InterruptedException { Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300), Duration.ofMillis(300)); EtherScanAPI api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.KOVAN).withHttpClient(supplier) .build(); - assertThrows(EtherScanTimeoutException.class, + assertThrows(EtherScanConnectionException.class, () -> api.account().blocksMined("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D")); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java b/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java index 0f90d37..7d9fe64 100644 --- a/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java @@ -38,10 +38,10 @@ static Stream<Arguments> source() { .build(); return Stream.of( - Arguments.of(single, 423), + Arguments.of(single, 424), Arguments.of(singleInvalidAddr, 0), Arguments.of(tupleAnd, 1), - Arguments.of(tupleOr, 425)); + Arguments.of(tupleOr, 426)); } @ParameterizedTest From 43a0693922d071aed0dd46d741854755873b6bfd Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 02:57:19 +0300 Subject: [PATCH 22/67] [2.0.0-SNAPSHOT] CI Report only for Java 17 (avoid rate limiter) --- .github/workflows/gradle.yml | 1 + src/test/java/io/goodforgod/api/etherscan/ApiRunner.java | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e4c7620..b4c0bb4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -37,6 +37,7 @@ jobs: API_KEY: ${{ secrets.API_KEY }} - name: SonarQube + if: matrix.java == '17' run: ./gradlew sonarqube env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index cd4a657..cd8c7d1 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -1,8 +1,6 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.manager.RequestQueueManager; -import io.goodforgod.api.etherscan.manager.impl.SemaphoreRequestQueueManager; -import java.time.Duration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -18,14 +16,11 @@ public class ApiRunner extends Assertions { static { final String key = System.getenv("API_KEY"); + final RequestQueueManager queueManager = RequestQueueManager.DEFAULT; + apiKey = (key == null || key.isEmpty()) ? DEFAULT_KEY : key; - - final RequestQueueManager queueManager = (DEFAULT_KEY.equals(apiKey)) - ? RequestQueueManager.DEFAULT - : new SemaphoreRequestQueueManager(1, Duration.ofMillis(1200L), Duration.ofMillis(1200L), 0); - api = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) .build(); apiKovan = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.KOVAN).withQueue(queueManager) From 9b366c3b58dc6470b841a9b2b91c21291f2ebc48 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 03:07:14 +0300 Subject: [PATCH 23/67] [2.0.0-SNAPSHOT] EthNetworks cleanup --- .../goodforgod/api/etherscan/EthNetworks.java | 5 +--- .../goodforgod/api/etherscan/ApiRunner.java | 24 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java b/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java index 4dbe138..9e18508 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthNetworks.java @@ -10,11 +10,8 @@ public enum EthNetworks implements EthNetwork { MAINNET("api", "io"), - ROPSTEN("api-ropsten", "io"), - KOVAN("api-kovan", "io"), - TOBALABA("api-tobalaba", "com"), GORLI("api-goerli", "io"), - RINKEBY("api-rinkeby", "io"); + SEPOLIA("api-sepolia", "io"); private final URI domain; diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index cd8c7d1..7a5ef52 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -9,9 +9,6 @@ public class ApiRunner extends Assertions { private static final String DEFAULT_KEY = "YourApiKeyToken"; private static final EtherScanAPI api; - private static final EtherScanAPI apiRopsten; - private static final EtherScanAPI apiRinkeby; - private static final EtherScanAPI apiKovan; private static final String apiKey; static { @@ -23,12 +20,6 @@ public class ApiRunner extends Assertions { : key; api = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) .build(); - apiKovan = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.KOVAN).withQueue(queueManager) - .build(); - apiRopsten = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.ROPSTEN).withQueue(queueManager) - .build(); - apiRinkeby = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.RINKEBY).withQueue(queueManager) - .build(); } public static String getApiKey() { @@ -39,23 +30,8 @@ public static EtherScanAPI getApi() { return api; } - public static EtherScanAPI getApiRopsten() { - return apiRopsten; - } - - public static EtherScanAPI getApiRinkeby() { - return apiRinkeby; - } - - public static EtherScanAPI getApiKovan() { - return apiKovan; - } - @AfterAll public static void cleanup() throws Exception { api.close(); - apiRopsten.close(); - apiRinkeby.close(); - apiKovan.close(); } } From bf9f02bad2328a37859fe52f4e58b95b210c0d76 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 03:29:43 +0300 Subject: [PATCH 24/67] [2.0.0-SNAPSHOT] SemaphoreRequestQueueManager improved --- .github/workflows/gradle.yml | 9 +++++- .../manager/RequestQueueManager.java | 7 ++-- .../impl/SemaphoreRequestQueueManager.java | 24 +++++++------- .../goodforgod/api/etherscan/ApiRunner.java | 32 ++++++++++++------- .../api/etherscan/EtherScanAPITests.java | 11 ++++--- ...Test.java => AccountBalanceListTests.java} | 2 +- ...anceTest.java => AccountBalanceTests.java} | 2 +- ...Test.java => AccountMinedBlocksTests.java} | 2 +- ...est.java => AccountTokenBalanceTests.java} | 2 +- ...rc20Test.java => AccountTxErc20Tests.java} | 2 +- ...java => AccountTxInternalByHashTests.java} | 2 +- ...lTest.java => AccountTxInternalTests.java} | 2 +- ...st.java => AccountTxRc1155TokenTests.java} | 2 +- ...est.java => AccountTxRc721TokenTests.java} | 2 +- ...countTxsTest.java => AccountTxsTests.java} | 2 +- .../{BlockApiTest.java => BlockApiTests.java} | 2 +- ...ractApiTest.java => ContractApiTests.java} | 2 +- ...erApiTest.java => GasTrackerApiTests.java} | 2 +- ...derTest.java => LogQueryBuilderTests.java} | 2 +- .../{LogsApiTest.java => LogsApiTests.java} | 2 +- ...=> SemaphoreRequestQueueManagerTests.java} | 15 ++------- .../etherscan/model/ModelBuilderTests.java | 7 ++++ ...ckApiTest.java => ProxyBlockApiTests.java} | 4 +-- ...est.java => ProxyBlockLastNoApiTests.java} | 2 +- ...Test.java => ProxyBlockUncleApiTests.java} | 2 +- ...allApiTest.java => ProxyCallApiTests.java} | 2 +- ...odeApiTest.java => ProxyCodeApiTests.java} | 2 +- ...yGasApiTest.java => ProxyGasApiTests.java} | 2 +- ...ApiTest.java => ProxyStorageApiTests.java} | 2 +- ...oxyTxApiTest.java => ProxyTxApiTests.java} | 2 +- ...ApiTest.java => ProxyTxCountApiTests.java} | 2 +- ...iTest.java => ProxyTxReceiptApiTests.java} | 2 +- ...iTest.java => ProxyTxSendRawApiTests.java} | 2 +- ...iTest.java => StatisticPriceApiTests.java} | 2 +- ...Test.java => StatisticSupplyApiTests.java} | 2 +- ...java => StatisticTokenSupplyApiTests.java} | 2 +- ...Test.java => TransactionExecApiTests.java} | 2 +- ...t.java => TransactionReceiptApiTests.java} | 2 +- 38 files changed, 89 insertions(+), 80 deletions(-) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountBalanceListTest.java => AccountBalanceListTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountBalanceTest.java => AccountBalanceTests.java} (96%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountMinedBlocksTest.java => AccountMinedBlocksTests.java} (96%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTokenBalanceTest.java => AccountTokenBalanceTests.java} (97%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxErc20Test.java => AccountTxErc20Tests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxInternalByHashTest.java => AccountTxInternalByHashTests.java} (97%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxInternalTest.java => AccountTxInternalTests.java} (97%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxRc1155TokenTest.java => AccountTxRc1155TokenTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxRc721TokenTest.java => AccountTxRc721TokenTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/account/{AccountTxsTest.java => AccountTxsTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/block/{BlockApiTest.java => BlockApiTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/contract/{ContractApiTest.java => ContractApiTests.java} (96%) rename src/test/java/io/goodforgod/api/etherscan/gastracker/{GasTrackerApiTest.java => GasTrackerApiTests.java} (95%) rename src/test/java/io/goodforgod/api/etherscan/logs/{LogQueryBuilderTest.java => LogQueryBuilderTests.java} (99%) rename src/test/java/io/goodforgod/api/etherscan/logs/{LogsApiTest.java => LogsApiTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/manager/{SemaphoreRequestQueueManagerTest.java => SemaphoreRequestQueueManagerTests.java} (70%) create mode 100644 src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyBlockApiTest.java => ProxyBlockApiTests.java} (97%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyBlockLastNoApiTest.java => ProxyBlockLastNoApiTests.java} (85%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyBlockUncleApiTest.java => ProxyBlockUncleApiTests.java} (94%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyCallApiTest.java => ProxyCallApiTests.java} (97%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyCodeApiTest.java => ProxyCodeApiTests.java} (95%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyGasApiTest.java => ProxyGasApiTests.java} (96%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyStorageApiTest.java => ProxyStorageApiTests.java} (95%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyTxApiTest.java => ProxyTxApiTests.java} (98%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyTxCountApiTest.java => ProxyTxCountApiTests.java} (96%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyTxReceiptApiTest.java => ProxyTxReceiptApiTests.java} (97%) rename src/test/java/io/goodforgod/api/etherscan/proxy/{ProxyTxSendRawApiTest.java => ProxyTxSendRawApiTests.java} (95%) rename src/test/java/io/goodforgod/api/etherscan/statistic/{StatisticPriceApiTest.java => StatisticPriceApiTests.java} (93%) rename src/test/java/io/goodforgod/api/etherscan/statistic/{StatisticSupplyApiTest.java => StatisticSupplyApiTests.java} (93%) rename src/test/java/io/goodforgod/api/etherscan/statistic/{StatisticTokenSupplyApiTest.java => StatisticTokenSupplyApiTests.java} (94%) rename src/test/java/io/goodforgod/api/etherscan/transaction/{TransactionExecApiTest.java => TransactionExecApiTests.java} (96%) rename src/test/java/io/goodforgod/api/etherscan/transaction/{TransactionReceiptApiTest.java => TransactionReceiptApiTests.java} (95%) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b4c0bb4..3eb55f0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -32,9 +32,16 @@ jobs: run: ./gradlew spotlessCheck - name: Test + if: matrix.java == '11' run: ./gradlew test jacocoTestReport env: - API_KEY: ${{ secrets.API_KEY }} + API_KEY: ${{ secrets.ETHERSCAN_API_KEY_1 }} + + - name: Test + if: matrix.java == '17' + run: ./gradlew test jacocoTestReport + env: + API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - name: SonarQube if: matrix.java == '17' 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 d568601..6b2740a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -12,11 +12,8 @@ */ public interface RequestQueueManager extends AutoCloseable { - RequestQueueManager DEFAULT = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5050L), - Duration.ofMillis(5050L), 0); - - RequestQueueManager PERSONAL = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1050L), - Duration.ofMillis(1050L), 5); + RequestQueueManager DEFAULT = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5005L)); + RequestQueueManager PERSONAL = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1005L)); /** * Waits in queue for chance to take turn diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java index a6e3037..2a3483c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java @@ -21,18 +21,10 @@ public final class SemaphoreRequestQueueManager implements RequestQueueManager, private final long queueResetTimeInMillis; public SemaphoreRequestQueueManager(int size, Duration resetIn) { - this(size, resetIn, resetIn); - } - - public SemaphoreRequestQueueManager(int size, Duration resetIn, Duration delayIn) { - this(size, resetIn, delayIn, size); - } - - public SemaphoreRequestQueueManager(int size, Duration queueResetTimeIn, Duration delayIn, int initialSize) { - this.semaphore = new Semaphore(initialSize); - this.queueResetTimeInMillis = queueResetTimeIn.toMillis(); - this.executorService.scheduleAtFixedRate(releaseLocks(size + 1), - delayIn.toMillis(), queueResetTimeInMillis, TimeUnit.MILLISECONDS); + this.semaphore = new Semaphore(0); + this.queueResetTimeInMillis = resetIn.toMillis(); + this.executorService.scheduleAtFixedRate(releaseLocks(size), + resetIn.toMillis(), queueResetTimeInMillis, TimeUnit.MILLISECONDS); } @SuppressWarnings("java:S899") @@ -46,7 +38,13 @@ public void takeTurn() { } private Runnable releaseLocks(int toRelease) { - return () -> semaphore.release(toRelease); + return () -> { + int availablePermits = semaphore.availablePermits(); + int neededPermits = toRelease - availablePermits; + if (neededPermits > 0) { + semaphore.release(neededPermits); + } + }; } @Override diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index 7a5ef52..d63bc73 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -8,30 +9,37 @@ public class ApiRunner extends Assertions { private static final String DEFAULT_KEY = "YourApiKeyToken"; - private static final EtherScanAPI api; - private static final String apiKey; + private static final String API_KEY; + private static final EtherScanAPI API; static { - final String key = System.getenv("API_KEY"); - final RequestQueueManager queueManager = RequestQueueManager.DEFAULT; - - apiKey = (key == null || key.isEmpty()) - ? DEFAULT_KEY - : key; - api = EtherScanAPI.builder().withApiKey(ApiRunner.apiKey).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) + API_KEY = System.getenv().entrySet().stream() + .filter(e -> e.getKey().startsWith("ETHERSCAN_API_KEY")) + .map(Map.Entry::getValue) + .findFirst() + .orElse(DEFAULT_KEY); + + final RequestQueueManager queueManager = (DEFAULT_KEY.equals(API_KEY)) + ? RequestQueueManager.DEFAULT + : RequestQueueManager.PERSONAL; + + API = EtherScanAPI.builder() + .withApiKey(ApiRunner.API_KEY) + .withNetwork(EthNetworks.MAINNET) + .withQueue(queueManager) .build(); } public static String getApiKey() { - return apiKey; + return API_KEY; } public static EtherScanAPI getApi() { - return api; + return API; } @AfterAll public static void cleanup() throws Exception { - api.close(); + API.close(); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java index 22c58d6..c50b03a 100644 --- a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -5,6 +5,7 @@ import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; import io.goodforgod.api.etherscan.model.Balance; +import java.net.URI; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -16,7 +17,7 @@ */ class EtherScanAPITests extends ApiRunner { - private final EthNetworks network = EthNetworks.KOVAN; + private final EthNetworks network = EthNetworks.SEPOLIA; @Test void validKey() { @@ -46,14 +47,12 @@ void noTimeoutOnRead() { @Test void noTimeoutOnReadGroli() { - Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300)); Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @Test void noTimeoutOnReadTobalala() { - Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(30000)); Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @@ -68,8 +67,12 @@ void noTimeoutUnlimitedAwait() { void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300), Duration.ofMillis(300)); - EtherScanAPI api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.KOVAN).withHttpClient(supplier) + EtherScanAPI api = EtherScanAPI.builder() + .withApiKey(getApiKey()) + .withNetwork(() -> URI.create("https://api-unknown.etherscan.io/api")) + .withHttpClient(supplier) .build(); + assertThrows(EtherScanConnectionException.class, () -> api.account().blocksMined("0x0010f94b296A852aAac52EA6c5Ac72e03afD032D")); } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java index cd3dac9..f611b62 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java @@ -13,7 +13,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountBalanceListTest extends ApiRunner { +class AccountBalanceListTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java similarity index 96% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java index 4c06c7c..f22a724 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountBalanceTest extends ApiRunner { +class AccountBalanceTests extends ApiRunner { private final EtherScanAPI api = getApi(); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTests.java similarity index 96% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTests.java index 13d5075..3e19e96 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountMinedBlocksTests.java @@ -11,7 +11,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountMinedBlocksTest extends ApiRunner { +class AccountMinedBlocksTests extends ApiRunner { private final EtherScanAPI api = getApi(); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java similarity index 97% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java index 4df75f3..4a7d921 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTokenBalanceTest extends ApiRunner { +class AccountTokenBalanceTests extends ApiRunner { private final EtherScanAPI api = getApi(); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Test.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Test.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java index 0a94289..928b2e3 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Test.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTxErc20Test extends ApiRunner { +class AccountTxErc20Tests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTests.java similarity index 97% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTests.java index 13036bc..eb06b60 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalByHashTests.java @@ -12,7 +12,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTxInternalByHashTest extends ApiRunner { +class AccountTxInternalByHashTests extends ApiRunner { private final EtherScanAPI api = getApi(); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTests.java similarity index 97% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTests.java index 6fb92b4..1d4220d 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxInternalTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTxInternalTest extends ApiRunner { +class AccountTxInternalTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java index ce3a680..0430dc8 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 14.05.2023 */ -class AccountTxRc1155TokenTest extends ApiRunner { +class AccountTxRc1155TokenTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java index b7988db..9a5a322 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java @@ -10,7 +10,7 @@ * @author NGuggs * @since 11.28.2021 */ -class AccountTxRc721TokenTest extends ApiRunner { +class AccountTxRc721TokenTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTest.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTest.java rename to src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java index a2cffd1..3ee8ad1 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class AccountTxsTest extends ApiRunner { +class AccountTxsTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTest.java b/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/block/BlockApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java index 8e3b529..3e84ab7 100644 --- a/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class BlockApiTest extends ApiRunner { +class BlockApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTest.java b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java similarity index 96% rename from src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java index 62aa7da..4fd0fdb 100644 --- a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ContractApiTest extends ApiRunner { +class ContractApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java similarity index 95% rename from src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java index ec904a6..1d92eb4 100644 --- a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 14.05.2023 */ -class GasTrackerApiTest extends ApiRunner { +class GasTrackerApiTests extends ApiRunner { @Test void estimate() { diff --git a/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTest.java b/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java similarity index 99% rename from src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTest.java rename to src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java index 752c34c..339f07e 100644 --- a/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class LogQueryBuilderTest extends ApiRunner { +class LogQueryBuilderTests extends ApiRunner { @Test void singleCorrect() { diff --git a/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java b/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTests.java index 7d9fe64..0197c5f 100644 --- a/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/logs/LogsApiTests.java @@ -14,7 +14,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class LogsApiTest extends ApiRunner { +class LogsApiTests extends ApiRunner { static Stream<Arguments> source() { LogQuery single = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") diff --git a/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java b/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTests.java similarity index 70% rename from src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java rename to src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTests.java index 0d6daf6..183c442 100644 --- a/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/manager/SemaphoreRequestQueueManagerTests.java @@ -11,7 +11,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class SemaphoreRequestQueueManagerTest extends ApiRunner { +class SemaphoreRequestQueueManagerTests extends ApiRunner { @Test void fakeManager() { @@ -37,20 +37,9 @@ void queueManager() { @Test @Timeout(4500) void queueManagerWithDelay() { - RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(2), - Duration.ofSeconds(2)); + RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(2)); requestQueueManager.takeTurn(); requestQueueManager.takeTurn(); assertNotNull(requestQueueManager); } - - @Test - void queueManagerTimeout() { - RequestQueueManager requestQueueManager = new SemaphoreRequestQueueManager(1, Duration.ofSeconds(3)); - requestQueueManager.takeTurn(); - long start = System.currentTimeMillis(); - requestQueueManager.takeTurn(); - long end = System.currentTimeMillis(); - assertEquals(3, Math.round((double) (end - start) / 1000)); - } } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java new file mode 100644 index 0000000..6f5fc22 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -0,0 +1,7 @@ +package io.goodforgod.api.etherscan.model; + +/** + * @author Anton Kurako (GoodforGod) + * @since 14.05.2023 + */ +class ModelBuilderTests {} diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java similarity index 97% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java index e31aab8..44d786d 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java @@ -12,11 +12,11 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyBlockApiTest extends ApiRunner { +class ProxyBlockApiTests extends ApiRunner { private final EtherScanAPI api; - ProxyBlockApiTest() { + ProxyBlockApiTests() { final RequestQueueManager queueManager = RequestQueueManager.DEFAULT; this.api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) .build(); diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTests.java similarity index 85% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTests.java index c4f5e31..568d9ae 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockLastNoApiTests.java @@ -7,7 +7,7 @@ * @author GoodforGod * @since 13.11.2018 */ -class ProxyBlockLastNoApiTest extends ApiRunner { +class ProxyBlockLastNoApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTests.java similarity index 94% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTests.java index c575072..01725c5 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockUncleApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 13.11.2018 */ -class ProxyBlockUncleApiTest extends ApiRunner { +class ProxyBlockUncleApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTests.java similarity index 97% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTests.java index 67e7682..d5168c6 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCallApiTests.java @@ -11,7 +11,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyCallApiTest extends ApiRunner { +class ProxyCallApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTests.java similarity index 95% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTests.java index c9dab25..1e3c696 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyCodeApiTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyCodeApiTest extends ApiRunner { +class ProxyCodeApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java similarity index 96% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java index 1b40705..0ab2a77 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyGasApiTest extends ApiRunner { +class ProxyGasApiTests extends ApiRunner { @Test void correctPrice() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTests.java similarity index 95% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTests.java index 2580e22..3c6d221 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyStorageApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyStorageApiTest extends ApiRunner { +class ProxyStorageApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java similarity index 98% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java index d6790bd..6c7dbb7 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyTxApiTest extends ApiRunner { +class ProxyTxApiTests extends ApiRunner { @Test void correctByHash() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTests.java similarity index 96% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTests.java index a2327da..95ed859 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxCountApiTests.java @@ -8,7 +8,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyTxCountApiTest extends ApiRunner { +class ProxyTxCountApiTests extends ApiRunner { @Test void correctSended() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java similarity index 97% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java index ba6370c..7a6624c 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class ProxyTxReceiptApiTest extends ApiRunner { +class ProxyTxReceiptApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTest.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTests.java similarity index 95% rename from src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTests.java index 9f69060..3910bf8 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxSendRawApiTests.java @@ -11,7 +11,7 @@ * @since 03.11.2018 */ // TODO contact etherscan and ask about method behavior -class ProxyTxSendRawApiTest extends ApiRunner { +class ProxyTxSendRawApiTests extends ApiRunner { void correct() { Optional<String> sendRaw = getApi().proxy() diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTest.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java similarity index 93% rename from src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java index eb43b6e..37e0ec0 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java @@ -8,7 +8,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class StatisticPriceApiTest extends ApiRunner { +class StatisticPriceApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTest.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java similarity index 93% rename from src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java index c1e8e58..fa79028 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class StatisticSupplyApiTest extends ApiRunner { +class StatisticSupplyApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTest.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java similarity index 94% rename from src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java index 84c086a..07f8eca 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class StatisticTokenSupplyApiTest extends ApiRunner { +class StatisticTokenSupplyApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTest.java b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java similarity index 96% rename from src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java index a2a5860..eb595c3 100644 --- a/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class TransactionExecApiTest extends ApiRunner { +class TransactionExecApiTests extends ApiRunner { @Test void correct() { diff --git a/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTest.java b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTests.java similarity index 95% rename from src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTest.java rename to src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTests.java index 83ca5af..8ff0817 100644 --- a/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTest.java +++ b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionReceiptApiTests.java @@ -9,7 +9,7 @@ * @author GoodforGod * @since 03.11.2018 */ -class TransactionReceiptApiTest extends ApiRunner { +class TransactionReceiptApiTests extends ApiRunner { @Test void correct() { From 55788d505b74d71978001d9fae8f929b0a7ec8ab Mon Sep 17 00:00:00 2001 From: guggio <sebastian.guggisberg@gmail.com> Date: Sun, 17 Jul 2022 12:10:23 +0300 Subject: [PATCH 25/67] [2.0.0-SNAPSHOT] Refactoring of token transfers - Inclusion of tokenID in Erc721 transfers - Support for Erc1155 transfers (cherry picked from commit ca4b7d5eca7d9d08688af3e38c6702f77c149d2d) --- .../io/api/etherscan/model/BaseTxToken.java | 59 +++++++++++++++++++ .../io/api/etherscan/model/TxErc1155.java | 25 ++++++++ .../java/io/api/etherscan/model/TxErc20.java | 19 ++++++ .../java/io/api/etherscan/model/TxErc721.java | 25 ++++++++ .../model/response/TxErc1155ResponseTO.java | 11 ---- .../model/response/TxErc20ResponseTO.java | 11 ---- .../model/response/TxErc721ResponseTO.java | 11 ---- 7 files changed, 128 insertions(+), 33 deletions(-) create mode 100644 src/main/java/io/api/etherscan/model/BaseTxToken.java create mode 100644 src/main/java/io/api/etherscan/model/TxErc1155.java create mode 100644 src/main/java/io/api/etherscan/model/TxErc20.java create mode 100644 src/main/java/io/api/etherscan/model/TxErc721.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java diff --git a/src/main/java/io/api/etherscan/model/BaseTxToken.java b/src/main/java/io/api/etherscan/model/BaseTxToken.java new file mode 100644 index 0000000..e1965ce --- /dev/null +++ b/src/main/java/io/api/etherscan/model/BaseTxToken.java @@ -0,0 +1,59 @@ +package io.api.etherscan.model; + +public abstract class BaseTxToken extends BaseTx { + + private long nonce; + private String blockHash; + private String tokenName; + private String tokenSymbol; + private int transactionIndex; + private long gasPrice; + private long cumulativeGasUsed; + private long confirmations; + + public long getNonce() { + return nonce; + } + + public String getBlockHash() { + return blockHash; + } + + public String getTokenName() { + return tokenName; + } + + public String getTokenSymbol() { + return tokenSymbol; + } + + public int getTransactionIndex() { + return transactionIndex; + } + + public long getGasPrice() { + return gasPrice; + } + + public long getCumulativeGasUsed() { + return cumulativeGasUsed; + } + + public long getConfirmations() { + return confirmations; + } + + @Override + public String toString() { + return "BaseTxToken{" + + "nonce=" + nonce + + ", blockHash='" + blockHash + '\'' + + ", tokenName='" + tokenName + '\'' + + ", tokenSymbol='" + tokenSymbol + '\'' + + ", transactionIndex=" + transactionIndex + + ", gasPrice=" + gasPrice + + ", cumulativeGasUsed=" + cumulativeGasUsed + + ", confirmations=" + confirmations + + '}' + super.toString(); + } +} diff --git a/src/main/java/io/api/etherscan/model/TxErc1155.java b/src/main/java/io/api/etherscan/model/TxErc1155.java new file mode 100644 index 0000000..b926b1b --- /dev/null +++ b/src/main/java/io/api/etherscan/model/TxErc1155.java @@ -0,0 +1,25 @@ +package io.api.etherscan.model; + +import java.math.BigInteger; + +public class TxErc1155 extends BaseTxToken { + + private BigInteger tokenID; + private BigInteger tokenValue; + + public BigInteger getTokenID() { + return tokenID; + } + + public BigInteger getTokenValue() { + return tokenValue; + } + + @Override + public String toString() { + return "TxErc1155{" + + "tokenID=" + tokenID + + ", tokenValue=" + tokenValue + + '}' + super.toString(); + } +} diff --git a/src/main/java/io/api/etherscan/model/TxErc20.java b/src/main/java/io/api/etherscan/model/TxErc20.java new file mode 100644 index 0000000..9c4e65e --- /dev/null +++ b/src/main/java/io/api/etherscan/model/TxErc20.java @@ -0,0 +1,19 @@ +package io.api.etherscan.model; + +import java.math.BigInteger; + +public class TxErc20 extends BaseTxToken { + + private BigInteger tokenDecimal; + + public BigInteger getTokenDecimal() { + return tokenDecimal; + } + + @Override + public String toString() { + return "TxErc20{" + + "tokenDecimal=" + tokenDecimal + + '}' + super.toString(); + } +} diff --git a/src/main/java/io/api/etherscan/model/TxErc721.java b/src/main/java/io/api/etherscan/model/TxErc721.java new file mode 100644 index 0000000..9f70c3d --- /dev/null +++ b/src/main/java/io/api/etherscan/model/TxErc721.java @@ -0,0 +1,25 @@ +package io.api.etherscan.model; + +import java.math.BigInteger; + +public class TxErc721 extends BaseTxToken { + + private BigInteger tokenID; + private BigInteger tokenDecimal; + + public BigInteger getTokenID() { + return tokenID; + } + + public BigInteger getTokenDecimal() { + return tokenDecimal; + } + + @Override + public String toString() { + return "TxErc721{" + + "tokenID=" + tokenID + + ", tokenDecimal=" + tokenDecimal + + '}' + super.toString(); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java deleted file mode 100644 index 994d2cd..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.goodforgod.api.etherscan.model.response; - -import io.goodforgod.api.etherscan.model.TxErc1155; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class TxErc1155ResponseTO extends BaseListResponseTO<TxErc1155> { - -} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java deleted file mode 100644 index d5d3f6e..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.goodforgod.api.etherscan.model.response; - -import io.goodforgod.api.etherscan.model.TxErc20; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class TxErc20ResponseTO extends BaseListResponseTO<TxErc20> { - -} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java deleted file mode 100644 index 2a9403f..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.goodforgod.api.etherscan.model.response; - -import io.goodforgod.api.etherscan.model.TxErc721; - -/** - * @author GoodforGod - * @since 29.10.2018 - */ -public class TxErc721ResponseTO extends BaseListResponseTO<TxErc721> { - -} From 8ec7d9349b6cbb07b97d21174216dea87a2111f4 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 03:33:45 +0300 Subject: [PATCH 26/67] [2.0.0-SNAPSHOT] Refactoring of token transfers - Inclusion of tokenID in Erc721 transfers - Support for Erc1155 transfers (cherry picked from commit ca4b7d5eca7d9d08688af3e38c6702f77c149d2d) --- .../io/api/etherscan/model/BaseTxToken.java | 59 ------------------- .../io/api/etherscan/model/TxErc1155.java | 25 -------- .../java/io/api/etherscan/model/TxErc20.java | 19 ------ .../java/io/api/etherscan/model/TxErc721.java | 25 -------- 4 files changed, 128 deletions(-) delete mode 100644 src/main/java/io/api/etherscan/model/BaseTxToken.java delete mode 100644 src/main/java/io/api/etherscan/model/TxErc1155.java delete mode 100644 src/main/java/io/api/etherscan/model/TxErc20.java delete mode 100644 src/main/java/io/api/etherscan/model/TxErc721.java diff --git a/src/main/java/io/api/etherscan/model/BaseTxToken.java b/src/main/java/io/api/etherscan/model/BaseTxToken.java deleted file mode 100644 index e1965ce..0000000 --- a/src/main/java/io/api/etherscan/model/BaseTxToken.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.api.etherscan.model; - -public abstract class BaseTxToken extends BaseTx { - - private long nonce; - private String blockHash; - private String tokenName; - private String tokenSymbol; - private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; - private long confirmations; - - public long getNonce() { - return nonce; - } - - public String getBlockHash() { - return blockHash; - } - - public String getTokenName() { - return tokenName; - } - - public String getTokenSymbol() { - return tokenSymbol; - } - - public int getTransactionIndex() { - return transactionIndex; - } - - public long getGasPrice() { - return gasPrice; - } - - public long getCumulativeGasUsed() { - return cumulativeGasUsed; - } - - public long getConfirmations() { - return confirmations; - } - - @Override - public String toString() { - return "BaseTxToken{" + - "nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + - ", tokenName='" + tokenName + '\'' + - ", tokenSymbol='" + tokenSymbol + '\'' + - ", transactionIndex=" + transactionIndex + - ", gasPrice=" + gasPrice + - ", cumulativeGasUsed=" + cumulativeGasUsed + - ", confirmations=" + confirmations + - '}' + super.toString(); - } -} diff --git a/src/main/java/io/api/etherscan/model/TxErc1155.java b/src/main/java/io/api/etherscan/model/TxErc1155.java deleted file mode 100644 index b926b1b..0000000 --- a/src/main/java/io/api/etherscan/model/TxErc1155.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.api.etherscan.model; - -import java.math.BigInteger; - -public class TxErc1155 extends BaseTxToken { - - private BigInteger tokenID; - private BigInteger tokenValue; - - public BigInteger getTokenID() { - return tokenID; - } - - public BigInteger getTokenValue() { - return tokenValue; - } - - @Override - public String toString() { - return "TxErc1155{" + - "tokenID=" + tokenID + - ", tokenValue=" + tokenValue + - '}' + super.toString(); - } -} diff --git a/src/main/java/io/api/etherscan/model/TxErc20.java b/src/main/java/io/api/etherscan/model/TxErc20.java deleted file mode 100644 index 9c4e65e..0000000 --- a/src/main/java/io/api/etherscan/model/TxErc20.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.api.etherscan.model; - -import java.math.BigInteger; - -public class TxErc20 extends BaseTxToken { - - private BigInteger tokenDecimal; - - public BigInteger getTokenDecimal() { - return tokenDecimal; - } - - @Override - public String toString() { - return "TxErc20{" + - "tokenDecimal=" + tokenDecimal + - '}' + super.toString(); - } -} diff --git a/src/main/java/io/api/etherscan/model/TxErc721.java b/src/main/java/io/api/etherscan/model/TxErc721.java deleted file mode 100644 index 9f70c3d..0000000 --- a/src/main/java/io/api/etherscan/model/TxErc721.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.api.etherscan.model; - -import java.math.BigInteger; - -public class TxErc721 extends BaseTxToken { - - private BigInteger tokenID; - private BigInteger tokenDecimal; - - public BigInteger getTokenID() { - return tokenID; - } - - public BigInteger getTokenDecimal() { - return tokenDecimal; - } - - @Override - public String toString() { - return "TxErc721{" + - "tokenID=" + tokenID + - ", tokenDecimal=" + tokenDecimal + - '}' + super.toString(); - } -} From a9dd8e0d2d84b64fef15980715c15220f3cd4f9b Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 03:45:36 +0300 Subject: [PATCH 27/67] [2.0.0-SNAPSHOT] Javadoc improvements --- .../api/etherscan/EthScanAPIBuilder.java | 5 ++--- .../api/etherscan/http/impl/UrlEthHttpClient.java | 5 +++-- .../etherscan/manager/RequestQueueManager.java | 15 +++++++++++++-- .../io/goodforgod/api/etherscan/ApiRunner.java | 4 ++-- .../api/etherscan/proxy/ProxyBlockApiTests.java | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 501bdb1..c9c1102 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -5,7 +5,6 @@ import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; -import io.goodforgod.api.etherscan.manager.impl.FakeRequestQueueManager; import io.goodforgod.api.etherscan.util.BasicUtils; import io.goodforgod.gson.configuration.GsonConfiguration; import java.util.function.Supplier; @@ -24,7 +23,7 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { private String apiKey = DEFAULT_KEY; private EthNetwork ethNetwork = EthNetworks.MAINNET; - private RequestQueueManager queueManager = RequestQueueManager.DEFAULT; + private RequestQueueManager queueManager = RequestQueueManager.ANONYMOUS; private Supplier<EthHttpClient> ethHttpClientSupplier = DEFAULT_SUPPLIER; private Supplier<Converter> converterSupplier = () -> new Converter() { @@ -42,7 +41,7 @@ public EtherScanAPI.Builder withApiKey(@NotNull String apiKey) { this.apiKey = apiKey; if (!DEFAULT_KEY.equals(apiKey)) { - queueManager = new FakeRequestQueueManager(); + queueManager = RequestQueueManager.UNLIMITED; } return this; } diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java index 4178be7..57c970b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; +import org.jetbrains.annotations.NotNull; /** * Http client implementation @@ -82,7 +83,7 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep } @Override - public String get(URI uri) { + public @NotNull String get(@NotNull URI uri) { try { final HttpURLConnection connection = buildConnection(uri, "GET"); final int status = connection.getResponseCode(); @@ -105,7 +106,7 @@ public String get(URI uri) { } @Override - public String post(URI uri, byte[] body) { + public @NotNull String post(@NotNull URI uri, byte[] body) { try { final HttpURLConnection connection = buildConnection(uri, "POST"); final int contentLength = body.length; 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 6b2740a..f19603f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -1,5 +1,6 @@ package io.goodforgod.api.etherscan.manager; +import io.goodforgod.api.etherscan.manager.impl.FakeRequestQueueManager; import io.goodforgod.api.etherscan.manager.impl.SemaphoreRequestQueueManager; import java.time.Duration; @@ -12,8 +13,18 @@ */ public interface RequestQueueManager extends AutoCloseable { - RequestQueueManager DEFAULT = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5005L)); - RequestQueueManager PERSONAL = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1005L)); + /** + * Is used by default when no API KEY is provided + */ + RequestQueueManager ANONYMOUS = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5005L)); + + /** + * Is available for all registered free API KEYs + * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> + */ + RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1005L)); + + RequestQueueManager UNLIMITED = new FakeRequestQueueManager(); /** * Waits in queue for chance to take turn diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index d63bc73..d28a8e0 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -20,8 +20,8 @@ public class ApiRunner extends Assertions { .orElse(DEFAULT_KEY); final RequestQueueManager queueManager = (DEFAULT_KEY.equals(API_KEY)) - ? RequestQueueManager.DEFAULT - : RequestQueueManager.PERSONAL; + ? RequestQueueManager.ANONYMOUS + : RequestQueueManager.FREE_PLAN; API = EtherScanAPI.builder() .withApiKey(ApiRunner.API_KEY) diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java index 44d786d..10dc6fd 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java @@ -17,7 +17,7 @@ class ProxyBlockApiTests extends ApiRunner { private final EtherScanAPI api; ProxyBlockApiTests() { - final RequestQueueManager queueManager = RequestQueueManager.DEFAULT; + final RequestQueueManager queueManager = RequestQueueManager.ANONYMOUS; this.api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) .build(); } From 225b21165f9bfa6defc4566a2f858f6f010498a0 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 12:47:36 +0300 Subject: [PATCH 28/67] [2.0.0-SNAPSHOT] Tx Responses restored GasOracle refactored and improved ModelBuilderTests added --- .../goodforgod/api/etherscan/model/Block.java | 2 + .../api/etherscan/model/BlockUncle.java | 6 + .../api/etherscan/model/GasOracle.java | 72 +++-- .../goodforgod/api/etherscan/model/Log.java | 2 + .../goodforgod/api/etherscan/model/Price.java | 10 +- .../api/etherscan/model/Status.java | 2 + .../io/goodforgod/api/etherscan/model/Tx.java | 2 + .../api/etherscan/model/TxErc1155.java | 18 +- .../api/etherscan/model/TxErc20.java | 18 +- .../api/etherscan/model/TxErc721.java | 18 +- .../api/etherscan/model/TxInternal.java | 4 +- .../api/etherscan/model/proxy/BlockProxy.java | 2 + .../etherscan/model/proxy/ReceiptProxy.java | 2 + .../api/etherscan/model/proxy/TxProxy.java | 2 + .../model/response/TxErc1155ResponseTO.java | 11 + .../model/response/TxErc20ResponseTO.java | 11 + .../model/response/TxErc721ResponseTO.java | 11 + .../api/etherscan/block/BlockApiTests.java | 2 +- .../etherscan/model/ModelBuilderTests.java | 265 +++++++++++++++++- .../etherscan/proxy/ProxyBlockApiTests.java | 2 +- .../api/etherscan/proxy/ProxyTxApiTests.java | 2 +- .../proxy/ProxyTxReceiptApiTests.java | 2 +- .../statistic/StatisticPriceApiTests.java | 2 +- .../transaction/TransactionExecApiTests.java | 2 +- 24 files changed, 407 insertions(+), 63 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java 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 129ca39..d46fb44 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -18,6 +18,8 @@ public class Block { @Expose(deserialize = false, serialize = false) LocalDateTime _timeStamp; + protected Block() {} + // <editor-fold desc="Getter"> public long getBlockNumber() { return blockNumber; 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 5cf1a3e..02ddc28 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -18,6 +18,8 @@ public static class Uncle { private BigInteger blockreward; private int unclePosition; + private Uncle() {} + // <editor-fold desc="Getters"> public String getMiner() { return miner; @@ -113,6 +115,10 @@ public Uncle build() { private List<Uncle> uncles; private String uncleInclusionReward; + protected BlockUncle() { + super(); + } + // <editor-fold desc="Getters"> public boolean isEmpty() { return getBlockNumber() == 0 && getBlockReward() == null diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index 69fa6b5..67dd82a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -1,7 +1,11 @@ package io.goodforgod.api.etherscan.model; +import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * @author Abhay Gupta @@ -16,28 +20,32 @@ public class GasOracle { private Double suggestBaseFee; private String gasUsedRatio; + protected GasOracle() {} + public Long getLastBlock() { return LastBlock; } - public BigInteger getSafeGasPriceInWei() { - return BigInteger.valueOf(SafeGasPrice).multiply(BigInteger.TEN.pow(9)); + public Wei getSafeGasPriceInWei() { + return new Wei(BigInteger.valueOf(SafeGasPrice).multiply(BigInteger.TEN.pow(9))); } - public BigInteger getProposeGasPriceInWei() { - return BigInteger.valueOf(ProposeGasPrice).multiply(BigInteger.TEN.pow(9)); + public Wei getProposeGasPriceInWei() { + return new Wei(BigInteger.valueOf(ProposeGasPrice).multiply(BigInteger.TEN.pow(9))); } - public BigInteger getFastGasPriceInWei() { - return BigInteger.valueOf(FastGasPrice).multiply(BigInteger.TEN.pow(9)); + public Wei getFastGasPriceInWei() { + return new Wei(BigInteger.valueOf(FastGasPrice).multiply(BigInteger.TEN.pow(9))); } public Double getSuggestBaseFee() { return suggestBaseFee; } - public String getGasUsedRatio() { - return gasUsedRatio; + public List<BigDecimal> getGasUsedRatio() { + return Arrays.stream(gasUsedRatio.split(",")) + .map(BigDecimal::new) + .collect(Collectors.toList()); } @Override @@ -75,32 +83,32 @@ public static GasOracleBuilder builder() { public static final class GasOracleBuilder { - private Long LastBlock; - private Integer SafeGasPrice; - private Integer ProposeGasPrice; - private Integer FastGasPrice; + private Long lastBlock; + private Wei safeGasPrice; + private Wei proposeGasPrice; + private Wei fastGasPrice; private Double suggestBaseFee; - private String gasUsedRatio; + private List<BigDecimal> gasUsedRatio; private GasOracleBuilder() {} - public GasOracleBuilder withLastBlock(Long LastBlock) { - this.LastBlock = LastBlock; + public GasOracleBuilder withLastBlock(Long lastBlock) { + this.lastBlock = lastBlock; return this; } - public GasOracleBuilder withSafeGasPrice(Integer SafeGasPrice) { - this.SafeGasPrice = SafeGasPrice; + public GasOracleBuilder withSafeGasPrice(Wei safeGasPrice) { + this.safeGasPrice = safeGasPrice; return this; } - public GasOracleBuilder withProposeGasPrice(Integer ProposeGasPrice) { - this.ProposeGasPrice = ProposeGasPrice; + public GasOracleBuilder withProposeGasPrice(Wei proposeGasPrice) { + this.proposeGasPrice = proposeGasPrice; return this; } - public GasOracleBuilder withFastGasPrice(Integer FastGasPrice) { - this.FastGasPrice = FastGasPrice; + public GasOracleBuilder withFastGasPrice(Wei fastGasPrice) { + this.fastGasPrice = fastGasPrice; return this; } @@ -109,19 +117,29 @@ public GasOracleBuilder withSuggestBaseFee(Double suggestBaseFee) { return this; } - public GasOracleBuilder withGasUsedRatio(String gasUsedRatio) { + public GasOracleBuilder withGasUsedRatio(List<BigDecimal> gasUsedRatio) { this.gasUsedRatio = gasUsedRatio; return this; } public GasOracle build() { GasOracle gasOracle = new GasOracle(); - gasOracle.ProposeGasPrice = this.ProposeGasPrice; - gasOracle.LastBlock = this.LastBlock; + gasOracle.LastBlock = this.lastBlock; gasOracle.suggestBaseFee = this.suggestBaseFee; - gasOracle.SafeGasPrice = this.SafeGasPrice; - gasOracle.FastGasPrice = this.FastGasPrice; - gasOracle.gasUsedRatio = this.gasUsedRatio; + if (this.proposeGasPrice != null) { + gasOracle.ProposeGasPrice = this.proposeGasPrice.asGwei().intValue(); + } + if (this.safeGasPrice != null) { + gasOracle.SafeGasPrice = this.safeGasPrice.asGwei().intValue(); + } + if (this.fastGasPrice != null) { + gasOracle.FastGasPrice = this.fastGasPrice.asGwei().intValue(); + } + if (this.gasUsedRatio != null) { + gasOracle.gasUsedRatio = this.gasUsedRatio.stream() + .map(BigDecimal::toString) + .collect(Collectors.joining(", ")); + } return gasOracle; } } 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 bd03103..5ed840a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -37,6 +37,8 @@ public class Log { @Expose(deserialize = false, serialize = false) private Long _logIndex; + protected Log() {} + // <editor-fold desc="Getters"> public Long getBlockNumber() { if (_blockNumber == null && !BasicUtils.isEmpty(blockNumber)) { 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 9c72792..b24fc65 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -19,6 +19,8 @@ public class Price { @Expose(deserialize = false, serialize = false) private LocalDateTime _ethbtc_timestamp; + protected Price() {} + public double inUsd() { return ethusd; } @@ -101,22 +103,22 @@ public static final class PriceBuilder { private PriceBuilder() {} - public PriceBuilder withEthusd(double ethusd) { + public PriceBuilder withEthUsd(double ethusd) { this.ethusd = ethusd; return this; } - public PriceBuilder withEthbtc(double ethbtc) { + public PriceBuilder withEthBtc(double ethbtc) { this.ethbtc = ethbtc; return this; } - public PriceBuilder withEthusdTimestamp(LocalDateTime ethusdTimestamp) { + public PriceBuilder withEthUsdTimestamp(LocalDateTime ethusdTimestamp) { this.ethusdTimestamp = ethusdTimestamp; return this; } - public PriceBuilder withEthbtcTimestamp(LocalDateTime ethbtcTimestamp) { + public PriceBuilder withEthBtcTimestamp(LocalDateTime ethbtcTimestamp) { this.ethbtcTimestamp = ethbtcTimestamp; return this; } 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 8cdc704..eaf9b8a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Status.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Status.java @@ -16,6 +16,8 @@ public class Status { private int isError; private String errDescription; + protected Status() {} + public boolean haveError() { return isError == 1; } 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 65c24ba..7e09768 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -22,6 +22,8 @@ public class Tx extends BaseTx { private String isError; private String txreceipt_status; + protected Tx() {} + // <editor-fold desc="Getters"> public BigInteger getValue() { return value; 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 84e2d40..edf578f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -18,10 +18,12 @@ public class TxErc1155 extends BaseTx { private String tokenSymbol; private String tokenValue; private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; private long confirmations; + protected TxErc1155() {} + // <editor-fold desc="Getters"> public long getNonce() { return nonce; @@ -51,11 +53,11 @@ public int getTransactionIndex() { return transactionIndex; } - public long getGasPrice() { + public BigInteger getGasPrice() { return gasPrice; } - public long getCumulativeGasUsed() { + public BigInteger getCumulativeGasUsed() { return cumulativeGasUsed; } @@ -120,8 +122,8 @@ public static final class TxErc1155Builder { private String tokenSymbol; private String tokenValue; private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; private long confirmations; private TxErc1155Builder() {} @@ -206,12 +208,12 @@ public TxErc1155Builder withTransactionIndex(int transactionIndex) { return this; } - public TxErc1155Builder withGasPrice(long gasPrice) { + public TxErc1155Builder withGasPrice(BigInteger gasPrice) { this.gasPrice = gasPrice; return this; } - public TxErc1155Builder withCumulativeGasUsed(long cumulativeGasUsed) { + public TxErc1155Builder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } 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 f51b855..9342c8e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -18,10 +18,12 @@ public class TxErc20 extends BaseTx { private String tokenSymbol; private String tokenDecimal; private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; private long confirmations; + protected TxErc20() {} + // <editor-fold desc="Getters"> public long getNonce() { return nonce; @@ -51,11 +53,11 @@ public int getTransactionIndex() { return transactionIndex; } - public long getGasPrice() { + public BigInteger getGasPrice() { return gasPrice; } - public long getCumulativeGasUsed() { + public BigInteger getCumulativeGasUsed() { return cumulativeGasUsed; } @@ -120,8 +122,8 @@ public static final class TxERC20Builder { private String tokenSymbol; private String tokenDecimal; private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; private long confirmations; private TxERC20Builder() {} @@ -206,12 +208,12 @@ public TxERC20Builder withTransactionIndex(int transactionIndex) { return this; } - public TxERC20Builder withGasPrice(long gasPrice) { + public TxERC20Builder withGasPrice(BigInteger gasPrice) { this.gasPrice = gasPrice; return this; } - public TxERC20Builder withCumulativeGasUsed(long cumulativeGasUsed) { + public TxERC20Builder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } 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 8fb2467..1276b71 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -18,10 +18,12 @@ public class TxErc721 extends BaseTx { private String tokenSymbol; private String tokenDecimal; private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; private long confirmations; + protected TxErc721() {} + // <editor-fold desc="Getters"> public long getNonce() { return nonce; @@ -51,11 +53,11 @@ public int getTransactionIndex() { return transactionIndex; } - public long getGasPrice() { + public BigInteger getGasPrice() { return gasPrice; } - public long getCumulativeGasUsed() { + public BigInteger getCumulativeGasUsed() { return cumulativeGasUsed; } @@ -120,8 +122,8 @@ public static final class TxERC721Builder { private String tokenSymbol; private String tokenDecimal; private int transactionIndex; - private long gasPrice; - private long cumulativeGasUsed; + private BigInteger gasPrice; + private BigInteger cumulativeGasUsed; private long confirmations; private TxERC721Builder() {} @@ -206,12 +208,12 @@ public TxERC721Builder withTransactionIndex(int transactionIndex) { return this; } - public TxERC721Builder withGasPrice(long gasPrice) { + public TxERC721Builder withGasPrice(BigInteger gasPrice) { this.gasPrice = gasPrice; return this; } - public TxERC721Builder withCumulativeGasUsed(long cumulativeGasUsed) { + public TxERC721Builder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } 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 84e10b3..68bdebf 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -17,6 +17,8 @@ public class TxInternal extends BaseTx { private int isError; private String errCode; + protected TxInternal() {} + // <editor-fold desc="Getters"> public BigInteger getValue() { return value; @@ -102,7 +104,7 @@ public TxInternalBuilder withBlockNumber(long blockNumber) { return this; } - public TxInternalBuilder with_timeStamp(LocalDateTime timeStamp) { + public TxInternalBuilder withTimeStamp(LocalDateTime timeStamp) { this.timeStamp = timeStamp; return this; } 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 0e6ff3a..1eb46f3 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 @@ -47,6 +47,8 @@ public class BlockProxy { private String transactionsRoot; private List<TxProxy> transactions; + protected BlockProxy() {} + // <editor-fold desc="Getters"> public Long getNumber() { if (_number == null && !BasicUtils.isEmpty(number)) 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 73a21b6..a57ce36 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 @@ -34,6 +34,8 @@ public class ReceiptProxy { private List<Log> logs; private String logsBloom; + protected ReceiptProxy() {} + // <editor-fold desc="Getters"> public String getRoot() { return root; 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 52fe41b..b6324e1 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 @@ -35,6 +35,8 @@ public class TxProxy { @Expose(deserialize = false, serialize = false) private Long _blockNumber; + protected TxProxy() {} + // <editor-fold desc="Getters"> public String getTo() { return to; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java new file mode 100644 index 0000000..3bf9d49 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc1155ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxErc1155; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +public class TxErc1155ResponseTO extends BaseListResponseTO<TxErc1155> { + +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java new file mode 100644 index 0000000..d5d3f6e --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc20ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxErc20; + +/** + * @author GoodforGod + * @since 29.10.2018 + */ +public class TxErc20ResponseTO extends BaseListResponseTO<TxErc20> { + +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java new file mode 100644 index 0000000..27518ae --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/TxErc721ResponseTO.java @@ -0,0 +1,11 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.TxErc721; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +public class TxErc721ResponseTO extends BaseListResponseTO<TxErc721> { + +} diff --git a/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java b/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java index 3e84ab7..7a923aa 100644 --- a/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/block/BlockApiTests.java @@ -25,7 +25,7 @@ void correct() { assertNotEquals(-1, uncle.get().getUncles().get(0).getUnclePosition()); assertNotNull(uncle.get().toString()); - BlockUncle empty = new BlockUncle(); + BlockUncle empty = BlockUncle.builder().build(); assertNotEquals(uncle.get().hashCode(), empty.hashCode()); assertNotEquals(uncle.get(), empty); assertTrue(empty.isEmpty()); diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 6f5fc22..7db6aae 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -1,7 +1,270 @@ package io.goodforgod.api.etherscan.model; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.util.Collections; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + /** * @author Anton Kurako (GoodforGod) * @since 14.05.2023 */ -class ModelBuilderTests {} +class ModelBuilderTests extends Assertions { + + @Test + void abiBuilder() { + Abi value = Abi.builder() + .withContractAbi("1") + .withIsVerified(true) + .build(); + + assertNotNull(value); + assertTrue(value.isVerified()); + assertEquals("1", value.getContractAbi()); + } + + @Test + void blockBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + Block value = Block.builder() + .withBlockNumber(1) + .withBlockReward(BigInteger.ONE) + .withTimeStamp(timestamp) + .build(); + + assertNotNull(value); + assertEquals(1, value.getBlockNumber()); + assertEquals(BigInteger.ONE, value.getBlockReward()); + assertEquals(timestamp, value.getTimeStamp()); + } + + @Test + void blockUncleBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + BlockUncle value = BlockUncle.builder() + .withBlockNumber(1) + .withBlockReward(BigInteger.ONE) + .withTimeStamp(timestamp) + .withBlockMiner("1") + .withUncleInclusionReward("1") + .withUncles(Collections.singletonList(BlockUncle.Uncle.builder() + .withBlockreward(BigInteger.ONE) + .withMiner("1") + .withUnclePosition(1) + .build())) + .build(); + + assertNotNull(value); + assertEquals(1, value.getBlockNumber()); + assertEquals(BigInteger.ONE, value.getBlockReward()); + assertEquals(timestamp, value.getTimeStamp()); + } + + @Test + void gasOracleBuilder() { + GasOracle value = GasOracle.builder() + .withFastGasPrice(new Wei(1000000000)) + .withProposeGasPrice(new Wei(1000000000)) + .withSafeGasPrice(new Wei(1000000000)) + .withGasUsedRatio(Collections.singletonList(new BigDecimal(1))) + .withLastBlock(1L) + .withSuggestBaseFee(1.0) + .build(); + + assertNotNull(value); + assertEquals(new Wei(1000000000), value.getFastGasPriceInWei()); + } + + @Test + void logBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + Log value = Log.builder() + .withAddress("1") + .withBlockNumber(1L) + .withData("1") + .withGasPrice(BigInteger.ONE) + .withGasUsed(BigInteger.ONE) + .withLogIndex(1L) + .withTimeStamp(timestamp) + .withTransactionHash("1") + .withTransactionIndex(1L) + .withTopics(Collections.singletonList("1")) + .build(); + + assertNotNull(value); + assertEquals(1, value.getTopics().size()); + } + + @Test + void priceBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + Price value = Price.builder() + .withEthBtc(1.0) + .withEthUsd(1.0) + .withEthBtcTimestamp(timestamp) + .withEthUsdTimestamp(timestamp) + .build(); + + assertNotNull(value); + assertEquals(1.0, value.inUsd()); + assertEquals(1.0, value.inBtc()); + } + + @Test + void statusBuilder() { + Status value = Status.builder() + .withIsError(1) + .withErrDescription("1") + .build(); + + assertNotNull(value); + assertTrue(value.haveError()); + assertEquals("1", value.getErrDescription()); + } + + @Test + void txBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + Tx value = Tx.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withConfirmations(1L) + .withContractAddress("1") + .withCumulativeGasUsed(BigInteger.ONE) + .withFrom("1") + .withTo("1") + .withGas(BigInteger.ONE) + .withGasPrice(BigInteger.ONE) + .withGasUsed(BigInteger.ONE) + .withHash("1") + .withInput("1") + .withIsError("1") + .withNonce(1L) + .withTimeStamp(timestamp) + .withValue(BigInteger.ONE) + .withTransactionIndex(1) + .withTxreceiptStatus("1") + .build(); + + assertNotNull(value); + assertTrue(value.haveError()); + assertEquals("1", value.getTo()); + assertEquals("1", value.getFrom()); + } + + @Test + void txErc20Builder() { + LocalDateTime timestamp = LocalDateTime.now(); + TxErc20 value = TxErc20.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withConfirmations(1L) + .withContractAddress("1") + .withCumulativeGasUsed(BigInteger.ONE) + .withFrom("1") + .withTo("1") + .withGas(BigInteger.ONE) + .withGasPrice(BigInteger.ONE) + .withGasUsed(BigInteger.ONE) + .withHash("1") + .withInput("1") + .withTokenName("1") + .withTokenSymbol("1") + .withTokenDecimal("1") + .withNonce(1L) + .withTimeStamp(timestamp) + .withValue(BigInteger.ONE) + .withTransactionIndex(1) + .build(); + + assertNotNull(value); + assertEquals("1", value.getTo()); + assertEquals("1", value.getFrom()); + } + + @Test + void txErc721Builder() { + LocalDateTime timestamp = LocalDateTime.now(); + TxErc721 value = TxErc721.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withConfirmations(1L) + .withContractAddress("1") + .withCumulativeGasUsed(BigInteger.ONE) + .withFrom("1") + .withTo("1") + .withGas(BigInteger.ONE) + .withGasPrice(BigInteger.ONE) + .withGasUsed(BigInteger.ONE) + .withHash("1") + .withInput("1") + .withTokenName("1") + .withTokenSymbol("1") + .withTokenDecimal("1") + .withTokenID("1") + .withNonce(1L) + .withTimeStamp(timestamp) + .withTransactionIndex(1) + .build(); + + assertNotNull(value); + assertEquals("1", value.getTo()); + assertEquals("1", value.getFrom()); + } + + @Test + void txErc1155Builder() { + LocalDateTime timestamp = LocalDateTime.now(); + TxErc1155 value = TxErc1155.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withConfirmations(1L) + .withContractAddress("1") + .withCumulativeGasUsed(BigInteger.ONE) + .withFrom("1") + .withTo("1") + .withGas(BigInteger.ONE) + .withGasPrice(BigInteger.ONE) + .withGasUsed(BigInteger.ONE) + .withHash("1") + .withInput("1") + .withTokenName("1") + .withTokenSymbol("1") + .withTokenDecimal("1") + .withTokenID("1") + .withNonce(1L) + .withTimeStamp(timestamp) + .withTransactionIndex(1) + .build(); + + assertNotNull(value); + assertEquals("1", value.getTo()); + assertEquals("1", value.getFrom()); + } + + @Test + void txInternalBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + TxInternal value = TxInternal.builder() + .withBlockNumber(1L) + .withContractAddress("1") + .withFrom("1") + .withTo("1") + .withGas(BigInteger.ONE) + .withGasUsed(BigInteger.ONE) + .withHash("1") + .withInput("1") + .withTimeStamp(timestamp) + .withErrCode("1") + .withIsError(1) + .withTraceId("1") + .withType("1") + .build(); + + assertNotNull(value); + assertEquals("1", value.getTo()); + assertEquals("1", value.getFrom()); + } +} diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java index 10dc6fd..874ccc0 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java @@ -50,7 +50,7 @@ void correct() { assertNotNull(proxy.getUncles()); assertNotNull(proxy.toString()); - BlockProxy empty = new BlockProxy(); + BlockProxy empty = BlockProxy.builder().build(); assertNotEquals(proxy, empty); assertNotEquals(proxy.hashCode(), empty.hashCode()); } diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java index 6c7dbb7..b20369e 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxApiTests.java @@ -24,7 +24,7 @@ void correctByHash() { assertNotNull(tx.get().getBlockNumber()); assertNotNull(tx.get().toString()); - TxProxy empty = new TxProxy(); + TxProxy empty = TxProxy.builder().build(); assertNotEquals(tx.get(), empty); assertNotEquals(tx.get().hashCode(), empty.hashCode()); } diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java index 7a6624c..e4322f2 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java @@ -32,7 +32,7 @@ void correct() { assertNull(infoProxy.get().getContractAddress()); assertNotNull(infoProxy.get().toString()); - ReceiptProxy empty = new ReceiptProxy(); + ReceiptProxy empty = ReceiptProxy.builder().build(); assertNotEquals(empty, infoProxy.get()); assertNotEquals(empty.hashCode(), infoProxy.get().hashCode()); } diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java index 37e0ec0..3525e21 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java @@ -20,7 +20,7 @@ void correct() { assertNotEquals(0.0, price.inUsd()); assertNotNull(price.toString()); - Price empty = new Price(); + Price empty = Price.builder().build(); assertNotEquals(price, empty); assertNotEquals(price.hashCode(), empty.hashCode()); } diff --git a/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java index eb595c3..23e512c 100644 --- a/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/transaction/TransactionExecApiTests.java @@ -20,7 +20,7 @@ void correct() { assertNotNull(status.get().getErrDescription()); assertNotNull(status.get().toString()); - Status empty = new Status(); + Status empty = Status.builder().build(); assertNotEquals(empty, status.get()); assertNotEquals(empty.hashCode(), status.get().hashCode()); } From aa251291f27a11347fc7f63fdbcb2ecf6c4c4de5 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 12:52:59 +0300 Subject: [PATCH 29/67] [2.0.0-SNAPSHOT] CI key env fixed --- .github/workflows/gradle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 3eb55f0..31c42f0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,13 +35,13 @@ jobs: if: matrix.java == '11' run: ./gradlew test jacocoTestReport env: - API_KEY: ${{ secrets.ETHERSCAN_API_KEY_1 }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_1 }} - name: Test if: matrix.java == '17' run: ./gradlew test jacocoTestReport env: - API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - name: SonarQube if: matrix.java == '17' From 873f5828e525e109190b409295b0ef507a68f053 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 13:04:56 +0300 Subject: [PATCH 30/67] [2.0.0-SNAPSHOT] Builders NPE fixes Hashcode & Equals improved --- .../goodforgod/api/etherscan/model/Abi.java | 19 ++----- .../api/etherscan/model/Balance.java | 14 ++--- .../goodforgod/api/etherscan/model/Block.java | 13 ++--- .../api/etherscan/model/BlockUncle.java | 51 ++++--------------- .../goodforgod/api/etherscan/model/Log.java | 51 +++++++------------ .../goodforgod/api/etherscan/model/Price.java | 51 +++++++------------ .../io/goodforgod/api/etherscan/model/Tx.java | 6 ++- .../api/etherscan/model/TxErc1155.java | 6 ++- .../api/etherscan/model/TxErc20.java | 6 ++- .../api/etherscan/model/TxErc721.java | 6 ++- .../api/etherscan/model/TxInternal.java | 6 ++- .../goodforgod/api/etherscan/model/Wei.java | 7 +-- .../api/etherscan/model/proxy/BlockProxy.java | 18 ++++--- .../etherscan/model/proxy/ReceiptProxy.java | 12 +++-- .../api/etherscan/model/proxy/TxProxy.java | 12 +++-- 15 files changed, 108 insertions(+), 170 deletions(-) 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 3fce40a..3536bf9 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan.model; import io.goodforgod.api.etherscan.util.BasicUtils; +import java.util.Objects; /** * @author GoodforGod @@ -40,27 +41,15 @@ public boolean isVerified() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Abi)) return false; - Abi abi = (Abi) o; - - if (isVerified != abi.isVerified) - return false; - return contractAbi != null - ? contractAbi.equals(abi.contractAbi) - : abi.contractAbi == null; + return isVerified == abi.isVerified && Objects.equals(contractAbi, abi.contractAbi); } @Override public int hashCode() { - int result = contractAbi != null - ? contractAbi.hashCode() - : 0; - result = 31 * result + (isVerified - ? 1 - : 0); - return result; + return Objects.hash(contractAbi, isVerified); } @Override 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 38379e6..4de8a54 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java @@ -48,23 +48,15 @@ public BigInteger getEther() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Balance)) return false; - Balance balance1 = (Balance) o; - - if (!balance.equals(balance1.balance)) - return false; - return Objects.equals(address, balance1.address); + return Objects.equals(balance, balance1.balance) && Objects.equals(address, balance1.address); } @Override public int hashCode() { - int result = balance.hashCode(); - result = 31 * result + (address != null - ? address.hashCode() - : 0); - return result; + return Objects.hash(balance, address); } @Override 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 d46fb44..95bfbcb 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -5,6 +5,7 @@ import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Objects; /** * @author GoodforGod @@ -40,17 +41,15 @@ public BigInteger getBlockReward() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Block)) return false; - Block block = (Block) o; - return blockNumber == block.blockNumber; } @Override public int hashCode() { - return (int) (blockNumber ^ (blockNumber >>> 32)); + return Objects.hash(blockNumber); } @Override @@ -98,8 +97,10 @@ public Block build() { Block block = new Block(); block.blockNumber = this.blockNumber; block.blockReward = this.blockReward; - block._timeStamp = this.timeStamp; - block.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + block._timeStamp = this.timeStamp; + block.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + } return block; } } 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 02ddc28..9b110d9 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; +import java.util.Objects; /** * @author GoodforGod @@ -38,31 +39,16 @@ public int getUnclePosition() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Uncle)) return false; - Uncle uncle = (Uncle) o; - if (unclePosition != uncle.unclePosition) - return false; - if (miner != null - ? !miner.equals(uncle.miner) - : uncle.miner != null) - return false; - return blockreward != null - ? blockreward.equals(uncle.blockreward) - : uncle.blockreward == null; + return unclePosition == uncle.unclePosition && Objects.equals(miner, uncle.miner) + && Objects.equals(blockreward, uncle.blockreward); } @Override public int hashCode() { - int result = miner != null - ? miner.hashCode() - : 0; - result = 31 * result + (blockreward != null - ? blockreward.hashCode() - : 0); - result = 31 * result + unclePosition; - return result; + return Objects.hash(miner, blockreward, unclePosition); } @Override @@ -139,27 +125,6 @@ public String getUncleInclusionReward() { } // </editor-fold> - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - if (!super.equals(o)) - return false; - - BlockUncle that = (BlockUncle) o; - - return getBlockNumber() != 0 && getBlockNumber() == that.getBlockNumber(); - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = (int) (31 * result + getBlockNumber()); - return result; - } - @Override public String toString() { return "UncleBlock{" + @@ -223,8 +188,10 @@ public BlockUncle build() { blockUncle.blockNumber = this.blockNumber; blockUncle.blockReward = this.blockReward; blockUncle.blockMiner = this.blockMiner; - blockUncle._timeStamp = this.timeStamp; - blockUncle.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + blockUncle._timeStamp = this.timeStamp; + blockUncle.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + } return blockUncle; } } 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 5ed840a..07e652f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -125,40 +125,17 @@ public Long getLogIndex() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Log)) return false; - Log log = (Log) o; - - if (!Objects.equals(blockNumber, log.blockNumber)) - return false; - if (!Objects.equals(address, log.address)) - return false; - if (!Objects.equals(transactionHash, log.transactionHash)) - return false; - if (!Objects.equals(timeStamp, log.timeStamp)) - return false; - return Objects.equals(logIndex, log.logIndex); + return Objects.equals(blockNumber, log.blockNumber) && Objects.equals(address, log.address) + && Objects.equals(transactionHash, log.transactionHash) && Objects.equals(transactionIndex, log.transactionIndex) + && Objects.equals(logIndex, log.logIndex); } @Override public int hashCode() { - int result = blockNumber != null - ? blockNumber.hashCode() - : 0; - result = 31 * result + (address != null - ? address.hashCode() - : 0); - result = 31 * result + (transactionHash != null - ? transactionHash.hashCode() - : 0); - result = 31 * result + (timeStamp != null - ? timeStamp.hashCode() - : 0); - result = 31 * result + (logIndex != null - ? logIndex.hashCode() - : 0); - return result; + return Objects.hash(blockNumber, address, transactionHash, transactionIndex, logIndex); } @Override @@ -255,17 +232,23 @@ public LogBuilder withLogIndex(Long logIndex) { public Log build() { Log log = new Log(); log.address = this.address; - log.gasPrice = String.valueOf(this.gasPrice); - log._gasPrice = this.gasPrice; + if (this.gasPrice != null) { + log.gasPrice = String.valueOf(this.gasPrice); + log._gasPrice = this.gasPrice; + } log._logIndex = this.logIndex; log._transactionIndex = this.transactionIndex; - log._gasUsed = this.gasUsed; log.blockNumber = String.valueOf(this.blockNumber); log.transactionIndex = String.valueOf(this.transactionIndex); - log.timeStamp = String.valueOf(this.timeStamp); + if (this.timeStamp != null) { + log.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + log._timeStamp = this.timeStamp; + } log.data = this.data; - log.gasUsed = String.valueOf(this.gasUsed); - log._timeStamp = this.timeStamp; + if (this.gasUsed != null) { + log.gasUsed = String.valueOf(this.gasUsed); + log._gasUsed = this.gasUsed; + } log.logIndex = String.valueOf(this.logIndex); log._blockNumber = this.blockNumber; log.topics = this.topics; 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 b24fc65..4ef4491 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -3,6 +3,7 @@ import com.google.gson.annotations.Expose; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.util.Objects; /** * @author GoodforGod @@ -30,14 +31,16 @@ public double inBtc() { } public LocalDateTime usdTimestamp() { - if (_ethusd_timestamp == null) + if (_ethusd_timestamp == null && ethusd_timestamp != null) { _ethusd_timestamp = LocalDateTime.ofEpochSecond(Long.parseLong(ethusd_timestamp), 0, ZoneOffset.UTC); + } return _ethusd_timestamp; } public LocalDateTime btcTimestamp() { - if (_ethbtc_timestamp == null) + if (_ethbtc_timestamp == null && ethbtc_timestamp != null) { _ethbtc_timestamp = LocalDateTime.ofEpochSecond(Long.parseLong(ethbtc_timestamp), 0, ZoneOffset.UTC); + } return _ethbtc_timestamp; } @@ -45,39 +48,17 @@ public LocalDateTime btcTimestamp() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Price)) return false; - Price price = (Price) o; - - if (Double.compare(price.ethusd, ethusd) != 0) - return false; - if (Double.compare(price.ethbtc, ethbtc) != 0) - return false; - if (ethusd_timestamp != null - ? !ethusd_timestamp.equals(price.ethusd_timestamp) - : price.ethusd_timestamp != null) - return false; - return (ethbtc_timestamp != null - ? !ethbtc_timestamp.equals(price.ethbtc_timestamp) - : price.ethbtc_timestamp != null); + return Double.compare(price.ethusd, ethusd) == 0 && Double.compare(price.ethbtc, ethbtc) == 0 + && Objects.equals(ethusd_timestamp, price.ethusd_timestamp) + && Objects.equals(ethbtc_timestamp, price.ethbtc_timestamp); } @Override public int hashCode() { - int result; - long temp; - temp = Double.doubleToLongBits(ethusd); - result = (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(ethbtc); - result = 31 * result + (int) (temp ^ (temp >>> 32)); - result = 31 * result + (ethusd_timestamp != null - ? ethusd_timestamp.hashCode() - : 0); - result = 31 * result + (ethbtc_timestamp != null - ? ethbtc_timestamp.hashCode() - : 0); - return result; + return Objects.hash(ethusd, ethbtc, ethusd_timestamp, ethbtc_timestamp); } @Override @@ -126,11 +107,15 @@ public PriceBuilder withEthBtcTimestamp(LocalDateTime ethbtcTimestamp) { public Price build() { Price price = new Price(); price.ethbtc = this.ethbtc; - price.ethbtc_timestamp = String.valueOf(this.ethbtcTimestamp.toEpochSecond(ZoneOffset.UTC)); - price._ethbtc_timestamp = this.ethbtcTimestamp; price.ethusd = this.ethusd; - price.ethusd_timestamp = String.valueOf(this.ethusdTimestamp.toEpochSecond(ZoneOffset.UTC)); - price._ethusd_timestamp = this.ethusdTimestamp; + if (this.ethbtcTimestamp != null) { + price.ethbtc_timestamp = String.valueOf(this.ethbtcTimestamp.toEpochSecond(ZoneOffset.UTC)); + price._ethbtc_timestamp = this.ethbtcTimestamp; + } + if (this.ethusdTimestamp != null) { + price.ethusd_timestamp = String.valueOf(this.ethusdTimestamp.toEpochSecond(ZoneOffset.UTC)); + price._ethusd_timestamp = this.ethusdTimestamp; + } return price; } } 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 7e09768..3d8cd1f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -228,10 +228,12 @@ public Tx build() { tx.value = this.value; tx.transactionIndex = this.transactionIndex; tx.confirmations = this.confirmations; - tx.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + tx.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + tx._timeStamp = this.timeStamp; + } tx.nonce = this.nonce; tx.blockNumber = this.blockNumber; - tx._timeStamp = this.timeStamp; tx.to = this.to; tx.input = this.input; tx.cumulativeGasUsed = this.cumulativeGasUsed; 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 edf578f..e6c20f0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -235,9 +235,11 @@ public TxErc1155 build() { txERC721.contractAddress = this.contractAddress; txERC721.cumulativeGasUsed = this.cumulativeGasUsed; txERC721.tokenID = this.tokenID; - txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC721._timeStamp = this.timeStamp; + } txERC721.blockNumber = this.blockNumber; - txERC721._timeStamp = this.timeStamp; txERC721.tokenValue = this.tokenValue; txERC721.transactionIndex = this.transactionIndex; txERC721.to = this.to; 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 9342c8e..197ab5d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -237,10 +237,12 @@ public TxErc20 build() { txERC20.nonce = this.nonce; txERC20.confirmations = this.confirmations; txERC20.value = this.value; - txERC20.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + txERC20.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC20._timeStamp = this.timeStamp; + } txERC20.blockHash = this.blockHash; txERC20.blockNumber = this.blockNumber; - txERC20._timeStamp = this.timeStamp; txERC20.gasPrice = this.gasPrice; txERC20.to = this.to; txERC20.input = this.input; 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 1276b71..644f738 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -235,9 +235,11 @@ public TxErc721 build() { txERC721.contractAddress = this.contractAddress; txERC721.cumulativeGasUsed = this.cumulativeGasUsed; txERC721.tokenID = this.tokenID; - txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC721._timeStamp = this.timeStamp; + } txERC721.blockNumber = this.blockNumber; - txERC721._timeStamp = this.timeStamp; txERC721.tokenDecimal = this.tokenDecimal; txERC721.transactionIndex = this.transactionIndex; txERC721.to = this.to; 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 68bdebf..fdd89ee 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -179,10 +179,12 @@ public TxInternal build() { txInternal.from = this.from; txInternal.contractAddress = this.contractAddress; txInternal.value = this.value; - txInternal.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timeStamp != null) { + txInternal.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txInternal._timeStamp = this.timeStamp; + } txInternal.errCode = this.errCode; txInternal.blockNumber = this.blockNumber; - txInternal._timeStamp = this.timeStamp; txInternal.isError = this.isError; txInternal.to = this.to; txInternal.input = this.input; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index e863b7a..2fc2014 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -45,18 +45,15 @@ public BigInteger asEther() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Wei)) return false; - Wei wei = (Wei) o; return Objects.equals(result, wei.result); } @Override public int hashCode() { - return result != null - ? result.hashCode() - : 0; + return Objects.hash(result); } @Override 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 1eb46f3..a9447ca 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 @@ -342,26 +342,32 @@ public BlockProxy build() { blockProxy.mixHash = this.mixHash; blockProxy.totalDifficulty = this.totalDifficulty; blockProxy.nonce = this.nonce; - blockProxy._gasUsed = this.gasUsed; blockProxy.uncles = this.uncles; blockProxy.transactionsRoot = this.transactionsRoot; blockProxy.number = String.valueOf(this.number); blockProxy.logsBloom = this.logsBloom; blockProxy.receiptsRoot = this.receiptsRoot; - blockProxy._gasLimit = this.gasLimit; blockProxy.hash = this.hash; blockProxy.parentHash = this.parentHash; blockProxy._size = this.size; - blockProxy.gasLimit = String.valueOf(this.gasLimit); blockProxy.difficulty = this.difficulty; - blockProxy.gasUsed = String.valueOf(this.gasUsed); + if (this.gasLimit != null) { + blockProxy.gasLimit = String.valueOf(this.gasLimit); + blockProxy._gasLimit = this.gasLimit; + } + if (this.gasUsed != null) { + blockProxy.gasUsed = String.valueOf(this.gasUsed); + blockProxy._gasUsed = this.gasUsed; + } blockProxy.size = String.valueOf(this.size); blockProxy.extraData = this.extraData; blockProxy.stateRoot = this.stateRoot; - blockProxy._timestamp = this.timestamp; blockProxy.sha3Uncles = this.sha3Uncles; blockProxy.miner = this.miner; - blockProxy.timestamp = String.valueOf(this.timestamp.toEpochSecond(ZoneOffset.UTC)); + if (this.timestamp != null) { + blockProxy.timestamp = String.valueOf(this.timestamp.toEpochSecond(ZoneOffset.UTC)); + blockProxy._timestamp = this.timestamp; + } blockProxy.transactions = this.transactions; blockProxy._number = this.number; return blockProxy; 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 a57ce36..61a7942 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 @@ -243,14 +243,18 @@ public ReceiptProxy build() { receiptProxy.blockHash = this.blockHash; receiptProxy.root = this.root; receiptProxy.contractAddress = this.contractAddress; - receiptProxy.gasUsed = String.valueOf(this.gasUsed); - receiptProxy._gasUsed = this.gasUsed; + if (this.gasUsed != null) { + receiptProxy.gasUsed = String.valueOf(this.gasUsed); + receiptProxy._gasUsed = this.gasUsed; + } receiptProxy.logs = this.logs; - receiptProxy.cumulativeGasUsed = String.valueOf(this.cumulativeGasUsed); receiptProxy.to = this.to; + if (this.cumulativeGasUsed != null) { + receiptProxy.cumulativeGasUsed = String.valueOf(this.cumulativeGasUsed); + receiptProxy._cumulativeGasUsed = this.cumulativeGasUsed; + } receiptProxy.transactionIndex = String.valueOf(this.transactionIndex); receiptProxy._blockNumber = this.blockNumber; - receiptProxy._cumulativeGasUsed = this.cumulativeGasUsed; return receiptProxy; } } 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 b6324e1..0ca7f3a 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 @@ -262,8 +262,10 @@ public TxProxyBuilder withBlockNumber(Long blockNumber) { public TxProxy build() { TxProxy txProxy = new TxProxy(); txProxy.input = this.input; - txProxy.gas = String.valueOf(this.gas); - txProxy._gas = this.gas; + if (this.gas != null) { + txProxy.gas = String.valueOf(this.gas); + txProxy._gas = this.gas; + } txProxy.s = this.s; txProxy.blockHash = this.blockHash; txProxy.to = this.to; @@ -274,12 +276,14 @@ public TxProxy build() { txProxy.v = this.v; txProxy.from = this.from; txProxy.nonce = String.valueOf(this.nonce); - txProxy._gasPrice = this.gasPrice; txProxy._transactionIndex = this.transactionIndex; txProxy.blockNumber = String.valueOf(this.blockNumber); txProxy._blockNumber = this.blockNumber; txProxy.hash = this.hash; - txProxy.gasPrice = String.valueOf(this.gasPrice); + if (this.gasPrice != null) { + txProxy.gasPrice = String.valueOf(this.gasPrice); + txProxy._gasPrice = this.gasPrice; + } return txProxy; } } From 1beaafdd691fb5ecfb7be0c0c87e9cd0690246df Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 13:13:59 +0300 Subject: [PATCH 31/67] [2.0.0-SNAPSHOT] Balance contract improved Wei contract improved Supply constructor added ProxyAPI contract refactored to Wei --- .../api/etherscan/GasTrackerAPIProvider.java | 2 +- .../io/goodforgod/api/etherscan/ProxyAPI.java | 8 ++++---- .../api/etherscan/ProxyAPIProvider.java | 16 +++++++-------- .../manager/RequestQueueManager.java | 4 ++-- .../api/etherscan/model/Balance.java | 20 ++----------------- .../api/etherscan/model/Supply.java | 4 ++++ .../goodforgod/api/etherscan/model/Wei.java | 2 +- .../account/AccountBalanceListTests.java | 10 +++------- .../api/etherscan/proxy/ProxyGasApiTests.java | 14 ++++++------- .../statistic/StatisticSupplyApiTests.java | 2 +- 10 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java index a4db5ae..0b559d8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -34,7 +34,7 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI @Override public @NotNull GasEstimate estimate(@NotNull Wei wei) throws EtherScanException { - final String urlParams = ACT_GAS_ESTIMATE_PARAM + GASPRICE_PARAM + wei.getValue().toString(); + final String urlParams = ACT_GAS_ESTIMATE_PARAM + GASPRICE_PARAM + wei.asWei().toString(); final GasEstimateResponseTO response = getRequest(urlParams, GasEstimateResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java index 0785d13..b379290 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java @@ -1,10 +1,10 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.model.proxy.BlockProxy; import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; import io.goodforgod.api.etherscan.model.proxy.TxProxy; -import java.math.BigInteger; import java.util.Optional; import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.NotNull; @@ -150,7 +150,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - BigInteger gasPrice() throws EtherScanException; + Wei gasPrice() throws EtherScanException; /** * Makes a call or transaction, which won't be added to the blockchain and returns the used gas, @@ -161,8 +161,8 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - BigInteger gasEstimated(String hexData) throws EtherScanException; + Wei gasEstimated(String hexData) throws EtherScanException; @NotNull - BigInteger gasEstimated() throws EtherScanException; + Wei gasEstimated() throws EtherScanException; } diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index a306541..27e00df 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -5,6 +5,7 @@ import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.model.proxy.BlockProxy; import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; import io.goodforgod.api.etherscan.model.proxy.TxProxy; @@ -13,7 +14,6 @@ import io.goodforgod.api.etherscan.model.proxy.utility.TxInfoProxyTO; import io.goodforgod.api.etherscan.model.proxy.utility.TxProxyTO; import io.goodforgod.api.etherscan.util.BasicUtils; -import java.math.BigInteger; import java.util.Optional; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; @@ -197,29 +197,29 @@ public Optional<String> storageAt(String address, long position) throws EtherSca @NotNull @Override - public BigInteger gasPrice() throws EtherScanException { + public Wei gasPrice() throws EtherScanException { final StringProxyTO response = getRequest(ACT_GASPRICE_PARAM, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) - ? BigInteger.valueOf(-1) - : BasicUtils.parseHex(response.getResult()); + ? new Wei(0) + : new Wei(BasicUtils.parseHex(response.getResult())); } @NotNull @Override - public BigInteger gasEstimated() throws EtherScanException { + public Wei gasEstimated() throws EtherScanException { return gasEstimated("606060405260728060106000396000f360606040526000"); } @NotNull @Override - public BigInteger gasEstimated(String hexData) throws EtherScanException { + public Wei gasEstimated(String hexData) throws EtherScanException { if (!BasicUtils.isEmpty(hexData) && BasicUtils.isNotHex(hexData)) throw new EtherScanInvalidDataHexException("Data is not in hex format."); final String urlParams = ACT_ESTIMATEGAS_PARAM + DATA_PARAM + hexData + GAS_PARAM + "2000000000000000"; final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) - ? BigInteger.valueOf(-1) - : BasicUtils.parseHex(response.getResult()); + ? new Wei(0) + : new Wei(BasicUtils.parseHex(response.getResult())); } } 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 f19603f..46a76e2 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -16,13 +16,13 @@ public interface RequestQueueManager extends AutoCloseable { /** * Is used by default when no API KEY is provided */ - RequestQueueManager ANONYMOUS = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5005L)); + RequestQueueManager ANONYMOUS = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5010L)); /** * Is available for all registered free API KEYs * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> */ - RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1005L)); + RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1010L)); RequestQueueManager UNLIMITED = new FakeRequestQueueManager(); 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 4de8a54..783b7d8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java @@ -23,24 +23,8 @@ public String getAddress() { return address; } - public BigInteger getWei() { - return balance.getValue(); - } - - public BigInteger getKwei() { - return balance.asKwei(); - } - - public BigInteger getMwei() { - return balance.asMwei(); - } - - public BigInteger getGwei() { - return balance.asGwei(); - } - - public BigInteger getEther() { - return balance.asEther(); + public Wei getBalanceInWei() { + return balance; } // </editor-fold> diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Supply.java b/src/main/java/io/goodforgod/api/etherscan/model/Supply.java index 80dc7d0..43e3a3f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Supply.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Supply.java @@ -8,6 +8,10 @@ */ public class Supply extends Wei { + public Supply(long value) { + super(value); + } + public Supply(BigInteger value) { super(value); } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index 2fc2014..cb136df 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -20,7 +20,7 @@ public Wei(BigInteger value) { } // <editor-fold desc="Getters"> - public BigInteger getValue() { + public BigInteger asWei() { return result; } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java index f611b62..0054a84 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceListTests.java @@ -29,13 +29,9 @@ void correct() { assertNotEquals(balances.get(0).hashCode(), balances.get(1).hashCode()); for (Balance balance : balances) { assertNotNull(balance.getAddress()); - assertNotNull(balance.getGwei()); - assertNotNull(balance.getKwei()); - assertNotNull(balance.getMwei()); - assertNotNull(balance.getEther()); - assertNotNull(balance.getGwei()); + assertNotNull(balance.getBalanceInWei()); assertNotNull(balance.getAddress()); - assertNotEquals(BigInteger.ZERO, balance.getWei()); + assertNotEquals(BigInteger.ZERO, balance.getBalanceInWei().asWei()); assertNotNull(balance.toString()); } } @@ -84,7 +80,7 @@ void correctParamWithEmptyExpectedResult() { assertEquals(2, balances.size()); for (Balance balance : balances) { assertNotNull(balance.getAddress()); - assertEquals(0, balance.getWei().intValue()); + assertEquals(0, balance.getBalanceInWei().asWei().intValue()); } } } diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java index 0ab2a77..4dea82e 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyGasApiTests.java @@ -2,7 +2,7 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.error.EtherScanInvalidDataHexException; -import java.math.BigInteger; +import io.goodforgod.api.etherscan.model.Wei; import org.junit.jupiter.api.Test; /** @@ -13,23 +13,23 @@ class ProxyGasApiTests extends ApiRunner { @Test void correctPrice() { - BigInteger price = getApi().proxy().gasPrice(); + Wei price = getApi().proxy().gasPrice(); assertNotNull(price); - assertNotEquals(0, price.intValue()); + assertNotEquals(0, price.asWei().intValue()); } @Test void correctEstimated() { - BigInteger price = getApi().proxy().gasEstimated(); + Wei price = getApi().proxy().gasEstimated(); assertNotNull(price); - assertNotEquals(0, price.intValue()); + assertNotEquals(0, price.asWei().intValue()); } @Test void correctEstimatedWithData() { String dataCustom = "606060405260728060106000396000f360606040526000606060405260728060106000396000f360606040526000"; - BigInteger price = getApi().proxy().gasEstimated(); - BigInteger priceCustom = getApi().proxy().gasEstimated(dataCustom); + Wei price = getApi().proxy().gasEstimated(); + Wei priceCustom = getApi().proxy().gasEstimated(dataCustom); assertNotNull(price); assertNotNull(priceCustom); assertNotEquals(price, priceCustom); diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java index fa79028..56469a9 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java @@ -15,7 +15,7 @@ class StatisticSupplyApiTests extends ApiRunner { void correct() { Supply supply = getApi().stats().supply(); assertNotNull(supply); - assertNotNull(supply.getValue()); + assertNotNull(supply.asWei()); assertNotNull(supply.asGwei()); assertNotNull(supply.asKwei()); assertNotNull(supply.asMwei()); From 948a6f3e7cd5b7aa0d6121c113035321dd059663 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 21:15:56 +0300 Subject: [PATCH 32/67] [2.0.0-SNAPSHOT] README.md updated Balance & TokenBalance constructor improved --- README.md | 111 +++++++++--------- .../api/etherscan/AccountAPIProvider.java | 6 +- .../api/etherscan/EtherScanAPI.java | 5 + .../api/etherscan/model/Balance.java | 5 +- .../api/etherscan/model/TokenBalance.java | 3 +- .../account/AccountBalanceTests.java | 10 +- .../account/AccountTokenBalanceTests.java | 8 +- 7 files changed, 73 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index cd981ca..4cff68d 100644 --- a/README.md +++ b/README.md @@ -43,82 +43,84 @@ implementation "com.github.goodforgod:java-etherscan-api:2.0.0-SNAPSHOT" ## Mainnet and Testnets -API support Ethereum: *[MAINNET](https://etherscan.io), - [ROPSTEN](https://ropsten.etherscan.io), - [KOVAN](https://kovan.etherscan.io), - [RINKEBY](https://rinkeby.etherscan.io), - [GORLI](https://goerli.etherscan.io), - [TOBALABA](https://tobalaba.etherscan.com)* networks. +API support Ethereum [default networks](https://docs.etherscan.io/getting-started/endpoint-urls): +- [Mainnet](https://api.etherscan.io/) +- [Goerli](https://api-goerli.etherscan.io/) +- [Sepolia](https://api-sepolia.etherscan.io/) + ```java -EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET); // Default -EtherScanApi apiRinkeby = new EtherScanApi(EthNetwork.RINKEBY); -EtherScanApi apiRopsten = new EtherScanApi(EthNetwork.ROPSTEN); -EtherScanApi apiKovan = new EtherScanApi("YourApiKey", EthNetwork.KOVAN); +EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI apiGoerli = EtherScanAPI.builder().withNetwork(EthNetworks.GORLI).build(); +EtherScanAPI apiSepolia = EtherScanAPI.builder().withNetwork(EthNetworks.SEPOLIA).build(); +``` + +### Custom Network + +In case you want to use API for other EtherScan compatible network, you can easily provide custom network with domain api URI. + +```java +EtherScanAPI api = EtherScanAPI.builder() + .withNetwork(() -> URI.create("https://api-my-custom.etherscan.io/api")) + .build(); ``` ## Custom HttpClient In case you need to set custom timeout, custom headers or better implementation for HttpClient, -just implement **IHttpExecutor** by your self or initialize it with your values. +just implement **EthHttpClient** by your self or initialize it with your values. ```java -int connectionTimeout = 10000; -int readTimeout = 7000; - -Supplier<IHttpExecutor> supplier = () -> new HttpExecutor(connectionTimeout); -Supplier<IHttpExecutor> supplierFull = () -> new HttpExecutor(connectionTimeout, readTimeout); - -EtherScanApi api = new EtherScanApi(EthNetwork.RINKEBY, supplier); -EtherScanApi apiWithKey = new EtherScanApi("YourApiKey", EthNetwork.MAINNET, supplierFull); +Supplier<EthHttpClient> ethHttpClientSupplier = () -> new UrlEthHttpClient(Duration.ofMillis(300), Duration.ofMillis(300)); +EtherScanAPI api = EtherScanAPI.builder() + .withHttpClient(supplier) + .build(); ``` ## API Examples -You can read about all API methods on [Etherscan](https://etherscan.io/apis) +You can read about all API methods on [Etherscan](https://docs.etherscan.io/api-endpoints/accounts) *Library support all available EtherScan API.* -You can use library *with or without* API key *([Check API request\sec restrictions when used without API key](https://ethereum.stackexchange.com/questions/34190/does-etherscan-require-the-use-of-an-api-key))*. +You can use library *with or without* API key *([Check API request\sec restrictions when used without API key](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics))*. -Library will automatically limit requests up to **5 req/sec** when used *without* key. +Library will automatically limit requests up to **1 requests in 5 seconds** when used *without* key and up to **5 requests in 1 seconds** when used with API KEY (free plan). ```java -EtherScanApi api = new EtherScanApi(); -EtherScanApi api = new EtherScanApi("YourApiKey"); +EtherScanAPI.builder() + .withApiKey(ApiRunner.API_KEY) + .build(); ``` Below are examples for each API category. -### Account Api +### Account API **Get Ether Balance for a single Address** - ```java -EtherScanApi api = new EtherScanApi(); +EtherScanAPI api = EtherScanAPI.build(); Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3b4F"); ``` -### Block Api +### Block API **Get uncles block for block height** - ```java -EtherScanApi api = new EtherScanApi(); +EtherScanAPI api = EtherScanAPI.build(); Optional<UncleBlock> uncles = api.block().uncles(200000); ``` -### Contract Api +### Contract API **Request contract ABI from [verified codes](https://etherscan.io/contractsVerified)** ```java -EtherScanApi api = new EtherScanApi(); +EtherScanAPI api = EtherScanAPI.build(); Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); ``` -### Logs Api +### Logs API **Get event logs for single topic** - ```java -EtherScanApi api = new EtherScanApi(); +EtherScanAPI api = EtherScanAPI.build(); LogQuery query = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); @@ -126,58 +128,55 @@ List<Log> logs = api.logs().logs(query); ``` **Get event logs for 3 topics with respectful operations** - ```java -EtherScanApi api = new EtherScanApi(); -LogQuery query = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c", 379224, 400000) - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", - "0x72657075746174696f6e00000000000000000000000000000000000000000000", - "0x72657075746174696f6e00000000000000000000000000000000000000000000") +EtherScanAPI api = EtherScanAPI.build(); +LogQuery query = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withBlockFrom(379224) + .withBlockTo(400000) + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", + "0x72657075746174696f6e00000000000000000000000000000000000000000000", + "0x72657075746174696f6e00000000000000000000000000000000000000000000") .setOpTopic0_1(LogOp.AND) - .setOpTopic0_2(LogOp.OR) + .setOpTopic0_2(null) .setOpTopic1_2(LogOp.AND) .build(); List<Log> logs = api.logs().logs(query); ``` -### Proxy Api - -**Get tx detailds with proxy endpoint** +### Proxy API +**Get tx details with proxy endpoint** ```java -EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET); +EtherScanAPI api = EtherScanAPI.build(); Optional<TxProxy> tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); ``` **Get block info with proxy endpoint** - ```java -EtherScanApi api = new EtherScanApi(EthNetwork.MAINNET); +EtherScanAPI api = EtherScanAPI.build(); Optional<BlockProxy> block = api.proxy().block(15215); ``` -### Stats Api +### Stats API **Statistic about last price** - ```java -EtherScanApi api = new EtherScanApi(); +EtherScanAPI api = EtherScanAPI.build(); Price price = api.stats().lastPrice(); ``` -### Transaction Api +### Transaction API **Request receipt status for tx** - ```java -EtherScanApi api = new EtherScanApi(); +EtherScanAPI api = EtherScanAPI.build(); Optional<Boolean> status = api.txs().receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); ``` -### Token Api +### Token API -You can read about token API [here](https://etherscan.io/apis#tokens) +You can read about token API [here](https://docs.etherscan.io/api-endpoints/tokens) Token API methods migrated to [Account](#account-api) & [Stats](#stats-api) respectfully. diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 1b7bce6..11bb192 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -64,7 +64,7 @@ public Balance balance(String address) throws EtherScanException { if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new Balance(address, new BigInteger(response.getResult())); + return new Balance(address, new Wei(new BigInteger(response.getResult()))); } @NotNull @@ -78,7 +78,7 @@ public TokenBalance balance(String address, String contract) throws EtherScanExc if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new TokenBalance(address, new BigInteger(response.getResult()), contract); + return new TokenBalance(address, new Wei(new BigInteger(response.getResult())), contract); } @NotNull @@ -101,7 +101,7 @@ public List<Balance> balances(List<String> addresses) throws EtherScanException if (!BasicUtils.isEmpty(response.getResult())) balances.addAll(response.getResult().stream() - .map(r -> new Balance(r.getAccount(), new BigInteger(r.getBalance()))) + .map(r -> new Balance(r.getAccount(), new Wei(new BigInteger(r.getBalance())))) .collect(Collectors.toList())); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java index 6da3d8f..dffb1aa 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -37,6 +37,11 @@ public interface EtherScanAPI extends AutoCloseable { @NotNull GasTrackerAPI gasTracker(); + @NotNull + static EtherScanAPI build() { + return builder().build(); + } + @NotNull static Builder builder() { return new EthScanAPIBuilder(); 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 783b7d8..079d4b6 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java @@ -1,6 +1,5 @@ package io.goodforgod.api.etherscan.model; -import java.math.BigInteger; import java.util.Objects; /** @@ -13,9 +12,9 @@ public class Balance { private final Wei balance; private final String address; - public Balance(String address, BigInteger balance) { + public Balance(String address, Wei balance) { this.address = address; - this.balance = new Wei(balance); + this.balance = balance; } // <editor-fold desc="Getters"> 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 d42fd05..0c1a5b5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java @@ -1,6 +1,5 @@ package io.goodforgod.api.etherscan.model; -import java.math.BigInteger; import java.util.Objects; /** @@ -11,7 +10,7 @@ public class TokenBalance extends Balance { private final String tokenContract; - public TokenBalance(String address, BigInteger balance, String tokenContract) { + public TokenBalance(String address, Wei balance, String tokenContract) { super(address, balance); this.tokenContract = tokenContract; } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java index f22a724..ed537c6 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountBalanceTests.java @@ -18,11 +18,7 @@ class AccountBalanceTests extends ApiRunner { void correct() { Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3b4F"); assertNotNull(balance); - assertNotNull(balance.getWei()); - assertNotNull(balance.getMwei()); - assertNotNull(balance.getKwei()); - assertNotNull(balance.getGwei()); - assertNotNull(balance.getEther()); + assertNotNull(balance.getBalanceInWei()); assertNotNull(balance.getAddress()); assertNotNull(balance.toString()); } @@ -37,8 +33,8 @@ void invalidParamWithError() { void correctParamWithEmptyExpectedResult() { Balance balance = api.account().balance("0x1d4426f94e42f721C7116E81d6688cd935cB3b4F"); assertNotNull(balance); - assertNotNull(balance.getWei()); + assertNotNull(balance.getBalanceInWei()); assertNotNull(balance.getAddress()); - assertEquals(0, balance.getWei().intValue()); + assertEquals(0, balance.getBalanceInWei().asWei().intValue()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java index 4a7d921..3919982 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTokenBalanceTests.java @@ -19,12 +19,12 @@ void correct() { TokenBalance balance = api.account().balance("0x5d807e7F124EC2103a59c5249187f772c0b8D6b2", "0x5EaC95ad5b287cF44E058dCf694419333b796123"); assertNotNull(balance); - assertNotNull(balance.getWei()); + assertNotNull(balance.getBalanceInWei()); assertNotNull(balance.getAddress()); assertNotNull(balance.getContract()); assertNotNull(balance.toString()); - TokenBalance balance2 = new TokenBalance("125161", balance.getWei(), balance.getContract()); + TokenBalance balance2 = new TokenBalance("125161", balance.getBalanceInWei(), balance.getContract()); assertNotEquals(balance, balance2); assertNotEquals(balance.hashCode(), balance2.hashCode()); } @@ -48,9 +48,9 @@ void correctParamWithEmptyExpectedResult() { TokenBalance balance = api.account().balance("0x1d807e7F124EC2103a59c5249187f772c0b8D6b2", "0x5EaC95ad5b287cF44E058dCf694419333b796123"); assertNotNull(balance); - assertNotNull(balance.getWei()); + assertNotNull(balance.getBalanceInWei()); assertNotNull(balance.getAddress()); assertNotNull(balance.getContract()); - assertEquals(0, balance.getWei().intValue()); + assertEquals(0, balance.getBalanceInWei().asWei().intValue()); } } From 3210c397fa652629f1ff0ba7cc7e198b26879976 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 21:39:08 +0300 Subject: [PATCH 33/67] [2.0.0-SNAPSHOT] EthHttpClient contract refactored to response with byte[] Converter contract refactored to receive byte[] --- .../api/etherscan/BasicProvider.java | 16 +++---- .../api/etherscan/BlockAPIProvider.java | 8 +++- .../api/etherscan/ContractAPIProvider.java | 3 +- .../goodforgod/api/etherscan/Converter.java | 2 +- .../api/etherscan/EthScanAPIBuilder.java | 6 ++- .../api/etherscan/http/EthHttpClient.java | 6 +-- .../etherscan/http/impl/UrlEthHttpClient.java | 42 +++++++++---------- .../manager/RequestQueueManager.java | 7 +++- .../goodforgod/api/etherscan/model/Wei.java | 4 ++ .../etherscan/model/ModelBuilderTests.java | 1 + 10 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index f1867d1..998f475 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -7,7 +7,6 @@ import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.response.StringResponseTO; -import io.goodforgod.api.etherscan.util.BasicUtils; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -44,7 +43,7 @@ abstract class BasicProvider { this.converter = converter; } - <T> T convert(String json, Class<T> tClass) { + <T> T convert(byte[] json, Class<T> tClass) { try { final T t = converter.fromJson(json, tClass); if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith("Max rate limit reached")) { @@ -53,32 +52,33 @@ <T> T convert(String json, Class<T> tClass) { return t; } catch (Exception e) { + final String jsonAsString = new String(json, StandardCharsets.UTF_8); try { final Map<String, Object> map = converter.fromJson(json, Map.class); final Object result = map.get("result"); if (result instanceof String && ((String) result).startsWith("Max rate limit reached")) throw new EtherScanRateLimitException(((String) result)); - throw new EtherScanParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); + throw new EtherScanParseException(e.getMessage() + ", for response: " + jsonAsString, e.getCause(), jsonAsString); } catch (EtherScanException ex) { throw ex; } catch (Exception ex) { - throw new EtherScanParseException(e.getMessage() + ", for response: " + json, e.getCause(), json); + throw new EtherScanParseException(e.getMessage() + ", for response: " + jsonAsString, e.getCause(), jsonAsString); } } } - String getRequest(String urlParameters) { + byte[] getRequest(String urlParameters) { queue.takeTurn(); final URI uri = URI.create(baseUrl + module + urlParameters); - final String result = executor.get(uri); - if (BasicUtils.isEmpty(result)) + final byte[] result = executor.get(uri); + if (result.length == 0) throw new EtherScanResponseException("Server returned null value for GET request at URL - " + uri); return result; } - String postRequest(String urlParameters, String dataToPost) { + 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)); diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java index 98a2d90..41d86dd 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -33,11 +33,15 @@ final class BlockAPIProvider extends BasicProvider implements BlockAPI { @Override public Optional<BlockUncle> uncles(long blockNumber) throws EtherScanException { final String urlParam = ACT_BLOCK_PARAM + BLOCKNO_PARAM + blockNumber; - final String response = getRequest(urlParam); - if (BasicUtils.isEmpty(response) || response.contains("NOTOK")) + final byte[] response = getRequest(urlParam); + if (response.length == 0) return Optional.empty(); final UncleBlockResponseTO responseTO = convert(response, UncleBlockResponseTO.class); + if (responseTO.getMessage().equals("NOTOK")) { + return Optional.empty(); + } + BasicUtils.validateTxResponse(responseTO); return (responseTO.getResult() == null || responseTO.getResult().isEmpty()) ? 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 1a8fa9a..7b75240 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -36,8 +36,9 @@ public Abi contractAbi(String address) throws EtherScanException { final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; final StringResponseTO response = getRequest(urlParam, StringResponseTO.class); - if (response.getStatus() != 1 && "NOTOK".equals(response.getMessage())) + if (response.getStatus() != 1 && "NOTOK".equals(response.getMessage())) { throw new EtherScanResponseException(response); + } return (response.getResult().startsWith("Contract sou")) ? Abi.nonVerified() diff --git a/src/main/java/io/goodforgod/api/etherscan/Converter.java b/src/main/java/io/goodforgod/api/etherscan/Converter.java index e8c577a..4025839 100644 --- a/src/main/java/io/goodforgod/api/etherscan/Converter.java +++ b/src/main/java/io/goodforgod/api/etherscan/Converter.java @@ -9,5 +9,5 @@ public interface Converter { @NotNull - <T> T fromJson(@NotNull String json, @NotNull Class<T> type); + <T> T fromJson(byte[] jsonAsByteArray, @NotNull Class<T> type); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index c9c1102..69474d9 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -7,6 +7,7 @@ import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.util.BasicUtils; import io.goodforgod.gson.configuration.GsonConfiguration; +import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; @@ -28,8 +29,9 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { private Supplier<Converter> converterSupplier = () -> new Converter() { @Override - public <T> @NotNull T fromJson(@NotNull String json, @NotNull Class<T> type) { - return gson.fromJson(json, type); + public <T> @NotNull T fromJson(byte[] jsonAsByteArray, @NotNull Class<T> type) { + final String jsonAsString = new String(jsonAsByteArray, StandardCharsets.UTF_8); + return gson.fromJson(jsonAsString, type); } }; diff --git a/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java index f4b559d..bd01f83 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java @@ -17,8 +17,7 @@ public interface EthHttpClient { * @param uri as string * @return result as string */ - @NotNull - String get(@NotNull URI uri); + byte[] get(@NotNull URI uri); /** * Performs a Http POST request @@ -27,6 +26,5 @@ public interface EthHttpClient { * @param body to post * @return result as string */ - @NotNull - String post(@NotNull URI uri, byte[] body); + byte[] post(@NotNull URI uri, byte[] body); } diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java index 57c970b..b298743 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java @@ -5,15 +5,11 @@ import io.goodforgod.api.etherscan.error.EtherScanConnectionException; import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; import io.goodforgod.api.etherscan.http.EthHttpClient; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; +import java.io.*; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; import java.util.HashMap; @@ -83,7 +79,7 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep } @Override - public @NotNull String get(@NotNull URI uri) { + public byte[] get(@NotNull URI uri) { try { final HttpURLConnection connection = buildConnection(uri, "GET"); final int status = connection.getResponseCode(); @@ -95,7 +91,7 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep throw new EtherScanConnectionException("Server error: " + connection.getResponseMessage()); } - final String data = readData(connection); + final byte[] data = readData(connection); connection.disconnect(); return data; } catch (SocketTimeoutException e) { @@ -106,7 +102,7 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep } @Override - public @NotNull String post(@NotNull URI uri, byte[] body) { + public byte[] post(@NotNull URI uri, byte[] body) { try { final HttpURLConnection connection = buildConnection(uri, "POST"); final int contentLength = body.length; @@ -129,7 +125,7 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep throw new EtherScanConnectionException("Server error: " + connection.getResponseMessage()); } - final String data = readData(connection); + final byte[] data = readData(connection); connection.disconnect(); return data; } catch (SocketTimeoutException e) { @@ -139,25 +135,29 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep } } - private String readData(HttpURLConnection connection) throws IOException { - final StringBuilder content = new StringBuilder(); - try (BufferedReader in = new BufferedReader(getStreamReader(connection))) { - String inputLine; - while ((inputLine = in.readLine()) != null) - content.append(inputLine); - } + private byte[] readData(HttpURLConnection connection) throws IOException { + try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + try (InputStream in = getStreamReader(connection)) { + byte[] data = new byte[256]; + int nRead; + while ((nRead = in.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + } - return content.toString(); + buffer.flush(); + return buffer.toByteArray(); + } } - private InputStreamReader getStreamReader(HttpURLConnection connection) throws IOException { + private InputStream getStreamReader(HttpURLConnection connection) throws IOException { switch (String.valueOf(connection.getContentEncoding())) { case "gzip": - return new InputStreamReader(new GZIPInputStream(connection.getInputStream()), StandardCharsets.UTF_8); + return new GZIPInputStream(connection.getInputStream()); case "deflate": - return new InputStreamReader(new InflaterInputStream(connection.getInputStream()), StandardCharsets.UTF_8); + return new InflaterInputStream(connection.getInputStream()); default: - return new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8); + return connection.getInputStream(); } } } 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 46a76e2..2fdfe82 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -5,8 +5,8 @@ import java.time.Duration; /** - * Queue manager to support API limits (EtherScan 5request\sec limit) Managers grants turn if the - * limit is not exhausted And resets queue each set period + * Queue manager to support API limits + * Manager grants turn if the limit is not exhausted And resets queue each set period * * @author GoodforGod * @since 30.10.2018 @@ -23,6 +23,9 @@ public interface RequestQueueManager extends AutoCloseable { * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> */ RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1010L)); + RequestQueueManager STANDARD_PLAN = new SemaphoreRequestQueueManager(10, Duration.ofMillis(1010L)); + RequestQueueManager ADVANCED_PLAN = new SemaphoreRequestQueueManager(20, Duration.ofMillis(1010L)); + RequestQueueManager PROFESSIONAL_PLAN = new SemaphoreRequestQueueManager(30, Duration.ofMillis(1010L)); RequestQueueManager UNLIMITED = new FakeRequestQueueManager(); diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index cb136df..e23ea51 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -11,6 +11,10 @@ public class Wei { private final BigInteger result; + public Wei(int value) { + this.result = BigInteger.valueOf(value); + } + public Wei(long value) { this.result = BigInteger.valueOf(value); } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 7db6aae..1bec491 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -252,6 +252,7 @@ void txInternalBuilder() { .withContractAddress("1") .withFrom("1") .withTo("1") + .withValue(BigInteger.ONE) .withGas(BigInteger.ONE) .withGasUsed(BigInteger.ONE) .withHash("1") From f5b2edb7db033600bc7e54b4c60836306554c5a6 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 22:06:44 +0300 Subject: [PATCH 34/67] [2.0.0-SNAPSHOT] EtherScanLogQueryException name fixed EtherScanResponseException response entity added StringResponseTO#builder added BasicProvider error handling improved --- .../api/etherscan/BasicProvider.java | 26 +++++++++++--- .../api/etherscan/BlockAPIProvider.java | 30 ++++++++++------ .../api/etherscan/ContractAPIProvider.java | 2 +- .../api/etherscan/EtherScanAPI.java | 5 --- .../api/etherscan/ProxyAPIProvider.java | 12 +++++-- ...n.java => EtherScanLogQueryException.java} | 4 +-- .../error/EtherScanResponseException.java | 14 ++++++-- .../model/query/LogQueryBuilderImpl.java | 24 ++++++------- .../etherscan/model/query/LogTopicQuadro.java | 14 ++++---- .../etherscan/model/query/LogTopicSingle.java | 4 +-- .../etherscan/model/query/LogTopicTriple.java | 10 +++--- .../etherscan/model/query/LogTopicTuple.java | 6 ++-- .../model/response/BaseResponseTO.java | 4 +-- .../model/response/StringResponseTO.java | 36 +++++++++++++++++++ .../api/etherscan/util/BasicUtils.java | 12 +++++-- .../etherscan/logs/LogQueryBuilderTests.java | 36 +++++++++---------- 16 files changed, 160 insertions(+), 79 deletions(-) rename src/main/java/io/goodforgod/api/etherscan/error/{ErtherScanLogQueryException.java => EtherScanLogQueryException.java} (50%) diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 998f475..3c88f3b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -20,6 +20,8 @@ */ abstract class BasicProvider { + private static final String MAX_RATE_LIMIT_REACHED = "Max rate limit reached"; + static final int MAX_END_BLOCK = Integer.MAX_VALUE; static final int MIN_START_BLOCK = 0; @@ -46,17 +48,26 @@ abstract class BasicProvider { <T> T convert(byte[] json, Class<T> tClass) { try { final T t = converter.fromJson(json, tClass); - if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith("Max rate limit reached")) { + if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith(MAX_RATE_LIMIT_REACHED)) { throw new EtherScanRateLimitException(((StringResponseTO) t).getResult()); } return t; } catch (Exception e) { + final StringResponseTO response = converter.fromJson(json, StringResponseTO.class); + if (response.getResult() != null && response.getStatus() == 0) { + if (response.getResult().startsWith(MAX_RATE_LIMIT_REACHED)) { + throw new EtherScanRateLimitException(response.getResult()); + } else { + throw new EtherScanResponseException(response); + } + } + final String jsonAsString = new String(json, StandardCharsets.UTF_8); try { final Map<String, Object> map = converter.fromJson(json, Map.class); final Object result = map.get("result"); - if (result instanceof String && ((String) result).startsWith("Max rate limit reached")) + if (result instanceof String && ((String) result).startsWith(MAX_RATE_LIMIT_REACHED)) throw new EtherScanRateLimitException(((String) result)); throw new EtherScanParseException(e.getMessage() + ", for response: " + jsonAsString, e.getCause(), jsonAsString); @@ -72,8 +83,15 @@ byte[] getRequest(String urlParameters) { queue.takeTurn(); final URI uri = URI.create(baseUrl + module + urlParameters); final byte[] result = executor.get(uri); - if (result.length == 0) - throw new EtherScanResponseException("Server returned null value for GET request at URL - " + uri); + if (result.length == 0) { + final StringResponseTO emptyResponse = StringResponseTO.builder() + .withStatus("0") + .withMessage("Server returned null value for GET request at URL - " + uri) + .withResult("") + .build(); + + throw new EtherScanResponseException(emptyResponse, "Server returned null value for GET request at URL - " + uri); + } return result; } diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java index 41d86dd..406ac19 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.BlockUncle; @@ -12,8 +13,8 @@ /** * Block API Implementation * - * @see BlockAPI * @author GoodforGod + * @see BlockAPI * @since 28.10.2018 */ final class BlockAPIProvider extends BasicProvider implements BlockAPI { @@ -34,17 +35,26 @@ final class BlockAPIProvider extends BasicProvider implements BlockAPI { public Optional<BlockUncle> 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(); - - final UncleBlockResponseTO responseTO = convert(response, UncleBlockResponseTO.class); - if (responseTO.getMessage().equals("NOTOK")) { + if (response.length == 0) { return Optional.empty(); } - BasicUtils.validateTxResponse(responseTO); - return (responseTO.getResult() == null || responseTO.getResult().isEmpty()) - ? Optional.empty() - : Optional.of(responseTO.getResult()); + try { + final UncleBlockResponseTO responseTO = convert(response, UncleBlockResponseTO.class); + if (responseTO.getMessage().startsWith("NOTOK")) { + return Optional.empty(); + } + + BasicUtils.validateTxResponse(responseTO); + return (responseTO.getResult() == null || responseTO.getResult().isEmpty()) + ? Optional.empty() + : Optional.of(responseTO.getResult()); + } catch (EtherScanResponseException e) { + if (e.getResponse().getMessage().startsWith("NOTOK")) { + return Optional.empty(); + } else { + throw e; + } + } } } diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java index 7b75240..bbb7335 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -36,7 +36,7 @@ public Abi contractAbi(String address) throws EtherScanException { final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; final StringResponseTO response = getRequest(urlParam, StringResponseTO.class); - if (response.getStatus() != 1 && "NOTOK".equals(response.getMessage())) { + if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) { throw new EtherScanResponseException(response); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java index dffb1aa..6da3d8f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -37,11 +37,6 @@ public interface EtherScanAPI extends AutoCloseable { @NotNull GasTrackerAPI gasTracker(); - @NotNull - static EtherScanAPI build() { - return builder().build(); - } - @NotNull static Builder builder() { return new EthScanAPIBuilder(); diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index 27e00df..a33f7c1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -13,6 +13,7 @@ import io.goodforgod.api.etherscan.model.proxy.utility.StringProxyTO; import io.goodforgod.api.etherscan.model.proxy.utility.TxInfoProxyTO; import io.goodforgod.api.etherscan.model.proxy.utility.TxProxyTO; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.Optional; import java.util.regex.Pattern; @@ -142,10 +143,17 @@ public Optional<String> txSendRaw(String hexEncodedTx) throws EtherScanException final String urlParams = ACT_SEND_RAW_TX_PARAM + HEX_PARAM + hexEncodedTx; final StringProxyTO response = postRequest(urlParams, "", StringProxyTO.class); - if (response.getError() != null) - throw new EtherScanResponseException("Error occurred with code " + response.getError().getCode() + if (response.getError() != null) { + final StringResponseTO responseError = StringResponseTO.builder() + .withStatus("0") + .withMessage(response.getError().getMessage()) + .withResult(response.getError().getCode()) + .build(); + + throw new EtherScanResponseException(responseError, "Error occurred with code " + response.getError().getCode() + " with message " + response.getError().getMessage() + ", error id " + response.getId() + ", jsonRPC " + response.getJsonrpc()); + } return Optional.ofNullable(response.getResult()); } diff --git a/src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanLogQueryException.java similarity index 50% rename from src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java rename to src/main/java/io/goodforgod/api/etherscan/error/EtherScanLogQueryException.java index b39dcee..e72d682 100644 --- a/src/main/java/io/goodforgod/api/etherscan/error/ErtherScanLogQueryException.java +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanLogQueryException.java @@ -4,9 +4,9 @@ * @author GoodforGod * @since 31.10.2018 */ -public class ErtherScanLogQueryException extends EtherScanException { +public class EtherScanLogQueryException extends EtherScanException { - public ErtherScanLogQueryException(String message) { + public EtherScanLogQueryException(String message) { super(message); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java index 21da798..19785ce 100644 --- a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanResponseException.java @@ -9,15 +9,23 @@ */ public class EtherScanResponseException extends EtherScanException { + private final transient BaseResponseTO response; + public EtherScanResponseException(BaseResponseTO response) { - this(response.getMessage() + ", with status: " + response.getStatus()); + this(response, response.getMessage() + ", with status: " + response.getStatus()); } public EtherScanResponseException(StringResponseTO response) { - this(response.getResult() + ", with status: " + response.getStatus() + ", with message: " + response.getMessage()); + this(response, + response.getResult() + ", with status: " + response.getStatus() + ", with message: " + response.getMessage()); } - public EtherScanResponseException(String message) { + public EtherScanResponseException(BaseResponseTO response, String message) { super(message); + this.response = response; + } + + public BaseResponseTO getResponse() { + return response; } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java index 716cfa4..549bd47 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java @@ -1,7 +1,7 @@ package io.goodforgod.api.etherscan.model.query; import io.goodforgod.api.etherscan.LogsAPI; -import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; import io.goodforgod.api.etherscan.util.BasicUtils; import org.jetbrains.annotations.NotNull; @@ -40,27 +40,27 @@ final class LogQueryBuilderImpl implements LogQuery.Builder { @Override public @NotNull LogTopicSingle withTopic(@NotNull String topic0) { if (BasicUtils.isNotHex(topic0)) - throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic0 can not be empty or non hex."); return new LogTopicSingle(address, startBlock, endBlock, topic0); } @Override public @NotNull LogTopicTuple withTopic(@NotNull String topic0, @NotNull String topic1) { if (BasicUtils.isNotHex(topic0)) - throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic0 can not be empty or non hex."); if (BasicUtils.isNotHex(topic1)) - throw new ErtherScanLogQueryException("topic1 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic1 can not be empty or non hex."); return new LogTopicTuple(address, startBlock, endBlock, topic0, topic1); } @Override public @NotNull LogTopicTriple withTopic(@NotNull String topic0, @NotNull String topic1, @NotNull String topic2) { if (BasicUtils.isNotHex(topic0)) - throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic0 can not be empty or non hex."); if (BasicUtils.isNotHex(topic1)) - throw new ErtherScanLogQueryException("topic1 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic1 can not be empty or non hex."); if (BasicUtils.isNotHex(topic2)) - throw new ErtherScanLogQueryException("topic2 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic2 can not be empty or non hex."); return new LogTopicTriple(address, startBlock, endBlock, topic0, topic1, topic2); } @@ -68,19 +68,19 @@ final class LogQueryBuilderImpl implements LogQuery.Builder { public @NotNull LogTopicQuadro withTopic(@NotNull String topic0, @NotNull String topic1, @NotNull String topic2, @NotNull String topic3) { if (BasicUtils.isNotHex(topic0)) - throw new ErtherScanLogQueryException("topic0 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic0 can not be empty or non hex."); if (BasicUtils.isNotHex(topic1)) - throw new ErtherScanLogQueryException("topic1 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic1 can not be empty or non hex."); if (BasicUtils.isNotHex(topic2)) - throw new ErtherScanLogQueryException("topic2 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic2 can not be empty or non hex."); if (BasicUtils.isNotHex(topic3)) - throw new ErtherScanLogQueryException("topic3 can not be empty or non hex."); + throw new EtherScanLogQueryException("topic3 can not be empty or non hex."); return new LogTopicQuadro(address, startBlock, endBlock, topic0, topic1, topic2, topic3); } @Override - public @NotNull LogQuery build() throws ErtherScanLogQueryException { + public @NotNull LogQuery build() throws EtherScanLogQueryException { return new LogQueryImpl("&address=" + this.address + "&fromBlock=" + this.startBlock + "&toBlock=" + this.endBlock); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java index 1469f97..7fdd9db 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java @@ -3,7 +3,7 @@ import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; import io.goodforgod.api.etherscan.LogsAPI; -import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; import org.jetbrains.annotations.NotNull; /** @@ -71,17 +71,17 @@ public LogTopicQuadro setOpTopic1_3(LogOp topic1_3_opr) { @Override public @NotNull LogQuery build() { if (topic0_1_opr == null) - throw new ErtherScanLogQueryException("topic0_1_opr can not be null."); + throw new EtherScanLogQueryException("topic0_1_opr can not be null."); if (topic0_2_opr == null) - throw new ErtherScanLogQueryException("topic0_2_opr can not be null."); + throw new EtherScanLogQueryException("topic0_2_opr can not be null."); if (topic0_3_opr == null) - throw new ErtherScanLogQueryException("topic0_3_opr can not be null."); + throw new EtherScanLogQueryException("topic0_3_opr can not be null."); if (topic1_2_opr == null) - throw new ErtherScanLogQueryException("topic1_2_opr can not be null."); + throw new EtherScanLogQueryException("topic1_2_opr can not be null."); if (topic2_3_opr == null) - throw new ErtherScanLogQueryException("topic2_3_opr can not be null."); + throw new EtherScanLogQueryException("topic2_3_opr can not be null."); if (topic1_3_opr == null) - throw new ErtherScanLogQueryException("topic1_3_opr can not be null."); + throw new EtherScanLogQueryException("topic1_3_opr can not be null."); return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java index 85bd18c..a736ffa 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java @@ -3,7 +3,7 @@ import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; import io.goodforgod.api.etherscan.LogsAPI; -import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; import org.jetbrains.annotations.NotNull; /** @@ -29,7 +29,7 @@ public final class LogTopicSingle implements LogTopicBuilder { } @Override - public @NotNull LogQuery build() throws ErtherScanLogQueryException { + public @NotNull LogQuery build() throws EtherScanLogQueryException { return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock + TOPIC_0_PARAM + topic0); diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java index d56edb5..ac9efb8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java @@ -3,7 +3,7 @@ import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; import io.goodforgod.api.etherscan.LogsAPI; -import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; import org.jetbrains.annotations.NotNull; /** @@ -52,13 +52,13 @@ public LogTopicTriple setOpTopic1_2(LogOp topic1_2_opr) { } @Override - public @NotNull LogQuery build() throws ErtherScanLogQueryException { + public @NotNull LogQuery build() throws EtherScanLogQueryException { if (topic0_1_opr == null) - throw new ErtherScanLogQueryException("topic0_1_opr can not be null."); + throw new EtherScanLogQueryException("topic0_1_opr can not be null."); if (topic0_2_opr == null) - throw new ErtherScanLogQueryException("topic0_2_opr can not be null."); + throw new EtherScanLogQueryException("topic0_2_opr can not be null."); if (topic1_2_opr == null) - throw new ErtherScanLogQueryException("topic1_2_opr can not be null."); + throw new EtherScanLogQueryException("topic1_2_opr can not be null."); return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java index 95a78a4..2ef2bba 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java @@ -3,7 +3,7 @@ import static io.goodforgod.api.etherscan.model.query.LogQueryParams.*; import io.goodforgod.api.etherscan.LogsAPI; -import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; +import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; import org.jetbrains.annotations.NotNull; /** @@ -40,9 +40,9 @@ public LogTopicTuple setOpTopic0_1(LogOp topic0_1_opr) { } @Override - public @NotNull LogQuery build() throws ErtherScanLogQueryException { + public @NotNull LogQuery build() throws EtherScanLogQueryException { if (topic0_1_opr == null) - throw new ErtherScanLogQueryException("topic0_1_opr can not be null."); + throw new EtherScanLogQueryException("topic0_1_opr can not be null."); return new LogQueryImpl(ADDRESS_PARAM + address + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java index 3e100d1..46c6ca0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/BaseResponseTO.java @@ -8,8 +8,8 @@ */ public abstract class BaseResponseTO { - private String status; - private String message; + String status; + String message; public int getStatus() { return BasicUtils.isEmpty(status) diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java index 4fb9f04..19fa0a1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java @@ -11,4 +11,40 @@ public class StringResponseTO extends BaseResponseTO { public String getResult() { return result; } + + public static StringResponseBuilder builder() { + return new StringResponseBuilder(); + } + + public static final class StringResponseBuilder { + + private String status; + private String message; + private String result; + + private StringResponseBuilder() {} + + public StringResponseBuilder withStatus(String status) { + this.status = status; + return this; + } + + public StringResponseBuilder withMessage(String message) { + this.message = message; + return this; + } + + public StringResponseBuilder withResult(String result) { + this.result = result; + return this; + } + + public StringResponseTO build() { + StringResponseTO stringResponseTO = new StringResponseTO(); + stringResponseTO.status = this.status; + stringResponseTO.message = this.message; + stringResponseTO.result = this.result; + return stringResponseTO; + } + } } 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 eda3ce2..216ab62 100644 --- a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java @@ -5,6 +5,7 @@ import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.model.response.BaseResponseTO; import io.goodforgod.api.etherscan.model.response.BlockParam; +import io.goodforgod.api.etherscan.model.response.StringResponseTO; import java.math.BigInteger; import java.util.*; import java.util.regex.Pattern; @@ -98,12 +99,17 @@ public static void validateTxHash(String txhash) { } public static <T extends BaseResponseTO> void validateTxResponse(T response) { - if (response == null) - throw new EtherScanResponseException("EtherScan responded with null value"); + if (response == null) { + final StringResponseTO emptyResponse = StringResponseTO.builder() + .withStatus("0") + .withMessage("EtherScan responded with null value") + .build(); + throw new EtherScanResponseException(emptyResponse, "EtherScan responded with null value"); + } if (response.getStatus() != 1) { if (response.getMessage() == null) { - throw new EtherScanResponseException( + throw new EtherScanResponseException(response, "Unexpected Etherscan exception, no information from server about error, code " + response.getStatus()); } else if (!response.getMessage().startsWith("No tra") && !response.getMessage().startsWith("No rec")) { throw new EtherScanResponseException(response); diff --git a/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java index 339f07e..955443c 100644 --- a/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/logs/LogQueryBuilderTests.java @@ -1,8 +1,8 @@ package io.goodforgod.api.etherscan.logs; import io.goodforgod.api.etherscan.ApiRunner; -import io.goodforgod.api.etherscan.error.ErtherScanLogQueryException; import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; import io.goodforgod.api.etherscan.model.query.*; import org.junit.jupiter.api.Test; @@ -32,7 +32,7 @@ void singleInCorrectAddress() { @Test void singleInCorrectTopic() { - assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic("6516=") .build()); } @@ -51,7 +51,7 @@ void tupleCorrect() { @Test void tupleInCorrectOp() { - assertThrows(ErtherScanLogQueryException.class, + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224) .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000") @@ -76,7 +76,7 @@ void tripleCorrect() { @Test void tripleInCorrectOp() { - assertThrows(ErtherScanLogQueryException.class, + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -89,7 +89,7 @@ void tripleInCorrectOp() { @Test void tripleInCorrectTopic1() { - assertThrows(ErtherScanLogQueryException.class, + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) .withTopic(null, "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -102,7 +102,7 @@ void tripleInCorrectTopic1() { @Test void tripleInCorrectTopic2() { - assertThrows(ErtherScanLogQueryException.class, + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null, @@ -115,7 +115,7 @@ void tripleInCorrectTopic2() { @Test void tripleInCorrectTopic3() { - assertThrows(ErtherScanLogQueryException.class, + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c").withBlockFrom(379224).withBlockTo(400000) .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -147,7 +147,7 @@ void quadroCorrect() { @Test void quadroIncorrectTopic2() { - assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null, "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -163,7 +163,7 @@ void quadroIncorrectTopic2() { @Test void tupleIncorrectTopic2() { - assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", null) .setOpTopic0_1(LogOp.AND) @@ -172,7 +172,7 @@ void tupleIncorrectTopic2() { @Test void tupleIncorrectTopic1() { - assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic(null, "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .setOpTopic0_1(LogOp.AND) @@ -187,7 +187,7 @@ void quadroIncorrectOp1() { "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro + assertThrows(EtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(null) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -205,7 +205,7 @@ void quadroIncorrectOp2() { "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro.setOpTopic0_1(LogOp.AND) + assertThrows(EtherScanLogQueryException.class, () -> topicQuadro.setOpTopic0_1(LogOp.AND) .setOpTopic0_2(null) .setOpTopic0_3(LogOp.AND) .setOpTopic1_2(LogOp.OR) @@ -222,7 +222,7 @@ void quadroIncorrectOp3() { "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000"); - assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro + assertThrows(EtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(null) @@ -234,7 +234,7 @@ void quadroIncorrectOp3() { @Test void quadroInCorrectAgainTopic() { - assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "0x72657075746174696f6e00000000000000000000000000000000000000000000", @@ -256,7 +256,7 @@ void quadroInCorrectOp4() { "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro + assertThrows(EtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -274,7 +274,7 @@ void quadroInCorrectOp5() { "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro + assertThrows(EtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -292,7 +292,7 @@ void quadroInCorrectOp6() { "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"); - assertThrows(ErtherScanLogQueryException.class, () -> topicQuadro + assertThrows(EtherScanLogQueryException.class, () -> topicQuadro .setOpTopic0_1(LogOp.AND) .setOpTopic0_2(LogOp.OR) .setOpTopic0_3(LogOp.AND) @@ -304,7 +304,7 @@ void quadroInCorrectOp6() { @Test void quadroInCorrectTopic() { - assertThrows(ErtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + assertThrows(EtherScanLogQueryException.class, () -> LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545", "0x72657075746174696f6e00000000000000000000000000000000000000000000", "", From 25751ab85f0dd4d87fb78d3495ac20f8e0dd9da4 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 22:11:09 +0300 Subject: [PATCH 35/67] [2.0.0-SNAPSHOT] Default converter parsing optimized --- .../goodforgod/api/etherscan/EthScanAPIBuilder.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 69474d9..57aeeae 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -2,11 +2,15 @@ import com.google.gson.Gson; import io.goodforgod.api.etherscan.error.EtherScanKeyException; +import io.goodforgod.api.etherscan.error.EtherScanParseException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.util.BasicUtils; import io.goodforgod.gson.configuration.GsonConfiguration; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; @@ -30,8 +34,11 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { @Override public <T> @NotNull T fromJson(byte[] jsonAsByteArray, @NotNull Class<T> type) { - final String jsonAsString = new String(jsonAsByteArray, StandardCharsets.UTF_8); - return gson.fromJson(jsonAsString, type); + try (InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(jsonAsByteArray))) { + return gson.fromJson(isr, type); + } catch (IOException e) { + throw new EtherScanParseException(e.getMessage(), e, new String(jsonAsByteArray, StandardCharsets.UTF_8)); + } } }; From 47e04a832fb504c4e7469d77cf18beb865820b55 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 22:41:24 +0300 Subject: [PATCH 36/67] [2.0.0-SNAPSHOT] EthSupply for StatisticAPI#supplyTotal added Javadoc fixed --- .../goodforgod/api/etherscan/AccountAPI.java | 2 +- .../api/etherscan/AccountAPIProvider.java | 6 +- .../io/goodforgod/api/etherscan/BlockAPI.java | 2 +- .../goodforgod/api/etherscan/ContractAPI.java | 2 +- .../io/goodforgod/api/etherscan/LogsAPI.java | 2 +- .../io/goodforgod/api/etherscan/ProxyAPI.java | 3 +- .../api/etherscan/ProxyAPIProvider.java | 8 +- .../api/etherscan/StatisticAPI.java | 29 +++-- .../api/etherscan/StatisticAPIProvider.java | 24 +++- .../api/etherscan/TransactionAPI.java | 2 +- .../api/etherscan/model/EthSupply.java | 110 ++++++++++++++++++ .../api/etherscan/model/GasOracle.java | 6 +- .../api/etherscan/model/Supply.java | 18 --- .../goodforgod/api/etherscan/model/Wei.java | 40 ++++--- .../model/response/EthSupplyResponseTO.java | 16 +++ .../gastracker/GasTrackerApiTests.java | 2 +- .../etherscan/model/ModelBuilderTests.java | 8 +- .../statistic/StatisticPriceApiTests.java | 2 +- .../statistic/StatisticSupplyApiTests.java | 6 +- .../StatisticSupplyTotalApiTests.java | 28 +++++ .../StatisticTokenSupplyApiTests.java | 7 +- 21 files changed, 250 insertions(+), 73 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/Supply.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/response/EthSupplyResponseTO.java create mode 100644 src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyTotalApiTests.java diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java index 294fb2a..45be8b8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java @@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#accounts">...</a> + * EtherScan - API Descriptions <a href="https://docs.etherscan.io/api-endpoints/accounts">...</a> * * @author GoodforGod * @since 28.10.2018 diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 11bb192..e5b6bd9 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -64,7 +64,7 @@ public Balance balance(String address) throws EtherScanException { if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new Balance(address, new Wei(new BigInteger(response.getResult()))); + return new Balance(address, Wei.ofWei(new BigInteger(response.getResult()))); } @NotNull @@ -78,7 +78,7 @@ public TokenBalance balance(String address, String contract) throws EtherScanExc if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new TokenBalance(address, new Wei(new BigInteger(response.getResult())), contract); + return new TokenBalance(address, Wei.ofWei(new BigInteger(response.getResult())), contract); } @NotNull @@ -101,7 +101,7 @@ public List<Balance> balances(List<String> addresses) throws EtherScanException if (!BasicUtils.isEmpty(response.getResult())) balances.addAll(response.getResult().stream() - .map(r -> new Balance(r.getAccount(), new Wei(new BigInteger(r.getBalance())))) + .map(r -> new Balance(r.getAccount(), Wei.ofWei(new BigInteger(r.getBalance())))) .collect(Collectors.toList())); } diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java index 55a8c3b..fdacaf5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPI.java @@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#blocks">...</a> + * EtherScan - API Descriptions <a href="https://docs.etherscan.io/api-endpoints/blocks">...</a> * * @author GoodforGod * @since 30.10.2018 diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java index 9271347..7564c98 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#contracts">...</a> + * EtherScan - API Descriptions <a href="https://docs.etherscan.io/api-endpoints/contracts">...</a> * * @author GoodforGod * @since 28.10.2018 diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java index 5b834df..01d79f7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#logs">...</a> + * EtherScan - API Descriptions <a href="https://docs.etherscan.io/api-endpoints/logs">...</a> * * @author GoodforGod * @since 30.10.2018 diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java index b379290..77d6769 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java @@ -10,7 +10,8 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#proxy">...</a> + * EtherScan - API Descriptions + * <a href="https://docs.etherscan.io/api-endpoints/geth-parity-proxy">...</a> * * @author GoodforGod * @since 30.10.2018 diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index a33f7c1..18edd90 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -208,8 +208,8 @@ public Optional<String> storageAt(String address, long position) throws EtherSca public Wei gasPrice() throws EtherScanException { final StringProxyTO response = getRequest(ACT_GASPRICE_PARAM, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) - ? new Wei(0) - : new Wei(BasicUtils.parseHex(response.getResult())); + ? Wei.ofWei(0) + : Wei.ofWei(BasicUtils.parseHex(response.getResult())); } @NotNull @@ -227,7 +227,7 @@ public Wei gasEstimated(String hexData) throws EtherScanException { final String urlParams = ACT_ESTIMATEGAS_PARAM + DATA_PARAM + hexData + GAS_PARAM + "2000000000000000"; final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) - ? new Wei(0) - : new Wei(BasicUtils.parseHex(response.getResult())); + ? Wei.ofWei(0) + : Wei.ofWei(BasicUtils.parseHex(response.getResult())); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java index 314f73e..10e41e3 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java @@ -1,13 +1,13 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; +import io.goodforgod.api.etherscan.model.EthSupply; import io.goodforgod.api.etherscan.model.Price; -import io.goodforgod.api.etherscan.model.Supply; -import java.math.BigInteger; +import io.goodforgod.api.etherscan.model.Wei; import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#stats">...</a> + * EtherScan - API Descriptions <a href="https://docs.etherscan.io/api-endpoints/stats-1">...</a> * * @author GoodforGod * @since 30.10.2018 @@ -16,22 +16,35 @@ public interface StatisticAPI { /** * ERC20 token total Supply - * + * <a href= + * "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan<a> + * * @param contract contract address * @return token supply for specified contract * @throws EtherScanException parent exception class */ @NotNull - BigInteger supply(String contract) throws EtherScanException; + Wei supply(String contract) throws EtherScanException; /** - * Eth total supply + * Returns the current amount of Ether in circulation excluding ETH2 Staking rewards and EIP1559 + * burnt fees. * * @return total ETH supply for moment * @throws EtherScanException parent exception class */ @NotNull - Supply supply() throws EtherScanException; + Wei supply() throws EtherScanException; + + /** + * Returns the current amount of Ether in circulation, ETH2 Staking rewards, EIP1559 burnt fees, and + * total withdrawn ETH from the beacon chain. + * + * @return total ETH supply for moment + * @throws EtherScanException parent exception class + */ + @NotNull + EthSupply supplyTotal() throws EtherScanException; /** * Eth last USD and BTC price @@ -40,5 +53,5 @@ public interface StatisticAPI { * @throws EtherScanException parent exception class */ @NotNull - Price lastPrice() throws EtherScanException; + Price priceLast() throws EtherScanException; } diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java index 1d1bcee..9555169 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java @@ -4,8 +4,10 @@ import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.EthSupply; import io.goodforgod.api.etherscan.model.Price; -import io.goodforgod.api.etherscan.model.Supply; +import io.goodforgod.api.etherscan.model.Wei; +import io.goodforgod.api.etherscan.model.response.EthSupplyResponseTO; import io.goodforgod.api.etherscan.model.response.PriceResponseTO; import io.goodforgod.api.etherscan.model.response.StringResponseTO; import io.goodforgod.api.etherscan.util.BasicUtils; @@ -22,6 +24,7 @@ final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { private static final String ACT_SUPPLY_PARAM = ACT_PREFIX + "ethsupply"; + private static final String ACT_SUPPLY2_PARAM = ACT_PREFIX + "ethsupply2"; private static final String ACT_TOKEN_SUPPLY_PARAM = ACT_PREFIX + "tokensupply"; private static final String ACT_LASTPRICE_PARAM = ACT_PREFIX + "ethprice"; @@ -36,17 +39,26 @@ final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { @NotNull @Override - public Supply supply() throws EtherScanException { + public Wei supply() throws EtherScanException { final StringResponseTO response = getRequest(ACT_SUPPLY_PARAM, StringResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new Supply(new BigInteger(response.getResult())); + return Wei.ofWei(new BigInteger(response.getResult())); + } + + @Override + public @NotNull EthSupply supplyTotal() throws EtherScanException { + final EthSupplyResponseTO response = getRequest(ACT_SUPPLY2_PARAM, EthSupplyResponseTO.class); + if (response.getStatus() != 1) + throw new EtherScanResponseException(response); + + return response.getResult(); } @NotNull @Override - public BigInteger supply(String contract) throws EtherScanException { + public Wei supply(String contract) throws EtherScanException { BasicUtils.validateAddress(contract); final String urlParams = ACT_TOKEN_SUPPLY_PARAM + CONTRACT_ADDRESS_PARAM + contract; @@ -54,12 +66,12 @@ public BigInteger supply(String contract) throws EtherScanException { if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new BigInteger(response.getResult()); + return Wei.ofWei(new BigInteger(response.getResult())); } @NotNull @Override - public Price lastPrice() throws EtherScanException { + public Price priceLast() throws EtherScanException { final PriceResponseTO response = getRequest(ACT_LASTPRICE_PARAM, PriceResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java index 6bfc545..a89a4a6 100644 --- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java @@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull; /** - * EtherScan - API Descriptions <a href="https://etherscan.io/apis#transactions">...</a> + * EtherScan - API Descriptions <a href="https://docs.etherscan.io/api-endpoints/stats">...</a> * * @author GoodforGod * @since 30.10.2018 diff --git a/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java b/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java new file mode 100644 index 0000000..f967360 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java @@ -0,0 +1,110 @@ +package io.goodforgod.api.etherscan.model; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Please Add Description Here. + * + * @author Anton Kurako (GoodforGod) + * @since 14.05.2023 + */ +public class EthSupply { + + private String EthSupply; + private String Eth2Staking; + private String BurntFees; + private String WithdrawnTotal; + + public Wei getEthSupply() { + return Wei.ofWei(new BigInteger(EthSupply)); + } + + public Wei getEth2Staking() { + return Wei.ofWei(new BigInteger(Eth2Staking)); + } + + public Wei getBurntFees() { + return Wei.ofWei(new BigInteger(BurntFees)); + } + + public Wei getTotal() { + final BigInteger total = getEthSupply().asWei() + .add(getEth2Staking().asWei()) + .min(getBurntFees().asWei()); + return Wei.ofWei(total); + } + + public Wei getWithdrawnTotal() { + return Wei.ofWei(new BigInteger(WithdrawnTotal)); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof EthSupply)) + return false; + EthSupply ethSupply = (EthSupply) o; + return Objects.equals(EthSupply, ethSupply.EthSupply) && Objects.equals(Eth2Staking, ethSupply.Eth2Staking) + && Objects.equals(BurntFees, ethSupply.BurntFees) && Objects.equals(WithdrawnTotal, ethSupply.WithdrawnTotal); + } + + @Override + public int hashCode() { + return Objects.hash(EthSupply, Eth2Staking, BurntFees, WithdrawnTotal); + } + + @Override + public String toString() { + return "EthSupply{" + + "EthSupply='" + EthSupply + '\'' + + ", Eth2Staking='" + Eth2Staking + '\'' + + ", BurntFees='" + BurntFees + '\'' + + ", WithdrawnTotal='" + WithdrawnTotal + '\'' + + '}'; + } + + public static EthSupplyBuilder builder() { + return new EthSupplyBuilder(); + } + + public static final class EthSupplyBuilder { + + private Wei ethSupply; + private Wei eth2Staking; + private Wei burntFees; + private Wei withdrawnTotal; + + private EthSupplyBuilder() {} + + public EthSupplyBuilder withEthSupply(Wei ethSupply) { + this.ethSupply = ethSupply; + return this; + } + + public EthSupplyBuilder withEth2Staking(Wei eth2Staking) { + this.eth2Staking = eth2Staking; + return this; + } + + public EthSupplyBuilder withBurntFees(Wei burntFees) { + this.burntFees = burntFees; + return this; + } + + public EthSupplyBuilder withWithdrawnTotal(Wei withdrawnTotal) { + this.withdrawnTotal = withdrawnTotal; + return this; + } + + public EthSupply build() { + EthSupply ethSupply = new EthSupply(); + ethSupply.BurntFees = this.burntFees.toString(); + ethSupply.Eth2Staking = this.eth2Staking.toString(); + ethSupply.EthSupply = this.ethSupply.toString(); + ethSupply.WithdrawnTotal = this.withdrawnTotal.toString(); + return ethSupply; + } + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index 67dd82a..d273357 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -27,15 +27,15 @@ public Long getLastBlock() { } public Wei getSafeGasPriceInWei() { - return new Wei(BigInteger.valueOf(SafeGasPrice).multiply(BigInteger.TEN.pow(9))); + return Wei.ofWei(BigInteger.valueOf(SafeGasPrice).multiply(BigInteger.TEN.pow(9))); } public Wei getProposeGasPriceInWei() { - return new Wei(BigInteger.valueOf(ProposeGasPrice).multiply(BigInteger.TEN.pow(9))); + return Wei.ofWei(BigInteger.valueOf(ProposeGasPrice).multiply(BigInteger.TEN.pow(9))); } public Wei getFastGasPriceInWei() { - return new Wei(BigInteger.valueOf(FastGasPrice).multiply(BigInteger.TEN.pow(9))); + return Wei.ofWei(BigInteger.valueOf(FastGasPrice).multiply(BigInteger.TEN.pow(9))); } public Double getSuggestBaseFee() { diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Supply.java b/src/main/java/io/goodforgod/api/etherscan/model/Supply.java deleted file mode 100644 index 43e3a3f..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/Supply.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.goodforgod.api.etherscan.model; - -import java.math.BigInteger; - -/** - * @author GoodforGod - * @since 30.10.2018 - */ -public class Supply extends Wei { - - public Supply(long value) { - super(value); - } - - public Supply(BigInteger value) { - super(value); - } -} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index e23ea51..004b5e1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -11,16 +11,32 @@ public class Wei { private final BigInteger result; - public Wei(int value) { - this.result = BigInteger.valueOf(value); + private Wei(BigInteger value) { + this.result = value; } - public Wei(long value) { - this.result = BigInteger.valueOf(value); + public static Wei ofWei(int value) { + return new Wei(BigInteger.valueOf(value)); } - public Wei(BigInteger value) { - this.result = value; + public static Wei ofWei(long value) { + return new Wei(BigInteger.valueOf(value)); + } + + public static Wei ofWei(BigInteger value) { + return new Wei(value); + } + + public static Wei ofEther(int value) { + return new Wei(BigInteger.valueOf(value).multiply(BigInteger.valueOf(1_000_000_000_000_000L))); + } + + public static Wei ofEther(long value) { + return new Wei(BigInteger.valueOf(value).multiply(BigInteger.valueOf(1_000_000_000_000_000L))); + } + + public static Wei ofEther(BigInteger value) { + return new Wei(value.multiply(BigInteger.valueOf(1_000_000_000_000_000L))); } // <editor-fold desc="Getters"> @@ -29,19 +45,19 @@ public BigInteger asWei() { } public BigInteger asKwei() { - return result.divide(BigInteger.valueOf(1000)); + return result.divide(BigInteger.valueOf(1_000)); } public BigInteger asMwei() { - return result.divide(BigInteger.valueOf(1000000)); + return result.divide(BigInteger.valueOf(1_000_000)); } public BigInteger asGwei() { - return result.divide(BigInteger.valueOf(1000000000)); + return result.divide(BigInteger.valueOf(1_000_000_000)); } public BigInteger asEther() { - return result.divide(BigInteger.valueOf(1000000000000000L)); + return result.divide(BigInteger.valueOf(1_000_000_000_000_000L)); } // </editor-fold> @@ -62,8 +78,6 @@ public int hashCode() { @Override public String toString() { - return "Wei{" + - "result=" + result + - '}'; + return result.toString(); } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/EthSupplyResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/EthSupplyResponseTO.java new file mode 100644 index 0000000..edbc2e3 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/EthSupplyResponseTO.java @@ -0,0 +1,16 @@ +package io.goodforgod.api.etherscan.model.response; + +import io.goodforgod.api.etherscan.model.EthSupply; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +public class EthSupplyResponseTO extends BaseResponseTO { + + private EthSupply result; + + public EthSupply getResult() { + return result; + } +} diff --git a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java index 1d92eb4..53b1c2c 100644 --- a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java @@ -14,7 +14,7 @@ class GasTrackerApiTests extends ApiRunner { @Test void estimate() { - GasEstimate estimate = getApi().gasTracker().estimate(new Wei(123)); + GasEstimate estimate = getApi().gasTracker().estimate(Wei.ofWei(123)); assertNotNull(estimate); assertNotNull(estimate.getDuration()); } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 1bec491..9a7e426 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -65,16 +65,16 @@ void blockUncleBuilder() { @Test void gasOracleBuilder() { GasOracle value = GasOracle.builder() - .withFastGasPrice(new Wei(1000000000)) - .withProposeGasPrice(new Wei(1000000000)) - .withSafeGasPrice(new Wei(1000000000)) + .withFastGasPrice(Wei.ofWei(1000000000)) + .withProposeGasPrice(Wei.ofWei(1000000000)) + .withSafeGasPrice(Wei.ofWei(1000000000)) .withGasUsedRatio(Collections.singletonList(new BigDecimal(1))) .withLastBlock(1L) .withSuggestBaseFee(1.0) .build(); assertNotNull(value); - assertEquals(new Wei(1000000000), value.getFastGasPriceInWei()); + assertEquals(Wei.ofWei(1000000000), value.getFastGasPriceInWei()); } @Test diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java index 3525e21..0dd89c2 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java @@ -12,7 +12,7 @@ class StatisticPriceApiTests extends ApiRunner { @Test void correct() { - Price price = getApi().stats().lastPrice(); + Price price = getApi().stats().priceLast(); assertNotNull(price); assertNotNull(price.btcTimestamp()); assertNotNull(price.usdTimestamp()); diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java index 56469a9..6564c93 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyApiTests.java @@ -1,7 +1,7 @@ package io.goodforgod.api.etherscan.statistic; import io.goodforgod.api.etherscan.ApiRunner; -import io.goodforgod.api.etherscan.model.Supply; +import io.goodforgod.api.etherscan.model.Wei; import java.math.BigInteger; import org.junit.jupiter.api.Test; @@ -13,7 +13,7 @@ class StatisticSupplyApiTests extends ApiRunner { @Test void correct() { - Supply supply = getApi().stats().supply(); + Wei supply = getApi().stats().supply(); assertNotNull(supply); assertNotNull(supply.asWei()); assertNotNull(supply.asGwei()); @@ -22,7 +22,7 @@ void correct() { assertNotNull(supply.asEther()); assertNotNull(supply.toString()); - Supply empty = new Supply(BigInteger.ONE); + Wei empty = Wei.ofWei(BigInteger.ONE); assertNotEquals(supply, empty); assertNotEquals(supply.hashCode(), empty.hashCode()); } diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyTotalApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyTotalApiTests.java new file mode 100644 index 0000000..b6098d8 --- /dev/null +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticSupplyTotalApiTests.java @@ -0,0 +1,28 @@ +package io.goodforgod.api.etherscan.statistic; + +import io.goodforgod.api.etherscan.ApiRunner; +import io.goodforgod.api.etherscan.model.EthSupply; +import org.junit.jupiter.api.Test; + +/** + * @author GoodforGod + * @since 14.05.2023 + */ +class StatisticSupplyTotalApiTests extends ApiRunner { + + @Test + void correct() { + EthSupply supply = getApi().stats().supplyTotal(); + assertNotNull(supply); + assertNotNull(supply.getBurntFees()); + assertNotEquals(0, supply.getBurntFees().asWei().intValue()); + assertNotNull(supply.getEthSupply()); + assertNotEquals(0, supply.getEthSupply().asWei().intValue()); + assertNotNull(supply.getEth2Staking()); + assertNotEquals(0, supply.getEth2Staking().asWei().intValue()); + assertNotNull(supply.getWithdrawnTotal()); + assertNotEquals(0, supply.getWithdrawnTotal().asWei().intValue()); + assertNotNull(supply.getTotal()); + assertNotEquals(0, supply.getTotal().asWei().intValue()); + } +} diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java index 07f8eca..b21b3b3 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java @@ -2,6 +2,7 @@ import io.goodforgod.api.etherscan.ApiRunner; import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException; +import io.goodforgod.api.etherscan.model.Wei; import java.math.BigInteger; import org.junit.jupiter.api.Test; @@ -13,7 +14,7 @@ class StatisticTokenSupplyApiTests extends ApiRunner { @Test void correct() { - BigInteger supply = getApi().stats().supply("0x57d90b64a1a57749b0f932f1a3395792e12e7055"); + Wei supply = getApi().stats().supply("0x57d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); assertNotEquals(BigInteger.ZERO, supply); } @@ -26,8 +27,8 @@ void invalidParamWithError() { @Test void correctParamWithEmptyExpectedResult() { - BigInteger supply = getApi().stats().supply("0x51d90b64a1a57749b0f932f1a3395792e12e7055"); + Wei supply = getApi().stats().supply("0x51d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); - assertEquals(0, supply.intValue()); + assertEquals(0, supply.asEther().intValue()); } } From 63f8909788f6bb48f91e5451bff71a9eda9e5ab3 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 14 May 2023 23:12:27 +0300 Subject: [PATCH 37/67] [2.0.0-SNAPSHOT] 1010L->1015L reset time --- .../api/etherscan/manager/RequestQueueManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 2fdfe82..4d6b586 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -16,16 +16,16 @@ public interface RequestQueueManager extends AutoCloseable { /** * Is used by default when no API KEY is provided */ - RequestQueueManager ANONYMOUS = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5010L)); + RequestQueueManager ANONYMOUS = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5015L)); /** * Is available for all registered free API KEYs * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> */ - RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1010L)); - RequestQueueManager STANDARD_PLAN = new SemaphoreRequestQueueManager(10, Duration.ofMillis(1010L)); - RequestQueueManager ADVANCED_PLAN = new SemaphoreRequestQueueManager(20, Duration.ofMillis(1010L)); - RequestQueueManager PROFESSIONAL_PLAN = new SemaphoreRequestQueueManager(30, Duration.ofMillis(1010L)); + RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1015L)); + RequestQueueManager STANDARD_PLAN = new SemaphoreRequestQueueManager(10, Duration.ofMillis(1015L)); + RequestQueueManager ADVANCED_PLAN = new SemaphoreRequestQueueManager(20, Duration.ofMillis(1015L)); + RequestQueueManager PROFESSIONAL_PLAN = new SemaphoreRequestQueueManager(30, Duration.ofMillis(1015L)); RequestQueueManager UNLIMITED = new FakeRequestQueueManager(); From 6d19b737db506844f4aa821f71a37f0bfdf9ca8e Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 00:05:46 +0300 Subject: [PATCH 38/67] [2.0.0-SNAPSHOT] Gas related fields replaced to Wei --- .../api/etherscan/model/BaseTx.java | 8 +- .../io/goodforgod/api/etherscan/model/Tx.java | 50 +++++++----- .../api/etherscan/model/TxErc1155.java | 76 ++++++++++--------- .../api/etherscan/model/TxErc20.java | 40 ++++++---- .../api/etherscan/model/TxErc721.java | 40 ++++++---- .../api/etherscan/model/TxInternal.java | 16 ++-- .../goodforgod/api/etherscan/model/Wei.java | 22 ++++-- .../api/etherscan/model/proxy/BlockProxy.java | 24 +++--- .../etherscan/model/proxy/ReceiptProxy.java | 24 +++--- .../api/etherscan/model/proxy/TxProxy.java | 24 +++--- 10 files changed, 180 insertions(+), 144 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java index c66e60f..64a9627 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java @@ -56,12 +56,12 @@ public String getInput() { return input; } - public BigInteger getGas() { - return gas; + public Wei getGas() { + return Wei.ofWei(gas); } - public BigInteger getGasUsed() { - return gasUsed; + public Wei getGasUsed() { + return Wei.ofWei(gasUsed); } // </editor-fold> 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 3d8cd1f..819252e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -41,20 +41,20 @@ public int getTransactionIndex() { return transactionIndex; } - public BigInteger getGasPrice() { - return gasPrice; + public Wei getGasPrice() { + return Wei.ofWei(gasPrice); } public boolean haveError() { return !BasicUtils.isEmpty(isError) && !isError.equals("0"); } - public String getTxreceipt_status() { + public String getTxReceiptStatus() { return txreceipt_status; } - public BigInteger getCumulativeGasUsed() { - return cumulativeGasUsed; + public Wei getGasUsedCumulative() { + return Wei.ofWei(cumulativeGasUsed); } public long getConfirmations() { @@ -112,16 +112,16 @@ public static final class TxBuilder { private BigInteger value; private String contractAddress; private String input; - private BigInteger gas; - private BigInteger gasUsed; + private Wei gas; + private Wei gasUsed; private long nonce; private String blockHash; private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; + private Wei gasPrice; + private Wei cumulativeGasUsed; private long confirmations; private String isError; - private String txreceiptStatus; + private String txReceiptStatus; private TxBuilder() {} @@ -165,12 +165,12 @@ public TxBuilder withInput(String input) { return this; } - public TxBuilder withGas(BigInteger gas) { + public TxBuilder withGas(Wei gas) { this.gas = gas; return this; } - public TxBuilder withGasUsed(BigInteger gasUsed) { + public TxBuilder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } @@ -190,12 +190,12 @@ public TxBuilder withTransactionIndex(int transactionIndex) { return this; } - public TxBuilder withGasPrice(BigInteger gasPrice) { + public TxBuilder withGasPrice(Wei gasPrice) { this.gasPrice = gasPrice; return this; } - public TxBuilder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + public TxBuilder withCumulativeGasUsed(Wei cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } @@ -210,20 +210,30 @@ public TxBuilder withIsError(String isError) { return this; } - public TxBuilder withTxreceiptStatus(String txreceiptStatus) { - this.txreceiptStatus = txreceiptStatus; + public TxBuilder withTxReceiptStatus(String txReceiptStatus) { + this.txReceiptStatus = txReceiptStatus; return this; } public Tx build() { Tx tx = new Tx(); - tx.gas = this.gas; tx.isError = this.isError; tx.blockHash = this.blockHash; tx.hash = this.hash; - tx.gasUsed = this.gasUsed; + if (this.gas != null) { + tx.gas = this.gas.asWei(); + } + if (this.gasUsed != null) { + tx.gasUsed = this.gasUsed.asWei(); + } + if (this.gasPrice != null) { + tx.gasPrice = this.gasPrice.asWei(); + } + if (this.cumulativeGasUsed != null) { + tx.cumulativeGasUsed = this.cumulativeGasUsed.asWei(); + } tx.from = this.from; - tx.txreceipt_status = this.txreceiptStatus; + tx.txreceipt_status = this.txReceiptStatus; tx.contractAddress = this.contractAddress; tx.value = this.value; tx.transactionIndex = this.transactionIndex; @@ -236,8 +246,6 @@ public Tx build() { tx.blockNumber = this.blockNumber; tx.to = this.to; tx.input = this.input; - tx.cumulativeGasUsed = this.cumulativeGasUsed; - tx.gasPrice = this.gasPrice; return tx; } } 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 e6c20f0..7be8aff 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -53,12 +53,12 @@ public int getTransactionIndex() { return transactionIndex; } - public BigInteger getGasPrice() { - return gasPrice; + public Wei getGasPrice() { + return Wei.ofWei(gasPrice); } - public BigInteger getCumulativeGasUsed() { - return cumulativeGasUsed; + public Wei getGasUsedCumulative() { + return Wei.ofWei(cumulativeGasUsed); } public long getConfirmations() { @@ -113,8 +113,6 @@ public static final class TxErc1155Builder { private String to; private String contractAddress; private String input; - private BigInteger gas; - private BigInteger gasUsed; private long nonce; private String blockHash; private String tokenID; @@ -122,8 +120,10 @@ public static final class TxErc1155Builder { private String tokenSymbol; private String tokenValue; private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; + private Wei gas; + private Wei gasUsed; + private Wei gasPrice; + private Wei cumulativeGasUsed; private long confirmations; private TxErc1155Builder() {} @@ -163,12 +163,12 @@ public TxErc1155Builder withInput(String input) { return this; } - public TxErc1155Builder withGas(BigInteger gas) { + public TxErc1155Builder withGas(Wei gas) { this.gas = gas; return this; } - public TxErc1155Builder withGasUsed(BigInteger gasUsed) { + public TxErc1155Builder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } @@ -208,12 +208,12 @@ public TxErc1155Builder withTransactionIndex(int transactionIndex) { return this; } - public TxErc1155Builder withGasPrice(BigInteger gasPrice) { + public TxErc1155Builder withGasPrice(Wei gasPrice) { this.gasPrice = gasPrice; return this; } - public TxErc1155Builder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + public TxErc1155Builder withCumulativeGasUsed(Wei cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } @@ -224,30 +224,38 @@ public TxErc1155Builder withConfirmations(long confirmations) { } public TxErc1155 build() { - TxErc1155 txERC721 = new TxErc1155(); - txERC721.gas = this.gas; - txERC721.tokenName = this.tokenName; - txERC721.hash = this.hash; - txERC721.gasUsed = this.gasUsed; - txERC721.nonce = this.nonce; - txERC721.from = this.from; - txERC721.gasPrice = this.gasPrice; - txERC721.contractAddress = this.contractAddress; - txERC721.cumulativeGasUsed = this.cumulativeGasUsed; - txERC721.tokenID = this.tokenID; + TxErc1155 txERC1155 = new TxErc1155(); + txERC1155.tokenName = this.tokenName; + txERC1155.hash = this.hash; + txERC1155.nonce = this.nonce; + txERC1155.from = this.from; + if (this.gas != null) { + txERC1155.gas = this.gas.asWei(); + } + if (this.gasUsed != null) { + txERC1155.gasUsed = this.gasUsed.asWei(); + } + if (this.gasPrice != null) { + txERC1155.gasPrice = this.gasPrice.asWei(); + } + if (this.cumulativeGasUsed != null) { + txERC1155.cumulativeGasUsed = this.cumulativeGasUsed.asWei(); + } + txERC1155.contractAddress = this.contractAddress; + txERC1155.tokenID = this.tokenID; if (this.timeStamp != null) { - txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); - txERC721._timeStamp = this.timeStamp; + txERC1155.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); + txERC1155._timeStamp = this.timeStamp; } - txERC721.blockNumber = this.blockNumber; - txERC721.tokenValue = this.tokenValue; - txERC721.transactionIndex = this.transactionIndex; - txERC721.to = this.to; - txERC721.confirmations = this.confirmations; - txERC721.input = this.input; - txERC721.blockHash = this.blockHash; - txERC721.tokenSymbol = this.tokenSymbol; - return txERC721; + txERC1155.blockNumber = this.blockNumber; + txERC1155.tokenValue = this.tokenValue; + txERC1155.transactionIndex = this.transactionIndex; + txERC1155.to = this.to; + txERC1155.confirmations = this.confirmations; + txERC1155.input = this.input; + txERC1155.blockHash = this.blockHash; + txERC1155.tokenSymbol = this.tokenSymbol; + return txERC1155; } } } 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 197ab5d..751044c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -53,12 +53,12 @@ public int getTransactionIndex() { return transactionIndex; } - public BigInteger getGasPrice() { - return gasPrice; + public Wei getGasPrice() { + return Wei.ofWei(gasPrice); } - public BigInteger getCumulativeGasUsed() { - return cumulativeGasUsed; + public Wei getGasUsedCumulative() { + return Wei.ofWei(cumulativeGasUsed); } public long getConfirmations() { @@ -114,16 +114,16 @@ public static final class TxERC20Builder { private BigInteger value; private String contractAddress; private String input; - private BigInteger gas; - private BigInteger gasUsed; + private Wei gas; + private Wei gasUsed; private long nonce; private String blockHash; private String tokenName; private String tokenSymbol; private String tokenDecimal; private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; + private Wei gasPrice; + private Wei cumulativeGasUsed; private long confirmations; private TxERC20Builder() {} @@ -168,12 +168,12 @@ public TxERC20Builder withInput(String input) { return this; } - public TxERC20Builder withGas(BigInteger gas) { + public TxERC20Builder withGas(Wei gas) { this.gas = gas; return this; } - public TxERC20Builder withGasUsed(BigInteger gasUsed) { + public TxERC20Builder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } @@ -208,12 +208,12 @@ public TxERC20Builder withTransactionIndex(int transactionIndex) { return this; } - public TxERC20Builder withGasPrice(BigInteger gasPrice) { + public TxERC20Builder withGasPrice(Wei gasPrice) { this.gasPrice = gasPrice; return this; } - public TxERC20Builder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + public TxERC20Builder withCumulativeGasUsed(Wei cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } @@ -225,11 +225,20 @@ public TxERC20Builder withConfirmations(long confirmations) { public TxErc20 build() { TxErc20 txERC20 = new TxErc20(); - txERC20.gas = this.gas; txERC20.tokenName = this.tokenName; txERC20.hash = this.hash; - txERC20.gasUsed = this.gasUsed; - txERC20.cumulativeGasUsed = this.cumulativeGasUsed; + if (this.gas != null) { + txERC20.gas = this.gas.asWei(); + } + if (this.gasUsed != null) { + txERC20.gasUsed = this.gasUsed.asWei(); + } + if (this.gasPrice != null) { + txERC20.gasPrice = this.gasPrice.asWei(); + } + if (this.cumulativeGasUsed != null) { + txERC20.cumulativeGasUsed = this.cumulativeGasUsed.asWei(); + } txERC20.from = this.from; txERC20.tokenSymbol = this.tokenSymbol; txERC20.transactionIndex = this.transactionIndex; @@ -243,7 +252,6 @@ public TxErc20 build() { } txERC20.blockHash = this.blockHash; txERC20.blockNumber = this.blockNumber; - txERC20.gasPrice = this.gasPrice; txERC20.to = this.to; txERC20.input = this.input; txERC20.tokenDecimal = this.tokenDecimal; 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 644f738..7b59393 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -53,12 +53,12 @@ public int getTransactionIndex() { return transactionIndex; } - public BigInteger getGasPrice() { - return gasPrice; + public Wei getGasPrice() { + return Wei.ofWei(gasPrice); } - public BigInteger getCumulativeGasUsed() { - return cumulativeGasUsed; + public Wei getGasUsedCumulative() { + return Wei.ofWei(cumulativeGasUsed); } public long getConfirmations() { @@ -113,8 +113,6 @@ public static final class TxERC721Builder { private String to; private String contractAddress; private String input; - private BigInteger gas; - private BigInteger gasUsed; private long nonce; private String blockHash; private String tokenID; @@ -122,8 +120,10 @@ public static final class TxERC721Builder { private String tokenSymbol; private String tokenDecimal; private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; + private Wei gas; + private Wei gasUsed; + private Wei gasPrice; + private Wei cumulativeGasUsed; private long confirmations; private TxERC721Builder() {} @@ -163,12 +163,12 @@ public TxERC721Builder withInput(String input) { return this; } - public TxERC721Builder withGas(BigInteger gas) { + public TxERC721Builder withGas(Wei gas) { this.gas = gas; return this; } - public TxERC721Builder withGasUsed(BigInteger gasUsed) { + public TxERC721Builder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } @@ -208,12 +208,12 @@ public TxERC721Builder withTransactionIndex(int transactionIndex) { return this; } - public TxERC721Builder withGasPrice(BigInteger gasPrice) { + public TxERC721Builder withGasPrice(Wei gasPrice) { this.gasPrice = gasPrice; return this; } - public TxERC721Builder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + public TxERC721Builder withCumulativeGasUsed(Wei cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } @@ -225,15 +225,23 @@ public TxERC721Builder withConfirmations(long confirmations) { public TxErc721 build() { TxErc721 txERC721 = new TxErc721(); - txERC721.gas = this.gas; txERC721.tokenName = this.tokenName; txERC721.hash = this.hash; - txERC721.gasUsed = this.gasUsed; txERC721.nonce = this.nonce; txERC721.from = this.from; - txERC721.gasPrice = this.gasPrice; + if (this.gas != null) { + txERC721.gas = this.gas.asWei(); + } + if (this.gasUsed != null) { + txERC721.gasUsed = this.gasUsed.asWei(); + } + if (this.gasPrice != null) { + txERC721.gasPrice = this.gasPrice.asWei(); + } + if (this.cumulativeGasUsed != null) { + txERC721.cumulativeGasUsed = this.cumulativeGasUsed.asWei(); + } txERC721.contractAddress = this.contractAddress; - txERC721.cumulativeGasUsed = this.cumulativeGasUsed; txERC721.tokenID = this.tokenID; if (this.timeStamp != null) { txERC721.timeStamp = String.valueOf(this.timeStamp.toEpochSecond(ZoneOffset.UTC)); 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 fdd89ee..dd74e99 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -90,8 +90,8 @@ public static final class TxInternalBuilder { private BigInteger value; private String contractAddress; private String input; - private BigInteger gas; - private BigInteger gasUsed; + private Wei gas; + private Wei gasUsed; private String type; private String traceId; private int isError; @@ -139,12 +139,12 @@ public TxInternalBuilder withInput(String input) { return this; } - public TxInternalBuilder withGas(BigInteger gas) { + public TxInternalBuilder withGas(Wei gas) { this.gas = gas; return this; } - public TxInternalBuilder withGasUsed(BigInteger gasUsed) { + public TxInternalBuilder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } @@ -171,9 +171,13 @@ public TxInternalBuilder withErrCode(String errCode) { public TxInternal build() { TxInternal txInternal = new TxInternal(); - txInternal.gas = this.gas; txInternal.hash = this.hash; - txInternal.gasUsed = this.gasUsed; + if (this.gas != null) { + txInternal.gas = this.gas.asWei(); + } + if (this.gasUsed != null) { + txInternal.gasUsed = this.gasUsed.asWei(); + } txInternal.traceId = this.traceId; txInternal.type = this.type; txInternal.from = this.from; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index 004b5e1..038fd4b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -1,6 +1,8 @@ package io.goodforgod.api.etherscan.model; +import java.math.BigDecimal; import java.math.BigInteger; +import java.math.RoundingMode; import java.util.Objects; /** @@ -39,25 +41,29 @@ public static Wei ofEther(BigInteger value) { return new Wei(value.multiply(BigInteger.valueOf(1_000_000_000_000_000L))); } + public static Wei ofEther(BigDecimal value) { + return new Wei(value.multiply(BigDecimal.valueOf(1_000_000_000_000_000L)).toBigInteger()); + } + // <editor-fold desc="Getters"> public BigInteger asWei() { return result; } - public BigInteger asKwei() { - return result.divide(BigInteger.valueOf(1_000)); + public BigDecimal asKwei() { + return new BigDecimal(result).divide(BigDecimal.valueOf(1_000), RoundingMode.HALF_UP); } - public BigInteger asMwei() { - return result.divide(BigInteger.valueOf(1_000_000)); + public BigDecimal asMwei() { + return new BigDecimal(result).divide(BigDecimal.valueOf(1_000_000), RoundingMode.HALF_UP); } - public BigInteger asGwei() { - return result.divide(BigInteger.valueOf(1_000_000_000)); + public BigDecimal asGwei() { + return new BigDecimal(result).divide(BigDecimal.valueOf(1_000_000_000), RoundingMode.HALF_UP); } - public BigInteger asEther() { - return result.divide(BigInteger.valueOf(1_000_000_000_000_000L)); + public BigDecimal asEther() { + return new BigDecimal(result).divide(BigDecimal.valueOf(1_000_000_000_000_000L), RoundingMode.HALF_UP); } // </editor-fold> 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 a9447ca..c98d5ee 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 @@ -1,8 +1,8 @@ package io.goodforgod.api.etherscan.model.proxy; import com.google.gson.annotations.Expose; +import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.util.BasicUtils; -import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; @@ -35,10 +35,10 @@ public class BlockProxy { private String mixHash; private String gasUsed; @Expose(deserialize = false, serialize = false) - private BigInteger _gasUsed; + private Wei _gasUsed; private String gasLimit; @Expose(deserialize = false, serialize = false) - private BigInteger _gasLimit; + private Wei _gasLimit; private String sha3Uncles; private List<String> uncles; @@ -108,15 +108,15 @@ public String getMixHash() { return mixHash; } - public BigInteger getGasUsed() { + public Wei getGasUsed() { if (_gasUsed == null && !BasicUtils.isEmpty(gasUsed)) - _gasUsed = BasicUtils.parseHex(gasUsed); + _gasUsed = Wei.ofWei(BasicUtils.parseHex(gasUsed)); return _gasUsed; } - public BigInteger getGasLimit() { + public Wei getGasLimit() { if (_gasLimit == null && !BasicUtils.isEmpty(gasLimit)) - _gasLimit = BasicUtils.parseHex(gasLimit); + _gasLimit = Wei.ofWei(BasicUtils.parseHex(gasLimit)); return _gasLimit; } @@ -227,8 +227,8 @@ public static final class BlockProxyBuilder { private String extraData; private String logsBloom; private String mixHash; - private BigInteger gasUsed; - private BigInteger gasLimit; + private Wei gasUsed; + private Wei gasLimit; private String sha3Uncles; private List<String> uncles; private String receiptsRoot; @@ -302,12 +302,12 @@ public BlockProxyBuilder withMixHash(String mixHash) { return this; } - public BlockProxyBuilder withGasUsed(BigInteger gasUsed) { + public BlockProxyBuilder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } - public BlockProxyBuilder withGasLimit(BigInteger gasLimit) { + public BlockProxyBuilder withGasLimit(Wei gasLimit) { this.gasLimit = gasLimit; return this; } @@ -352,11 +352,9 @@ public BlockProxy build() { blockProxy._size = this.size; blockProxy.difficulty = this.difficulty; if (this.gasLimit != null) { - blockProxy.gasLimit = String.valueOf(this.gasLimit); blockProxy._gasLimit = this.gasLimit; } if (this.gasUsed != null) { - blockProxy.gasUsed = String.valueOf(this.gasUsed); blockProxy._gasUsed = this.gasUsed; } blockProxy.size = String.valueOf(this.size); 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 61a7942..2b616c3 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 @@ -2,8 +2,8 @@ import com.google.gson.annotations.Expose; import io.goodforgod.api.etherscan.model.Log; +import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.util.BasicUtils; -import java.math.BigInteger; import java.util.List; /** @@ -25,10 +25,10 @@ public class ReceiptProxy { private Long _transactionIndex; private String gasUsed; @Expose(serialize = false, deserialize = false) - private BigInteger _gasUsed; + private Wei _gasUsed; private String cumulativeGasUsed; @Expose(serialize = false, deserialize = false) - private BigInteger _cumulativeGasUsed; + private Wei _cumulativeGasUsed; private String contractAddress; private List<Log> logs; @@ -69,15 +69,15 @@ public Long getTransactionIndex() { return _transactionIndex; } - public BigInteger getGasUsed() { + public Wei getGasUsed() { if (_gasUsed == null && !BasicUtils.isEmpty(gasUsed)) - _gasUsed = BasicUtils.parseHex(gasUsed); + _gasUsed = Wei.ofWei(BasicUtils.parseHex(gasUsed)); return _gasUsed; } - public BigInteger getCumulativeGasUsed() { + public Wei getCumulativeGasUsed() { if (_cumulativeGasUsed == null && !BasicUtils.isEmpty(cumulativeGasUsed)) - _cumulativeGasUsed = BasicUtils.parseHex(cumulativeGasUsed); + _cumulativeGasUsed = Wei.ofWei(BasicUtils.parseHex(cumulativeGasUsed)); return _cumulativeGasUsed; } @@ -165,8 +165,8 @@ public static final class ReceiptProxyBuilder { private String blockHash; private String transactionHash; private Long transactionIndex; - private BigInteger gasUsed; - private BigInteger cumulativeGasUsed; + private Wei gasUsed; + private Wei cumulativeGasUsed; private String contractAddress; private List<Log> logs; private String logsBloom; @@ -208,12 +208,12 @@ public ReceiptProxyBuilder withTransactionIndex(Long transactionIndex) { return this; } - public ReceiptProxyBuilder withGasUsed(BigInteger gasUsed) { + public ReceiptProxyBuilder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } - public ReceiptProxyBuilder withCumulativeGasUsed(BigInteger cumulativeGasUsed) { + public ReceiptProxyBuilder withCumulativeGasUsed(Wei cumulativeGasUsed) { this.cumulativeGasUsed = cumulativeGasUsed; return this; } @@ -244,13 +244,11 @@ public ReceiptProxy build() { receiptProxy.root = this.root; receiptProxy.contractAddress = this.contractAddress; if (this.gasUsed != null) { - receiptProxy.gasUsed = String.valueOf(this.gasUsed); receiptProxy._gasUsed = this.gasUsed; } receiptProxy.logs = this.logs; receiptProxy.to = this.to; if (this.cumulativeGasUsed != null) { - receiptProxy.cumulativeGasUsed = String.valueOf(this.cumulativeGasUsed); receiptProxy._cumulativeGasUsed = this.cumulativeGasUsed; } receiptProxy.transactionIndex = String.valueOf(this.transactionIndex); 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 0ca7f3a..b2b412b 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 @@ -1,8 +1,8 @@ package io.goodforgod.api.etherscan.model.proxy; import com.google.gson.annotations.Expose; +import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.util.BasicUtils; -import java.math.BigInteger; /** * @author GoodforGod @@ -26,10 +26,10 @@ public class TxProxy { private String value; private String gas; @Expose(deserialize = false, serialize = false) - private BigInteger _gas; + private Wei _gas; private String gasPrice; @Expose(deserialize = false, serialize = false) - private BigInteger _gasPrice; + private Wei _gasPrice; private String blockHash; private String blockNumber; @Expose(deserialize = false, serialize = false) @@ -56,9 +56,9 @@ public String getFrom() { return from; } - public BigInteger getGas() { + public Wei getGas() { if (_gas == null && !BasicUtils.isEmpty(gas)) - _gas = BasicUtils.parseHex(gas); + _gas = Wei.ofWei(BasicUtils.parseHex(gas)); return _gas; } @@ -88,9 +88,9 @@ public String getValue() { return value; } - public BigInteger getGasPrice() { + public Wei getGasPrice() { if (_gasPrice == null && !BasicUtils.isEmpty(gasPrice)) - _gasPrice = BasicUtils.parseHex(gasPrice); + _gasPrice = Wei.ofWei(BasicUtils.parseHex(gasPrice)); return _gasPrice; } @@ -182,8 +182,8 @@ public static final class TxProxyBuilder { private String r; private Long nonce; private String value; - private BigInteger gas; - private BigInteger gasPrice; + private Wei gas; + private Wei gasPrice; private String blockHash; private Long blockNumber; @@ -239,12 +239,12 @@ public TxProxyBuilder withValue(String value) { return this; } - public TxProxyBuilder withGas(BigInteger gas) { + public TxProxyBuilder withGas(Wei gas) { this.gas = gas; return this; } - public TxProxyBuilder withGasPrice(BigInteger gasPrice) { + public TxProxyBuilder withGasPrice(Wei gasPrice) { this.gasPrice = gasPrice; return this; } @@ -263,7 +263,6 @@ public TxProxy build() { TxProxy txProxy = new TxProxy(); txProxy.input = this.input; if (this.gas != null) { - txProxy.gas = String.valueOf(this.gas); txProxy._gas = this.gas; } txProxy.s = this.s; @@ -281,7 +280,6 @@ public TxProxy build() { txProxy._blockNumber = this.blockNumber; txProxy.hash = this.hash; if (this.gasPrice != null) { - txProxy.gasPrice = String.valueOf(this.gasPrice); txProxy._gasPrice = this.gasPrice; } return txProxy; From 1416a232e4a8c13b46e6cd7bf571f063b9bb0da0 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 00:06:23 +0300 Subject: [PATCH 39/67] [2.0.0-SNAPSHOT] RequestQueueManager static consts -> static method to produce uniq request queue managers Tests manager provision fixed --- .../api/etherscan/AccountAPIProvider.java | 9 +++-- .../api/etherscan/EthScanAPIBuilder.java | 17 ++++++--- .../manager/RequestQueueManager.java | 27 ++++++++++--- .../goodforgod/api/etherscan/ApiRunner.java | 8 +--- .../api/etherscan/EtherScanAPITests.java | 1 - .../account/AccountTxErc20Tests.java | 4 +- .../account/AccountTxRc1155TokenTests.java | 2 +- .../account/AccountTxRc721TokenTests.java | 2 +- .../etherscan/account/AccountTxsTests.java | 4 +- .../etherscan/model/ModelBuilderTests.java | 38 +++++++++---------- .../etherscan/proxy/ProxyBlockApiTests.java | 17 ++------- 11 files changed, 69 insertions(+), 60 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index e5b6bd9..08e9dd5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -84,8 +84,9 @@ public TokenBalance balance(String address, String contract) throws EtherScanExc @NotNull @Override public List<Balance> balances(List<String> addresses) throws EtherScanException { - if (BasicUtils.isEmpty(addresses)) + if (BasicUtils.isEmpty(addresses)) { return Collections.emptyList(); + } BasicUtils.validateAddresses(addresses); @@ -96,13 +97,15 @@ public List<Balance> balances(List<String> addresses) throws EtherScanException for (final List<String> batch : addressesAsBatches) { final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + toAddressParam(batch); final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); - if (response.getStatus() != 1) + if (response.getStatus() != 1) { throw new EtherScanResponseException(response); + } - if (!BasicUtils.isEmpty(response.getResult())) + if (!BasicUtils.isEmpty(response.getResult())) { balances.addAll(response.getResult().stream() .map(r -> new Balance(r.getAccount(), Wei.ofWei(new BigInteger(r.getBalance())))) .collect(Collectors.toList())); + } } return balances; diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 57aeeae..dad9c50 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -27,8 +27,8 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { private final Gson gson = new GsonConfiguration().builder().create(); private String apiKey = DEFAULT_KEY; + private RequestQueueManager queueManager; private EthNetwork ethNetwork = EthNetworks.MAINNET; - private RequestQueueManager queueManager = RequestQueueManager.ANONYMOUS; private Supplier<EthHttpClient> ethHttpClientSupplier = DEFAULT_SUPPLIER; private Supplier<Converter> converterSupplier = () -> new Converter() { @@ -49,9 +49,6 @@ public EtherScanAPI.Builder withApiKey(@NotNull String apiKey) { throw new EtherScanKeyException("API key can not be null or empty"); this.apiKey = apiKey; - if (!DEFAULT_KEY.equals(apiKey)) { - queueManager = RequestQueueManager.UNLIMITED; - } return this; } @@ -92,6 +89,16 @@ public EtherScanAPI.Builder withConverter(@NotNull Supplier<Converter> converter @Override public @NotNull EtherScanAPI build() { - return new EtherScanAPIProvider(apiKey, ethNetwork, queueManager, ethHttpClientSupplier.get(), converterSupplier.get()); + RequestQueueManager requestQueueManager; + if (queueManager != null) { + requestQueueManager = queueManager; + } else if (DEFAULT_KEY.equals(apiKey)) { + requestQueueManager = RequestQueueManager.anonymous(); + } else { + requestQueueManager = RequestQueueManager.planFree(); + } + + return new EtherScanAPIProvider(apiKey, ethNetwork, requestQueueManager, ethHttpClientSupplier.get(), + converterSupplier.get()); } } 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 4d6b586..449daca 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java @@ -16,18 +16,33 @@ public interface RequestQueueManager extends AutoCloseable { /** * Is used by default when no API KEY is provided */ - RequestQueueManager ANONYMOUS = new SemaphoreRequestQueueManager(1, Duration.ofMillis(5015L)); + static RequestQueueManager anonymous() { + return new SemaphoreRequestQueueManager(1, Duration.ofMillis(5005L)); + } /** * Is available for all registered free API KEYs * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> */ - RequestQueueManager FREE_PLAN = new SemaphoreRequestQueueManager(5, Duration.ofMillis(1015L)); - RequestQueueManager STANDARD_PLAN = new SemaphoreRequestQueueManager(10, Duration.ofMillis(1015L)); - RequestQueueManager ADVANCED_PLAN = new SemaphoreRequestQueueManager(20, Duration.ofMillis(1015L)); - RequestQueueManager PROFESSIONAL_PLAN = new SemaphoreRequestQueueManager(30, Duration.ofMillis(1015L)); + static RequestQueueManager planFree() { + return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1005L)); + } - RequestQueueManager UNLIMITED = new FakeRequestQueueManager(); + static RequestQueueManager planStandard() { + return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1005L)); + } + + static RequestQueueManager planAdvanced() { + return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1005L)); + } + + static RequestQueueManager planProfessional() { + return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1005L)); + } + + static RequestQueueManager unlimited() { + return new FakeRequestQueueManager(); + } /** * Waits in queue for chance to take turn diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index d28a8e0..4b52c00 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -20,8 +20,8 @@ public class ApiRunner extends Assertions { .orElse(DEFAULT_KEY); final RequestQueueManager queueManager = (DEFAULT_KEY.equals(API_KEY)) - ? RequestQueueManager.ANONYMOUS - : RequestQueueManager.FREE_PLAN; + ? RequestQueueManager.anonymous() + : RequestQueueManager.planFree(); API = EtherScanAPI.builder() .withApiKey(ApiRunner.API_KEY) @@ -30,10 +30,6 @@ public class ApiRunner extends Assertions { .build(); } - public static String getApiKey() { - return API_KEY; - } - public static EtherScanAPI getApi() { return API; } diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java index c50b03a..36e23ec 100644 --- a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -68,7 +68,6 @@ void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300), Duration.ofMillis(300)); EtherScanAPI api = EtherScanAPI.builder() - .withApiKey(getApiKey()) .withNetwork(() -> URI.create("https://api-unknown.etherscan.io/api")) .withHttpClient(supplier) .build(); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java index 928b2e3..b26bcee 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java @@ -18,7 +18,7 @@ void correct() { assertNotNull(txs); assertEquals(3, txs.size()); assertTxs(txs); - assertNotEquals(0, txs.get(0).getGasPrice()); + assertNotEquals(0, txs.get(0).getGasPrice().asWei().intValue()); assertNotEquals(-1, txs.get(0).getNonce()); assertNotNull(txs.get(0).toString()); @@ -71,7 +71,7 @@ private void assertTxs(List<TxErc20> txs) { assertNotNull(tx.getTokenDecimal()); assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1, tx.getCumulativeGasUsed()); + assertNotEquals(-1, tx.getGasUsedCumulative()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java index 0430dc8..f8cae43 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java @@ -75,7 +75,7 @@ private void asserTx(TxErc1155 tx) { assertNotNull(tx.getTokenValue()); assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1, tx.getCumulativeGasUsed()); + assertNotEquals(-1, tx.getGasUsedCumulative()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java index 9a5a322..ca86256 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java @@ -73,7 +73,7 @@ private void assertTxs(List<TxErc721> txs) { assertNotNull(tx.getTokenDecimal()); assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1, tx.getCumulativeGasUsed()); + assertNotEquals(-1, tx.getGasUsedCumulative()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java index 3ee8ad1..653f62a 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java @@ -24,7 +24,7 @@ void correct() { assertNotNull(txs.get(0).getTo()); assertNotNull(txs.get(0).getBlockHash()); assertNotNull(txs.get(0).getGas()); - assertNotNull(txs.get(0).getCumulativeGasUsed()); + assertNotNull(txs.get(0).getGasUsedCumulative()); assertNotNull(txs.get(0).getGasPrice()); assertNotNull(txs.get(0).getValue()); assertNotNull(txs.get(0).getContractAddress()); @@ -75,7 +75,7 @@ private void assertTxs(List<Tx> txs) { assertNotEquals(-1, (tx.getNonce())); assertNotEquals(0, (tx.getTransactionIndex())); assertNotEquals(0, tx.getConfirmations()); - assertNotNull(tx.getTxreceipt_status()); + assertNotNull(tx.getTxReceiptStatus()); } } } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 9a7e426..806865d 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -132,12 +132,12 @@ void txBuilder() { .withBlockNumber(1L) .withConfirmations(1L) .withContractAddress("1") - .withCumulativeGasUsed(BigInteger.ONE) .withFrom("1") .withTo("1") - .withGas(BigInteger.ONE) - .withGasPrice(BigInteger.ONE) - .withGasUsed(BigInteger.ONE) + .withCumulativeGasUsed(Wei.ofWei(BigInteger.ONE)) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasPrice(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) .withHash("1") .withInput("1") .withIsError("1") @@ -145,7 +145,7 @@ void txBuilder() { .withTimeStamp(timestamp) .withValue(BigInteger.ONE) .withTransactionIndex(1) - .withTxreceiptStatus("1") + .withTxReceiptStatus("1") .build(); assertNotNull(value); @@ -162,12 +162,12 @@ void txErc20Builder() { .withBlockNumber(1L) .withConfirmations(1L) .withContractAddress("1") - .withCumulativeGasUsed(BigInteger.ONE) .withFrom("1") .withTo("1") - .withGas(BigInteger.ONE) - .withGasPrice(BigInteger.ONE) - .withGasUsed(BigInteger.ONE) + .withCumulativeGasUsed(Wei.ofWei(BigInteger.ONE)) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasPrice(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) .withHash("1") .withInput("1") .withTokenName("1") @@ -192,12 +192,12 @@ void txErc721Builder() { .withBlockNumber(1L) .withConfirmations(1L) .withContractAddress("1") - .withCumulativeGasUsed(BigInteger.ONE) .withFrom("1") .withTo("1") - .withGas(BigInteger.ONE) - .withGasPrice(BigInteger.ONE) - .withGasUsed(BigInteger.ONE) + .withCumulativeGasUsed(Wei.ofWei(BigInteger.ONE)) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasPrice(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) .withHash("1") .withInput("1") .withTokenName("1") @@ -222,12 +222,12 @@ void txErc1155Builder() { .withBlockNumber(1L) .withConfirmations(1L) .withContractAddress("1") - .withCumulativeGasUsed(BigInteger.ONE) .withFrom("1") .withTo("1") - .withGas(BigInteger.ONE) - .withGasPrice(BigInteger.ONE) - .withGasUsed(BigInteger.ONE) + .withCumulativeGasUsed(Wei.ofWei(BigInteger.ONE)) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasPrice(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) .withHash("1") .withInput("1") .withTokenName("1") @@ -253,8 +253,8 @@ void txInternalBuilder() { .withFrom("1") .withTo("1") .withValue(BigInteger.ONE) - .withGas(BigInteger.ONE) - .withGasUsed(BigInteger.ONE) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) .withHash("1") .withInput("1") .withTimeStamp(timestamp) diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java index 874ccc0..363d5a2 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java @@ -1,9 +1,6 @@ package io.goodforgod.api.etherscan.proxy; import io.goodforgod.api.etherscan.ApiRunner; -import io.goodforgod.api.etherscan.EthNetworks; -import io.goodforgod.api.etherscan.EtherScanAPI; -import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.proxy.BlockProxy; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -14,17 +11,9 @@ */ class ProxyBlockApiTests extends ApiRunner { - private final EtherScanAPI api; - - ProxyBlockApiTests() { - final RequestQueueManager queueManager = RequestQueueManager.ANONYMOUS; - this.api = EtherScanAPI.builder().withApiKey(getApiKey()).withNetwork(EthNetworks.MAINNET).withQueue(queueManager) - .build(); - } - @Test void correct() { - Optional<BlockProxy> block = api.proxy().block(5120); + Optional<BlockProxy> block = getApi().proxy().block(5120); assertTrue(block.isPresent()); BlockProxy proxy = block.get(); assertNotNull(proxy.getHash()); @@ -57,13 +46,13 @@ void correct() { @Test void correctParamWithEmptyExpectedResult() { - Optional<BlockProxy> block = api.proxy().block(99999999999L); + Optional<BlockProxy> block = getApi().proxy().block(99999999999L); assertFalse(block.isPresent()); } @Test void correctParamNegativeNo() { - Optional<BlockProxy> block = api.proxy().block(-1); + Optional<BlockProxy> block = getApi().proxy().block(-1); assertTrue(block.isPresent()); assertNotNull(block.get().getHash()); } From 14ccb53db6d1b6e28a273dd1dbb79ba5a8123986 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 00:19:41 +0300 Subject: [PATCH 40/67] [2.0.0-SNAPSHOT] Test asserts fixed Log contract improved Tests reinforced --- .../api/etherscan/model/EthSupply.java | 26 ++++- .../goodforgod/api/etherscan/model/Log.java | 23 ++-- .../account/AccountTxErc20Tests.java | 2 +- .../account/AccountTxRc1155TokenTests.java | 4 +- .../account/AccountTxRc721TokenTests.java | 4 +- .../etherscan/model/ModelBuilderTests.java | 104 +++++++++++++++++- .../StatisticTokenSupplyApiTests.java | 2 +- 7 files changed, 138 insertions(+), 27 deletions(-) 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 f967360..344e754 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java @@ -4,8 +4,6 @@ import java.util.Objects; /** - * Please Add Description Here. - * * @author Anton Kurako (GoodforGod) * @since 14.05.2023 */ @@ -100,10 +98,26 @@ public EthSupplyBuilder withWithdrawnTotal(Wei withdrawnTotal) { public EthSupply build() { EthSupply ethSupply = new EthSupply(); - ethSupply.BurntFees = this.burntFees.toString(); - ethSupply.Eth2Staking = this.eth2Staking.toString(); - ethSupply.EthSupply = this.ethSupply.toString(); - ethSupply.WithdrawnTotal = this.withdrawnTotal.toString(); + if (this.burntFees != null) { + ethSupply.BurntFees = this.burntFees.toString(); + } else { + ethSupply.BurntFees = BigInteger.ZERO.toString(); + } + if (this.eth2Staking != null) { + ethSupply.Eth2Staking = this.eth2Staking.toString(); + } else { + ethSupply.Eth2Staking = BigInteger.ZERO.toString(); + } + if (this.ethSupply != null) { + ethSupply.EthSupply = this.ethSupply.toString(); + } else { + ethSupply.EthSupply = BigInteger.ZERO.toString(); + } + if (this.withdrawnTotal != null) { + ethSupply.WithdrawnTotal = this.withdrawnTotal.toString(); + } else { + ethSupply.WithdrawnTotal = BigInteger.ZERO.toString(); + } return ethSupply; } } 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 07e652f..8e14b16 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -2,7 +2,6 @@ import com.google.gson.annotations.Expose; import io.goodforgod.api.etherscan.util.BasicUtils; -import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; @@ -28,10 +27,10 @@ public class Log { private String data; private String gasPrice; @Expose(deserialize = false, serialize = false) - private BigInteger _gasPrice; + private Wei _gasPrice; private String gasUsed; @Expose(deserialize = false, serialize = false) - private BigInteger _gasUsed; + private Wei _gasUsed; private List<String> topics; private String logIndex; @Expose(deserialize = false, serialize = false) @@ -93,17 +92,17 @@ public String getData() { return data; } - public BigInteger getGasPrice() { + public Wei getGasPrice() { if (!BasicUtils.isEmpty(gasPrice)) { - _gasPrice = BasicUtils.parseHex(gasPrice); + _gasPrice = Wei.ofWei(BasicUtils.parseHex(gasPrice)); } return _gasPrice; } - public BigInteger getGasUsed() { + public Wei getGasUsed() { if (!BasicUtils.isEmpty(gasUsed)) { - _gasUsed = BasicUtils.parseHex(gasUsed); + _gasUsed = Wei.ofWei(BasicUtils.parseHex(gasUsed)); } return _gasUsed; @@ -172,8 +171,8 @@ public static final class LogBuilder { private Long transactionIndex; private LocalDateTime timeStamp; private String data; - private BigInteger gasPrice; - private BigInteger gasUsed; + private Wei gasPrice; + private Wei gasUsed; private List<String> topics; private Long logIndex; @@ -209,12 +208,12 @@ public LogBuilder withData(String data) { return this; } - public LogBuilder withGasPrice(BigInteger gasPrice) { + public LogBuilder withGasPrice(Wei gasPrice) { this.gasPrice = gasPrice; return this; } - public LogBuilder withGasUsed(BigInteger gasUsed) { + public LogBuilder withGasUsed(Wei gasUsed) { this.gasUsed = gasUsed; return this; } @@ -233,7 +232,6 @@ public Log build() { Log log = new Log(); log.address = this.address; if (this.gasPrice != null) { - log.gasPrice = String.valueOf(this.gasPrice); log._gasPrice = this.gasPrice; } log._logIndex = this.logIndex; @@ -246,7 +244,6 @@ public Log build() { } log.data = this.data; if (this.gasUsed != null) { - log.gasUsed = String.valueOf(this.gasUsed); log._gasUsed = this.gasUsed; } log.logIndex = String.valueOf(this.logIndex); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java index b26bcee..4239bcd 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxErc20Tests.java @@ -71,7 +71,7 @@ private void assertTxs(List<TxErc20> txs) { assertNotNull(tx.getTokenDecimal()); assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1, tx.getGasUsedCumulative()); + assertNotEquals(-1, tx.getGasUsedCumulative().asWei().intValue()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java index f8cae43..d8cbb73 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc1155TokenTests.java @@ -18,7 +18,7 @@ void correct() { assertNotNull(txs); assertFalse(txs.isEmpty()); assertTxs(txs); - assertNotEquals(0, txs.get(0).getGasPrice()); + assertNotEquals(0, txs.get(0).getGasPrice().asWei().intValue()); assertNotEquals(-1, txs.get(0).getNonce()); assertNotNull(txs.get(0).toString()); @@ -75,7 +75,7 @@ private void asserTx(TxErc1155 tx) { assertNotNull(tx.getTokenValue()); assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1, tx.getGasUsedCumulative()); + assertNotEquals(-1, tx.getGasUsedCumulative().asWei().intValue()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java index ca86256..6c61a4c 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxRc721TokenTests.java @@ -18,7 +18,7 @@ void correct() { assertNotNull(txs); assertEquals(16, txs.size()); assertTxs(txs); - assertNotEquals(0, txs.get(0).getGasPrice()); + assertNotEquals(0, txs.get(0).getGasPrice().asWei().intValue()); assertNotEquals(-1, txs.get(0).getNonce()); assertNotNull(txs.get(0).toString()); @@ -73,7 +73,7 @@ private void assertTxs(List<TxErc721> txs) { assertNotNull(tx.getTokenDecimal()); assertNotEquals(-1, (tx.getConfirmations())); assertNotNull(tx.getGasUsed()); - assertNotEquals(-1, tx.getGasUsedCumulative()); + assertNotEquals(-1, tx.getGasUsedCumulative().asWei().intValue()); assertNotEquals(-1, tx.getTransactionIndex()); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 806865d..9018ec8 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -1,8 +1,12 @@ package io.goodforgod.api.etherscan.model; +import io.goodforgod.api.etherscan.model.proxy.BlockProxy; +import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; +import io.goodforgod.api.etherscan.model.proxy.TxProxy; import java.math.BigDecimal; import java.math.BigInteger; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -84,8 +88,8 @@ void logBuilder() { .withAddress("1") .withBlockNumber(1L) .withData("1") - .withGasPrice(BigInteger.ONE) - .withGasUsed(BigInteger.ONE) + .withGasPrice(Wei.ofWei(1)) + .withGasUsed(Wei.ofWei(1)) .withLogIndex(1L) .withTimeStamp(timestamp) .withTransactionHash("1") @@ -268,4 +272,100 @@ void txInternalBuilder() { assertEquals("1", value.getTo()); assertEquals("1", value.getFrom()); } + + @Test + void ethSupplyBuilder() { + EthSupply value = EthSupply.builder() + .withBurntFees(Wei.ofWei(1)) + .withEth2Staking(Wei.ofWei(1)) + .withEthSupply(Wei.ofWei(1)) + .withWithdrawnTotal(Wei.ofWei(1)) + .build(); + + assertNotNull(value); + assertEquals(BigInteger.valueOf(1), value.getTotal().asWei()); + + EthSupply valueEmpty = EthSupply.builder() + .build(); + assertNotNull(valueEmpty); + assertEquals(BigInteger.ZERO, valueEmpty.getTotal().asWei()); + } + + @Test + void receiptProxyBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + ReceiptProxy value = ReceiptProxy.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withContractAddress("1") + .withCumulativeGasUsed(Wei.ofWei(1)) + .withFrom("1") + .withTo("1") + .withGasUsed(Wei.ofWei(1)) + .withRoot("1") + .withLogsBloom("1") + .withTransactionHash("1") + .withTransactionIndex(1L) + .withLogs(Arrays.asList(Log.builder() + .withTopics(Arrays.asList("1")) + .withTransactionIndex(1L) + .withTransactionHash("1") + .withTimeStamp(timestamp) + .withLogIndex(1L) + .withGasUsed(Wei.ofWei(1)) + .withGasPrice(Wei.ofWei(1)) + .withData("1") + .withAddress("1") + .build())) + .build(); + + assertNotNull(value); + assertEquals(BigInteger.valueOf(1), value.getGasUsed().asWei()); + } + + @Test + void blockProxyBuilder() { + LocalDateTime timestamp = LocalDateTime.now(); + BlockProxy value = BlockProxy.builder() + .withGasUsed(Wei.ofWei(1)) + .withLogsBloom("1") + .withDifficulty("1") + .withExtraData("1") + .withGasLimit(Wei.ofWei(1)) + .withHash("1") + .withMiner("1") + .withMixHash("1") + .withNonce("1") + .withNumber(1L) + .withParentHash("1") + .withReceiptsRoot("1") + .withSha3Uncles("1") + .withSize(1L) + .withStateRoot("1") + .withTimestamp(timestamp) + .withTotalDifficulty("1") + .withTransactionsRoot("1") + .withUncles(Arrays.asList("1")) + .withTransactions(Arrays.asList(TxProxy.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withFrom("1") + .withGas(Wei.ofWei(1)) + .withGasPrice(Wei.ofWei(1)) + .withHash("1") + .withInput("1") + .withNonce(1L) + .withR("1") + .withS("1") + .withTo("1") + .withTransactionIndex(1L) + .withV("1") + .withValue("1") + .withV("1") + .build())) + .build(); + + assertNotNull(value); + assertEquals(BigInteger.valueOf(1), value.getGasUsed().asWei()); + } } diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java index b21b3b3..6eff846 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticTokenSupplyApiTests.java @@ -16,7 +16,7 @@ class StatisticTokenSupplyApiTests extends ApiRunner { void correct() { Wei supply = getApi().stats().supply("0x57d90b64a1a57749b0f932f1a3395792e12e7055"); assertNotNull(supply); - assertNotEquals(BigInteger.ZERO, supply); + assertNotEquals(BigInteger.ZERO, supply.asWei()); } @Test From bf30d9a2df7567a86ae494cbbb7ba62b49a2dfcb Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 00:27:58 +0300 Subject: [PATCH 41/67] [2.0.0-SNAPSHOT] 1005L->1015L --- .../api/etherscan/manager/RequestQueueManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 449daca..0f36b23 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(5005L)); + return new SemaphoreRequestQueueManager(1, Duration.ofMillis(5015L)); } /** @@ -25,19 +25,19 @@ static RequestQueueManager anonymous() { * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> */ static RequestQueueManager planFree() { - return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1005L)); + return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1015L)); } static RequestQueueManager planStandard() { - return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1005L)); + return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1015L)); } static RequestQueueManager planAdvanced() { - return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1005L)); + return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1015L)); } static RequestQueueManager planProfessional() { - return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1005L)); + return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1015L)); } static RequestQueueManager unlimited() { From 3a3e409ac48b4d039e9ef2077e98e12ca189293d Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 00:41:05 +0300 Subject: [PATCH 42/67] [2.0.0-SNAPSHOT] Tests reinforced --- .../api/etherscan/model/GasEstimate.java | 4 +-- .../etherscan/model/ModelBuilderTests.java | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java b/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java index 7f1e61d..198e53c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java @@ -40,8 +40,6 @@ public int hashCode() { @Override public String toString() { - return "GasEstimate{" + - "duration=" + duration + - '}'; + return duration.toString(); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 9018ec8..464b379 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -79,6 +79,18 @@ void gasOracleBuilder() { assertNotNull(value); assertEquals(Wei.ofWei(1000000000), value.getFastGasPriceInWei()); + + GasOracle value2 = GasOracle.builder() + .withFastGasPrice(Wei.ofWei(1000000000)) + .withProposeGasPrice(Wei.ofWei(1000000000)) + .withSafeGasPrice(Wei.ofWei(1000000000)) + .withGasUsedRatio(Collections.singletonList(new BigDecimal(1))) + .withLastBlock(1L) + .withSuggestBaseFee(1.0) + .build(); + assertEquals(value, value2); + assertEquals(value.hashCode(), value2.hashCode()); + assertEquals(value.toString(), value2.toString()); } @Test @@ -289,6 +301,16 @@ void ethSupplyBuilder() { .build(); assertNotNull(valueEmpty); assertEquals(BigInteger.ZERO, valueEmpty.getTotal().asWei()); + + EthSupply value2 = EthSupply.builder() + .withBurntFees(Wei.ofWei(1)) + .withEth2Staking(Wei.ofWei(1)) + .withEthSupply(Wei.ofWei(1)) + .withWithdrawnTotal(Wei.ofWei(1)) + .build(); + assertEquals(value, value2); + assertEquals(value.hashCode(), value2.hashCode()); + assertEquals(value.toString(), value2.toString()); } @Test @@ -368,4 +390,13 @@ void blockProxyBuilder() { assertNotNull(value); assertEquals(BigInteger.valueOf(1), value.getGasUsed().asWei()); } + + @Test + void gasEstimate() { + GasEstimate gas1 = new GasEstimate(1); + GasEstimate gas2 = new GasEstimate(1); + assertEquals(gas1, gas2); + assertEquals(gas1.hashCode(), gas2.hashCode()); + assertEquals(gas1.toString(), gas2.toString()); + } } From 2d666bcdc67a838483fda2bc81b1f98c9601f355 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 00:57:16 +0300 Subject: [PATCH 43/67] [2.0.0-SNAPSHOT] Log simplified Tests reinforced --- .../goodforgod/api/etherscan/model/Log.java | 12 ++-- .../etherscan/model/ModelBuilderTests.java | 72 +++++++++++++++++++ 2 files changed, 77 insertions(+), 7 deletions(-) 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 8e14b16..2808462 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -64,28 +64,26 @@ public Long getTransactionIndex() { public LocalDateTime getTimeStamp() { if (_timeStamp == null && !BasicUtils.isEmpty(timeStamp)) { - long formatted = (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') - ? BasicUtils.parseHex(timeStamp).longValue() - : Long.parseLong(timeStamp); + long formatted = getTimeStampAsSeconds(); _timeStamp = LocalDateTime.ofEpochSecond(formatted, 0, ZoneOffset.UTC); } return _timeStamp; } /** - * Return the "timeStamp" field of the event record as a long-int representing the milliseconds + * Return the "timeStamp" field of the event record as a long-int representing the seconds * since the Unix epoch (1970-01-01 00:00:00). * * @return milliseconds between Unix epoch and `timeStamp`. If field is empty or null, returns null */ - public Long getTimeStampAsMillis() { + public Long getTimeStampAsSeconds() { if (BasicUtils.isEmpty(timeStamp)) { return null; } - long tsSecs = (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') + + return (timeStamp.charAt(0) == '0' && timeStamp.charAt(1) == 'x') ? BasicUtils.parseHex(timeStamp).longValue() : Long.parseLong(timeStamp); - return tsSecs * 1000; } public String getData() { diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index 464b379..f895595 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -168,6 +168,31 @@ void txBuilder() { assertTrue(value.haveError()); assertEquals("1", value.getTo()); assertEquals("1", value.getFrom()); + + Tx value2 = Tx.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withConfirmations(1L) + .withContractAddress("1") + .withFrom("1") + .withTo("1") + .withCumulativeGasUsed(Wei.ofWei(BigInteger.ONE)) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasPrice(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) + .withHash("1") + .withInput("1") + .withIsError("1") + .withNonce(1L) + .withTimeStamp(timestamp) + .withValue(BigInteger.ONE) + .withTransactionIndex(1) + .withTxReceiptStatus("1") + .build(); + + assertEquals(value, value2); + assertEquals(value.hashCode(), value2.hashCode()); + assertEquals(value.toString(), value2.toString()); } @Test @@ -258,6 +283,32 @@ void txErc1155Builder() { assertNotNull(value); assertEquals("1", value.getTo()); assertEquals("1", value.getFrom()); + + TxErc1155 value2 = TxErc1155.builder() + .withBlockHash("1") + .withBlockNumber(1L) + .withConfirmations(1L) + .withContractAddress("1") + .withFrom("1") + .withTo("1") + .withCumulativeGasUsed(Wei.ofWei(BigInteger.ONE)) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasPrice(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) + .withHash("1") + .withInput("1") + .withTokenName("1") + .withTokenSymbol("1") + .withTokenDecimal("1") + .withTokenID("1") + .withNonce(1L) + .withTimeStamp(timestamp) + .withTransactionIndex(1) + .build(); + + assertEquals(value, value2); + assertEquals(value.hashCode(), value2.hashCode()); + assertEquals(value.toString(), value2.toString()); } @Test @@ -283,6 +334,27 @@ void txInternalBuilder() { assertNotNull(value); assertEquals("1", value.getTo()); assertEquals("1", value.getFrom()); + + TxInternal value2 = TxInternal.builder() + .withBlockNumber(1L) + .withContractAddress("1") + .withFrom("1") + .withTo("1") + .withValue(BigInteger.ONE) + .withGas(Wei.ofWei(BigInteger.ONE)) + .withGasUsed(Wei.ofWei(BigInteger.ONE)) + .withHash("1") + .withInput("1") + .withTimeStamp(timestamp) + .withErrCode("1") + .withIsError(1) + .withTraceId("1") + .withType("1") + .build(); + + assertEquals(value, value2); + assertEquals(value.hashCode(), value2.hashCode()); + assertEquals(value.toString(), value2.toString()); } @Test From f09f38a1403a9849ddbb1628a8de61b52620618a Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 01:01:44 +0300 Subject: [PATCH 44/67] [2.0.0] Release prepared --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e809e6c..821da06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=2.0.0-SNAPSHOT +artifactVersion=2.0.0 ##### GRADLE ##### From d1ec9e52ccb84be8bc42805dba075549c9b158d6 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 01:04:11 +0300 Subject: [PATCH 45/67] [2.0.0] Release prepared --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4cff68d..dc939bf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) -[](https://jitpack.io/#GoodforGod/java-etherscan-api) [Etherscan.io](https://etherscan.io/apis) Java API implementation. @@ -15,7 +14,7 @@ Library supports all available EtherScan *API* calls for all available *Ethereum **Gradle** ```groovy -implementation "com.github.goodforgod:java-etherscan-api:2.0.0-SNAPSHOT" +implementation "com.github.goodforgod:java-etherscan-api:2.0.0" ``` **Maven** @@ -23,7 +22,7 @@ implementation "com.github.goodforgod:java-etherscan-api:2.0.0-SNAPSHOT" <dependency> <groupId>com.github.goodforgod</groupId> <artifactId>java-etherscan-api</artifactId> - <version>2.0.0-SNAPSHOT</version> + <version>2.0.0</version> </dependency> ``` @@ -41,9 +40,9 @@ implementation "com.github.goodforgod:java-etherscan-api:2.0.0-SNAPSHOT" - [Token](#token-api) - [Version History](#version-history) -## Mainnet and Testnets +## MainNet and TestNets -API support Ethereum [default networks](https://docs.etherscan.io/getting-started/endpoint-urls): +API support all Ethereum [default networks](https://docs.etherscan.io/getting-started/endpoint-urls): - [Mainnet](https://api.etherscan.io/) - [Goerli](https://api-goerli.etherscan.io/) - [Sepolia](https://api-sepolia.etherscan.io/) @@ -121,8 +120,8 @@ Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413 **Get event logs for single topic** ```java EtherScanAPI api = EtherScanAPI.build(); -LogQuery query = LogQueryBuilder.with("0x33990122638b9132ca29c723bdf037f1a891a70c") - .topic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") +LogQuery query = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") + .withTopic("0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545") .build(); List<Log> logs = api.logs().logs(query); ``` @@ -163,7 +162,7 @@ Optional<BlockProxy> block = api.proxy().block(15215); **Statistic about last price** ```java EtherScanAPI api = EtherScanAPI.build(); -Price price = api.stats().lastPrice(); +Price price = api.stats().priceLast(); ``` ### Transaction API From b05bd8adf89d56857d0769227b28f3584d8051ca Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 20:49:06 +0300 Subject: [PATCH 46/67] [2.0.0] Tx refactored and simplified and common parts moved to BlockTx Comparable for multiple models added GasOracle simplified and reinforced Price reinforced GasEstimate.java removed as useless --- .../api/etherscan/GasTrackerAPI.java | 8 +- .../api/etherscan/GasTrackerAPIProvider.java | 6 +- .../api/etherscan/model/BaseTx.java | 17 +--- .../goodforgod/api/etherscan/model/Block.java | 9 +- .../api/etherscan/model/BlockTx.java | 79 +++++++++++++++++ .../api/etherscan/model/GasEstimate.java | 45 ---------- .../api/etherscan/model/GasOracle.java | 40 +++++---- .../goodforgod/api/etherscan/model/Log.java | 6 -- .../goodforgod/api/etherscan/model/Price.java | 19 ++-- .../api/etherscan/model/Status.java | 14 +-- .../api/etherscan/model/TokenBalance.java | 9 +- .../io/goodforgod/api/etherscan/model/Tx.java | 75 ++++------------ .../api/etherscan/model/TxErc1155.java | 54 ++++-------- .../api/etherscan/model/TxErc20.java | 53 ++++------- .../api/etherscan/model/TxErc721.java | 54 ++++-------- .../api/etherscan/model/TxInternal.java | 17 +++- .../goodforgod/api/etherscan/model/Wei.java | 87 +++++++++++++++---- .../api/etherscan/model/proxy/BlockProxy.java | 42 +++------ .../etherscan/model/proxy/ReceiptProxy.java | 36 ++------ .../api/etherscan/model/proxy/TxProxy.java | 46 ++++------ .../gastracker/GasTrackerApiTests.java | 5 +- .../etherscan/model/ModelBuilderTests.java | 60 ++++++++++--- .../proxy/ProxyTxReceiptApiTests.java | 2 +- 23 files changed, 368 insertions(+), 415 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java index 355d62a..6fce763 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPI.java @@ -1,9 +1,9 @@ package io.goodforgod.api.etherscan; import io.goodforgod.api.etherscan.error.EtherScanException; -import io.goodforgod.api.etherscan.model.GasEstimate; import io.goodforgod.api.etherscan.model.GasOracle; import io.goodforgod.api.etherscan.model.Wei; +import java.time.Duration; import org.jetbrains.annotations.NotNull; /** @@ -16,13 +16,13 @@ public interface GasTrackerAPI { /** - * Returns the estimated time, in seconds, for a transaction to be confirmed on the blockchain. + * Returns the estimated time for a transaction to be confirmed on the blockchain. * - * @return fast, suggested gas price + * @return estimated time * @throws EtherScanException parent exception class */ @NotNull - GasEstimate estimate(@NotNull Wei wei) throws EtherScanException; + Duration estimate(@NotNull Wei wei) throws EtherScanException; /** * Returns the current Safe, Proposed and Fast gas prices. diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java index 0b559d8..cbe0a75 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -4,11 +4,11 @@ import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; -import io.goodforgod.api.etherscan.model.GasEstimate; import io.goodforgod.api.etherscan.model.GasOracle; import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.model.response.GasEstimateResponseTO; import io.goodforgod.api.etherscan.model.response.GasOracleResponseTO; +import java.time.Duration; import org.jetbrains.annotations.NotNull; /** @@ -33,13 +33,13 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI } @Override - public @NotNull GasEstimate estimate(@NotNull Wei wei) throws EtherScanException { + public @NotNull Duration estimate(@NotNull Wei wei) throws EtherScanException { final String urlParams = ACT_GAS_ESTIMATE_PARAM + GASPRICE_PARAM + wei.asWei().toString(); final GasEstimateResponseTO response = getRequest(urlParams, GasEstimateResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); - return new GasEstimate(Long.parseLong(response.getResult())); + return Duration.ofSeconds(Long.parseLong(response.getResult())); } @NotNull diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java index 64a9627..8de679a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java @@ -6,12 +6,13 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; +import org.jetbrains.annotations.NotNull; /** * @author GoodforGod * @since 28.10.2018 */ -abstract class BaseTx { +abstract class BaseTx implements Comparable<BaseTx> { long blockNumber; String timeStamp; @@ -84,17 +85,7 @@ public int hashCode() { } @Override - public String toString() { - return "BaseTx{" + - "blockNumber=" + blockNumber + - ", timeStamp='" + timeStamp + '\'' + - ", hash='" + hash + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", contractAddress='" + contractAddress + '\'' + - ", input='" + input + '\'' + - ", gas=" + gas + - ", gasUsed=" + gasUsed + - '}'; + public int compareTo(@NotNull BaseTx o) { + return Long.compare(blockNumber, o.blockNumber); } } 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 95bfbcb..9fe4e02 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -6,12 +6,13 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; +import org.jetbrains.annotations.NotNull; /** * @author GoodforGod * @since 28.10.2018 */ -public class Block { +public class Block implements Comparable<Block> { long blockNumber; BigInteger blockReward; @@ -58,10 +59,14 @@ public String toString() { "blockNumber=" + blockNumber + ", blockReward=" + blockReward + ", timeStamp='" + timeStamp + '\'' + - ", _timeStamp=" + _timeStamp + '}'; } + @Override + public int compareTo(@NotNull Block o) { + return Long.compare(blockNumber, o.blockNumber); + } + public static BlockBuilder builder() { return new BlockBuilder(); } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java new file mode 100644 index 0000000..f3c4d67 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java @@ -0,0 +1,79 @@ +package io.goodforgod.api.etherscan.model; + +import java.math.BigInteger; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +/** + * @author Anton Kurako (GoodforGod) + * @since 15.05.2023 + */ +abstract class BlockTx extends BaseTx { + + long nonce; + String blockHash; + long transactionIndex; + long confirmations; + BigInteger gasPrice; + BigInteger cumulativeGasUsed; + + public long getNonce() { + return nonce; + } + + public String getBlockHash() { + return blockHash; + } + + public long getTransactionIndex() { + return transactionIndex; + } + + public Wei getGasPrice() { + return Wei.ofWei(gasPrice); + } + + public Wei getGasUsedCumulative() { + return Wei.ofWei(cumulativeGasUsed); + } + + public long getConfirmations() { + return confirmations; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof BlockTx)) + return false; + if (!super.equals(o)) + return false; + BlockTx blockTx = (BlockTx) o; + return nonce == blockTx.nonce && transactionIndex == blockTx.transactionIndex + && Objects.equals(blockHash, blockTx.blockHash); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), nonce, blockHash, transactionIndex); + } + + @Override + public int compareTo(@NotNull BaseTx o) { + final int superCompare = super.compareTo(o); + if (superCompare == 0) { + if (o instanceof Tx) { + return Long.compare(transactionIndex, ((Tx) o).getTransactionIndex()); + } else if (o instanceof TxErc20) { + return Long.compare(transactionIndex, ((TxErc20) o).getTransactionIndex()); + } else if (o instanceof TxErc721) { + return Long.compare(transactionIndex, ((TxErc721) o).getTransactionIndex()); + } else if (o instanceof TxErc1155) { + return Long.compare(transactionIndex, ((TxErc1155) o).getTransactionIndex()); + } + } + + return superCompare; + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java b/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java deleted file mode 100644 index 198e53c..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasEstimate.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.goodforgod.api.etherscan.model; - -import java.time.Duration; -import java.util.Objects; - -/** - * @author GoodforGod - * @since 14.05.2023 - */ -public class GasEstimate { - - private final Duration duration; - - public GasEstimate(long durationInSeconds) { - this.duration = Duration.ofSeconds(durationInSeconds); - } - - public GasEstimate(Duration duration) { - this.duration = duration; - } - - public Duration getDuration() { - return duration; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof GasEstimate)) - return false; - GasEstimate that = (GasEstimate) o; - return Objects.equals(duration, that.duration); - } - - @Override - public int hashCode() { - return Objects.hash(duration); - } - - @Override - public String toString() { - return duration.toString(); - } -} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index d273357..6fe1231 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -14,10 +14,10 @@ public class GasOracle { private Long LastBlock; - private Integer SafeGasPrice; - private Integer ProposeGasPrice; - private Integer FastGasPrice; - private Double suggestBaseFee; + private BigInteger SafeGasPrice; + private BigInteger ProposeGasPrice; + private BigInteger FastGasPrice; + private BigDecimal suggestBaseFee; private String gasUsedRatio; protected GasOracle() {} @@ -27,18 +27,18 @@ public Long getLastBlock() { } public Wei getSafeGasPriceInWei() { - return Wei.ofWei(BigInteger.valueOf(SafeGasPrice).multiply(BigInteger.TEN.pow(9))); + return Wei.ofGwei(SafeGasPrice); } public Wei getProposeGasPriceInWei() { - return Wei.ofWei(BigInteger.valueOf(ProposeGasPrice).multiply(BigInteger.TEN.pow(9))); + return Wei.ofGwei(ProposeGasPrice); } public Wei getFastGasPriceInWei() { - return Wei.ofWei(BigInteger.valueOf(FastGasPrice).multiply(BigInteger.TEN.pow(9))); + return Wei.ofGwei(FastGasPrice); } - public Double getSuggestBaseFee() { + public BigDecimal getSuggestBaseFee() { return suggestBaseFee; } @@ -52,12 +52,14 @@ public List<BigDecimal> getGasUsedRatio() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof GasOracle)) return false; GasOracle gasOracle = (GasOracle) o; - return LastBlock.equals(gasOracle.LastBlock) && SafeGasPrice.equals(gasOracle.SafeGasPrice) - && ProposeGasPrice.equals(gasOracle.ProposeGasPrice) && FastGasPrice.equals(gasOracle.FastGasPrice) - && suggestBaseFee.equals(gasOracle.suggestBaseFee) && gasUsedRatio.equals(gasOracle.gasUsedRatio); + return Objects.equals(LastBlock, gasOracle.LastBlock) && Objects.equals(SafeGasPrice, gasOracle.SafeGasPrice) + && Objects.equals(ProposeGasPrice, gasOracle.ProposeGasPrice) + && Objects.equals(FastGasPrice, gasOracle.FastGasPrice) + && Objects.equals(suggestBaseFee, gasOracle.suggestBaseFee) + && Objects.equals(gasUsedRatio, gasOracle.gasUsedRatio); } @Override @@ -73,7 +75,7 @@ public String toString() { ", ProposeGasPrice=" + ProposeGasPrice + ", FastGasPrice=" + FastGasPrice + ", suggestBaseFee=" + suggestBaseFee + - ", gasUsedRatio='" + gasUsedRatio + '\'' + + ", gasUsedRatio=" + gasUsedRatio + '}'; } @@ -87,7 +89,7 @@ public static final class GasOracleBuilder { private Wei safeGasPrice; private Wei proposeGasPrice; private Wei fastGasPrice; - private Double suggestBaseFee; + private BigDecimal suggestBaseFee; private List<BigDecimal> gasUsedRatio; private GasOracleBuilder() {} @@ -112,7 +114,7 @@ public GasOracleBuilder withFastGasPrice(Wei fastGasPrice) { return this; } - public GasOracleBuilder withSuggestBaseFee(Double suggestBaseFee) { + public GasOracleBuilder withSuggestBaseFee(BigDecimal suggestBaseFee) { this.suggestBaseFee = suggestBaseFee; return this; } @@ -127,18 +129,18 @@ public GasOracle build() { gasOracle.LastBlock = this.lastBlock; gasOracle.suggestBaseFee = this.suggestBaseFee; if (this.proposeGasPrice != null) { - gasOracle.ProposeGasPrice = this.proposeGasPrice.asGwei().intValue(); + gasOracle.ProposeGasPrice = this.proposeGasPrice.asGwei().toBigInteger(); } if (this.safeGasPrice != null) { - gasOracle.SafeGasPrice = this.safeGasPrice.asGwei().intValue(); + gasOracle.SafeGasPrice = this.safeGasPrice.asGwei().toBigInteger(); } if (this.fastGasPrice != null) { - gasOracle.FastGasPrice = this.fastGasPrice.asGwei().intValue(); + gasOracle.FastGasPrice = this.fastGasPrice.asGwei().toBigInteger(); } if (this.gasUsedRatio != null) { gasOracle.gasUsedRatio = this.gasUsedRatio.stream() .map(BigDecimal::toString) - .collect(Collectors.joining(", ")); + .collect(Collectors.joining(",")); } return gasOracle; } 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 2808462..da6c295 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -139,21 +139,15 @@ public int hashCode() { public String toString() { return "Log{" + "blockNumber='" + blockNumber + '\'' + - ", _blockNumber=" + _blockNumber + ", address='" + address + '\'' + ", transactionHash='" + transactionHash + '\'' + ", transactionIndex='" + transactionIndex + '\'' + - ", _transactionIndex=" + _transactionIndex + ", timeStamp='" + timeStamp + '\'' + - ", _timeStamp=" + _timeStamp + ", data='" + data + '\'' + ", gasPrice='" + gasPrice + '\'' + - ", _gasPrice=" + _gasPrice + ", gasUsed='" + gasUsed + '\'' + - ", _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 4ef4491..d2a8091 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan.model; import com.google.gson.annotations.Expose; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; @@ -11,8 +12,8 @@ */ public class Price { - private double ethusd; - private double ethbtc; + private BigDecimal ethusd; + private BigDecimal ethbtc; private String ethusd_timestamp; private String ethbtc_timestamp; @Expose(deserialize = false, serialize = false) @@ -22,11 +23,11 @@ public class Price { protected Price() {} - public double inUsd() { + public BigDecimal inUsd() { return ethusd; } - public double inBtc() { + public BigDecimal inBtc() { return ethbtc; } @@ -51,7 +52,7 @@ public boolean equals(Object o) { if (!(o instanceof Price)) return false; Price price = (Price) o; - return Double.compare(price.ethusd, ethusd) == 0 && Double.compare(price.ethbtc, ethbtc) == 0 + return Objects.equals(ethusd, price.ethusd) && Objects.equals(ethbtc, price.ethbtc) && Objects.equals(ethusd_timestamp, price.ethusd_timestamp) && Objects.equals(ethbtc_timestamp, price.ethbtc_timestamp); } @@ -77,19 +78,19 @@ public static PriceBuilder builder() { public static final class PriceBuilder { - private double ethusd; - private double ethbtc; + private BigDecimal ethusd; + private BigDecimal ethbtc; private LocalDateTime ethusdTimestamp; private LocalDateTime ethbtcTimestamp; private PriceBuilder() {} - public PriceBuilder withEthUsd(double ethusd) { + public PriceBuilder withEthUsd(BigDecimal ethusd) { this.ethusd = ethusd; return this; } - public PriceBuilder withEthBtc(double ethbtc) { + public PriceBuilder withEthBtc(BigDecimal ethbtc) { this.ethbtc = ethbtc; return this; } 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 eaf9b8a..052c187 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Status.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Status.java @@ -30,23 +30,15 @@ public String getErrDescription() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof Status)) return false; - Status status = (Status) o; - - if (isError != status.isError) - return false; - return Objects.equals(errDescription, status.errDescription); + return isError == status.isError && Objects.equals(errDescription, status.errDescription); } @Override public int hashCode() { - int result = isError; - result = 31 * result + (errDescription != null - ? errDescription.hashCode() - : 0); - return result; + return Objects.hash(isError, errDescription); } @Override 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 0c1a5b5..bb40ee2 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TokenBalance.java @@ -23,22 +23,17 @@ public String getContract() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof TokenBalance)) return false; if (!super.equals(o)) return false; - TokenBalance that = (TokenBalance) o; return Objects.equals(tokenContract, that.tokenContract); } @Override public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (tokenContract != null - ? tokenContract.hashCode() - : 0); - return result; + return Objects.hash(super.hashCode(), tokenContract); } @Override 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 819252e..7ef0e22 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -4,21 +4,14 @@ import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.Objects; /** * @author GoodforGod * @since 28.10.2018 */ -public class Tx extends BaseTx { +public class Tx extends BlockTx { private BigInteger value; - private long nonce; - private String blockHash; - private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; - private long confirmations; private String isError; private String txreceipt_status; @@ -29,22 +22,6 @@ public BigInteger getValue() { return value; } - public long getNonce() { - return nonce; - } - - public String getBlockHash() { - return blockHash; - } - - public int getTransactionIndex() { - return transactionIndex; - } - - public Wei getGasPrice() { - return Wei.ofWei(gasPrice); - } - public boolean haveError() { return !BasicUtils.isEmpty(isError) && !isError.equals("0"); } @@ -52,50 +29,30 @@ public boolean haveError() { public String getTxReceiptStatus() { return txreceipt_status; } - - public Wei getGasUsedCumulative() { - return Wei.ofWei(cumulativeGasUsed); - } - - public long getConfirmations() { - return confirmations; - } // </editor-fold> - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof Tx)) - return false; - if (!super.equals(o)) - return false; - Tx tx = (Tx) o; - return nonce == tx.nonce && transactionIndex == tx.transactionIndex && confirmations == tx.confirmations - && Objects.equals(value, tx.value) && Objects.equals(blockHash, tx.blockHash) - && Objects.equals(gasPrice, tx.gasPrice) && Objects.equals(cumulativeGasUsed, tx.cumulativeGasUsed) - && Objects.equals(isError, tx.isError) && Objects.equals(txreceipt_status, tx.txreceipt_status); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), value, nonce, blockHash, transactionIndex, gasPrice, cumulativeGasUsed, - confirmations, isError, txreceipt_status); - } - @Override public String toString() { return "Tx{" + - "nonce=" + nonce + - ", value='" + value + '\'' + + "value=" + value + + ", isError='" + isError + '\'' + + ", txreceipt_status='" + txreceipt_status + '\'' + + ", nonce=" + nonce + ", blockHash='" + blockHash + '\'' + ", transactionIndex=" + transactionIndex + + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + - ", confirmations=" + confirmations + - ", isError='" + isError + '\'' + - ", txreceipt_status='" + txreceipt_status + '\'' + - "} " + super.toString(); + ", blockNumber=" + blockNumber + + ", timeStamp='" + timeStamp + '\'' + + ", hash='" + hash + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", contractAddress='" + contractAddress + '\'' + + ", input='" + input + '\'' + + ", gas=" + gas + + ", gasUsed=" + gasUsed + + '}'; } public static TxBuilder builder() { 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 7be8aff..16d4457 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -1,6 +1,5 @@ package io.goodforgod.api.etherscan.model; -import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; @@ -9,30 +8,16 @@ * @author GoodforGod * @since 28.10.2018 */ -public class TxErc1155 extends BaseTx { +public class TxErc1155 extends BlockTx { - private long nonce; - private String blockHash; private String tokenID; private String tokenName; private String tokenSymbol; private String tokenValue; - private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; - private long confirmations; protected TxErc1155() {} // <editor-fold desc="Getters"> - public long getNonce() { - return nonce; - } - - public String getBlockHash() { - return blockHash; - } - public String getTokenID() { return tokenID; } @@ -48,22 +33,6 @@ public String getTokenSymbol() { public String getTokenValue() { return tokenValue; } - - public int getTransactionIndex() { - return transactionIndex; - } - - public Wei getGasPrice() { - return Wei.ofWei(gasPrice); - } - - public Wei getGasUsedCumulative() { - return Wei.ofWei(cumulativeGasUsed); - } - - public long getConfirmations() { - return confirmations; - } // </editor-fold> @Override @@ -86,18 +55,27 @@ public int hashCode() { @Override public String toString() { - return "TxERC721{" + - "nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + - ", tokenID='" + tokenID + '\'' + + return "TxErc1155{" + + "tokenID='" + tokenID + '\'' + ", tokenName='" + tokenName + '\'' + ", tokenSymbol='" + tokenSymbol + '\'' + ", tokenValue='" + tokenValue + '\'' + + ", nonce=" + nonce + + ", blockHash='" + blockHash + '\'' + ", transactionIndex=" + transactionIndex + + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + - ", confirmations=" + confirmations + - "} " + super.toString(); + ", blockNumber=" + blockNumber + + ", timeStamp='" + timeStamp + '\'' + + ", hash='" + hash + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", contractAddress='" + contractAddress + '\'' + + ", input='" + input + '\'' + + ", gas=" + gas + + ", gasUsed=" + gasUsed + + '}'; } public static TxErc1155Builder builder() { 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 751044c..3dc22fd 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -9,30 +9,16 @@ * @author GoodforGod * @since 28.10.2018 */ -public class TxErc20 extends BaseTx { +public class TxErc20 extends BlockTx { private BigInteger value; - private long nonce; - private String blockHash; private String tokenName; private String tokenSymbol; private String tokenDecimal; - private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; - private long confirmations; protected TxErc20() {} // <editor-fold desc="Getters"> - public long getNonce() { - return nonce; - } - - public String getBlockHash() { - return blockHash; - } - public BigInteger getValue() { return value; } @@ -48,22 +34,6 @@ public String getTokenSymbol() { public String getTokenDecimal() { return tokenDecimal; } - - public int getTransactionIndex() { - return transactionIndex; - } - - public Wei getGasPrice() { - return Wei.ofWei(gasPrice); - } - - public Wei getGasUsedCumulative() { - return Wei.ofWei(cumulativeGasUsed); - } - - public long getConfirmations() { - return confirmations; - } // </editor-fold> @Override @@ -86,18 +56,27 @@ public int hashCode() { @Override public String toString() { - return "TxERC20{" + - "nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + - ", value='" + value + '\'' + + return "TxErc20{" + + "value=" + value + ", tokenName='" + tokenName + '\'' + ", tokenSymbol='" + tokenSymbol + '\'' + ", tokenDecimal='" + tokenDecimal + '\'' + + ", nonce=" + nonce + + ", blockHash='" + blockHash + '\'' + ", transactionIndex=" + transactionIndex + + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + - ", confirmations=" + confirmations + - "} " + super.toString(); + ", blockNumber=" + blockNumber + + ", timeStamp='" + timeStamp + '\'' + + ", hash='" + hash + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", contractAddress='" + contractAddress + '\'' + + ", input='" + input + '\'' + + ", gas=" + gas + + ", gasUsed=" + gasUsed + + '}'; } public static TxERC20Builder builder() { 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 7b59393..2180019 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -1,6 +1,5 @@ package io.goodforgod.api.etherscan.model; -import java.math.BigInteger; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Objects; @@ -9,30 +8,16 @@ * @author GoodforGod * @since 28.10.2018 */ -public class TxErc721 extends BaseTx { +public class TxErc721 extends BlockTx { - private long nonce; - private String blockHash; private String tokenID; private String tokenName; private String tokenSymbol; private String tokenDecimal; - private int transactionIndex; - private BigInteger gasPrice; - private BigInteger cumulativeGasUsed; - private long confirmations; protected TxErc721() {} // <editor-fold desc="Getters"> - public long getNonce() { - return nonce; - } - - public String getBlockHash() { - return blockHash; - } - public String getTokenID() { return tokenID; } @@ -48,22 +33,6 @@ public String getTokenSymbol() { public String getTokenDecimal() { return tokenDecimal; } - - public int getTransactionIndex() { - return transactionIndex; - } - - public Wei getGasPrice() { - return Wei.ofWei(gasPrice); - } - - public Wei getGasUsedCumulative() { - return Wei.ofWei(cumulativeGasUsed); - } - - public long getConfirmations() { - return confirmations; - } // </editor-fold> @Override @@ -86,18 +55,27 @@ public int hashCode() { @Override public String toString() { - return "TxERC721{" + - "nonce=" + nonce + - ", blockHash='" + blockHash + '\'' + - ", tokenID='" + tokenID + '\'' + + return "TxErc721{" + + "tokenID='" + tokenID + '\'' + ", tokenName='" + tokenName + '\'' + ", tokenSymbol='" + tokenSymbol + '\'' + ", tokenDecimal='" + tokenDecimal + '\'' + + ", nonce=" + nonce + + ", blockHash='" + blockHash + '\'' + ", transactionIndex=" + transactionIndex + + ", confirmations=" + confirmations + ", gasPrice=" + gasPrice + ", cumulativeGasUsed=" + cumulativeGasUsed + - ", confirmations=" + confirmations + - "} " + super.toString(); + ", blockNumber=" + blockNumber + + ", timeStamp='" + timeStamp + '\'' + + ", hash='" + hash + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", contractAddress='" + contractAddress + '\'' + + ", input='" + input + '\'' + + ", gas=" + gas + + ", gasUsed=" + gasUsed + + '}'; } public static TxERC721Builder builder() { 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 dd74e99..a61cf83 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -68,12 +68,21 @@ public int hashCode() { @Override public String toString() { return "TxInternal{" + - "type='" + type + '\'' + - ", traceId=" + traceId + - ", value=" + value + + "value=" + value + + ", type='" + type + '\'' + + ", traceId='" + traceId + '\'' + ", isError=" + isError + ", errCode='" + errCode + '\'' + - "} " + super.toString(); + ", blockNumber=" + blockNumber + + ", timeStamp='" + timeStamp + '\'' + + ", hash='" + hash + '\'' + + ", from='" + from + '\'' + + ", to='" + to + '\'' + + ", contractAddress='" + contractAddress + '\'' + + ", input='" + input + '\'' + + ", gas=" + gas + + ", gasUsed=" + gasUsed + + '}'; } public static TxInternalBuilder builder() { diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java index 038fd4b..3180478 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Wei.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Wei.java @@ -4,12 +4,18 @@ import java.math.BigInteger; import java.math.RoundingMode; import java.util.Objects; +import org.jetbrains.annotations.NotNull; /** * @author GoodforGod * @since 30.10.2018 */ -public class Wei { +public class Wei implements Comparable<Wei> { + + private static final BigDecimal KWEI_POW = BigDecimal.ONE.pow(3); + private static final BigDecimal MWEI_POW = BigDecimal.ONE.pow(6); + private static final BigDecimal GWEI_POW = BigDecimal.ONE.pow(9); + private static final BigDecimal WEI_POW = BigDecimal.ONE.pow(18); private final BigInteger result; @@ -18,54 +24,100 @@ private Wei(BigInteger value) { } public static Wei ofWei(int value) { - return new Wei(BigInteger.valueOf(value)); + return ofWei(BigInteger.valueOf(value)); } public static Wei ofWei(long value) { - return new Wei(BigInteger.valueOf(value)); + return ofWei(BigInteger.valueOf(value)); } public static Wei ofWei(BigInteger value) { return new Wei(value); } + public static Wei ofKwei(int value) { + return ofKwei(BigInteger.valueOf(value)); + } + + public static Wei ofKwei(long value) { + return ofKwei(BigInteger.valueOf(value)); + } + + public static Wei ofKwei(BigDecimal value) { + return new Wei(value.multiply(KWEI_POW).toBigInteger()); + } + + public static Wei ofKwei(BigInteger value) { + return new Wei(value.multiply(KWEI_POW.toBigInteger())); + } + + public static Wei ofMwei(int value) { + return ofMwei(BigInteger.valueOf(value)); + } + + public static Wei ofMwei(long value) { + return ofMwei(BigInteger.valueOf(value)); + } + + public static Wei ofMwei(BigDecimal value) { + return new Wei(value.multiply(MWEI_POW).toBigInteger()); + } + + public static Wei ofMwei(BigInteger value) { + return new Wei(value.multiply(MWEI_POW.toBigInteger())); + } + + public static Wei ofGwei(int value) { + return ofGwei(BigInteger.valueOf(value)); + } + + public static Wei ofGwei(long value) { + return ofGwei(BigInteger.valueOf(value)); + } + + public static Wei ofGwei(BigDecimal value) { + return new Wei(value.multiply(GWEI_POW).toBigInteger()); + } + + public static Wei ofGwei(BigInteger value) { + return new Wei(value.multiply(GWEI_POW.toBigInteger())); + } + public static Wei ofEther(int value) { - return new Wei(BigInteger.valueOf(value).multiply(BigInteger.valueOf(1_000_000_000_000_000L))); + return ofEther(BigInteger.valueOf(value)); } public static Wei ofEther(long value) { - return new Wei(BigInteger.valueOf(value).multiply(BigInteger.valueOf(1_000_000_000_000_000L))); + return ofEther(BigInteger.valueOf(value)); } - public static Wei ofEther(BigInteger value) { - return new Wei(value.multiply(BigInteger.valueOf(1_000_000_000_000_000L))); + public static Wei ofEther(BigDecimal value) { + return new Wei(value.multiply(WEI_POW).toBigInteger()); } - public static Wei ofEther(BigDecimal value) { - return new Wei(value.multiply(BigDecimal.valueOf(1_000_000_000_000_000L)).toBigInteger()); + public static Wei ofEther(BigInteger value) { + return new Wei(value.multiply(WEI_POW.toBigInteger())); } - // <editor-fold desc="Getters"> public BigInteger asWei() { return result; } public BigDecimal asKwei() { - return new BigDecimal(result).divide(BigDecimal.valueOf(1_000), RoundingMode.HALF_UP); + return new BigDecimal(result).divide(KWEI_POW, RoundingMode.HALF_UP); } public BigDecimal asMwei() { - return new BigDecimal(result).divide(BigDecimal.valueOf(1_000_000), RoundingMode.HALF_UP); + return new BigDecimal(result).divide(MWEI_POW, RoundingMode.HALF_UP); } public BigDecimal asGwei() { - return new BigDecimal(result).divide(BigDecimal.valueOf(1_000_000_000), RoundingMode.HALF_UP); + return new BigDecimal(result).divide(GWEI_POW, RoundingMode.HALF_UP); } public BigDecimal asEther() { - return new BigDecimal(result).divide(BigDecimal.valueOf(1_000_000_000_000_000L), RoundingMode.HALF_UP); + return new BigDecimal(result).divide(WEI_POW, RoundingMode.HALF_UP); } - // </editor-fold> @Override public boolean equals(Object o) { @@ -82,6 +134,11 @@ public int hashCode() { return Objects.hash(result); } + @Override + public int compareTo(@NotNull Wei o) { + return result.compareTo(o.result); + } + @Override public String toString() { return result.toString(); 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 c98d5ee..4a2b624 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 @@ -6,12 +6,14 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; /** * @author GoodforGod * @since 31.10.2018 */ -public class BlockProxy { +public class BlockProxy implements Comparable<BlockProxy> { private String number; @Expose(deserialize = false, serialize = false) @@ -145,61 +147,36 @@ public List<TxProxy> getTransactions() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof BlockProxy)) return false; - BlockProxy that = (BlockProxy) o; - - if (number != null - ? !number.equals(that.number) - : that.number != null) - return false; - if (hash != null - ? !hash.equals(that.hash) - : that.hash != null) - return false; - return parentHash != null - ? parentHash.equals(that.parentHash) - : that.parentHash == null; + return Objects.equals(number, that.number) && Objects.equals(hash, that.hash) + && Objects.equals(parentHash, that.parentHash) && Objects.equals(nonce, that.nonce); } @Override public int hashCode() { - int result = number != null - ? number.hashCode() - : 0; - result = 31 * result + (hash != null - ? hash.hashCode() - : 0); - result = 31 * result + (parentHash != null - ? parentHash.hashCode() - : 0); - return result; + return Objects.hash(number, hash, parentHash, nonce); } @Override public String toString() { return "BlockProxy{" + "number='" + number + '\'' + - ", _number=" + _number + ", hash='" + hash + '\'' + ", parentHash='" + parentHash + '\'' + ", stateRoot='" + stateRoot + '\'' + ", size='" + size + '\'' + - ", _size=" + _size + ", difficulty='" + difficulty + '\'' + ", totalDifficulty='" + totalDifficulty + '\'' + ", timestamp='" + timestamp + '\'' + - ", _timestamp=" + _timestamp + ", miner='" + miner + '\'' + ", nonce='" + nonce + '\'' + ", extraData='" + extraData + '\'' + ", logsBloom='" + logsBloom + '\'' + ", mixHash='" + mixHash + '\'' + ", gasUsed='" + gasUsed + '\'' + - ", _gasUsed=" + _gasUsed + ", gasLimit='" + gasLimit + '\'' + - ", _gasLimit=" + _gasLimit + ", sha3Uncles='" + sha3Uncles + '\'' + ", uncles=" + uncles + ", receiptsRoot='" + receiptsRoot + '\'' + @@ -208,6 +185,11 @@ public String toString() { '}'; } + @Override + public int compareTo(@NotNull BlockProxy o) { + return Long.compare(getNumber(), o.getNumber()); + } + public static BlockProxyBuilder builder() { return new BlockProxyBuilder(); } 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 2b616c3..e6df01c 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 @@ -5,6 +5,7 @@ import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.util.BasicUtils; import java.util.List; +import java.util.Objects; /** * @author GoodforGod @@ -75,7 +76,7 @@ public Wei getGasUsed() { return _gasUsed; } - public Wei getCumulativeGasUsed() { + public Wei getGasUsedCumulative() { if (_cumulativeGasUsed == null && !BasicUtils.isEmpty(cumulativeGasUsed)) _cumulativeGasUsed = Wei.ofWei(BasicUtils.parseHex(cumulativeGasUsed)); return _cumulativeGasUsed; @@ -98,36 +99,17 @@ public String getLogsBloom() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof ReceiptProxy)) return false; - ReceiptProxy that = (ReceiptProxy) o; - - if (blockNumber != null - ? !blockNumber.equals(that.blockNumber) - : that.blockNumber != null) - return false; - if (transactionHash != null - ? !transactionHash.equals(that.transactionHash) - : that.transactionHash != null) - return false; - return transactionIndex != null - ? transactionIndex.equals(that.transactionIndex) - : that.transactionIndex == null; + return Objects.equals(blockNumber, that.blockNumber) && Objects.equals(blockHash, that.blockHash) + && Objects.equals(transactionHash, that.transactionHash) + && Objects.equals(transactionIndex, that.transactionIndex); } @Override public int hashCode() { - int result = blockNumber != null - ? blockNumber.hashCode() - : 0; - result = 31 * result + (transactionHash != null - ? transactionHash.hashCode() - : 0); - result = 31 * result + (transactionIndex != null - ? transactionIndex.hashCode() - : 0); - return result; + return Objects.hash(blockNumber, blockHash, transactionHash, transactionIndex); } @Override @@ -137,15 +119,11 @@ public String toString() { ", from='" + from + '\'' + ", to='" + to + '\'' + ", blockNumber='" + blockNumber + '\'' + - ", _blockNumber=" + _blockNumber + ", blockHash='" + blockHash + '\'' + ", transactionHash='" + transactionHash + '\'' + ", transactionIndex='" + transactionIndex + '\'' + - ", _transactionIndex=" + _transactionIndex + ", gasUsed='" + gasUsed + '\'' + - ", _gasUsed=" + _gasUsed + ", cumulativeGasUsed='" + cumulativeGasUsed + '\'' + - ", _cumulativeGasUsed=" + _cumulativeGasUsed + ", contractAddress='" + contractAddress + '\'' + ", logs=" + logs + ", 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 b2b412b..70b4fd7 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 @@ -3,12 +3,14 @@ import com.google.gson.annotations.Expose; import io.goodforgod.api.etherscan.model.Wei; import io.goodforgod.api.etherscan.util.BasicUtils; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; /** * @author GoodforGod * @since 31.10.2018 */ -public class TxProxy { +public class TxProxy implements Comparable<TxProxy> { private String to; private String hash; @@ -109,36 +111,17 @@ public Long getBlockNumber() { public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) + if (!(o instanceof TxProxy)) return false; - TxProxy txProxy = (TxProxy) o; - - if (hash != null - ? !hash.equals(txProxy.hash) - : txProxy.hash != null) - return false; - if (blockHash != null - ? !blockHash.equals(txProxy.blockHash) - : txProxy.blockHash != null) - return false; - return blockNumber != null - ? blockNumber.equals(txProxy.blockNumber) - : txProxy.blockNumber == null; + return Objects.equals(hash, txProxy.hash) && Objects.equals(transactionIndex, txProxy.transactionIndex) + && Objects.equals(nonce, txProxy.nonce) && Objects.equals(blockHash, txProxy.blockHash) + && Objects.equals(blockNumber, txProxy.blockNumber); } @Override public int hashCode() { - int result = hash != null - ? hash.hashCode() - : 0; - result = 31 * result + (blockHash != null - ? blockHash.hashCode() - : 0); - result = 31 * result + (blockNumber != null - ? blockNumber.hashCode() - : 0); - return result; + return Objects.hash(hash, transactionIndex, nonce, blockHash, blockNumber); } @Override @@ -147,25 +130,28 @@ public String toString() { "to='" + to + '\'' + ", hash='" + hash + '\'' + ", transactionIndex='" + transactionIndex + '\'' + - ", _transactionIndex=" + _transactionIndex + ", from='" + from + '\'' + ", v='" + v + '\'' + ", input='" + input + '\'' + ", s='" + s + '\'' + ", r='" + r + '\'' + ", nonce='" + nonce + '\'' + - ", _nonce=" + _nonce + ", value='" + value + '\'' + ", gas='" + gas + '\'' + - ", _gas=" + _gas + ", gasPrice='" + gasPrice + '\'' + - ", _gasPrice=" + _gasPrice + ", blockHash='" + blockHash + '\'' + ", blockNumber='" + blockNumber + '\'' + - ", _blockNumber=" + _blockNumber + '}'; } + @Override + public int compareTo(@NotNull TxProxy o) { + final int firstCompare = Long.compare(getBlockNumber(), o.getBlockNumber()); + return (firstCompare == 0) + ? Long.compare(getTransactionIndex(), o.getTransactionIndex()) + : firstCompare; + } + public static TxProxyBuilder builder() { return new TxProxyBuilder(); } diff --git a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java index 53b1c2c..b309dd9 100644 --- a/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/gastracker/GasTrackerApiTests.java @@ -1,9 +1,9 @@ package io.goodforgod.api.etherscan.gastracker; import io.goodforgod.api.etherscan.ApiRunner; -import io.goodforgod.api.etherscan.model.GasEstimate; import io.goodforgod.api.etherscan.model.GasOracle; import io.goodforgod.api.etherscan.model.Wei; +import java.time.Duration; import org.junit.jupiter.api.Test; /** @@ -14,9 +14,8 @@ class GasTrackerApiTests extends ApiRunner { @Test void estimate() { - GasEstimate estimate = getApi().gasTracker().estimate(Wei.ofWei(123)); + Duration estimate = getApi().gasTracker().estimate(Wei.ofWei(123)); assertNotNull(estimate); - assertNotNull(estimate.getDuration()); } @Test diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index f895595..efbb856 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -74,7 +74,7 @@ void gasOracleBuilder() { .withSafeGasPrice(Wei.ofWei(1000000000)) .withGasUsedRatio(Collections.singletonList(new BigDecimal(1))) .withLastBlock(1L) - .withSuggestBaseFee(1.0) + .withSuggestBaseFee(BigDecimal.valueOf(1.0)) .build(); assertNotNull(value); @@ -86,7 +86,7 @@ void gasOracleBuilder() { .withSafeGasPrice(Wei.ofWei(1000000000)) .withGasUsedRatio(Collections.singletonList(new BigDecimal(1))) .withLastBlock(1L) - .withSuggestBaseFee(1.0) + .withSuggestBaseFee(BigDecimal.valueOf(1.0)) .build(); assertEquals(value, value2); assertEquals(value.hashCode(), value2.hashCode()); @@ -117,15 +117,15 @@ void logBuilder() { void priceBuilder() { LocalDateTime timestamp = LocalDateTime.now(); Price value = Price.builder() - .withEthBtc(1.0) - .withEthUsd(1.0) + .withEthBtc(BigDecimal.valueOf(1.0)) + .withEthUsd(BigDecimal.valueOf(1.0)) .withEthBtcTimestamp(timestamp) .withEthUsdTimestamp(timestamp) .build(); assertNotNull(value); - assertEquals(1.0, value.inUsd()); - assertEquals(1.0, value.inBtc()); + assertEquals(BigDecimal.valueOf(1.0), value.inUsd()); + assertEquals(BigDecimal.valueOf(1.0), value.inBtc()); } @Test @@ -193,6 +193,7 @@ void txBuilder() { assertEquals(value, value2); assertEquals(value.hashCode(), value2.hashCode()); assertEquals(value.toString(), value2.toString()); + assertEquals(0, value.compareTo(value2)); } @Test @@ -464,11 +465,46 @@ void blockProxyBuilder() { } @Test - void gasEstimate() { - GasEstimate gas1 = new GasEstimate(1); - GasEstimate gas2 = new GasEstimate(1); - assertEquals(gas1, gas2); - assertEquals(gas1.hashCode(), gas2.hashCode()); - assertEquals(gas1.toString(), gas2.toString()); + void weiTests() { + Wei w1 = Wei.ofWei(1); + Wei w2 = Wei.ofWei(1L); + Wei w3 = Wei.ofWei(BigInteger.valueOf(1)); + assertEquals(w1, w2); + assertEquals(w1, w3); + assertEquals(w1.hashCode(), w2.hashCode()); + assertEquals(w1.hashCode(), w3.hashCode()); + assertEquals(w1.toString(), w3.toString()); + + Wei kw1 = Wei.ofKwei(1); + Wei kw2 = Wei.ofKwei(1L); + Wei kw3 = Wei.ofKwei(BigInteger.valueOf(1)); + Wei kw4 = Wei.ofKwei(BigDecimal.valueOf(1)); + assertEquals(kw1, kw2); + assertEquals(kw1, kw3); + assertEquals(kw1, kw4); + + Wei mw1 = Wei.ofMwei(1); + Wei mw2 = Wei.ofMwei(1L); + Wei mw3 = Wei.ofMwei(BigInteger.valueOf(1)); + Wei mw4 = Wei.ofMwei(BigDecimal.valueOf(1)); + assertEquals(mw1, mw2); + assertEquals(mw1, mw3); + assertEquals(mw1, mw4); + + Wei gw1 = Wei.ofGwei(1); + Wei gw2 = Wei.ofGwei(1L); + Wei gw3 = Wei.ofGwei(BigInteger.valueOf(1)); + Wei gw4 = Wei.ofGwei(BigDecimal.valueOf(1)); + assertEquals(gw1, gw2); + assertEquals(gw1, gw3); + assertEquals(gw1, gw4); + + Wei ew1 = Wei.ofEther(1); + Wei ew2 = Wei.ofEther(1L); + Wei ew3 = Wei.ofEther(BigInteger.valueOf(1)); + Wei ew4 = Wei.ofEther(BigDecimal.valueOf(1)); + assertEquals(ew1, ew2); + assertEquals(ew1, ew3); + assertEquals(ew1, ew4); } } diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java index e4322f2..662fec2 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyTxReceiptApiTests.java @@ -26,7 +26,7 @@ void correct() { assertNotNull(infoProxy.get().getTransactionHash()); assertNotNull(infoProxy.get().getTransactionIndex()); assertNotNull(infoProxy.get().getGasUsed()); - assertNotNull(infoProxy.get().getCumulativeGasUsed()); + assertNotNull(infoProxy.get().getGasUsedCumulative()); assertNotNull(infoProxy.get().getLogs()); assertNotNull(infoProxy.get().getLogsBloom()); assertNull(infoProxy.get().getContractAddress()); From e6bee19e2affac147be8d9a1aebeab1bf5f0c293 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 15 May 2023 20:58:07 +0300 Subject: [PATCH 47/67] [2.0.0] BasicProvider simplified StatisticPriceApiTests assert fixed --- .../api/etherscan/BasicProvider.java | 28 ++----------------- .../statistic/StatisticPriceApiTests.java | 4 +-- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 3c88f3b..5c61aad 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -1,6 +1,5 @@ package io.goodforgod.api.etherscan; -import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanParseException; import io.goodforgod.api.etherscan.error.EtherScanRateLimitException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; @@ -9,7 +8,6 @@ import io.goodforgod.api.etherscan.model.response.StringResponseTO; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.util.Map; /** * Base provider for API Implementations @@ -64,36 +62,14 @@ <T> T convert(byte[] json, Class<T> tClass) { } final String jsonAsString = new String(json, StandardCharsets.UTF_8); - try { - final Map<String, Object> map = converter.fromJson(json, Map.class); - final Object result = map.get("result"); - if (result instanceof String && ((String) result).startsWith(MAX_RATE_LIMIT_REACHED)) - throw new EtherScanRateLimitException(((String) result)); - - throw new EtherScanParseException(e.getMessage() + ", for response: " + jsonAsString, e.getCause(), jsonAsString); - } catch (EtherScanException ex) { - throw ex; - } catch (Exception ex) { - throw new EtherScanParseException(e.getMessage() + ", for response: " + jsonAsString, e.getCause(), jsonAsString); - } + throw new EtherScanParseException(e.getMessage() + ", for response: " + jsonAsString, e.getCause(), jsonAsString); } } byte[] getRequest(String urlParameters) { queue.takeTurn(); final URI uri = URI.create(baseUrl + module + urlParameters); - final byte[] result = executor.get(uri); - if (result.length == 0) { - final StringResponseTO emptyResponse = StringResponseTO.builder() - .withStatus("0") - .withMessage("Server returned null value for GET request at URL - " + uri) - .withResult("") - .build(); - - throw new EtherScanResponseException(emptyResponse, "Server returned null value for GET request at URL - " + uri); - } - - return result; + return executor.get(uri); } byte[] postRequest(String urlParameters, String dataToPost) { diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java index 0dd89c2..ffc37a9 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java @@ -16,8 +16,8 @@ void correct() { assertNotNull(price); assertNotNull(price.btcTimestamp()); assertNotNull(price.usdTimestamp()); - assertNotEquals(0.0, price.inBtc()); - assertNotEquals(0.0, price.inUsd()); + assertNotEquals(0.0, price.inBtc().doubleValue()); + assertNotEquals(0.0, price.inUsd().doubleValue()); assertNotNull(price.toString()); Price empty = Price.builder().build(); From c64a3017f1eb99f7e4c4828de66dd9b0da0efff5 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Wed, 17 May 2023 00:04:51 +0300 Subject: [PATCH 48/67] [2.0.0] Javadoc improved --- README.md | 4 +- .../goodforgod/api/etherscan/AccountAPI.java | 62 +++++++++-------- .../api/etherscan/AccountAPIProvider.java | 68 ++++++++++--------- .../goodforgod/api/etherscan/ContractAPI.java | 2 +- .../api/etherscan/ContractAPIProvider.java | 2 +- .../io/goodforgod/api/etherscan/LogsAPI.java | 2 +- .../api/etherscan/LogsAPIProvider.java | 2 +- .../io/goodforgod/api/etherscan/ProxyAPI.java | 16 ++--- .../api/etherscan/ProxyAPIProvider.java | 16 ++--- .../api/etherscan/StatisticAPI.java | 2 +- .../api/etherscan/StatisticAPIProvider.java | 2 +- .../api/etherscan/TransactionAPI.java | 4 +- .../api/etherscan/TransactionAPIProvider.java | 4 +- .../goodforgod/api/etherscan/model/Block.java | 4 -- .../goodforgod/api/etherscan/model/Price.java | 20 +++--- .../etherscan/model/ModelBuilderTests.java | 8 +-- .../statistic/StatisticPriceApiTests.java | 4 +- 17 files changed, 114 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index dc939bf..dd244b5 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) -[Etherscan.io](https://etherscan.io/apis) Java API implementation. +[Etherscan.io](https://docs.etherscan.io/) Java API implementation. -Library supports all available EtherScan *API* calls for all available *Ethereum Networks* for *etherscan.io* +Library supports EtherScan *API* for all available *Ethereum Networks* for *etherscan.io* ## Dependency :rocket: diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java index 45be8b8..09c49eb 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPI.java @@ -21,7 +21,7 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - Balance balance(String address) throws EtherScanException; + Balance balance(@NotNull String address) throws EtherScanException; /** * ERC20 token balance for address @@ -32,7 +32,7 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - TokenBalance balance(String address, String contract) throws EtherScanException; + TokenBalance balance(@NotNull String address, @NotNull String contract) throws EtherScanException; /** * Maximum 20 address for single batch request If address MORE THAN 20, then there will be more than @@ -43,7 +43,7 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<Balance> balances(List<String> addresses) throws EtherScanException; + List<Balance> balances(@NotNull List<String> addresses) throws EtherScanException; /** * All txs for given address @@ -55,13 +55,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<Tx> txs(String address, long startBlock, long endBlock) throws EtherScanException; + List<Tx> txs(@NotNull String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<Tx> txs(String address, long startBlock) throws EtherScanException; + List<Tx> txs(@NotNull String address, long startBlock) throws EtherScanException; @NotNull - List<Tx> txs(String address) throws EtherScanException; + List<Tx> txs(@NotNull String address) throws EtherScanException; /** * All internal txs for given address @@ -73,13 +73,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxInternal> txsInternal(String address, long startBlock, long endBlock) throws EtherScanException; + List<TxInternal> txsInternal(@NotNull String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxInternal> txsInternal(String address, long startBlock) throws EtherScanException; + List<TxInternal> txsInternal(@NotNull String address, long startBlock) throws EtherScanException; @NotNull - List<TxInternal> txsInternal(String address) throws EtherScanException; + List<TxInternal> txsInternal(@NotNull String address) throws EtherScanException; /** * All internal tx for given transaction hash @@ -89,7 +89,7 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxInternal> txsInternalByHash(String txhash) throws EtherScanException; + List<TxInternal> txsInternalByHash(@NotNull String txhash) throws EtherScanException; /** * All ERC-20 token txs for given address @@ -101,13 +101,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxErc20> txsErc20(String address, long startBlock, long endBlock) throws EtherScanException; + List<TxErc20> txsErc20(@NotNull String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxErc20> txsErc20(String address, long startBlock) throws EtherScanException; + List<TxErc20> txsErc20(@NotNull String address, long startBlock) throws EtherScanException; @NotNull - List<TxErc20> txsErc20(String address) throws EtherScanException; + List<TxErc20> txsErc20(@NotNull String address) throws EtherScanException; /** * All ERC-20 token txs for given address and contract address @@ -120,13 +120,14 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxErc20> txsErc20(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; + List<TxErc20> txsErc20(@NotNull String address, @NotNull String contractAddress, long startBlock, long endBlock) + throws EtherScanException; @NotNull - List<TxErc20> txsErc20(String address, String contractAddress, long startBlock) throws EtherScanException; + List<TxErc20> txsErc20(@NotNull String address, @NotNull String contractAddress, long startBlock) throws EtherScanException; @NotNull - List<TxErc20> txsErc20(String address, String contractAddress) throws EtherScanException; + List<TxErc20> txsErc20(@NotNull String address, @NotNull String contractAddress) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address @@ -138,13 +139,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxErc721> txsErc721(String address, long startBlock, long endBlock) throws EtherScanException; + List<TxErc721> txsErc721(@NotNull String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxErc721> txsErc721(String address, long startBlock) throws EtherScanException; + List<TxErc721> txsErc721(@NotNull String address, long startBlock) throws EtherScanException; @NotNull - List<TxErc721> txsErc721(String address) throws EtherScanException; + List<TxErc721> txsErc721(@NotNull String address) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address @@ -156,13 +157,14 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxErc721> txsErc721(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; + List<TxErc721> txsErc721(@NotNull String address, @NotNull String contractAddress, long startBlock, long endBlock) + throws EtherScanException; @NotNull - List<TxErc721> txsErc721(String address, String contractAddress, long startBlock) throws EtherScanException; + List<TxErc721> txsErc721(@NotNull String address, @NotNull String contractAddress, long startBlock) throws EtherScanException; @NotNull - List<TxErc721> txsErc721(String address, String contractAddress) throws EtherScanException; + List<TxErc721> txsErc721(@NotNull String address, @NotNull String contractAddress) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address @@ -174,13 +176,13 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxErc1155> txsErc1155(String address, long startBlock, long endBlock) throws EtherScanException; + List<TxErc1155> txsErc1155(@NotNull String address, long startBlock, long endBlock) throws EtherScanException; @NotNull - List<TxErc1155> txsErc1155(String address, long startBlock) throws EtherScanException; + List<TxErc1155> txsErc1155(@NotNull String address, long startBlock) throws EtherScanException; @NotNull - List<TxErc1155> txsErc1155(String address) throws EtherScanException; + List<TxErc1155> txsErc1155(@NotNull String address) throws EtherScanException; /** * All ERC-721 (NFT) token txs for given address @@ -192,13 +194,15 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock, long endBlock) throws EtherScanException; + List<TxErc1155> txsErc1155(@NotNull String address, @NotNull String contractAddress, long startBlock, long endBlock) + throws EtherScanException; @NotNull - List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock) throws EtherScanException; + List<TxErc1155> txsErc1155(@NotNull String address, @NotNull String contractAddress, long startBlock) + throws EtherScanException; @NotNull - List<TxErc1155> txsErc1155(String address, String contractAddress) throws EtherScanException; + List<TxErc1155> txsErc1155(@NotNull String address, @NotNull String contractAddress) throws EtherScanException; /** * All blocks mined by address @@ -208,5 +212,5 @@ public interface AccountAPI { * @throws EtherScanException parent exception class */ @NotNull - List<Block> blocksMined(String address) throws EtherScanException; + List<Block> blocksMined(@NotNull String address) throws EtherScanException; } diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 08e9dd5..442edff 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -56,7 +56,7 @@ final class AccountAPIProvider extends BasicProvider implements AccountAPI { @NotNull @Override - public Balance balance(String address) throws EtherScanException { + public Balance balance(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_BALANCE_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + address; @@ -69,7 +69,7 @@ public Balance balance(String address) throws EtherScanException { @NotNull @Override - public TokenBalance balance(String address, String contract) throws EtherScanException { + public TokenBalance balance(@NotNull String address, @NotNull String contract) throws EtherScanException { BasicUtils.validateAddress(address); BasicUtils.validateAddress(contract); @@ -83,7 +83,7 @@ public TokenBalance balance(String address, String contract) throws EtherScanExc @NotNull @Override - public List<Balance> balances(List<String> addresses) throws EtherScanException { + public List<Balance> balances(@NotNull List<String> addresses) throws EtherScanException { if (BasicUtils.isEmpty(addresses)) { return Collections.emptyList(); } @@ -117,19 +117,19 @@ private String toAddressParam(List<String> addresses) { @NotNull @Override - public List<Tx> txs(String address) throws EtherScanException { + public List<Tx> txs(@NotNull String address) throws EtherScanException { return txs(address, MIN_START_BLOCK); } @NotNull @Override - public List<Tx> txs(String address, long startBlock) throws EtherScanException { + public List<Tx> txs(@NotNull String address, long startBlock) throws EtherScanException { return txs(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<Tx> txs(String address, long startBlock, long endBlock) throws EtherScanException { + public List<Tx> txs(@NotNull String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -171,19 +171,19 @@ private <T, R extends BaseListResponseTO<T>> List<T> getRequestUsingOffset(final @NotNull @Override - public List<TxInternal> txsInternal(String address) throws EtherScanException { + public List<TxInternal> txsInternal(@NotNull String address) throws EtherScanException { return txsInternal(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxInternal> txsInternal(String address, long startBlock) throws EtherScanException { + public List<TxInternal> txsInternal(@NotNull String address, long startBlock) throws EtherScanException { return txsInternal(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxInternal> txsInternal(String address, long startBlock, long endBlock) + public List<TxInternal> txsInternal(@NotNull String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -198,7 +198,7 @@ public List<TxInternal> txsInternal(String address, long startBlock, long endBlo @NotNull @Override - public List<TxInternal> txsInternalByHash(String txhash) throws EtherScanException { + public List<TxInternal> txsInternalByHash(@NotNull String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_INTERNAL_ACTION + TXHASH_PARAM + txhash; @@ -212,19 +212,19 @@ public List<TxInternal> txsInternalByHash(String txhash) throws EtherScanExcepti @NotNull @Override - public List<TxErc20> txsErc20(String address) throws EtherScanException { + public List<TxErc20> txsErc20(@NotNull String address) throws EtherScanException { return txsErc20(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxErc20> txsErc20(String address, long startBlock) throws EtherScanException { + public List<TxErc20> txsErc20(@NotNull String address, long startBlock) throws EtherScanException { return txsErc20(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxErc20> txsErc20(String address, long startBlock, long endBlock) throws EtherScanException { + public List<TxErc20> txsErc20(@NotNull String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -238,19 +238,20 @@ public List<TxErc20> txsErc20(String address, long startBlock, long endBlock) th @NotNull @Override - public List<TxErc20> txsErc20(String address, String contractAddress) throws EtherScanException { + public List<TxErc20> txsErc20(@NotNull String address, @NotNull String contractAddress) throws EtherScanException { return txsErc20(address, contractAddress, MIN_START_BLOCK); } @NotNull @Override - public List<TxErc20> txsErc20(String address, String contractAddress, long startBlock) throws EtherScanException { + public List<TxErc20> txsErc20(@NotNull String address, @NotNull String contractAddress, long startBlock) + throws EtherScanException { return txsErc20(address, contractAddress, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxErc20> txsErc20(String address, String contractAddress, long startBlock, long endBlock) + public List<TxErc20> txsErc20(@NotNull String address, @NotNull String contractAddress, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -265,19 +266,19 @@ public List<TxErc20> txsErc20(String address, String contractAddress, long start @NotNull @Override - public List<TxErc721> txsErc721(String address) throws EtherScanException { + public List<TxErc721> txsErc721(@NotNull String address) throws EtherScanException { return txsErc721(address, MIN_START_BLOCK); } @NotNull @Override - public List<TxErc721> txsErc721(String address, long startBlock) throws EtherScanException { + public List<TxErc721> txsErc721(@NotNull String address, long startBlock) throws EtherScanException { return txsErc721(address, startBlock, MAX_END_BLOCK); } @NotNull @Override - public List<TxErc721> txsErc721(String address, long startBlock, long endBlock) throws EtherScanException { + public List<TxErc721> txsErc721(@NotNull String address, long startBlock, long endBlock) throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -290,8 +291,9 @@ public List<TxErc721> txsErc721(String address, long startBlock, long endBlock) } @Override - public @NotNull List<TxErc721> txsErc721(String address, String contractAddress, long startBlock, long endBlock) - throws EtherScanException { + public @NotNull List<TxErc721> + txsErc721(@NotNull String address, @NotNull String contractAddress, long startBlock, long endBlock) + throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -304,17 +306,19 @@ public List<TxErc721> txsErc721(String address, long startBlock, long endBlock) } @Override - public @NotNull List<TxErc721> txsErc721(String address, String contractAddress, long startBlock) throws EtherScanException { + public @NotNull List<TxErc721> txsErc721(@NotNull String address, @NotNull String contractAddress, long startBlock) + throws EtherScanException { return txsErc721(address, contractAddress, startBlock, MAX_END_BLOCK); } @Override - public @NotNull List<TxErc721> txsErc721(String address, String contractAddress) throws EtherScanException { + public @NotNull List<TxErc721> txsErc721(@NotNull String address, @NotNull String contractAddress) throws EtherScanException { return txsErc721(address, contractAddress, MIN_START_BLOCK); } @Override - public @NotNull List<TxErc1155> txsErc1155(String address, long startBlock, long endBlock) throws EtherScanException { + public @NotNull List<TxErc1155> txsErc1155(@NotNull String address, long startBlock, long endBlock) + throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -327,18 +331,19 @@ public List<TxErc721> txsErc721(String address, long startBlock, long endBlock) } @Override - public @NotNull List<TxErc1155> txsErc1155(String address, long startBlock) throws EtherScanException { + public @NotNull List<TxErc1155> txsErc1155(@NotNull String address, long startBlock) throws EtherScanException { return txsErc1155(address, startBlock, MAX_END_BLOCK); } @Override - public @NotNull List<TxErc1155> txsErc1155(String address) throws EtherScanException { + public @NotNull List<TxErc1155> txsErc1155(@NotNull String address) throws EtherScanException { return txsErc1155(address, MIN_START_BLOCK); } @Override - public @NotNull List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock, long endBlock) - throws EtherScanException { + public @NotNull List<TxErc1155> + txsErc1155(@NotNull String address, @NotNull String contractAddress, long startBlock, long endBlock) + throws EtherScanException { BasicUtils.validateAddress(address); final BlockParam blocks = BasicUtils.compensateBlocks(startBlock, endBlock); @@ -351,19 +356,20 @@ public List<TxErc721> txsErc721(String address, long startBlock, long endBlock) } @Override - public @NotNull List<TxErc1155> txsErc1155(String address, String contractAddress, long startBlock) + public @NotNull List<TxErc1155> txsErc1155(@NotNull String address, @NotNull String contractAddress, long startBlock) throws EtherScanException { return txsErc1155(address, contractAddress, startBlock, MAX_END_BLOCK); } @Override - public @NotNull List<TxErc1155> txsErc1155(String address, String contractAddress) throws EtherScanException { + public @NotNull List<TxErc1155> txsErc1155(@NotNull String address, @NotNull String contractAddress) + throws EtherScanException { return txsErc1155(address, contractAddress, MIN_START_BLOCK); } @NotNull @Override - public List<Block> blocksMined(String address) throws EtherScanException { + public List<Block> blocksMined(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_MINED_ACTION + PAGE_PARAM + "%s" + OFFSET_PARAM + OFFSET_MAX + BLOCK_TYPE_PARAM diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java index 7564c98..af0852c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java @@ -20,5 +20,5 @@ public interface ContractAPI { * @throws EtherScanException parent exception class */ @NotNull - Abi contractAbi(String address) throws EtherScanException; + Abi contractAbi(@NotNull String address) 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 bbb7335..6b4404a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -31,7 +31,7 @@ final class ContractAPIProvider extends BasicProvider implements ContractAPI { @NotNull @Override - public Abi contractAbi(String address) throws EtherScanException { + public Abi contractAbi(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java index 01d79f7..0330f9f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPI.java @@ -23,5 +23,5 @@ public interface LogsAPI { * @see LogQuery */ @NotNull - List<Log> logs(LogQuery query) throws EtherScanException; + List<Log> logs(@NotNull LogQuery query) throws EtherScanException; } diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java index fe9d420..d294fb5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java @@ -31,7 +31,7 @@ final class LogsAPIProvider extends BasicProvider implements LogsAPI { @NotNull @Override - public List<Log> logs(LogQuery query) throws EtherScanException { + public List<Log> logs(@NotNull LogQuery query) throws EtherScanException { final String urlParams = ACT_LOGS_PARAM + query.params(); final LogResponseTO response = getRequest(urlParams, LogResponseTO.class); BasicUtils.validateTxResponse(response); diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java index 77d6769..30c4f96 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPI.java @@ -56,7 +56,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<TxProxy> tx(String txhash) throws EtherScanException; + Optional<TxProxy> tx(@NotNull String txhash) throws EtherScanException; /** * Returns information about a transaction by block number and transaction index position @@ -87,7 +87,7 @@ public interface ProxyAPI { * @return transactions send amount from address * @throws EtherScanException parent exception class */ - int txSendCount(String address) throws EtherScanException; + int txSendCount(@NotNull String address) throws EtherScanException; /** * Creates new message call transaction or a contract creation for signed transactions @@ -98,7 +98,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<String> txSendRaw(String hexEncodedTx) throws EtherScanException; + Optional<String> txSendRaw(@NotNull String hexEncodedTx) throws EtherScanException; /** * Returns the receipt of a transaction by transaction hash eth_getTransactionReceipt @@ -108,7 +108,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<ReceiptProxy> txReceipt(String txhash) throws EtherScanException; + Optional<ReceiptProxy> txReceipt(@NotNull String txhash) throws EtherScanException; /** * Executes a new message call immediately without creating a transaction on the block chain @@ -120,7 +120,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<String> call(String address, String data) throws EtherScanException; + Optional<String> call(@NotNull String address, @NotNull String data) throws EtherScanException; /** * Returns code at a given address eth_getCode @@ -130,7 +130,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<String> code(String address) throws EtherScanException; + Optional<String> code(@NotNull String address) throws EtherScanException; /** * (**experimental) Returns the value from a storage position at a given address eth_getStorageAt @@ -142,7 +142,7 @@ public interface ProxyAPI { */ @Experimental @NotNull - Optional<String> storageAt(String address, long position) throws EtherScanException; + Optional<String> storageAt(@NotNull String address, long position) throws EtherScanException; /** * Returns the current price per gas in wei eth_gasPrice @@ -162,7 +162,7 @@ public interface ProxyAPI { * @throws EtherScanException parent exception class */ @NotNull - Wei gasEstimated(String hexData) throws EtherScanException; + Wei gasEstimated(@NotNull String hexData) throws EtherScanException; @NotNull Wei gasEstimated() throws EtherScanException; diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index 18edd90..4dff589 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -96,7 +96,7 @@ public Optional<BlockProxy> blockUncle(long blockNo, long index) throws EtherSca @NotNull @Override - public Optional<TxProxy> tx(String txhash) throws EtherScanException { + public Optional<TxProxy> tx(@NotNull String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_BY_HASH_PARAM + TXHASH_PARAM + txhash; @@ -127,7 +127,7 @@ public int txCount(long blockNo) throws EtherScanException { } @Override - public int txSendCount(String address) throws EtherScanException { + public int txSendCount(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_TX_COUNT_PARAM + ADDRESS_PARAM + address + TAG_LAST_PARAM; @@ -137,7 +137,7 @@ public int txSendCount(String address) throws EtherScanException { @Override @NotNull - public Optional<String> txSendRaw(String hexEncodedTx) throws EtherScanException { + public Optional<String> txSendRaw(@NotNull String hexEncodedTx) throws EtherScanException { if (BasicUtils.isNotHex(hexEncodedTx)) throw new EtherScanInvalidDataHexException("Data is not encoded in hex format - " + hexEncodedTx); @@ -160,7 +160,7 @@ public Optional<String> txSendRaw(String hexEncodedTx) throws EtherScanException @NotNull @Override - public Optional<ReceiptProxy> txReceipt(String txhash) throws EtherScanException { + public Optional<ReceiptProxy> txReceipt(@NotNull String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_RECEIPT_PARAM + TXHASH_PARAM + txhash; @@ -170,7 +170,7 @@ public Optional<ReceiptProxy> txReceipt(String txhash) throws EtherScanException @NotNull @Override - public Optional<String> call(String address, String data) throws EtherScanException { + public Optional<String> call(@NotNull String address, @NotNull String data) throws EtherScanException { BasicUtils.validateAddress(address); if (BasicUtils.isNotHex(data)) throw new EtherScanInvalidDataHexException("Data is not hex encoded."); @@ -182,7 +182,7 @@ public Optional<String> call(String address, String data) throws EtherScanExcept @NotNull @Override - public Optional<String> code(String address) throws EtherScanException { + public Optional<String> code(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_CODE_PARAM + ADDRESS_PARAM + address + TAG_LAST_PARAM; @@ -192,7 +192,7 @@ public Optional<String> code(String address) throws EtherScanException { @NotNull @Override - public Optional<String> storageAt(String address, long position) throws EtherScanException { + public Optional<String> storageAt(@NotNull String address, long position) throws EtherScanException { BasicUtils.validateAddress(address); final long compPosition = BasicUtils.compensateMinBlock(position); @@ -220,7 +220,7 @@ public Wei gasEstimated() throws EtherScanException { @NotNull @Override - public Wei gasEstimated(String hexData) throws EtherScanException { + public Wei gasEstimated(@NotNull String hexData) throws EtherScanException { if (!BasicUtils.isEmpty(hexData) && BasicUtils.isNotHex(hexData)) throw new EtherScanInvalidDataHexException("Data is not in hex format."); diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java index 10e41e3..0a39eae 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java @@ -24,7 +24,7 @@ public interface StatisticAPI { * @throws EtherScanException parent exception class */ @NotNull - Wei supply(String contract) throws EtherScanException; + Wei supply(@NotNull String contract) throws EtherScanException; /** * Returns the current amount of Ether in circulation excluding ETH2 Staking rewards and EIP1559 diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java index 9555169..131df71 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java @@ -58,7 +58,7 @@ public Wei supply() throws EtherScanException { @NotNull @Override - public Wei supply(String contract) throws EtherScanException { + public Wei supply(@NotNull String contract) throws EtherScanException { BasicUtils.validateAddress(contract); final String urlParams = ACT_TOKEN_SUPPLY_PARAM + CONTRACT_ADDRESS_PARAM + contract; diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java index a89a4a6..c719e5b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPI.java @@ -21,7 +21,7 @@ public interface TransactionAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<Status> statusExec(String txhash) throws EtherScanException; + Optional<Status> statusExec(@NotNull String txhash) throws EtherScanException; /** * Check Transaction Receipt Status (Only applicable for Post Byzantium fork transactions) @@ -31,5 +31,5 @@ public interface TransactionAPI { * @throws EtherScanException parent exception class */ @NotNull - Optional<Boolean> statusReceipt(String txhash) throws EtherScanException; + Optional<Boolean> statusReceipt(@NotNull String txhash) throws EtherScanException; } diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java index c131079..da26b51 100644 --- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java @@ -33,7 +33,7 @@ final class TransactionAPIProvider extends BasicProvider implements TransactionA @NotNull @Override - public Optional<Status> statusExec(String txhash) throws EtherScanException { + public Optional<Status> statusExec(@NotNull String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_EXEC_STATUS_PARAM + TXHASH_PARAM + txhash; @@ -45,7 +45,7 @@ public Optional<Status> statusExec(String txhash) throws EtherScanException { @NotNull @Override - public Optional<Boolean> statusReceipt(String txhash) throws EtherScanException { + public Optional<Boolean> statusReceipt(@NotNull String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_RECEIPT_STATUS_PARAM + TXHASH_PARAM + txhash; 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 9fe4e02..0550000 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java @@ -79,10 +79,6 @@ public static class BlockBuilder { BlockBuilder() {} - public static BlockBuilder aBlock() { - return new BlockBuilder(); - } - public BlockBuilder withBlockNumber(long blockNumber) { this.blockNumber = blockNumber; return this; 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 d2a8091..565dbed 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -31,14 +31,14 @@ public BigDecimal inBtc() { return ethbtc; } - public LocalDateTime usdTimestamp() { + public LocalDateTime timestampUsd() { if (_ethusd_timestamp == null && ethusd_timestamp != null) { _ethusd_timestamp = LocalDateTime.ofEpochSecond(Long.parseLong(ethusd_timestamp), 0, ZoneOffset.UTC); } return _ethusd_timestamp; } - public LocalDateTime btcTimestamp() { + public LocalDateTime timestampBtc() { if (_ethbtc_timestamp == null && ethbtc_timestamp != null) { _ethbtc_timestamp = LocalDateTime.ofEpochSecond(Long.parseLong(ethbtc_timestamp), 0, ZoneOffset.UTC); } @@ -85,23 +85,23 @@ public static final class PriceBuilder { private PriceBuilder() {} - public PriceBuilder withEthUsd(BigDecimal ethusd) { - this.ethusd = ethusd; + public PriceBuilder withUsd(BigDecimal ethToUsd) { + this.ethusd = ethToUsd; return this; } - public PriceBuilder withEthBtc(BigDecimal ethbtc) { - this.ethbtc = ethbtc; + public PriceBuilder withBtc(BigDecimal ethToBtc) { + this.ethbtc = ethToBtc; return this; } - public PriceBuilder withEthUsdTimestamp(LocalDateTime ethusdTimestamp) { - this.ethusdTimestamp = ethusdTimestamp; + public PriceBuilder withTimestampUsd(LocalDateTime ethToUsdTimestamp) { + this.ethusdTimestamp = ethToUsdTimestamp; return this; } - public PriceBuilder withEthBtcTimestamp(LocalDateTime ethbtcTimestamp) { - this.ethbtcTimestamp = ethbtcTimestamp; + public PriceBuilder withTimestampBtc(LocalDateTime ethToBtcTimestamp) { + this.ethbtcTimestamp = ethToBtcTimestamp; return this; } diff --git a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java index efbb856..8f9a728 100644 --- a/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/model/ModelBuilderTests.java @@ -117,10 +117,10 @@ void logBuilder() { void priceBuilder() { LocalDateTime timestamp = LocalDateTime.now(); Price value = Price.builder() - .withEthBtc(BigDecimal.valueOf(1.0)) - .withEthUsd(BigDecimal.valueOf(1.0)) - .withEthBtcTimestamp(timestamp) - .withEthUsdTimestamp(timestamp) + .withBtc(BigDecimal.valueOf(1.0)) + .withUsd(BigDecimal.valueOf(1.0)) + .withTimestampBtc(timestamp) + .withTimestampUsd(timestamp) .build(); assertNotNull(value); diff --git a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java index ffc37a9..76b87d5 100644 --- a/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/statistic/StatisticPriceApiTests.java @@ -14,8 +14,8 @@ class StatisticPriceApiTests extends ApiRunner { void correct() { Price price = getApi().stats().priceLast(); assertNotNull(price); - assertNotNull(price.btcTimestamp()); - assertNotNull(price.usdTimestamp()); + assertNotNull(price.timestampBtc()); + assertNotNull(price.timestampUsd()); assertNotEquals(0.0, price.inBtc().doubleValue()); assertNotEquals(0.0, price.inUsd().doubleValue()); assertNotNull(price.toString()); From 0e1dcccea1c3723fe39fe40871341b2dc946265a Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Thu, 18 May 2023 01:11:42 +0300 Subject: [PATCH 49/67] [2.0.0] Javadoc fixed --- src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java index 0a39eae..d7b48b8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java @@ -15,9 +15,7 @@ public interface StatisticAPI { /** - * ERC20 token total Supply - * <a href= - * "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan<a> + * Returns the current amount of an ERC-20 token in circulation. * * @param contract contract address * @return token supply for specified contract From 333cfe4e4a08fb8c45973327370abb253a83c44b Mon Sep 17 00:00:00 2001 From: Blackmorse <blackmorse@live.com> Date: Mon, 25 Sep 2023 00:02:09 +0400 Subject: [PATCH 50/67] 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<Balance> balances(@NotNull List<String> addresses) throws EtherScanE final List<List<String>> addressesAsBatches = BasicUtils.partition(addresses, 20); for (final List<String> 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<Balance> balances(@NotNull List<String> addresses) throws EtherScanE return balances; } - private String toAddressParam(List<String> addresses) { - return String.join(",", addresses); - } - @NotNull @Override public List<Tx> 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 <a href="https://docs.etherscan.io/api-endpoints/contracts">...</a> * @@ -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> contractCreation(@NotNull List<String> 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> contractCreation(@NotNull List<String> 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<ContractCreationTO> { +} 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<List<String>> partition(List<String> list, int pairSize) { return partitioned; } + + public static String toAddressParam(List<String> 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<ContractCreation> 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<ContractCreation> 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 <blackmorse@live.com> Date: Fri, 29 Sep 2023 12:51:00 +0400 Subject: [PATCH 51/67] 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<Balance> balances(@NotNull List<String> addresses) throws EtherScanE final List<List<String>> addressesAsBatches = BasicUtils.partition(addresses, 20); for (final List<String> 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 <a href="https://docs.etherscan.io/api-endpoints/contracts">...</a> @@ -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> contractCreation(@NotNull List<String> 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> contractCreation(@NotNull List<String> 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 + * <a href= + * "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan<a> * 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<ContractCreationTO> { -} +public class ContractCreationResponseTO extends BaseListResponseTO<ContractCreationTO> {} 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<ContractCreation> contractCreations = - getApi().contract().contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413")); + List<ContractCreation> 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<ContractCreation> contractCreations = - getApi().contract().contractCreation(Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123")); + List<ContractCreation> 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 <blackmorse@live.com> Date: Sun, 1 Oct 2023 16:59:32 +0400 Subject: [PATCH 52/67] 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 <blackmorse@live.com> Date: Thu, 5 Oct 2023 00:58:32 +0400 Subject: [PATCH 53/67] 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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:10:05 +0300 Subject: [PATCH 54/67] [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> T convert(byte[] json, Class<T> tClass) { + private <T> T convert(byte[] json, Class<T> 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> T convert(byte[] json, Class<T> 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> T getRequest(String urlParameters, Class<T> tClass) { - return convert(getRequest(urlParameters), tClass); + return getRequest(urlParameters, tClass, 0); + } + + private <T> T getRequest(String urlParameters, Class<T> 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> T postRequest(String urlParameters, String dataToPost, Class<T> tClass) { - return convert(postRequest(urlParameters, dataToPost), tClass); + return postRequest(urlParameters, dataToPost, tClass, 0); + } + + private <T> T postRequest(String urlParameters, String dataToPost, Class<T> 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<BlockUncle> 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> 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> 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 <a href="https://etherscan.io/apis">...</a> @@ -62,6 +64,15 @@ interface Builder { @NotNull Builder withConverter(@NotNull Supplier<Converter> 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() { * <a href="https://docs.etherscan.io/getting-started/viewing-api-usage-statistics">Free API KEY</a> */ 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() {} // <editor-fold desc="Getters"> 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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:10:53 +0300 Subject: [PATCH 55/67] [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 [](https://openjdk.org/projects/jdk8/) -[](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3A%22Java+CI%22) +[](https://maven-badges.herokuapp.com/maven-central/com.github.goodforgod/java-etherscan-api) +[](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3ACI+Master) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) [](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" <dependency> <groupId>com.github.goodforgod</groupId> <artifactId>java-etherscan-api</artifactId> - <version>2.0.0</version> + <version>2.1.0</version> </dependency> ``` 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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 delta 35022 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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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:11:01 +0300 Subject: [PATCH 56/67] [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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:15:47 +0300 Subject: [PATCH 57/67] [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 * <a href= - * "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan<a> + * "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan</a> * 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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:21:15 +0300 Subject: [PATCH 58/67] [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> 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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:21:55 +0300 Subject: [PATCH 59/67] [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 <goodforgod.dev@gmail.com> Date: Fri, 6 Oct 2023 02:22:51 +0300 Subject: [PATCH 60/67] [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 <goodforgod.dev@gmail.com> Date: Mon, 15 Jan 2024 12:39:58 +0300 Subject: [PATCH 61/67] [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<UncleBlock> 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<Log> 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<Log> logs = api.logs().logs(query); **Get tx details with proxy endpoint** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional<TxProxy> tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); ``` **Get block info with proxy endpoint** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional<BlockProxy> block = api.proxy().block(15215); ``` @@ -162,7 +162,7 @@ Optional<BlockProxy> 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<Boolean> status = api.txs().receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); ``` From bb87f9014587b7e83a5b6c7ffa898d840b117587 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 7 Sep 2025 15:09:54 +0300 Subject: [PATCH 62/67] [3.0.0-SNAPSHOT] Added JdkEthHttpClient All contracts open or protected Updated dependencies Updated Gradle --- README.md | 14 +- build.gradle | 16 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 37 +++-- gradlew.bat | 26 +-- .../api/etherscan/AccountAPIProvider.java | 50 ++---- .../api/etherscan/BasicProvider.java | 81 +++++++--- .../api/etherscan/BlockAPIProvider.java | 14 +- .../api/etherscan/ContractAPIProvider.java | 16 +- .../api/etherscan/EthScanAPIBuilder.java | 6 +- .../api/etherscan/EtherScanAPIProvider.java | 14 +- .../api/etherscan/GasTrackerAPIProvider.java | 16 +- .../api/etherscan/LogsAPIProvider.java | 14 +- .../api/etherscan/ProxyAPIProvider.java | 38 ++--- .../api/etherscan/StatisticAPIProvider.java | 20 +-- .../api/etherscan/TransactionAPIProvider.java | 16 +- .../EtherScanConnectionTimeoutException.java | 12 ++ .../error/EtherScanTimeoutException.java | 12 -- .../api/etherscan/http/EthHttpClient.java | 4 +- .../api/etherscan/http/EthResponse.java | 30 ++++ .../api/etherscan/http/SimpleEthResponse.java | 64 ++++++++ .../etherscan/http/impl/JdkEthHttpClient.java | 148 ++++++++++++++++++ .../etherscan/http/impl/UrlEthHttpClient.java | 58 ++++--- .../manager/impl/FakeRequestQueueManager.java | 2 +- .../impl/SemaphoreRequestQueueManager.java | 2 +- .../goodforgod/api/etherscan/model/Abi.java | 2 +- .../api/etherscan/model/BaseTx.java | 2 +- .../api/etherscan/model/BlockTx.java | 2 +- .../api/etherscan/model/BlockUncle.java | 4 +- .../api/etherscan/model/ContractCreation.java | 2 +- .../api/etherscan/model/EthSupply.java | 2 +- .../api/etherscan/model/GasOracle.java | 2 +- .../goodforgod/api/etherscan/model/Log.java | 2 +- .../goodforgod/api/etherscan/model/Price.java | 2 +- .../api/etherscan/model/Status.java | 2 +- .../io/goodforgod/api/etherscan/model/Tx.java | 2 +- .../api/etherscan/model/TxErc1155.java | 2 +- .../api/etherscan/model/TxErc20.java | 2 +- .../api/etherscan/model/TxErc721.java | 2 +- .../api/etherscan/model/TxInternal.java | 2 +- .../api/etherscan/model/proxy/BlockProxy.java | 2 +- .../etherscan/model/proxy/ReceiptProxy.java | 2 +- .../api/etherscan/model/proxy/TxProxy.java | 2 +- .../model/proxy/utility/BaseProxyTO.java | 2 +- .../model/query/LogQueryBuilderImpl.java | 2 +- .../etherscan/model/query/LogQueryImpl.java | 2 +- .../etherscan/model/query/LogQueryParams.java | 2 +- .../etherscan/model/query/LogTopicQuadro.java | 2 +- .../etherscan/model/query/LogTopicSingle.java | 2 +- .../etherscan/model/query/LogTopicTriple.java | 2 +- .../etherscan/model/query/LogTopicTuple.java | 2 +- .../model/response/StringResponseTO.java | 2 +- .../api/etherscan/util/BasicUtils.java | 2 +- 55 files changed, 536 insertions(+), 237 deletions(-) create mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionTimeoutException.java delete mode 100644 src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/http/EthResponse.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/http/SimpleEthResponse.java create mode 100644 src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java diff --git a/README.md b/README.md index 0d06c99..160f8e4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Library supports EtherScan *API* for all available *Ethereum Networks* for *ethe **Gradle** ```groovy -implementation "com.github.goodforgod:java-etherscan-api:2.1.0" +implementation "com.github.goodforgod:java-etherscan-api:3.0.0" ``` **Maven** @@ -23,7 +23,7 @@ implementation "com.github.goodforgod:java-etherscan-api:2.1.0" <dependency> <groupId>com.github.goodforgod</groupId> <artifactId>java-etherscan-api</artifactId> - <version>2.1.0</version> + <version>3.0.0</version> </dependency> ``` @@ -76,6 +76,14 @@ EtherScanAPI api = EtherScanAPI.builder() .build(); ``` +Also you can use Java 11+ HttpClient: +```java +Supplier<EthHttpClient> ethHttpClientSupplier = () -> new JdkEthHttpClient(); +EtherScanAPI api = EtherScanAPI.builder() + .withHttpClient(supplier) + .build(); +``` + ## API Examples You can read about all API methods on [Etherscan](https://docs.etherscan.io/api-endpoints/accounts) @@ -149,7 +157,7 @@ List<Log> logs = api.logs().logs(query); **Get tx details with proxy endpoint** ```java EtherScanAPI api = EtherScanAPI.builder().build(); -Optional<TxProxy> tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); +Optional<TxProxy> tx = api.proxy().tx("0x1e2910a263.0.08d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); ``` **Get block info with proxy endpoint** diff --git a/build.gradle b/build.gradle index 7e28207..f946f1d 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,9 @@ plugins { id "java-library" id "maven-publish" - id "org.sonarqube" version "4.3.0.3225" + id "org.sonarqube" version "6.3.1.5724" id "com.diffplug.spotless" version "6.19.0" - id "io.github.gradle-nexus.publish-plugin" version "1.3.0" + id "io.github.gradle-nexus.publish-plugin" version "2.0.0" } repositories { @@ -17,16 +17,16 @@ group = groupId 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 +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 dependencies { - compileOnly "org.jetbrains:annotations:23.0.0" + compileOnly "org.jetbrains:annotations:24.0.1" implementation "io.goodforgod:gson-configuration:2.0.0" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.3" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.3" - testImplementation "org.junit.jupiter:junit-jupiter-params:5.9.3" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.4" + testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4" + testImplementation "org.junit.jupiter:junit-jupiter-params:5.11.4" } test { diff --git a/gradle.properties b/gradle.properties index ee5fd3b..7b97567 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=2.1.0-SNAPSHOT +artifactVersion=3.0.0-SNAPSHOT ##### GRADLE ##### diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch literal 43764 zcma&OWmKeVvL#I6?i3D%6z=Zs?ofE*?rw#<YvCkbxVyW%yKCX@!|9$o-`u{_=j&YS z`H?I0&zrGh@7NLXq=GaUI64S4G&BeZ2+`;H|6a%-Fd(v`DuT3<a$@u{lER{L%A%?& z5FlUv`pUYdmg6ehSD(qsE;AF;KiFcDt!L*A-b#i=s_aS3@$IR6LZlP`VN@Cc&4u@8 zUd%O$VYz!}-qeNMuz&!^rwLcYTPd$&)9F!%%04Kal8N8y^leY{#+huHk1p>G$eqJB ziT4y8-Y@s9rkH0Tz>ll(^xkcTl)CY?rS<gSMh9En?Vgv_y9?ZEjrCAh*V6R;w`-ev zl#7dn9e|@&=-t`i-THVhx0m*m7W?S<o5#})7$4D>&9VNd66Yc)g^6)JcWaY(5$5gt z8gr3SBXUTN;~cBgz&})qX%#!Fxom2Yau_`&8)+6aSN7YY+pS410rRUU*>J}qL0TnJ zRxt*7QeUqTh8j)Q&iavh<}L+$Jqz))<`IfKussVk%%Ah-Ti?Eo0hQH!rK%K=#EAw0 zwq@@~XNUXRnv8$;zv<6rCRJ6fPD^hfrh;0K<JwE-P9|b;^L#U}45X2ingSgIx$t0w z)V+kY*mtvJIMSC@hBjAyoQj=+$b!s{)`1w6nr$e&iGPjQ$qC^d-9|XvX|K)~=U>?n z=p!u^3xOgWZ%f3+?+>H)9+w^$Tn1e;?UpVMJb!!;f)`6f&4|8mr+g)^@x>_rvnL0< zvD0Hu_N>$(Li7|Jgu0mRh&MV+<}`~Wi*+avM01E)Jtg=)-vViQKax!GeDc!xv$^mL z{#OVBA$U{(Zr8~Xm|cP@odkHC*1R8z6hcLY#N@3E-A8XEvpt066+3t9L_6Zg6j@9Q zj$$%~yO-OS6PUVrM2s)(T4#6=JpI_@Uz+!6=GdyVU?`!F=d;8#ZB@(5g7$A0(`eqY z8_i@3w$0*es5mrSjhW*qzrl!_LQWs4?VfLmo1Sd@Ztt53+etwzAT^8ow_*7Jp`Y|l z*UgSEwvxq+FYO!O*aLf-PinZYne7Ib6ny3u>MjQz=((r3NTEeU4=-i0LBq3H-VJH< z^>1RE3_JwrclUn9vb7HcGUaFRA0QHcnE;6)hnkp%lY1UII#WPAv?-;c?YH}LWB8Nl z{sx-@Z;QxWh9fX8SxLZk8;kMFlGD3Jc^QZVL4nO)1I$zQwvwM&_!kW+LMf&lApv#< zur|EyC|U@5OQuph$TC_ZU`{!vJp`13e9alaR0Dbn5ikLFH7>eIz4QbV|C=%7)F=qo z_>M&5N)d)7G(A%c<lKH4^8Hp9b@dxXM-pP@q9T=A6O<Ug2$CU&jp%ugHpd_5=E*LQ z3|0XI-?6$A#RO``@NO19`9M%OTwFds=!*lM^frbvhY`X2*t_pmCOA^C8ilSc0Xh@j zKGB0;icTb-F?Z&?HaxJ#H(W_K)DcQGe;fLg=xT<e*TLcEsj9Jx4+WevW81(@``gb9 ztLZ=$S%MI2jmC$I#LW+x%`q2DagEFkpI#u3ct>>}UCrW!Ql_6_A{?R7&CL`;!KOb3 z8Z=$YkV-IF;c7zs{3-WDEFJzuakFbd*4LWd<_kBE8~BFcv}js_2OowRNzWCtCQ6&k z{&~Me92$m*@e0ANcWKuz)?YjB*VoSTx??-3Cc0l2U!X^;Bv@m87eKHukAljrD54R+ zE;@_w4NPe1>3`i5Qy*3^E9x#VB6?}v=~qIprrrd5|DFkg;v5ixo0IsBmik8=Y;zv2 z%Bcf%NE$a44bk^`i4VwDLTbX=q@j9;JWT9JncQ!+Y%2&HHk@1~*L8-{ZpY?(<U(yK zX>-a9J-1~<1ltr9i~D9`P{XTIFWA6IG8c4;6bFw*lzU-{+?b&%OcIoCiw00n>A1ra zFPE$y@>ebbZlf(sN_iWBzQKDV<!UJ72G|`KiacUcsDc}BB~-4vL+Gp7t%iaxo{r68 zJ?RC6VAG9`S8y{_Yxg=lKKg0~%ntT<)gINU!!tDE#t^DXMv$n`L^;gW96JyKh}(%> zmmaLX#zK!@ZdvCANfwV}9@2O&w)!5gSgQzHdk2Q`jG6KD7S+1R5&F)j6QTD^=hq&7 zHUW+r^da^%V(h(wonR(j?BOiC!;y=<Nr<L)(WX+R%k#*yH1@o;<Zej5qW$1oy%ORi zw*o-Du{utPhvmHd+{&cZD182c2Co@Sq(G`LWiRYR>%nJvz?*aW&5E87qq;2z`EI(f zBJNNSMFF9U{sR-af5{IY&AtoGcoG)Iq-S^v{7+t0>7N(KRoPj;+2N5;9o_nxIGjJ@ z7bYQK)bX)vEhy~VL%N6g^NE@D5VtV+Q8U2%{ji_=6+i^G%xeskEhH>Sqr194PJ$fB zu1y^){?9Vkg(FY2h)3ZHrw0Z<@;(gd_dtF#6y_;Iwi{yX$?asr?0N0_B*CifEi7<6 zq`?OdQjCYbhVcg+7MSgIM|pJRu~`g?g3x?Tl+V}#$It`iD1j+!x+!;wS0+2e>#g?Z z*EA^k7W{jO1r^K~cD#5pamp+o@8&yw6;%b|uiT?{Wa=4+9<}aXWUuL#ZwN1a;lQod zW{pxWCYGXdEq9qAmvAB904}?97=re$>!I%wxPV#|f#@A*Y=qa%zHlDv^yWbR03%V0 zprLP+b(#fBqxI%F<U7v<Ri}drrj>iF*-n8HtH6$8f(P6!H3V^ysgd8de-N(@|K!A< z^qP}jp(RaM9kQ(^K(U8O84?D)aU(g?1S8iWwe)gqpHCaFlJxb*ilr{KTnu4_@5{K- z)n=CCeCrPHO0WHz)dDtkbZfUfVBd?53}K>C5*-wC4hpDN8cGk3lu-ypq+EYpb_2H; z%vP4@&+c2p;thaTs$dc^1CDGlPG@A;yGR5@$UEqk6p58qpw#7lc<+W(WR;(vr(D>W z#(K$vE#uBkT=*q&uaZwzz=P5mjiee6>!lV?c}QIX%ZdkO1dHg>Fa#xcGT6~}1*2m9 zkc7l3ItD6Ie~o_aFjI$Ri=C!8uF4!Ky7iG9QTrxVbsQroi|r)SAon#*B*{}TB-?=@ z8~jJs;_R2iDd!$+n$%X6FO&PYS{YhDAS+U2o4su9x~1+U3z7YN5o0qUK&|g^klZ6X zj_vrM5SUTnz5`*}Hyts9ADwLu#x_L=nv$Z0`HqN`Zo=V>OQI)fh01n~*a%01%cx%0 z4LTFVjmW+ipVQv5rYcn3;d2o4qunWUY!p+?s~X~(ost@WR@r@EuDOSs8*MT4fiP>! zkfo^!PWJJ1MHgKS2D_hc?Bs?isSDO61>ebl$U*9*QY(b=i&rp3@3GV@z>KzcZOxip z^dzA~44;R~cnhWz7s$$v?_8y-k!DZys}Q?4IkSyR!)C0j$(Gm|t#e3|QAOFaV2}36 z?dPNY;@I=FaCwylc_;~kXlZsk$_eLkNb~TIl8QQ`mmH&$*zwwR8zHU*sId)rxHu*K z;yZWa8UmCwju%aSNLwD5fBl^b0Ux1%q8YR*uG`53Mi<`5uA<ivoX#Y(ieK-lAtu4f zF2re9qU41VXdc_#XWRG3A1YtJe+l(0rzo$B{}~<_EyMepDZmYM!wk99W#+nl#{6RY z0`whH0Sh`*SYyo1$rzdws&H*N3K?fZub9yrW)Z8Vzxjs|3&)+(J&Nh`JIT?W;V(Tz zL0lHZxcC*7(s#W2PTn5>^Dc6Ync)J3N7;zQ*<u6@Y&UTjsN0HAStvP+$%9&=bpK5+ z)+PneC8eL)5x8f?&OOqrnqHvqFfN<}q@g7?F>75)hf%a@{$H+%S?SGT)ks60)?6j$ zspl|4Ad6@%-r1t*$tT(en!gIXTUDcsj?28ZEzz)dH)SV3bZ+pjMaW0oc~rOPZP@g! zb9E+ndeVO_<?rK9DaFm<PP6sRw&OaPk!0DHN{W(HhMdk&fz03%N{}wh4Iau8rop=L zasSwE04c51rB}aL9G$0c?A)w4hr4<?dtR^1+21+b57~bsL>Ib9c_>{)`01^`ZS198 z)(t=+{Azi11$eu%aU7jbwuQrO`vLOixuh~%4z@mKr_O<Zd6~i5J})tO+{G$_*&%e5 zg3O+ta$Psgxh3MZ2C}$<Qp2r$vP8yQ{NkyWHgJGhemG7uxWRvL$@vQ%(|gLH6t{&( z&tM-z;Ae~9tv>c;F%Uq01fA)^W&y+g16e?rkLhTxV!EqC%2}sx_1u7IBq|}Be&7WI z4I<;1-9tJsI&pQIhj>FPkQV9{(m!wYYV@i5h?A0#BN2wqlEwNDIq06|^2oYVa7<~h zI_OLan0Do*4R5P=a3H9`s5*><zFT1grh~4TfLTVf-v&oTgO_QvD$jyN8!Ml+({P$$ z-><mrqX};2-gz$ryStrCw<*||bu7fCOvZUZ_NPCW%;?6m52!_fLoi}0ZOJXzwCW_w zbA~BH2pHXPo8aAAAn+S&1Z~w0$Q)Q95>xU}_PSztg`+2mv)|3nIy=5#Z$%+@tZnr> zLcTI!Mxa`PY7%{;KW~!=;*t)R_sl<^b>eNO@w#fEt(tPMg_jpJpW$q_DoUlkY|uo> z0-1{ouA#;t%spf*7VjkK&$QrvwUERKt^Sdo)5@?qAP)>}Y!h4(JQ!7{wIdkA+|)bv z&8hBwoX4v|+fie}iTslaBX^i*TjwO}f{V)8*!dMmRPi%XAWc8<_IqK1jUsApk)+~R zNFTCD-h>M5Y{qTQ&0#j@I@tmXGj%rzhTW5%Bkh&sSc=$Fv;M@1y!zvYG5P2(2|(&W zlcbR1{--rJ&s!rB{G-sX5^PaM@3EqWVz_y9cwLR9xMig&9gq(voeI)W&{d6j1jh&< zARXi&APWE1FQWh7eo<CXPEt%%S%pqk?EiR1+Q?DZ9tLF52fwF~*DmmN6U@5MDWiQu z-i2!L85SA;_l|OB&q>Zj<!Jf}1X~)`iRF{_Wj`+Y?^k`zidtfAeaTBF2~*6~9dJoK zpd-p-ZW1b`^R<yCul6^~P0P2P?JYSiGBJHv$*b&nxRAP9iw|xH-B0JxuydGyJw>uP z;vdgX>zep^{{2%hem;e*gDJhK1Hj12nBLIJoL<=0+8SVEBx7!4Ea+hBY;A1gB<f~p zZ0BTdYeQ#f1#ooyKaVIYKQ0Bvh@7>wvY<)tj~T=H`^?3>zeWWm|LAwo*S4Z%bDVUe z6r)CH1H!(>OH#MXFJ2V(U(qxD{4Px2`8qfFLG+=a;B^~Te_Z!r3RO%Oc#ZAHKQxV5 zRYXxZ9T2A%NVJIu5Pu7!Mj>t%YDO$T@M=RR(~mi%sv(YXVl`yMLD;+WZ{vG9(@P#e zMo}ZiK^7^h6TV%cG+;jhJ0s>h&VERs=tuZz^Tlu~%d{ZHtq6hX$V9h)Bw|jVCMudd zwZ5l7In8NT)qEPGF$VSK<eY2{8;?IUDm2QDDl4gC>g&fb0%R2RnUnqa){)V(X(s0U zkCdVZe6wy{+_WhZh3qLp245Y2RR$@g-!9PjJ&4~0cFSHMUn=>dapv)hy}|y91ZWTV zCh=z*!S3_?`$&-eZ6xIXUq8RGl9oK0BJw*TdU6A`LJqX9eS3X@F)g$jLkBWFscPhR z<VgCW){ry5c9vGHT*hnuTcpPlF;X)FuY1==?z!cl6;$1cjMSU)hw;=@y`LR#APf=T zgC9ISC#h}gxY%6+EiEF_stYh!dmYne4uH37@w=)1I~`RJNZ`C|ZVF5|Jxef}ayhg% z_T0##<>pCv8#KeAc^y>>Y$k^=r|K(DTC}T$0#jQBOwB#@`P6~*IuW_8JxCG}J4va{ zsZzt}tt+cv7=l&CEuVtjD6G2~_Meh%p4RGuY?hSt?(sreO_F}8r7Kp$qQdvCdZnDQ zxzc*qchE*E2=WK)^oRNa>Ttj`fpvF-JZ5tu5>X1xw)J@1!IqWjq)ESBG?J|ez`-Tc zi5a}GZx|w-h%5lNDE_3ho0hEXMoaofo#Z;$8|2;EDF&*L+e$u}K=u?pb;dv$SXeQM zD-~7P0i_`Wk$#YP$=hw3UVU+=^@Kuy$>6?~gIXx636jh{PHly_a2xNYe1l60`|y!7 z(u%;ILuW0DDJ)2%y`Zc~hOALnj1~txJtcdD#o4BCT<c`C3s{Fa9nJ8f9aXHKr$OPM zg|kC>68+8gZe`=^te6H_egxY#nZH&P*)hgYaoJ^qt<Ifn@*Qw*6a&7R$<m-yG(ctW zUd2p=qK(#tQz-MJYkq(;De4^$<OcVv^<&qh7F!#6QZNMqxG;xHfrRZwgtC}ydIb8n z+%@S?Set`=rrK+R`ycN~!fQswa)h~kmxu`vElJou2Is?=GU%N<69XZgo%~;_RUlkr zAefA>mpeea`35Fw)cy!w@c#v6E29co8&D9CTCl%^GV|X;SpneSXzV~LXyRn-@K0Df z{tK-nDWA!q38M1~`xUIt_(MO^R(yNY#9@es9RQb<k-%Zd#d<U>Y@Ia*xHhD&=k^T+ zJi@<Efq$RwgSrGpx~`%kr?H%qIhr11IbNi4yZU@QUBPvM!op`qN9(HbVX@kr!I|Sq z2dlD61!FuSEbhkPO3Wfz3BE|V`G@#twDPc&>j2I|WcgW=PuAc>hs`(&CvgjL2a9Rx zCbZyUpi8NWUOi@S%t+Su4|r&UoU|ze9SVe7p@f1GBkrjkkq)T}X%Qo1g!SQ{O{P?m z-OfGyyWta+UC<kO6&rXO+i)KFQe2XV+qbCDEj$VcVCttsgF<kgv88w^t!uHbB+Rgk zBz~A8L*{u93BcK8!_|&4JoY8o+$I9Z7ICvS4UQ(u_Zw%X;ue4ajwmA11T2AMK;K~q ztDu9*93ND#TSdztlH2m`45h7@{dn>XH+-+(D^%kw#A1-U;?9129at7MeCCzC{DNgO zeSqsV>W^NIfTO~4({c}KUiuoH8A*J!Cb0*sp*w-Bg@YfBIPZFH!M}C=S=S7PLLcIG zs7K77g~W)~^|+mx9onzMm0qh(f~OsDT<zGyR(}~_oq|s3Gmth}>zVmRtz=aZTllgR zGUn~_5hw_k&rll<4G=G+`^Xlnw;jNYDJz@bE?|r866F2hA9v0-8=JO3g}IHB#b`hy zA42a0>{0L7CcabSD+F7?pGbS1KMvT{@1_@k!_+Ki|5~EMGt7T%u=79F)8xEiL5!EJ zzuxQ`NBliCoJMJdwu|);zRCD<5Sf?Y>U$trQ-;xj6!s5&w=9E7)%pZ+1Nh&8nCCwM zv5>Ket%I?cxr3vVva`YeR?dGxbG@pi{H#8@kFEf0Jq6~K4>kt26*bxv=P&jyE#e$| zDJB_~i<Xq~nb8@S9L8s6ncG@^0W=JftLyPTLSr5sQ*gl%%*C=Z&yB;3n~YJ<=iS31 zdJr-DfbIF4L)RMldE&*CKpiD=6>mk^-z|o!2njF2hL*|7sHCnzluhJjwLQGDmC)Y9 zr9ZN`s)uCd^XDvn)VirMgW~qfn1~SaN^7vcX#K1<Nh7_a(#8cARE-AH23jr|$J4<r zlIW36BE}$?*2in8W!L*L4!IW{ob03lV1pxflV^=nQe@&5WJ!3>G`==UGaDVVx$<t` z%ZG^$ey=-ydeW?}rUwRXYv-SrTOeQT!Mftcw@rEz0h569l~}qT=8(>0BQnubhX|{e z^i0}>k-;BP#Szk{cFjO{2x~LjK{^Upqd&<+03_iMLp0$<BzI;S_z9k!Q66iD){Kc4 zK?}$9@4|73IAIzoVBwOhK(!-LA$DOeQ?G-7U-!dDg7m^PBT<21@Sd41^tLCGW`RI* z7VR4ZX72#cq#wHDlpKM*XB?p<@H*)Y-|-C1=-{#e`n=Ox{o+Col(Oj}uyWtXb~&CK z_OTBHdn66|WoLgho6;{&gyWC!LN?@Vp+kb2*au$?_1R2qPNfRNx8CtT(yll<vQtVB z&&%QGGlrH^p_>!6_$@TbX>8U-f*-w-ew1?`CtD_0y_Lo|PfKi52p?`5$Jzx0E8`M0 zNIb?#!K$mM4X%`Ry_yhG5k@*+n4||2!~*+&pYLh~{`~o(W|o64^NrjP?-1Lgu?iK^ zTX6u3?#$?R?N!{599vg>G8RGHw)Hx&=|g4599y}mXNpM{EPKKXB&+m?==R3GsIq?G zL5fH={=zawB(sMlDBJ+{dgb)Vx3pu>L=mDV0{r1Qs{0Pn%TpopH{m(By4;{FBvi{I z$}x!Iw~MJOL~&)p93SDIfP3x%ROjg}X{Sme#hiJ&Yk&a;iR}V|n%PriZBY8SX2*;6 z4hdb^&h;Xz%)BDACY5AUsV!($lib4>11UmcgXKWpzRL8r2Srl*9Y(1uBQsY&hO&uv znDNff0tpHlLISam?o(lOp#CmFdH<6HmA0{UwfU#Y{8M+7od8b8|B|7ZYR9f<#+V<x zEXFHNBAN>|ZSaCQvI$~es~g(Pv{2&m_rKSB2Q<kr%g_1^%Veb1sX#n<Z}zXNgS$>Q zMvT}$?Ll>V+!9Xh5^iy3?UG;dF-zh~RL#++roOCsW^cZ&({6q|?Jt6`?S8=16Y{oH zp50I7r1AC1(#{b`Aq5cw>ypNggHKM9vBx!W$eYIzD!4KbLsZGr2o8>g<@inmS3*>J zx8oG((8f!ei|M@JZB`p7+n<<b>Q}?>h249<`7xJ?u}_n;Gq(&km#1ULN87CeTO~FY zS_<lEORnOk;&2#(^=C#_&3&S5?1D#WxzR?KN=z;_R;CQs)!0hYG&6Y4RINi+Q~ofP zE7r1;`@Y2Im$qmN`2udr1~C2{Fb^e@%e8<~3)A@c`FI5&20wLx1y61MdS=VlNy$P{ zW4NlAl7b)3UqR8w)-(-KL(LFi%f)d&XP^lqR1u%k*qD#fD6%d!v|qOm>Ty}0TgQhV zOh3T7{{x&LSYGQfKR1PDIkP!WnfC1$l+fs@Di+d4O=eVKeF~2fq#1<8hEvpwuqcaH z4A8u~r^gnY3u6}zj*RHjk{AHhrrDqaj?|6GaVJbV%o-nATw}ASFr!f`Oz|u_QPkR# z0mDudY1dZRlk@TyQ?%Eti=$_WNFtLpSx9=S^be{wXINp%MU?a`F66LNU<<I=7-@Jg zTLNhlJp(-E@b8OPXm^>c;0&ngifmP9i;bj6&hdGMW^Kf8e6ZDXbQD&$QAAMo;OQ)G zW(qlHh;}!ZP)JKEjm$VZjTs@hk&4{?@+NADuYr<BCQBRUR6Oa9gNejJBgH2aoU1s~ ztQC~P$>r!R^cJzU{kGc1yB?;7mIyAW<oWTcl$+`*Lk|cT(3Feu)o_cjWUkr0fNG1j znl&^L%pG{GmdOC|X_|A2!hNa=k5K9oPJA??GhknYoQid02%$?D38bW}OZZZ|mr~Yy zfH$$cWUzJZ!FM1xBH$(>whbeA_l_lw-i<C}y@Ytc@7wd0o`2f*d3_1<tjra`DWQ=B z_WU|PbXRU=f!Dm1yrLTcesrRsnYt1er}6t5R-defpkZTQED)%)VdF>DVi7wcFurf5 z#Uw)A@a9fOf{D}AWE%<`s1L_AwpZ?F!Vac$LYkp<#A!!`XKaDC{A%)~K#5z6>Hv@V zBEqF(D5?@6r3Pwj$^krpPDCjB+UOszqUS;b2n>&iAFcw<*im2(b3|5u6SK!n9Sg4I z0KLcwA6{Mq?p%t>aW0W!PQ>iUeYvNjdKYqII!CE7SsS&Rj)eIw-K4jtI?II+0IdGq z2WT|L3RL?;GtGgt1LWfI4Ka`9dbZXc$TMJ~8#Juv@K^1RJN@yzdLS8$AJ(>g!U9`# zx}qr7JWlU+&m)VG*Se;rGisutS%!6yybi%B`bv|9rjS(xOUIvbNz5qtvC$_JYY+c& za*3*2$RUH8p%pSq>48xR)4qsp!Q7BEiJ*`^>^6INRbC@>+2q9?x(h0bpc>GaNFi$K zPH$6!#(~{8@0QZk=)QnM#I=bDx5vTvjm$f4K}%*s+((H2>tUTf==$wqyoI`oxI7>C z&>5fe)Yg)SmT)eA(|j@JYR1M%KixxC-Eceknf-;N=jJTwKvk#@|J^&5H0c+%KxHUI z6<NTsIgR{TYR||exLTu(zN`_bWgeQwBG#`i4*(I5b=QFZn_^9t_~hHReHp&^Ewhym zl$I9Rv<v**_8bRiYTvz|q_*=3ON&x|y@~u5wRUE>dQbwwVx3p?X<_VRVb2fStH?HH zFR@Mp=qX%#L3XL)+$PXKV|o|#DpHAoqvj6uQKe@M-mnhCSou7Dj4YuO6^*V`m)1lf z<mm4Xt-ji2xs~+<z?bUEEiN(Bp1WM7MWroLP*p-XZrFIGX~lMp;||6bKhm4yR2tUv z^Z{p`U%4^tcoHfROP=5es_f)o`1xKq>;)@e%1!Qg$10w8uEmz{ENb$^%u}B;J7sDd zump}onoD#!l=agcBR)iG!3AF0-63%@`<F(Jc&WhS)AJ*a^B2&Vv0h)-m)+IDD{x-5 z4OEWk1<<zOB02qc$+F+E38vwkSKXb+wojmy3>K9G(CzKrm$VJ{v7^O9Ps7Zej|3m= zVXlR&yW6=Y%mD30G@|tf=yC7-#L!16Q=dq&@beWgaIL<l?P_Lzv{(7Q7fOeBG)_JK z1W`2V(QsiFac;%V2-3GXg5J4rkOjwT7O_0_gY9$QkYW!E=e|uUdA13dd!~>40k0n% z)QHrp2Jck#evLMM1RGt3WvQ936ZC9vEje0nFMfvmOHVI+&okB_K|l-;|4vW;qk>n~ z+|kk8#`K?x`q>`(f6A${wfw9Cx(^)~tX7<#TpxR#zYG2P+FY~mG{tnEkv~d6oUQA+ z&hNTL=~Y@rF`v-RZlts$nb$3(OL1&@Y11hhL9+zUb6)SP!;CD)^GUtUpCHBE`j1te zAGud@miCVFLk$fjsrcpjsadP__yj9iEZUW{Ll<!3mjY9RIs{k;6$}slS-57U_~+oc z`OdVx`#QbRESh&7^JVKqWt&*n+`NNC;#H$jRQN%4R+NFZ(h+Um+fe8cuTj_K4!vd( z9rr%4kwoqLcV5ao)%e{C4_>7PPi<$R;m1o!&Xdl~R_v0;oDX2z^!&8}zNGA}iYG|k zmehMd1%?R)u6R#<)B)1oe9TgYH5-CqUT8N7K-A-dm3hbm_W21p%8)H{<f;74D%Fg_ ztO7%x0qpGTuPz$lg?(Es{~VvD)V<W%bX&J<bv(NA5jCuN7uwF=oFgRvHbpwQZI-L| zAc`TBzI~!Q7KLixCzgpFyxvIikN8b&B7Wkn;c!)MKIv}JG$eV{y3^3ugWbY+!fB*I zReNk{bL}1sj6-`Os+xh=tG!_Ikj^(V71bC_GP#7p>O)xUlBVb+iUR}-v5dFaCyfSd zC6Bd7=N4A@+Bna=!-l|*_(nWGDpoyU>nH=}IOrLfS+-d40&(Wo*dDB9nQiA2Tse$R z;uq{`X7LLzP)%Y9aHa4YQ%H?htkWd3Owv&UYbr5NUDAH^<<uTbBd8`#8U(vD<=p1T zB=BTumLd`jdk}Bv;k-*IRC97<B@gFV?1=rr*k^>l@Z0Cx%`N+B*i!!1u>D8%;Qt1$ zE5O0{-`9gdDxZ!`0m}ywH!;c{oBfL-(BH<&SQ~smbcobU!j49O^f4&IIYh~f+hK*M zZwTp%{ZSAhMFj1qFaOA+3)p^gnXH^=)`NTYgTu!CLpEV2NF=~-`(}7p^Eof=@VUbd z_9U|8qF7Rueg&$qpSSkN%%%DpbV?8E8ivu@ensI0toJ7Eas^jyFReQ1JeY9plb^{m z&eQO)qPLZQ6O;FTr*aJq=$cMN)QlQO@G&%z?BKUs1&I^`lq>=QLODwa`(mFGC`0H< zOlc*|N?B<J8H|EBCQ^D0;sPxbi)ypgHOjJBrD$pOA-{Pr{=+dSI{G;07*8R=SfU^E zPr>5&!U6BuJvkL?s1&nsi$*5cCv7^j_*l&$-sBmRS<Lve>85UIrE--7eD8Gr3^+o? zqG-Yl4S&E;>H>k^a0GdUI(|n1`ws@)1%sq2XBdK`mqrNq_b4N{#VpouCXLzNvjoFv zo9wMQ6l0+FT+?%N(ka*;%m~(?338bu32v26!{r)|w8J`E<l8Lz7bgSK=GDu6AiUva zcZL#pNTT9t@g@s;sDRR0<^@bj*Si?AcP1nXS1f@(@C<e9!U~s>L|t$}TA4q_FJRX5 zCPa{hc_I(7TGE#@rO-(!$1H3N-C0{R$J=yPCXCtGk{4>=*B56JdXU9cQVwB`6~cQZ zf^q<RV!PDX6j+AE?17DD_9-P+5&f0MKOiNyAjo>K21x_d>X%dT!!<b>)CJQ3mlHA@ z{Prkgfs6=Tz%63$6Zr8CO0Ak3A)Cv#@BVKr&aiKG7RYxY$Yx>Bj#3gJk*~Ps-jc1l z;4nltQwwT4@Z)}Pb!3xM?+EW0qEKA)sqzw~!C6wd^{03-9aGf3Jmt=}w-*!yXupLf z;)>-7uvWN4Unn8b4kfIza-X=x*e4n5pU`HtgpFFd))s$C@#d>aUl3helLom+RYb&g zI7A9GXLRZPl}iQS*d$Azxg-VgcUr*lpLnbPKU<Zg&@G3{h>V{QI|bsG{8bLG<%CF( zMoS4pRDtLVYOWG^@ox^h8xL~afW_9DcE#^1eEC1SVSb1BfDi^@g?#f6e%v~Aw>@w- zIY0k+2lGWNV|aA*e#`U3=+oBDmGeInfcL)>*!w|*;mWiKNG6wP6AW4-4imN!W)!hE zA02~S1*@Q`fD*+qX@f3!2yJX&6FsEfPditB%TWo3=HA;T3o2IrjS@9SSxv%{{7&4_ zdS#r4OU41~GYMiib#z#O;zohNbhJknrPPZS6sN$%HB=jUnlCO_w5Gw5EeE@KV>soy z2EZ?Y|4RQDDjt5y!WBlZ(8M)|HP<0YyG|D%RqD+K#e7-##o3IZxS^wQ5{Kbzb6h(i z#(wZ|^ei>8`%ta*!2tJzwMv+IFHLF`zTU8E^Mu!R*45_=ccqI};Zbyxw@U%a#2}%f zF>q?SrUa_a4H9l+uW8JHh2Oob>NyUwG=QH~-^ZebU*R@67DcXdz2{HVB4#@edz?B< z5!rQH3O0>A&ylROO%G^fimV*LX7>!%re{_Sm6N>S{+GW1LCnGImHRoF@csnFzn@P0 zM=jld0z%oz;j=>c7mMwzq$B^2mae7NiG}%>(wtmsDXkWk{?BeMpTrIt3Mizq?vRsf zi_WjNp+61uV(%gEU-Vf0;>~vcDhe(dzWdaf#4mH3o^v{0EWhj?E?$5v02sV@xL0l4 zX0_IMFtQ44PfWBbPYN#}qxa%=J%dlR{O!KyZvk^g5s?sTNycWYPJ^FK(nl3k?z-5t z39#hKrdO7V(@!TU)LAPY&ngnZ1MzL<jwyInuL2dKP0?1+sPY@3ZOeFdEU}W0jxU(? z;g`WWOJ%M`;{3ZWlXPk^8uN=&XMS71RE+Xkh9urM!3Jq=Anc?e<!Z1;k@k+kTwJB? zW$CP1XF@Yek@UxIjhImUdF{YcYQR!@7x4E#j6M{h$5NBpb>EeEiZznn7e-jLCy8LO zu^7_#z*%I-BjS#Pg-;zKWWqX-+Ly$T!4`vTe5ZOV0j?TJVA*2?*=82^GVlZIuH%9s zXiV&(T(QGHHah=s&7e|6y?g+XxZGm<Q3>K<c%aSbhMpyV8%{}hN%xY_45G%cjsf_h z)v&PcR81soW<?nGb~f)@P8$L}dbN~t<RA;o>55`wGV>@1U)Th&=JTgJq>4mI&Av2C z)w+kRoj_dA!;SfTfkgMPO>7Dw6&1*Hi1q?54Yng`JO&q->^CX21^PrU^JU#CJ_qhV zSG>afB%>2fx<~g8p=P8Yzxqc}s@>>{g7}F!;lCXvF#RV)^fyYb_)iKVCz1xEq=fJ| z0a7DMCK*FuP=NM*5h;*D`R4y$6cpW-E&-i{v`x=Jbk_xSn@2T3q!3HoAOB`@5Vg6) z{PW|@9o!e;v1jZ2{=Uw6S6o{g82x6g=k!)cFSC*oemHaVjg?VpEmtUuD2_J^A~$4* z3O7HsbA6wxw{TP5Kk)(Vm?gKo+_}11vbo{Tp_5x79P~#F)ahQXT)tSH5;;14?s)On zel1J>1x>+7;g1Iz2FRpnYz;sD0wG9Q!vuzE9yKi3@4a9Nh1!GGN?hA)!mZEnnHh&i zf?<MZ@ks8)O>#ZEN2sFbf~kV;>K3UNj1&vFhc^sxgj8FCL4v>EOYL?2uuT`0eDH}R zmtUJMxVrV5H{L53hu3#qaWLUa#5zY?f5ozIn|PkMW<hLvS%h?FOpA+kCaz@P<{$T5 ze76+ql;oUabl&kD7|`#Ah=w!|ypI%zmEJ7oBxq8FJm3;PWOC1j#@-%#fcn+y>NP%n zWB5!B0LZB0kLw$k39=!akkE9Q>F4j+q434jB<oVEi1VGraixlK!6|rw{>4VmslQ;$ zKiO#FZ`p|dKS716jpcvR{QJkSNfDVhr2%~eHrW;fU45>>snr*S8Vik-5eN5k*c2Mp zyxvX&_cFb<o;&c*VN<^T4iaHk2-5_uC@!h_V=c3*$wVr%<HP&&+b+4U`xuiKABUUb zd$D?RJvpeB-dwqLd$AT!&ykm)6`=nJq@|z>B6lODXznHHT|rsURe2!swomtrqc~w5 zymTM8!w`1{04CBprR!_F{5LB+2_SOuZN{b*!J~1ZiPpP-M;);!ce!rOPDLtgR@Ie1 zPreuqm4!H)hYePcW1WZ0Fyaqe%l}F~Orr)~+;mkS&pOhP5Ebb`cnUt!X_QhP4_4p( z8YKQCDKGIy>?WIFm3-}Br2-N`T&FOi?t)$hjphB9wOhBXU#Hb+zm&We_-O)s(wc`2 z8?VsvU;J>Ju7n}uUb3s1yPx_F*|FlAi=Ge=-kN?1;`~6szP%$3B0|8Sqp%ebM)F8v zADFrbeT0cgE>M0DMV@_Ze*GHM>q}wWMzt|GYC%}r{OXRG3Ij&<+nx9;4jE${Fj_r* z`{z1AW_6Myd)i6e0E-h&m{{CvzH=Xg!&(bLYgRMO_YVd8JU7W+7MuGWNE=4@OvP9+ zxi^vqS@5%+#gf*Z@RVyU9N1sO-(rY$24LGsg1>w>s6ST^@)|D9>cT50maXLUD{Fzf zt~tp{OSTEKg3ZSQyQQ5r51){%=?xlZ54*t1;Ow)zLe3i?8tD8YyY^k%M)e`V*r+vL zPqUf&m)U+zxps+NprxMHF{QSxv}>lE{JZETNk1&F+R~bp{_T$dbXL2UGnB|hgh*<T zfaS#49D6ed16&J#R4HWCpZ{ob>p4h$clt#6;NO~>zuyY@C-MD@)JCc5XrYOt`wW7! z_ti<LSkauY&7*=6L7=0&nnh|*#(opD7Lg8FC;GAUXJ8#cs$BQ+wq<g+K=*!NwLvrI z?)RCqt;cls)Q6&{Nw50T<+1Lci46C{G0qFln{&dC*J~UBBn$XS0^YQvpUBzUD{WP` zj{@51QLY25txBvi=hzKNB}*;eT1jNObp#`f&kO5nrViFM`=gM?9A+^xuPi84x91ay zyvJ_uoGp2aU`}r<&$7!?$*|X$<eg_#G`{zu_Ar*A{=f{}o%vyOadC2XB7bZ^^v(c2 zfK0c<DvqWOul!j~DNCi`RiqP_r;2e|s#C4ah!4+lxN2`xKr{Wx-i7;|5tSxROpd{f zD_5m#3;1|BaAc=X7!L#&4l*Ys2%~WZFBKzIxCf`Yh)Ax<$2d-+MYBq9%h{!PNujQd z29KJ`0GX?_;ALIr8+A2l+?c?6Y_1)XTK5@slL(s3R~{;~Zj!wxM;0F#{_vV=1iShX z#hP`Z2%_DG0CQ4-?&(MnP(2)>2hhZBMJNbn0O-uTxl_b6Hm313^fG@e;RrhIUK9@# z+DHGv_Ow$%S8D%<X`BHRyr^%H-ZT65!3%w75qg^yd_%v~%CGG#yc5$p60DH6!Ge2R zr}DRiAqzI>RB}`doJjJy*aOa5mGHVHz0e0>>O_%+^56?IkA5eN+L1BVCp4~m=1eeL zb;#G!#^5G%6Mw}<v_>r1KnaKsLvJB%HZL)!3OxT{k$Yo-XrJ?|7{s4!H+S2o?N|^Z z)+?IE9H7h~Vxn5hTis^3wHYuOU84+bWd)cUKuHapq=&}WV#OxHpLab`NpwHm8LmOo zjri+!k;7j_?FP##CpM+pOVx*0wE<t!E{7HE!T?mGw4?Vb6{i-NR`qB3tA!5g`wMBJ z3Bse=D(cND%|B=zPheYW(@GS%wDYK1oXF=JRMJ0EZBAjgnUd@lTqyX0Yq_Ub>xEex z@`#)K<-ZrGyArK;a%Km`^+We|eT+#MygHOT6lXBmz`8|lyZOwL1+b+?Z$0OhMEp3R z&J=iRERpv~TC=p2-BYLC<?xE5Gxo#UJ}=%zB_T&^J-o1uXBc_svI%4iOD81`D=)oU zivY;@_DNJP1{#+_V+7th;kx3$JoU4A1C1f{;&ZT6#Lt)KYG}j8f$)_p!^7}(3>*?4 zxvPs9V@g=JT0>zky5Poj=fW_M!c)Xxz1<=&_ZcL=LMZJqlnO1P^xwGGW*Z+yTBvbV z-IFe6;(k1@$1;tS>{%pXZ_7w+i?N4A2=TXnGf=YhePg8bH8M|Lk<trxeRKB}%OAdV zF?Siu2Aejr$DUT_(7^jK0Nj)3cm>-->+w8Y+FjZ;L=wSGwxfA`gqSn)f(XNuSm>6Y z@|#e-)I(PQ^G@N`%|_DZSb4_pkaEF0!-nqY+t#pyA>{9^*I-zw4SYA1_z2Bs$<h)! zpv38fsXDZM_X@INe56j?k@)tNMkFMj8cTQJ$X4r5v(BGg-1{&C`!Yj_Y<k0!?}aJf z#JGJ1@OqW;BxgeF9$hm=6!m{keCJ@#?tE&!@yuq7*#L|@YvJ%;)gr|jBY}SWL-&Cs zQ!mw@OlbIdG3(#EkpBl8{(}OI{;!?2|G|c<E@-M)?=A^0GshZcISK-#B2ue(IIA-_ z9w4il;AwRdi8#d}!((z>XGUZbGA;VeMo%CezHK0lO={L%G)dI-+8w?r9iexdoB{?l zbJ}C?huIhWXBVs7oo{!$lOTlvCLZ_KN1N+XJGuG$rh<^eUQIqcI7^pmqhBSaOKNRq zrx~w^?9C?*&rNwP_SPYmo;J-#!G|{`$JZK7DxsM3N^8iR4vvn>E4MU&Oe1DKJvLc~ zCT<I_aNTBA2AT={UaDBvY;L-F;-3x<CQR6iPd<;QAXotz)uYh^wpe7Q(93c}igqgN zdobfHsIDr5`_CmhG$qMdxn8KR@Pn@N-mHKUHKOX&)%`SbV9fR}9n{?>>KLZ1;t@My zRj_2<UYRwO^~^d=O?@mH&b9sI#vdWBJxpt83?Z0j-W8qx2-hK5d34-C4sp4|jBBWk ze>hI^61T&LIz)S!+AQIV23n1>ng+LUvzv;xu!4;wpqb#EZz;F)BLUzT;8UA1x*6vJ zicB!3Mj03s*kGV{g`fpC?V^s(=JG-k1EMHbkdP4P*1^8p_TqO|;!Zr%GuP$8KLxuf z=pv*H;kzd;P|2`JmBt~h6|GxdU~@weK5O=X&5~w$HpfO}@l-T7@vTCxVOwCkoPQv8 z@aV_)I5HQtfs7^X=C03zYmH4m0S!V@JINm6#(JmZRHBD?T!m^DdiZJrhKpBcur2u1 zf9e4%k$$vcFopK5!CC`;ww(CKL~}mlxK_Pv!cOsFgVkNIghA2Au@)t6;Y3*2gK=5d z?|@1a)-(sQ%uFOmJ7v2iG&l&m^u&^6DJM#XzCrF%r>{2XKyxLD2rgWBD;i(!e4<I4 ztQMCn++p-JQL#X;e0_*gCd!zJZkgSoi4d`Mjp8@2dGE6jg-6iGK%B`{ImPc{6)xtl z-JQzUC3!Dy-wa_^6Du~LdJPu6AAg3EXnK*dDw$y>InDQBDg==^z;AzT2z~OmV0!?Z z0S9pX$+E;w3WN;v&NYT=+G8hf=6w0E1$0AOr61}eOvE8W1jX%>&Mjo7&!ulawgzLH zbcb+IF(s^3aj12WSi#pzIpijJJzkP?J<Q+8aE479sW}w;?buYie;*-X{oX07nvC=A z=<iVI7X^e2cQ}P!soX-4VR!VRV$Z_U+ZTj4TebMRB2^3cM!DhJ-HMZL2+)kbATc-$ z5OVijFU;ogcZNLP!4a`?jVcxKY0kz`E!3Q1tX<M~bkPPol_}bsXqTnZ-hn%iHu;2( z)25L;0vU356xqEa<YRnd@OE}h>zRawnxmNDSUR#7!29vHULCE<3Aa#be}ie~d|!V+ z%l~s9Odo$G&fH!t!+`rUT0T9DulF!Yq&BfQWFZV1L9D($r4H(}Gnf6k3^wa7g5|Ws zj7%d`!3(0bb55yhC6@Q{?H|2os{_F%o=;-h{@Yyyn*V7?{s%Grvpe!H^kl6tF4Zf5 z{Jv1~yZ*iIWL_9C*8pBMQArfJJ0d9Df6Kl#wa}7Xa#<FZ_+HQp4vCoJ5ri+1gVyba zY3!kPbL`K<<RGRmSQ%Vf<RJSvc_;O}@63?xQ_11Q6P0xE%8<6Hd8`o%qwR34Q=fre zi@=>Ef_5B7=X}DzbQXVPfCwTO@9+@;A^Ti6il_C>g?A-GFwA0#U;t4;wOm-4oS})h z5&on>NAu67O?YCQr%7XIzY%LS4bha9*e*4bU4{lGCUmO2UQ2U)QOqClLo61Kx~3dI zmV3*(P6F_Tr-oP%x!0kTnnT?Ep5j;_IQ^pTRp=e8dmJtI4YgWd0}+b2=ATkOhgpXe z;jmw+FBLE}UIs4!&HflFr4)vMFOJ19W4f2^W(=2)F%TAL)+=F>IE$=e=@j-*bFLSg z)wf|uFQu+!=N-UzSef<j70eWG|Bw@9vuGvdPa9eGdH%he{7X~I+~#v5(FR~eZ({zr z3atQeGE;E?xEMP)0<471j14V~9sVVF&dOSjvtsD)Y;17`V+FbSm^R^>62u0-C8Zc7 zo6@F)c+nZA{H|+~7i$DCU0pL{0Ye|fKLuV^w!0Y^tT$isu%i1Iw&N|tX3kwFKJN(M zXS`k9js66o$r)x?TWL}Kxl`wUDUpwFx(w4Yk%49;$sgVvT~n8AgfG~HUcDt1TRo^s zdla@6heJB@<NR79jI)Sv0d=ENlvCW(+?-E?L$ssjovwpB)DTo9X7RwBdWJzNC{_^% zwS_J}S37XAU9?j7G}QU<H_U*7xiJ`aD77LDmPIQ3o5Hmm%`3&cP$AEKtI@Ts-Fhpn z`$X@q7@eF1m~rqM(i7K%VOH?_<XH&u6yyMQNw}*hfnz3lpQk}#^Vz~ob8*tLJP>JV z!vK;BUMznhzGK6PVtj0)GB=zT<IjLD<U#t9yeP$Zfm)|X%b=ZIkrBG_wgAB$rzkH2 z<1oX4J6VSjLjcV@j}%5P@WBw5mriS^(N6+Qk-;lUn6no!bM-BTSE_n$j@TCCUhAE* z#)H84G5D*`xl5aX#4$^OE*qmXhVh8>v6)Q9Yt@l#fv7>wKovLobMV-+(8)NJmyF8R zcB|_K7=FJGGn^X@JdFaat0uhKjp3>k#^&xE_}6NYNG?kgTp>2Iu?ElUjt4~E-?`Du z?mDCS9wbuS%fU?5BU@Ijx>1HG*N?gIP+<~xE4u=>H`8o((cS5M6@_OK%jSjFHirQK zN9@~NXFx*jS{<|bgSpC|SAnA@I)+GB=2W|JJChLI_mx+-J(mS<m?-TB<6Nz%8Hwzd z?CB=0evDUzZui$A1)C{rgQdm%QAh9Cx}di4)oKcP04Dq%QZKlS2X@8uIa1HyQRjYi zm2GHOJs<A&7V?A6X@r6OY<&mL_7L*!PlJ{E=%_9D>J!b)uUom6nH0#2^(L@JBlV#t zLl?j54s`Y3vE^c_3^Hl0TGu*tw_n?@HyO@ZrENxA+^!)OvUX28gDSF*xFtQzM$A+O zCG=n#6~r|3zt=<qZz2Ec*etR#lzjQ@;br`-j}`py1o_`n&C1r$^0Q@3|9Q>8%GuG} z<#VCZ%2?3Q(Ad#Y7GMJ~{U3>E{5e@z6+rgZLX{Cxk^p-7dip^d29;2N1_mm4QkASo z-L`GWWPCq$uCo;X_BmGIpJFBlhl<8~EG{vOD1o|X$aB9KPhWO_cKiU*$HWEgtf=fn zsO%9bp<GsMA}q0Q!Y|=THz$G1T%{Nl?`t9vo<U1MJ0@WzV!bmy=zXhZ$!h5Rfn)6O z^>~D2c@?*K9jVN@_vhR03>M_8h!_~%aN!Cnr?s-!;U3SVfmhRwk11A^8Ns`@KeE}+ zN$H}a1U6E;*j5&~Og!xHdfK5M<~xka)x-0N<vTH$BZ<0`r@@8=Lodqa#!_?w!uogG z6UJ7g)SaY<`QacDpb;S}Cp!}Q0LRm^fbVrH&{JQJjIWu1XbGOrnif=J1ziINhbtn7 z_Oe5LQ{4;L5qe!;bm3T3Rb8sW7nszh&@^5;!7HU}Pe#5hfa8#RQNd5`9nX70DAM*` z(W>)K_&e7AjMz`toDzasH+^1bZlC!n()crk9kg@$(Y{wdKvbuUd04N^8}t1iOgsKF zGa%%XWx@WoVaNC1!|&{5ZbkopFre-Lu(LCE5HWZBoE#W@er9W<>R=^oYxBvypN#x3 zq#LC8&q)GFP=5^-bpHj?LW=)-g+3_)Ylps!3<C}hi+6^cZ6jE1ZTj+Z2i6nOJ8YH| zaabkUJH;*y1gqH&X~HnC;{@-+nAgYEKD94G8G{!e&ziM2yoWYk>^YQ{9~O9&K)xgy zMkCWaApU-MI~e^cV{Je75Qr7eF%&_H)BvfyKL=gIA>;OSq(<D<(NV{W$5DHhYY_>y z052BFz3E(Prg~09>|_Z@!qj}@;8yxnw+#Ej0?Rk<y}4ghbD569B{9hSFr*^ygZ zr6j7P#gtZh6tMk6?4V$*Jgz+#&ug;yOr>=qdI#9U&^am2qoh4Jy}H2%a|#Fs{E(5r z%!ijh;VuGA6)W)cJZx+;9Bp1LMUzN~x_8lQ#D3+sL{be-Jyeo@@dv7XguJ&S5vrH` z>QxOMWn7N-T<n9;DXDyyePE0UiuZb3JGNp$lAhQ~X-kNU1*6=sH}s~FXJN-8c;lZs z7HBLf-e&8qlhuKLu(^X1^j_<4k9W!j)|_=T*7%#dQKXFe{b<A{XekMUEnLIS(63na zzInu29ME_T?nj$zv5aE0A=sV3LW;)u{QrV@Zy06%e@vYQR22_;K4Fvhx3HoA@33+G zCs62xY;B!Bu>!D@1(@4>ZlL^y5>m#0!HKovs12GRav4z!>p(1~<W`Ga6-lK!B5@6B z-xpH3&o#px-=@03vpbI3+S1f&&TrR5USODX-|V-*9w|gtzkESWF?V)WbsiskAk^#p z!ZDx|gp`s(4|Oqvat!CPd&@GPP?vsEQWjNUMwOB@{|n;5Diw6pPKsz&c>xok8+_{| z#Ae4{9#NLh#Vj2&JuIn5$d6t@__`o}umFo(n0QxUtd2GKCyE+erwXY?`cm*h&^9*8 zJ+8x6fRZI-e$CRygofIQN^dWysCxgkyr{(_oBwwSRxZora1(%(aC!5BTtj^+YuevI zx?)H#(xlALUp6QJ!=l9N__$cxBZ5p&7;qD3PsXRFVd<({Kh+mShFWJNpy`N@ab7?9 zv5=klvCJ4bx|-pvOO2-+G)6O?$&)ncA#Urze2rlBfp#htudhx-NeRnJ@u%^_bfw4o z4|{b8SkPV3b>Wera1W(+N@p9H>dc6{cnkh-sgr?e%(YkWvK+0YXVwk0=d`)}*47*B z5JGkEdVix!w7-<%r0JF~`ZMMPe;f0EQHuYHxya`puazyph*ZSb1mJAt^k4549BfS; zK7~T&lRb=W{s&t`DJ$B}s-eH1&&-wEOH1KWsKn0a(ZI+G!v&W4A*cl>qAvUv6pbUR z#(f#EKV8~hk&8oayBz4vaswc(?q<a;C|S=8pd~bXu$*aw<)wp+CwqNTN9!T?p;yPx z*SniakmO6wURNZ7GWCm8Ya45rO|}%-Qp1UKwW-rqg3n8~^mm|I3fi#pmkLuRDIwoW zsmsb4&VvpdLOg-L*k9y+559a?-#1gIiddL&?-@Bcy~$j<p+I(V|6hE)Q*>w1vn`yC zZQDl2PCB-&Uu@g9ZQHhO+v(W0bNig{-k0;;`+wM@#@J)8r?qOYs#&vUna8ILxN7S{ zp1s41KnR8miQJtJtOr|+qk}wrLt+N*z#5o`TmD1)E&QD(Vh&p<CN>jZJ_J*0!8dy_ z>^=@v=J)C`x&gjqAYu`}t^S=DFCtc0MkBU2zf|69?xW`Ck~(6zLD)gSE{7n~6w8j_ zoH&~$ED2k5-yRa0!<lv8j}}xh*FvhnF1Y<Gf~b6=&rsjF@~p_JYoQ#L?BQP}dHq15 zWCkltOfk{W5GW85Ogs_nL~tiudBBRFKjRUe{@6zCS_V?=zFo1DBO;p+S8s>r8fMRy z;QjBYUaUnpd}mf%iVFPR%Dg9!d>g`01m~>2s))`W|5!kc+_&Y>wD@@C9%>-lE`WB0 zOIf%FVD^cj#2hCkFgi-fgzIfOi+ya)MZK@IZhHT5FVEaSbv-oDDs0W)pA0&^nM0TW zmgJmd7b1R7b0a`UwWJYZXp4AJPteYLH>@M|xZFKwm!t3D3&q~av?i<Ag?bba16@IS zHUOCdhda!e?bJ5*J3Y%j(81eUmutb(EchCFupe9}n&AMTHI_+DfPR)GI#KjHpOoBI z5Gu<|<UZl+PkBh-W4ClAZU`l?G`f~9|6u+icbWk=N7hFF<D&3Bl*%;CZ7yaBhFcnr z_a|=;)pyWfZ(;I0^YdllR-qm=v)11do16ZvL2c0&8&|F5M-*i!_H3CSKL^H4a$oAq zoxw95T9aeyvlB|=#ZC^_NJ!tIwI^M9bF|#O#J>)WvAKHE{RqpD{{%OhYkK?47}+}` zrR2(Iv9bhVa;cDzJ%6ntcSbx7v7J@Y4x&+eWSKZ*eR7_=CVIUSB$^lfYe@g+p|LD{ zPSpQmxx@b$%d!05|H}WzBT4_cq?@~dvy<7s&QWtieJ9)hd4)$SZz}#H2<n7tNkOV1 z4dGzRFA8e?>UTi$CkFWW|I)v_-NjuH!VypONC=1`A=rm_jfzQ8Fu~1r8i{q-+S_j$ z#u^t&Xnfi5tZtl@^!fUJhx@~Cg0*vXMK}D{>|$#T*+mj(J_@c{jXBF|rm4-8%Z2o! z2z0o(4%8KljCm^>6HDK!{jI7p+RAPcty_~GZ~R_+=+UzZ0qzOwD=;YeZt*?3%UGdr z`c|BPE;yUbnyARUl&XWSNJ<+uRt%!xPF&K;(l$^JcA_CMH6)FZt{>6ah$|(9$2fc~ z=CD00uHM{qv;{Zk9FR0~u|3|Eiqv9?z2#^GqylT5>6JNZwKqKBzzQpKU2_pmtD;CT zi%Ktau!Y2Tldfu&b0UgmF(SSBID)15*r08eoUe#bT_K-G4VecJL2Pa=6D1K6({zj6 za(2Z{r!FY5W^y{qZ}08+h9f>EKd&PN90f}S<lY>c0ejf%kB4+f#T8Q1=Pj=~#pi$U zp#5rMR%W25>k?<$;$x72pkLibu1N|jX4cWjD3q^Pk3js!uK6h7!dlvw24crL|MZs_ zb%Y%?Fyp0bY0HkG^XyS76Ts*|Giw{31LR~+WU5NejqfPr73Rp!xQ1mLgq@mdWncLy z%8}|nzS4P&`^;zAR-&nm5f;D-%yNQPwq4N7&yULM8bkttkD)hVU>h>t47`{8?n2&4 zjEfL}UEagLUYwdx0sB2QXGeRmL?sZ%J!XM`$@ODc2!y|2#7hys=b$LrGbvvjx`Iqi z&RDDm3YBrlKhl`O@%%&rhLWZ*ABFz2n<MWYz-RUtKMwMUqMt-GyQl@$O;c9*!K~Rl z-w}`_$>Hu7k~3@e4)kO3%$=?GEFUcCF=6-1n!x^vmu+Ai*amgXH+Rknl6U>#9w;A} zn2xanZSDu`4%%x}+~FG{Wbi1jo@wq<ZmB717T<ta0(G%F$g_)puW#{-gzhq|8#wv@ zEAoBdoShv1yY7(wBioATKReX_2yBYgpuBKZuzq@t*txfDh}K*Di<@GI^4nb&H(C4z z69GgJM43hObSvZ6lO`G4>Bc5(5Xl~d0KW(^Iu(U3>WB@<Z5H7?3v)CC;X7fG;SWCf zy8<6u6ZUOV%S*Y3N!#AHo|m2j@70eT!&&~<-+s;<lXi!@{W&8bXdseKX_oa7B@w53 zBHkj(awSVa2IU>-(&vn_PJt9{1`e9Iic@+{VPc`vP776L*viP{wYB2Iff8hB%E3|o zGMOu)tJX!`qJ}ZPzq7>=`*9TmETN7xwU;^AmFZ-ckZjV5B2T09pYliaqGFY|X#E-8 z20b>y?(r-Fn5*WZ-GsK}4WM>@TTqsxvSYWL6>18q8Q`~JO1{vLND2wg@58OaU!EvT z1|o+f1mVXz2EKAbL!Q=QWQKDZpV|jznuJ<tYNNoap&N{uS8a_zBs&dE>}@-)1&cdo z^&~b4Mx{*1gurlH;Vh<Pn4KysHk|=SYZZ1dZdH=E=d@s|;|z)@=#bQ^@3{k?S3Hnr zX_?iGliVmy5sA62^Ate%IE<rJlxS*UMpRn9d#i^W0cb@y2O9(%c>k5g_cM&6LOHS2 zRkLfO#HabR1JD4Vc2t828dCUG#DL}f5QDSBg?o)IYYi@_xVwR2w_ntlpAW0NWk$F1 z$If?*lP&Ka1oWfl!)1c3fl`g*lMW3JOn#)R1+tfwrs`aiFUgz3;XIJ>{QFxLCkK30 zNS-)#DON3yb!7LBHQJ$)4y%TN82DC2-9tOIqzhZ27@WY^<6}vXCWcR5iN{LN8{0u9 zNXayqD=G|e?O^*ms*4P?G%o@J1tN9_76e}E#66mr89%W_&w4n66~R;X_vWD(oArwj z4CpY`)_mH2FvDuxgT+akffhX0b_slJJ*?Jn3O3~moqu2Fs1oL*>7m=oVek2bnprnW zixkaIFU%+3XhNA@@9hyhFwqsH2bM|`P?G>i<-gy>NflhrN{$9?LZ1ynSE_Mj0rADF zhOz4FnK}wpL<E5S%bk;<_YM|zN)6Odj=s9IFm70~oT9*2M90&mdd1}jloKyX>mQuV zgO4_Oz9GBu_NN>cPLA=`SP^$gxAnj;WjJnBi%Q1zg`*^cG;Q)#3Gv@c^j6L{arv>- zAW%8WrSAVY1sj$=umcAf#ZgC8UGZGoamK}hR7j6}i8#np8ruUlvgQ$j+AQglFsQQq zOjyHf22pxh9+h#n$21&$h?<m}C**mjEbK}U6*-H=*?~P<k%4YtPj%^mGy2|WI_l)D z4XncW<5HU%TGsxxBh`SdfYrD^WayR((yUya=$IDAhu(ZE^A(oZ?S5<t984RWMwFi7 zqrYs^oT<3#A9r08IXzul4KkddhDp2P>2uq0>C9P?P=<d@7_L!Qg~Rn03}E|#%b9vL zOx~!}ZHG65!djIu%4~Gp4!DO#2KP-1mGt(7tvB+n2f4F>Juw0|;oE~c$H{#RGfa>| zj)Iv&uOnaf@foiBJ}_;zyPHcZt1U~nOcNB{)og8Btv+;f@PIT*xz$x!G?u0Di$lo7 zOugtQ$Wx|C($fyJTZE1<oh60v#3Ol$+Bb7DKr_o1`VSwY2JFNKATWV{>JvR~i7LP{ zbdIwqYghQAJi9p}V&$=*2Azev$6K@pyblphgpv8^9bN!?V}{BkC!o#bl&AP!3DAjM zmWFsvn2fKWCfjcAQmE+=c3Y7j@#7|{;;0f~PIodmq*;W9Fiak|gil6$w3%b_Pr6K_ zJEG@&!J%DgBZJDCMn^7mk`JV0&l07Bt`1ymM|;a)MOWz*bh<tL5GbhQ;9|g3@34F{ zGtLov`|T$cSqh#RS;N*v?A-X`ued_<Ip_%ZlMd(!H~kn?#1(7>2#d{i?SDe9IcHs7 zjCrnyQ*Y5GzIt}>`bD91o#~5H?4_nckAgotN{2%!?wsSl|LVmJht$uhGa+HiH>;av z8c?mcMYM7;mvWr6noUR{)gE!=i7cZUY7e;HXa221KkRoc2UB>s$Y(k%NzTSEr>W(u z<(4mcc)4rB_&bPzX*1?*ra%V<J((n?Vrk|-pdzEONVhIss_4|XNG8@~?;$z1`sk{- z_eIGLvq65it;k;@B%CbFB2!C1k3F-(AyNEoSULpx#FO3i(0JL0G{L7$`bn&%ksp7^ zxnFA*FgG1nLfRhmR~{IJsqvNDRDrQT2>F}P1nwiP5cykJ&W{!OTlz&Td0pOkVp+wc z@k=-Hg=()hNg=Q!Ub%`BONH<U#%O5@_k;aYhjfJBEXJmI2;COdBh$G!5oGHW?S*G$ zf#*1%q7cT|prELhiE({B?pP9M!Aq<zNL32T@D)S<LtdKaPn!$26v<U$8)aa!Qd?Ah zp$#-EtgAk=Hz<~D0TJ?9)&N%sESs~8KJy@!c+~@C%F-rgJhx@w6P9-S$LYFK^C;oB z8QW+|RRe30Hf3d1^yM~pc;$JFq2-r-ldPd<QMV0pr$y{RM)K%<e19_v+Y7PNK?m&% zT~Ig)fHm4qSbQw6H+z0Z+!UPMYJ9g?rUte^<{B0}Q_|w%Tn$|NmusF8@_GHUIjq>{ z_=ZFgetj@)NvppAK2>8r!KAgi><!5;HN9jwV{MZ`YL5sQ$}aIi+m*cRLvGQ!n&)3l z@6}XW%EJG?u1{TGGf$E^&=Zy?znegDy9&CBKRH{CBvxK{u?R4B!vowv8bgeWk%2PQ z4R4u5a)xsg>#%*7;O-o9MOOfQjV-n@BX6;Xw;I`%HBkk20v`qoVd0)}L6_49y1IhR z_OS}+eto}OPVRn*?UHC{eGyFU7JkPz!+gX4P>?h3QOwGS63fv4D1*no^6PveUeE5% zlehjv_3_^j^C({a2&RSoVlOn71D8WwMu9@Nb@=E_>1R*ve3`#TF(NA0?d9IR_tm=P zOP-x<pw+U9N#+bGG$p95t%l(mF})pxqA;7~5YP$&IV@Cqg|ZnW!oeTeX6u4@S+@)L zB;GAtvWbM`ljxT3E4^)k%k6JubNbYk1rt({ep3<lryFs!t2Zb}-YZ>;gS*vtyE1Cm zG0L?2nRUFj#aLr-R1fX*$sXhad)~xdA*=hF3zPZhha<2<ifj)Cxwo~vXzj;p?dV5p z4G%UIR9j!J4==@Hu$atwPf^Z$?MFNB5cE5}vclk<PVlbxzd@?K;sXH_C?Fte<o{Gf zqWix`Z({$gsH5^v>O$Ps+F07w*3#MTe?)T8|A!P!v+a|ot{|^$q(TX`35O{WI0RbU zCj?hgOv=Z)xV?F`@HKI11IKtT^ocP78cqHU<ePNYVivU@$iVD4v*mcx?s~lWGOPQm z%1~l=`^>!YS@cHI@{fPD?YXL)?sD~9thOAv4JM|K8OlQhPXgnevF=F7GKD2#sZW*d za}ma31wLm81IZxX(W#A9mBvLZr|PoLnP>S4BhpK8{YV_}C|p<)4#yO{#ISbco92^3 zv&kCE(q9Wi;9%7>>PQ!zSkM%qqqLZW7O`VXvcj;WcJ`2~v?ZTYB@$Q&^CTfvy?1r^ z;<NESMud)0BQj2D=&7qE*VfJG!5M&~XQ{$Xlh%`o>Cdi+PTtmQwHX<SsT^n%-J~N3 zyE}DUc@-|y)HmKEtut??GCZ08c(uJ1RH_249<&<hJ(X7F^Ll1TrEi->_7Kz?r#1>D zS5lWU(Mw_$B&`ZPmqxpIvK<~fbXq?x20k1~9az-Q!uR78mCgRj*eQ>zh3c$W<sN(? zDGvf#TKQ+=EAVx6QY4w#*oi&V3s}SKTYY0BtixwNsOd@PorKUYksSgi%@NRCzc+?7 zk0+$XqkD>}>^+w^dIr-u{@s30J=)1zF8?Wn|H`GS<=>Om|DjzC{}Jt?{!fSJe*@$H zg>wFnlT)k#<ArP7%y0V4*qC@llbJ+cY%HPGY2E-u7uTYtn4z}Wl-a}rXZ(>T?LslW zu$^7Uy~$SQ21cE?3Ijl+bLfuH^U5P^$@~*UY#|_`uvAIe(+wD2eF}z_y!pvomuVO; zS^9fbdv)pcm-B@CW|Upm<7s|0+$@@<&*>$a{aW+oJ%f+VMO<#wa)7n|JL5egEgoBv zl$BY(NQjE0#*nv=!kMnp&{2Le#30b)Ql2e!VkPLK*+{jv77H7)xG7&=aPHL7LK9ER z5lfHxBI5O{-3S?GU4X6$yVk>lFn;ApnwZybdC-GAvaznGW-lScIls-P?Km2mF>%B2 zkcrXTk+__hj-3f48U%|jX9*|Ps41U_cd>2QW81Lz9}%`mTDIhE)jYI$q$ma7Y-`>% z8=u+Oftgcj%~TU}3nP8&h7k+}$D-CCgS~wtWvM|UU77r^pUw3YCV80Ou*+bH0!mf0 zxzUq4ed6y>oYFz7+l18PGGzhB^pqSt)si=9M>~0(Bx9*5r~W7sa#w+_1TSj3Jn9mW zMuG9BxN=}4645Cpa#SVKjFst;9UUY@O<|wpnZk$kE+to^4!?0@?Cwr3(>!NjYbu?x z1!U-?0_O?k!NdM^-rIQ8p)%?M+2xkhltt*|l=%z2WFJhme7*2xD~@zk#`dQR$6Lmd zb3LOD4fdt$Cq>?1<%&Y^wTWX=eHQ49Xl_<VMA5Fa@n0dk{QZhV5lk|{5SEsw$jPdU z#nQ>lFUA(YQYHGHhd}@!VpYHHm=(1-O=yfK#kKe|2Xc*9<S;k$Lb`9XJZE>}?BDFN zD7FJM-AjVi)T~OG)hpSWqH>vlb41V#^G2B_EvYlWhDB{Z;Q9-0)ja(O+By`31=biA zG&Fs#5!%_mHi|E4Nm$;vVQ!*>=_F;ZC=1DTPB#CICS5fL2T3XmzyHu?bI;m7D4@#; ztr~;dGYwb?m^VebuULtS4lkC_7>KCS)F@)0OdxZIFZp@FM_pHnJes8YOvwB|++#G( z&dm*OP^cz95Wi15vh`Q+yB>R{8zqEhz5of>Po$9LNE{xS<)lg2*roP*sQ}3r3t<}; zPbDl{lk{pox~2(XY5=qg0z!W-x^PJ`VVtz$git7?)!h>`91&&hESZy1KCJ2nS^yMH z!=Q$eTyRi68rKxdDsdt+%J_&lapa{ds^HV9Ngp^YDvtq&-Xp}60B_w@Ma>_1TTC;^ zpbe!#gH}#fFLkNo#|`jcn?5LeUYto%==XBk6Ik0kc4$6Z+L3x^4=M6OI1=z5u#M%0 z0E`kevJE<LhoU57#IA4~)w5}*NogHTS;X7|h4dZZBN@fD3S$VO8tGP|%1zbj7WmDo z0R`gXIGqi|7*CpR8IJMZ8Nwjz^lvy|b!dMnI%wIWmT7VFD<gPf*9C6nj==BiaROQd z!;i~O<&NwfItv9O@Kvz5mog`s?$q+L<^X;7${kyyOS|jwC$wkdj!bi@TUuF5L)$cX zMxk~vpKFCP??-~+BD3|H#U*CVTydM9@gmZpOmm|kWhJyF4fRkDKJgdTIfzps5o)pz zrtMg(kmmv|ehbaL=sCRnSmn9Uv%)eIbIbOo9MrB+DS}c6&@GCzb6q9f=fcq?X}fR2 zPu60%movB7G;U~o02@|dw%q(6HLq~4NrE6zuq;zB;Gr$W2U2}Zj43(-h(j%#zhD8A z7rHt~@T10@Prh`{^fqBZ8g;cQ6(3`3X`U)6#Hxlt%kJh}&vf1DOT$C)k1u@ss(7k+ z=L@b>pJjvvN>+g`?gtnbo$@p4VumliZV3Z%CfXXB&wPS^5C+7of2tyVkMwNWBiTE2 z8CdPu3i{*vR-I(NY5syRR}I1TJOV@DJy-Xmvxn^IInF>Tx2e)eE9jVSz69$6T`<ec zlID!#w}wrx&@7BiZzQn3JxyA(oA<AEOV0BE$`iZNXJOSL-7P8pI;ky$_zD=zd=TKh z(c$m8{nFyF_4x5HzmIZBew5-yVv`~nmV))G_qtRcdh81BQ{S|iOnOO6`uM2G_YWyl z0?@u%We<fWa+`VIQUxI)<Kf(3Skf=LFWI<P-9v=MI`-~Ii21QDao?c&ITAuT^yQ0U z{zb7M6F@@~JS#kzGfMZMuJyw=Tox&#R$8Fyw;z#ielHBo7YGyfnN8swhTB=x+^rB| zz_FH7#PO;4Xg6(m&OVV#QcIEQS<<@7sWy2=#Tl5sgK1!@v8u95qZlP>M9-&om!T+I znia!ZWJRB28o<?tgK1VsFie_eEEw36g5mbotA>_srWlAxtz4VVft8)cYloIoVF=pL zugnk@vFLXQ_^7;%hn9x;Vq?lzg7%CQR^c#S)O<ljma6ec*xm>c-8d=q_!2ZVH764V z!wDKSgP}BrV<?jcdJgkEf-J*#!LA4ASTjB)`ay^yEEt1U;P4eL@*B}+JT_Bk_ZH;O z!9T2ZZ;ISzuDc5yJ|tMm<ZwHDp(%s`1u+eWK1-2n9ud||f@YUHX5=xTd4-X#MFTDm z5nQpoDBUj-;&P=>V6SfCLZnYe-7f;igDs9t+K*rbMAKsp9L$Kh<6Z;e7;xxced<XP zVo%9V$_ySaa)p<v<w4SkV6(owfPcoZwz*h;vAcq&g9oq?upG^7b1tyAL5y|`T;_1> z<TXUUWQJYkDMbY;wmqgJW@`z!d}K!30mlif%Hg{_3WEz<Hx}6Yq#}X4(bGhy#Tzui z;(e8b#q5ftQWs@RgeMob62T;kKXq&M2GbM!S<36R+emL-{G6LqyTecRe6wH)G@gmX z1OeIi>n=FGY<}CUz31a2G}$Q(`_r~75PzM4l_({Hg&b@d8&jC}B?2<+ed`f#qMEWi z`gm!STV9E4sLaQX+sp5Nu9*;9g12naf5?=P9p@H@f}dxYprH+3ju)uDFt^V{G0APn zS;16Dk{*fm6&BCg#2vo?7cbkkI4R`S9SSEJ=#KBk3rl69SxnCnS#{*$!^T<a5W&b; z4V99((J~*x)Rx_H9|P_5`uS!Y$Gxb5LQ+0e%aE-{jw{OyAtE6Aq!IHxl1|S$-Cu?B zx9C1P%Kp4h1B)+`al$6$R*~b3Jo#@Qdm&gjg|Ov%YGdYb`=U+2D`u}q+L0<|xx(IN zJpAwWjBFRNqodaKqhF3_BsBUl35}Y)JDC!{>9UUmO#&XXKjHKBqLdt^3yVvu8yn|{ zZ#%1CP)8t-PAz(+_g?xyq;C2<9<5Yy<~C74Iw(y>uUL$+$mp(DRcCWbCKiGCZw@?_ zdomfp+C5xt;j5L@VfhF*xvZdXwA5pcdsG>G<8II-|1dhAgzS&K<sojZQd=y4ed-H1 z7O7v&Z6mO?kb`qAme`O%PVrs04f~IwqPNFBfB-`*#&EBh$NP8VE58|j#+~Nmp!VXU z)1f-_f0iFxE;p2EP>Arcb0BD4ZZ#WfiEY{hkCq5%z9@f|!EwTm;UEjKJsUo696V>h zy##eXYX}GUu%t{Gql8vVZKkNhQeQ4C%n|RmxL4ee5$cgwlU+?V7a?(jI<vY=MZkag z#_-?xEBW}J1+0VEyji3(S{uz^OBd&;(vw0jO<X5WhqR#el-)0Wl5Nsd(cmt{Cd4<} z)eg?yswpSDRsOaRVsa7RtL@Jy7qXAK1Nh9$e5CPHBcI+gpOQwwZp7NIUtn_0<QBEj zq_V|Rlr|y#?T=bAIZUvxWDPTRa?{ou>#&3wid+Kz5+x^G!bb#$q>QpR#BZ}Xo5UW^ zD&I`;?(a}Oys7-`I^|AkN?{XLZNa{@27Dv^s4pGowuyhHuXc<Jd`?qDC_yZB!z3?> zuctKG2x0{WCvg_sGN^n9myJ}&FXyGmUQnW7fR$=bj$AHR88-q$D!*8MNB{YvTTEyS zn22f@WMdvg5~o_2wkjItJN@?mDZ9UUlat2zCh(zVE=dGi$rjXF7&}*sxac^%HFD`Y zTM5D3u5x**{bW!68DL1A!s&$2XG@ytB~dX-?BF9U@XZABO`a|LM1X3HWCllgl0+uL z<Duh>04S*PX$%|^WAq%jkzp~%9HyYIF{Ym?k)j3nMwPZ=hlCg9!G+t>tf0o|J2%t1 ztC+`((dUplgm3`+0JN~}&FRRJ3?l*>Y&TfjS>!ShS`*MwO{WIbAZR#<%M|4c4^dY8 z{Rh;-!qhY=dz5JthbWoovLY~jNaw>%tS4gHVlt5epV8ekXm#==Po$)}mh^u*<i{Fn zABDzg*Hq`EFv=B^gUXHI&qE-YHeD7ODlb)V+q5-_V8iZ;xM=v^`}$)t$S!jTvc7nz zD3_s~U}>cE>q7*kvX&gq)(AHoItMYH6^s6f(deNw%}1=7O~bTHSj1rm<NrQ-o!APR zT)+k^%z5H1kYa~`6YnU8J@RIzl;fo-e2L_q7tI8Q@0PYuEEm+&axheYCX>2|Cq+3M z93djjdomWCTCYu!3Slx2bZVy#CWDozNedIHbqa<Iqc%rT8Z|nXj$knDa2B(vWR+KT z)YF=*`9rLuoukPGLZfD+#zu*JOvdg&3;oUlm^>|otsUl+ut?>a;}OqPfQA05Yim_2 zs@^BjPoFHOYNc6VbNaR5QZfSMh2S*`BGwcHMM(1@w{-4jVqE8Eu0Bi%d!E*^Rj?cR z7qgxkINXZR)K^=fh{pc0DCKtrydVbVILI>@Y0!<nrt`C<NKl}CQ5p!6a5p96f{OH0 z@HuuG?)@b+*%BA$ttJUF{D50?JzP7yl>Jm>x-xM!gu%deh<qO{Bg{B$oy;WYJF_S? zBJ;inff0?SMQz(4CyeC>m?cC6ok_msDVA*J#{75%4IZt}X|tIVPReZS#aCvuHkZxc zHVMtUhT(wp09+w9j9eRqz~LtuSNi2rQx_QgQ(}jBt7NqyT&ma61ldD(s9x%@q~PQl zp6N*?=N$BtvjQ_xIT{+vhb1>{pM0Arde0!X-y))A4znDrVx8yrP3B1(7bKPE5jR@5 zwpzwT4cu~_qUG#zYMZ_!2Tkl9zP>M%cy>9Y(@&VoB84#%>amTAH{(hL4cDYt!^{8L z645F>BWO6QaFJ-{C-i|-d%j7#&7)$X7pv#%9J6d<LuBVRA0B5EIxLf7Vi!LO^wq|0 zaEo8~1z#A0w|!nn9vMpxyf$u-d-=y7LB&Ilc(OTWUOtW{x)2l5qZTpkz|LRC(oO5W z)pK$<WPS3+<!4U5?)!OovrB73bVy9``@UDJzQF7GdpYI)S|R%_1AVo?X<j)1EWh5p za(XD@Vt~#*&=vm>a#9F<HFzRhoeVw}cA6vk`5OG{6e}~x@<GQd{^3Id#wHwuE_$YM ze;>B5KyDhkA+~)G0^87!^}AP>XaCSScr;kL;Z%RSP<WTg@yRXa5AQA|a?1I3$np{q zux0~%6%a5mgzD6g7i-=L>D2CgoJ;gpYT5&6NUK$86$T?jRH=w8nI9Z534O?5fk{kd z`(-t$8W|#$3>xoMfXvV^-A(Q~$8SKDE^!T;J+rQXP71XZ(kCCbP%bAQ1|%$%Ov9_a zyC`QP3uPvFoBqr_+$HenHklqyIr>PU_Fk5$2C+0eYy^~7U&(!B&&P2%7#mBUhM!z> z_B$Ko?{Pf6?)gpYs~N*y%-3!1>o-4;@1Z<T7SABeyANF59*;d}{thg|BJ$2!74z=W zPh#LV{@J4R;hVDd^x9$^$>z9VQHh)j5U1aL-Hyu@<lEsJ*>1d?X;jtDBNk*vMXPn@ z+u@wxHN*{uHR!*g*4Xo&w;5A+=Pf9w#PeZ^x@UD?iQ&${K2c}U<e!-=D8wZ=@<;J1 zv&>QgLRik-rKM#Y5rdDphdcNTF~cCX&9ViRP}`>L<z$&?1>)QA4zNXeG)KXFzSDa6 zd^St;inY6J_i=5mcGTx4_^Ys`M3l%Q==f<P)@-FgjtZ8}=wq6515Lx{bA#E-kNy05 z_W<JF!!S#LbrRbjn<XQe!>>{8S1LEHn{y(kbxn5g1ezt4CELqy)~TV6{;VW>O9?5^ ztcoxHRa0jQY7>wwHWcxA-BCwzsP>63Kt&3fy*n#Cha687CQurXaRQnf5wc9o8v7Rw zNwGr2fac;Wr-Ldehn7tF^(-gPJwPt@VR1f;AmKgxN&YPL;j=0^xKM{!wuU|^mh3NE z<j^WbT|M5_%me#P$ug0?Tt?t%YjqMb=E+GRaO-zoY%hgitgrmg#~#)7@$Z4lQkBof zq*z}cX|0R>y35quf}MeL!PU;|{OW_x$TBothLylT-J>_x6p}B_jW1L>k)ps6n%7Rh z96mPkJIM0QFNYUM2H}YF5bs%@Chs6#pEnloQhEl?J-)es!(SoJpEPoMTdgA14-#mC zghayD-DJWtUu`TD8?4mR)w5E`^EHbsz2EjH5aQLYRcF{l7_Q5?CEEvzDo(zjh|BKg z3aJl_n#j&eFHsUw4~lxqnr!6NL*se)6H=A+T1e3xUJGQrd}oSPwSy5+$tt{2t<j$M zij$>5J5@(lF<gGl)!2zGkjc|}Ih}H7&aelsMY59|{IgO>xl43amsARG74iyNC}uuS zd2$=(r6RdamdGx^eatX@F2D8?U23tDpR+Os?0Gq2&^dF+$9wiWf?=mDWfjo4LfRwL zI#SRV9iSz>XCSgEj!cW&9H-njJopYiYuq|2w<5R2!nZ27DyvU4UDrHpoNQZiGPkp@ z1$h4H46Zn~eqdj$pWrv;*t!rTYTfZ1_bdkZmVVIRC21YeU$iS-*XMNK`#p8Z_DJx| zk3Jssf^XP7v0X?MWFO{rACltn$^~q(M9rMYoVxG$15N;nP)A98k^m3C<GDbV)SP1X zN8D-CWOO?+70Fz}+($|kiPADnj|6nClsWepAUcOC!rsD^8CRBh12G((-8Bg*+`y)# z_!1brR{1`A9G$~g@MR7_fw3$Eo$Ex?0B0=Vq^c-+h7c_KN%^8HNtJU|F}pGNJX=j^ z%-s(=i_yXfzc4cUW!~Zhvf7?cJe8wYDwRvOU^1	!2!Jx#p;82Cd+!CX(v%ya=ua z	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 Mi07c5C&+LA2gVWY(f|Me literal 61574 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%0D=uyzE7ST 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a363877..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # 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"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -133,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +147,7 @@ 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 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,16 +200,20 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java index 750d525..aceb9a2 100644 --- a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java @@ -21,9 +21,9 @@ * @author GoodforGod * @since 28.10.2018 */ -final class AccountAPIProvider extends BasicProvider implements AccountAPI { +public class AccountAPIProvider extends BasicProvider implements AccountAPI { - private static final int OFFSET_MAX = 10000; + private static final int OFFSET_MAX = 9999; private static final String ACT_BALANCE_ACTION = ACT_PREFIX + "balance"; private static final String ACT_TOKEN_BALANCE_PARAM = ACT_PREFIX + "tokenbalance"; @@ -47,11 +47,11 @@ final class AccountAPIProvider extends BasicProvider implements AccountAPI { private static final String OFFSET_PARAM = "&offset="; private static final String PAGE_PARAM = "&page="; - AccountAPIProvider(RequestQueueManager requestQueueManager, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retryCount) { + public AccountAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { super(requestQueueManager, "account", baseUrl, executor, converter, retryCount); } @@ -61,7 +61,7 @@ public Balance balance(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_BALANCE_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + address; - final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); + final StringResponseTO response = getResponse(urlParams, StringResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); @@ -75,7 +75,7 @@ public TokenBalance balance(@NotNull String address, @NotNull String contract) t BasicUtils.validateAddress(contract); final String urlParams = ACT_TOKEN_BALANCE_PARAM + ADDRESS_PARAM + address + CONTRACT_PARAM + contract; - final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); + final StringResponseTO response = getResponse(urlParams, StringResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); @@ -98,7 +98,7 @@ public List<Balance> balances(@NotNull List<String> addresses) throws EtherScanE for (final List<String> batch : addressesAsBatches) { final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + BasicUtils.toAddressParam(batch); - final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); + final BalanceResponseTO response = getResponse(urlParams, BalanceResponseTO.class); if (response.getStatus() != 1) { throw new EtherScanResponseException(response); } @@ -139,34 +139,6 @@ public List<Tx> txs(@NotNull String address, long startBlock, long endBlock) thr return getRequestUsingOffset(urlParams, TxResponseTO.class); } - /** - * Generic search for txs using offset api param To avoid 10k limit per response - * - * @param urlParams Url params for #getRequest() - * @param tClass responseListTO class - * @param <T> responseTO list T type - * @param <R> responseListTO type - * @return List of T values - */ - private <T, R extends BaseListResponseTO<T>> List<T> getRequestUsingOffset(final String urlParams, Class<R> tClass) - throws EtherScanException { - final List<T> result = new ArrayList<>(); - int page = 1; - while (true) { - final String formattedUrl = String.format(urlParams, page++); - final R response = getRequest(formattedUrl, tClass); - BasicUtils.validateTxResponse(response); - if (BasicUtils.isEmpty(response.getResult())) - break; - - result.addAll(response.getResult()); - if (response.getResult().size() < OFFSET_MAX) - break; - } - - return result; - } - @NotNull @Override public List<TxInternal> txsInternal(@NotNull String address) throws EtherScanException { @@ -200,7 +172,7 @@ public List<TxInternal> txsInternalByHash(@NotNull String txhash) throws EtherSc BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_INTERNAL_ACTION + TXHASH_PARAM + txhash; - final TxInternalResponseTO response = getRequest(urlParams, TxInternalResponseTO.class); + final TxInternalResponseTO response = getResponse(urlParams, TxInternalResponseTO.class); BasicUtils.validateTxResponse(response); return BasicUtils.isEmpty(response.getResult()) diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 41abd16..2abb2e0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -1,13 +1,21 @@ package io.goodforgod.api.etherscan; +import io.goodforgod.api.etherscan.error.EtherScanException; import io.goodforgod.api.etherscan.error.EtherScanParseException; import io.goodforgod.api.etherscan.error.EtherScanRateLimitException; import io.goodforgod.api.etherscan.error.EtherScanResponseException; import io.goodforgod.api.etherscan.http.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthResponse; import io.goodforgod.api.etherscan.manager.RequestQueueManager; +import io.goodforgod.api.etherscan.model.response.BaseListResponseTO; import io.goodforgod.api.etherscan.model.response.StringResponseTO; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import io.goodforgod.api.etherscan.util.BasicUtils; +import org.jetbrains.annotations.ApiStatus.Internal; /** * Base provider for API Implementations @@ -16,10 +24,13 @@ * @see EtherScanAPIProvider * @since 28.10.2018 */ -abstract class BasicProvider { +@Internal +public abstract class BasicProvider { private static final String MAX_RATE_LIMIT_REACHED = "Max rate limit reached"; + private static final int OFFSET_MAX = 9999; + static final int MAX_END_BLOCK = Integer.MAX_VALUE; static final int MIN_START_BLOCK = 0; @@ -32,12 +43,12 @@ abstract class BasicProvider { private final Converter converter; private final int retryCountLimit; - BasicProvider(RequestQueueManager requestQueueManager, - String module, - String baseUrl, - EthHttpClient ethHttpClient, - Converter converter, - int retryCountLimit) { + public BasicProvider(RequestQueueManager requestQueueManager, + String module, + String baseUrl, + EthHttpClient ethHttpClient, + Converter converter, + int retryCountLimit) { this.queue = requestQueueManager; this.module = "&module=" + module; this.baseUrl = baseUrl; @@ -46,7 +57,7 @@ abstract class BasicProvider { this.retryCountLimit = retryCountLimit; } - private <T> T convert(byte[] json, Class<T> tClass) { + protected <T> T convert(byte[] json, Class<T> tClass) { try { final T t = converter.fromJson(json, tClass); if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith(MAX_RATE_LIMIT_REACHED)) { @@ -69,25 +80,58 @@ private <T> T convert(byte[] json, Class<T> tClass) { } } - private byte[] getRequest(String urlParameters) { + protected int getMaximumOffset() { + return OFFSET_MAX; + } + + /** + * Generic search for txs using offset api param To avoid 10k limit per response + * + * @param urlParams Url params for #getRequest() + * @param tClass responseListTO class + * @param <T> responseTO list T type + * @param <R> responseListTO type + * @return List of T values + */ + protected <T, R extends BaseListResponseTO<T>> List<T> getRequestUsingOffset(final String urlParams, Class<R> tClass) + throws EtherScanException { + final List<T> result = new ArrayList<>(); + int page = 1; + while (true) { + final String formattedUrl = String.format(urlParams, page++); + final R response = getResponse(formattedUrl, tClass); + BasicUtils.validateTxResponse(response); + if (BasicUtils.isEmpty(response.getResult())) + break; + + result.addAll(response.getResult()); + if (response.getResult().size() < getMaximumOffset()) + break; + } + + return result; + } + + protected EthResponse getResponse(String urlParameters) { queue.takeTurn(); final URI uri = URI.create(baseUrl + module + urlParameters); return executor.get(uri); } - private byte[] postRequest(String urlParameters, String dataToPost) { + protected EthResponse 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> T getRequest(String urlParameters, Class<T> tClass) { - return getRequest(urlParameters, tClass, 0); + protected <T> T getResponse(String urlParameters, Class<T> tClass) { + return getResponse(urlParameters, tClass, 0); } - private <T> T getRequest(String urlParameters, Class<T> tClass, int retryCount) { + protected <T> T getResponse(String urlParameters, Class<T> tClass, int retryCount) { try { - return convert(getRequest(urlParameters), tClass); + EthResponse response = getResponse(urlParameters); + return convert(response.body(), tClass); } catch (Exception e) { if (retryCount < retryCountLimit) { try { @@ -96,20 +140,21 @@ private <T> T getRequest(String urlParameters, Class<T> tClass, int retryCount) throw new IllegalStateException(ex); } - return getRequest(urlParameters, tClass, retryCount + 1); + return getResponse(urlParameters, tClass, retryCount + 1); } else { throw e; } } } - <T> T postRequest(String urlParameters, String dataToPost, Class<T> tClass) { + protected <T> T postRequest(String urlParameters, String dataToPost, Class<T> tClass) { return postRequest(urlParameters, dataToPost, tClass, 0); } - private <T> T postRequest(String urlParameters, String dataToPost, Class<T> tClass, int retryCount) { + protected <T> T postRequest(String urlParameters, String dataToPost, Class<T> tClass, int retryCount) { try { - return convert(postRequest(urlParameters, dataToPost), tClass); + EthResponse response = postRequest(urlParameters, dataToPost); + return convert(response.body(), tClass); } catch (EtherScanRateLimitException e) { if (retryCount < retryCountLimit) { try { diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java index b3604a7..7228943 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -17,17 +17,17 @@ * @see BlockAPI * @since 28.10.2018 */ -final class BlockAPIProvider extends BasicProvider implements BlockAPI { +public class BlockAPIProvider extends BasicProvider implements BlockAPI { private static final String ACT_BLOCK_PARAM = ACT_PREFIX + "getblockreward"; private static final String BLOCKNO_PARAM = "&blockno="; - BlockAPIProvider(RequestQueueManager requestQueueManager, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retryCount) { + public BlockAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { super(requestQueueManager, "block", baseUrl, executor, converter, retryCount); } @@ -36,7 +36,7 @@ final class BlockAPIProvider extends BasicProvider implements BlockAPI { public Optional<BlockUncle> uncles(long blockNumber) throws EtherScanException { final String urlParam = ACT_BLOCK_PARAM + BLOCKNO_PARAM + blockNumber; try { - final UncleBlockResponseTO responseTO = getRequest(urlParam, UncleBlockResponseTO.class); + final UncleBlockResponseTO responseTO = getResponse(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 898a7b7..0719569 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java @@ -20,7 +20,7 @@ * @author GoodforGod * @since 28.10.2018 */ -final class ContractAPIProvider extends BasicProvider implements ContractAPI { +public class ContractAPIProvider extends BasicProvider implements ContractAPI { private static final String ACT_ABI_PARAM = ACT_PREFIX + "getabi"; @@ -32,11 +32,11 @@ final class ContractAPIProvider extends BasicProvider implements ContractAPI { private static final String ACT_CONTRACT_ADDRESSES_PARAM = "&contractaddresses="; - ContractAPIProvider(RequestQueueManager requestQueueManager, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retryCount) { + public ContractAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { super(requestQueueManager, "contract", baseUrl, executor, converter, retryCount); } @@ -46,7 +46,7 @@ public Abi contractAbi(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParam = ACT_ABI_PARAM + ADDRESS_PARAM + address; - final StringResponseTO response = getRequest(urlParam, StringResponseTO.class); + final StringResponseTO response = getResponse(urlParam, StringResponseTO.class); if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) { throw new EtherScanResponseException(response); } @@ -62,7 +62,7 @@ public List<ContractCreation> contractCreation(@NotNull List<String> contractAdd BasicUtils.validateAddresses(contractAddresses); final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM + BasicUtils.toAddressParam(contractAddresses); - final ContractCreationResponseTO response = getRequest(urlParam, ContractCreationResponseTO.class); + final ContractCreationResponseTO response = getResponse(urlParam, ContractCreationResponseTO.class); if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) { throw new EtherScanResponseException(response); } diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 2b70711..12bb9d3 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -4,7 +4,7 @@ import io.goodforgod.api.etherscan.error.EtherScanKeyException; import io.goodforgod.api.etherscan.error.EtherScanParseException; import io.goodforgod.api.etherscan.http.EthHttpClient; -import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; +import io.goodforgod.api.etherscan.http.impl.JdkEthHttpClient; import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.util.BasicUtils; import io.goodforgod.gson.configuration.GsonConfiguration; @@ -19,9 +19,9 @@ * @author Anton Kurako (GoodforGod) * @since 11.05.2023 */ -final class EthScanAPIBuilder implements EtherScanAPI.Builder { +public class EthScanAPIBuilder implements EtherScanAPI.Builder { - private static final Supplier<EthHttpClient> DEFAULT_SUPPLIER = UrlEthHttpClient::new; + private static final Supplier<EthHttpClient> DEFAULT_SUPPLIER = JdkEthHttpClient::new; private static final String DEFAULT_KEY = "YourApiKeyToken"; private final Gson gson = new GsonConfiguration().builder().create(); diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java index ab6e863..daf1e4a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPIProvider.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 28.10.2018 */ -final class EtherScanAPIProvider implements EtherScanAPI { +public class EtherScanAPIProvider implements EtherScanAPI { private final RequestQueueManager requestQueueManager; private final AccountAPI account; @@ -22,12 +22,12 @@ final class EtherScanAPIProvider implements EtherScanAPI { private final TransactionAPI txs; private final GasTrackerAPI gasTracker; - EtherScanAPIProvider(String apiKey, - EthNetwork network, - RequestQueueManager queue, - EthHttpClient ethHttpClient, - Converter converter, - int retryCount) { + public EtherScanAPIProvider(String apiKey, + EthNetwork network, + RequestQueueManager queue, + EthHttpClient ethHttpClient, + Converter converter, + int retryCount) { // EtherScan 1request\5sec limit support by queue manager final String baseUrl = network.domain() + "?apikey=" + apiKey; diff --git a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java index ed717a9..e7ecb7c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -18,25 +18,25 @@ * @author Abhay Gupta * @since 14.11.2022 */ -final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI { +public class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI { private static final String ACT_GAS_ORACLE_PARAM = ACT_PREFIX + "gasoracle"; private static final String ACT_GAS_ESTIMATE_PARAM = ACT_PREFIX + "gasestimate"; private static final String GASPRICE_PARAM = "&gasprice="; - GasTrackerAPIProvider(RequestQueueManager queue, - String baseUrl, - EthHttpClient ethHttpClient, - Converter converter, - int retryCount) { + public GasTrackerAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient ethHttpClient, + Converter converter, + int retryCount) { super(queue, "gastracker", baseUrl, ethHttpClient, converter, retryCount); } @Override public @NotNull Duration estimate(@NotNull Wei wei) throws EtherScanException { final String urlParams = ACT_GAS_ESTIMATE_PARAM + GASPRICE_PARAM + wei.asWei().toString(); - final GasEstimateResponseTO response = getRequest(urlParams, GasEstimateResponseTO.class); + final GasEstimateResponseTO response = getResponse(urlParams, GasEstimateResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); @@ -46,7 +46,7 @@ final class GasTrackerAPIProvider extends BasicProvider implements GasTrackerAPI @NotNull @Override public GasOracle oracle() throws EtherScanException { - final GasOracleResponseTO response = getRequest(ACT_GAS_ORACLE_PARAM, GasOracleResponseTO.class); + final GasOracleResponseTO response = getResponse(ACT_GAS_ORACLE_PARAM, GasOracleResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); diff --git a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java index 237cafd..1002dc8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java @@ -18,15 +18,15 @@ * @author GoodforGod * @since 28.10.2018 */ -final class LogsAPIProvider extends BasicProvider implements LogsAPI { +public class LogsAPIProvider extends BasicProvider implements LogsAPI { private static final String ACT_LOGS_PARAM = ACT_PREFIX + "getLogs"; - LogsAPIProvider(RequestQueueManager queue, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retryCount) { + public LogsAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { super(queue, "logs", baseUrl, executor, converter, retryCount); } @@ -34,7 +34,7 @@ final class LogsAPIProvider extends BasicProvider implements LogsAPI { @Override public List<Log> logs(@NotNull LogQuery query) throws EtherScanException { final String urlParams = ACT_LOGS_PARAM + query.params(); - final LogResponseTO response = getRequest(urlParams, LogResponseTO.class); + final LogResponseTO response = getResponse(urlParams, LogResponseTO.class); BasicUtils.validateTxResponse(response); return (BasicUtils.isEmpty(response.getResult())) diff --git a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java index 428b48f..e35fd08 100644 --- a/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/ProxyAPIProvider.java @@ -26,7 +26,7 @@ * @author GoodforGod * @since 28.10.2018 */ -final class ProxyAPIProvider extends BasicProvider implements ProxyAPI { +public class ProxyAPIProvider extends BasicProvider implements ProxyAPI { private static final String ACT_BLOCKNO_PARAM = ACT_PREFIX + "eth_blockNumber"; private static final String ACT_BY_BLOCKNO_PARAM = ACT_PREFIX + "eth_getBlockByNumber"; @@ -57,17 +57,17 @@ final class ProxyAPIProvider extends BasicProvider implements ProxyAPI { private static final Pattern EMPTY_HEX = Pattern.compile("0x0+"); - ProxyAPIProvider(RequestQueueManager queue, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retryCount) { + public ProxyAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { super(queue, "proxy", baseUrl, executor, converter, retryCount); } @Override public long blockNoLast() throws EtherScanException { - final StringProxyTO response = getRequest(ACT_BLOCKNO_PARAM, StringProxyTO.class); + final StringProxyTO response = getResponse(ACT_BLOCKNO_PARAM, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) ? -1 : BasicUtils.parseHex(response.getResult()).longValue(); @@ -79,7 +79,7 @@ public Optional<BlockProxy> block(long blockNo) throws EtherScanException { final long compBlockNo = BasicUtils.compensateMinBlock(blockNo); final String urlParams = ACT_BY_BLOCKNO_PARAM + TAG_PARAM + compBlockNo + BOOLEAN_PARAM; - final BlockProxyTO response = getRequest(urlParams, BlockProxyTO.class); + final BlockProxyTO response = getResponse(urlParams, BlockProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -91,7 +91,7 @@ public Optional<BlockProxy> blockUncle(long blockNo, long index) throws EtherSca final String urlParams = ACT_UNCLE_BY_BLOCKNOINDEX_PARAM + TAG_PARAM + "0x" + Long.toHexString(compBlockNo) + INDEX_PARAM + "0x" + Long.toHexString(compIndex); - final BlockProxyTO response = getRequest(urlParams, BlockProxyTO.class); + final BlockProxyTO response = getResponse(urlParams, BlockProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -101,7 +101,7 @@ public Optional<TxProxy> tx(@NotNull String txhash) throws EtherScanException { BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_BY_HASH_PARAM + TXHASH_PARAM + txhash; - final TxProxyTO response = getRequest(urlParams, TxProxyTO.class); + final TxProxyTO response = getResponse(urlParams, TxProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -115,7 +115,7 @@ public Optional<TxProxy> tx(long blockNo, long index) throws EtherScanException final String urlParams = ACT_TX_BY_BLOCKNOINDEX_PARAM + TAG_PARAM + compBlockNo + INDEX_PARAM + "0x" + Long.toHexString(compIndex); - final TxProxyTO response = getRequest(urlParams, TxProxyTO.class); + final TxProxyTO response = getResponse(urlParams, TxProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -123,7 +123,7 @@ public Optional<TxProxy> tx(long blockNo, long index) throws EtherScanException public int txCount(long blockNo) throws EtherScanException { final long compensatedBlockNo = BasicUtils.compensateMinBlock(blockNo); final String urlParams = ACT_BLOCKTX_COUNT_PARAM + TAG_PARAM + "0x" + Long.toHexString(compensatedBlockNo); - final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); + final StringProxyTO response = getResponse(urlParams, StringProxyTO.class); return BasicUtils.parseHex(response.getResult()).intValue(); } @@ -132,7 +132,7 @@ public int txSendCount(@NotNull String address) throws EtherScanException { BasicUtils.validateAddress(address); final String urlParams = ACT_TX_COUNT_PARAM + ADDRESS_PARAM + address + TAG_LAST_PARAM; - final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); + final StringProxyTO response = getResponse(urlParams, StringProxyTO.class); return BasicUtils.parseHex(response.getResult()).intValue(); } @@ -165,7 +165,7 @@ public Optional<ReceiptProxy> txReceipt(@NotNull String txhash) throws EtherScan BasicUtils.validateTxHash(txhash); final String urlParams = ACT_TX_RECEIPT_PARAM + TXHASH_PARAM + txhash; - final TxInfoProxyTO response = getRequest(urlParams, TxInfoProxyTO.class); + final TxInfoProxyTO response = getResponse(urlParams, TxInfoProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -177,7 +177,7 @@ public Optional<String> call(@NotNull String address, @NotNull String data) thro throw new EtherScanInvalidDataHexException("Data is not hex encoded."); final String urlParams = ACT_CALL_PARAM + TO_PARAM + address + DATA_PARAM + data + TAG_LAST_PARAM; - final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); + final StringProxyTO response = getResponse(urlParams, StringProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -187,7 +187,7 @@ public Optional<String> code(@NotNull String address) throws EtherScanException BasicUtils.validateAddress(address); final String urlParams = ACT_CODE_PARAM + ADDRESS_PARAM + address + TAG_LAST_PARAM; - final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); + final StringProxyTO response = getResponse(urlParams, StringProxyTO.class); return Optional.ofNullable(response.getResult()); } @@ -198,7 +198,7 @@ public Optional<String> storageAt(@NotNull String address, long position) throws final long compPosition = BasicUtils.compensateMinBlock(position); final String urlParams = ACT_STORAGEAT_PARAM + ADDRESS_PARAM + address + POSITION_PARAM + compPosition + TAG_LAST_PARAM; - final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); + final StringProxyTO response = getResponse(urlParams, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult()) || EMPTY_HEX.matcher(response.getResult()).matches()) ? Optional.empty() : Optional.of(response.getResult()); @@ -207,7 +207,7 @@ public Optional<String> storageAt(@NotNull String address, long position) throws @NotNull @Override public Wei gasPrice() throws EtherScanException { - final StringProxyTO response = getRequest(ACT_GASPRICE_PARAM, StringProxyTO.class); + final StringProxyTO response = getResponse(ACT_GASPRICE_PARAM, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) ? Wei.ofWei(0) : Wei.ofWei(BasicUtils.parseHex(response.getResult())); @@ -226,7 +226,7 @@ public Wei gasEstimated(@NotNull String hexData) throws EtherScanException { throw new EtherScanInvalidDataHexException("Data is not in hex format."); final String urlParams = ACT_ESTIMATEGAS_PARAM + DATA_PARAM + hexData + GAS_PARAM + "2000000000000000"; - final StringProxyTO response = getRequest(urlParams, StringProxyTO.class); + final StringProxyTO response = getResponse(urlParams, StringProxyTO.class); return (BasicUtils.isEmpty(response.getResult())) ? Wei.ofWei(0) : Wei.ofWei(BasicUtils.parseHex(response.getResult())); diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java index a2bba16..006017a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java @@ -21,7 +21,7 @@ * @author GoodforGod * @since 28.10.2018 */ -final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { +public class StatisticAPIProvider extends BasicProvider implements StatisticAPI { private static final String ACT_SUPPLY_PARAM = ACT_PREFIX + "ethsupply"; private static final String ACT_SUPPLY2_PARAM = ACT_PREFIX + "ethsupply2"; @@ -30,18 +30,18 @@ final class StatisticAPIProvider extends BasicProvider implements StatisticAPI { private static final String CONTRACT_ADDRESS_PARAM = "&contractaddress="; - StatisticAPIProvider(RequestQueueManager queue, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retry) { + public StatisticAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retry) { super(queue, "stats", baseUrl, executor, converter, retry); } @NotNull @Override public Wei supply() throws EtherScanException { - final StringResponseTO response = getRequest(ACT_SUPPLY_PARAM, StringResponseTO.class); + final StringResponseTO response = getResponse(ACT_SUPPLY_PARAM, StringResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); @@ -50,7 +50,7 @@ public Wei supply() throws EtherScanException { @Override public @NotNull EthSupply supplyTotal() throws EtherScanException { - final EthSupplyResponseTO response = getRequest(ACT_SUPPLY2_PARAM, EthSupplyResponseTO.class); + final EthSupplyResponseTO response = getResponse(ACT_SUPPLY2_PARAM, EthSupplyResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); @@ -63,7 +63,7 @@ public Wei supply(@NotNull String contract) throws EtherScanException { BasicUtils.validateAddress(contract); final String urlParams = ACT_TOKEN_SUPPLY_PARAM + CONTRACT_ADDRESS_PARAM + contract; - final StringResponseTO response = getRequest(urlParams, StringResponseTO.class); + final StringResponseTO response = getResponse(urlParams, StringResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); @@ -73,7 +73,7 @@ public Wei supply(@NotNull String contract) throws EtherScanException { @NotNull @Override public Price priceLast() throws EtherScanException { - final PriceResponseTO response = getRequest(ACT_LASTPRICE_PARAM, PriceResponseTO.class); + final PriceResponseTO response = getResponse(ACT_LASTPRICE_PARAM, PriceResponseTO.class); if (response.getStatus() != 1) throw new EtherScanResponseException(response); diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java index 7374335..61f2484 100644 --- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java @@ -17,18 +17,18 @@ * @see TransactionAPI * @since 28.10.2018 */ -final class TransactionAPIProvider extends BasicProvider implements TransactionAPI { +public class TransactionAPIProvider extends BasicProvider implements TransactionAPI { private static final String ACT_EXEC_STATUS_PARAM = ACT_PREFIX + "getstatus"; private static final String ACT_RECEIPT_STATUS_PARAM = ACT_PREFIX + "gettxreceiptstatus"; private static final String TXHASH_PARAM = "&txhash="; - TransactionAPIProvider(RequestQueueManager queue, - String baseUrl, - EthHttpClient executor, - Converter converter, - int retryCount) { + public TransactionAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { super(queue, "transaction", baseUrl, executor, converter, retryCount); } @@ -38,7 +38,7 @@ public Optional<Status> statusExec(@NotNull String txhash) throws EtherScanExcep BasicUtils.validateTxHash(txhash); final String urlParams = ACT_EXEC_STATUS_PARAM + TXHASH_PARAM + txhash; - final StatusResponseTO response = getRequest(urlParams, StatusResponseTO.class); + final StatusResponseTO response = getResponse(urlParams, StatusResponseTO.class); BasicUtils.validateTxResponse(response); return Optional.ofNullable(response.getResult()); @@ -50,7 +50,7 @@ public Optional<Boolean> statusReceipt(@NotNull String txhash) throws EtherScanE BasicUtils.validateTxHash(txhash); final String urlParams = ACT_RECEIPT_STATUS_PARAM + TXHASH_PARAM + txhash; - final ReceiptStatusResponseTO response = getRequest(urlParams, ReceiptStatusResponseTO.class); + final ReceiptStatusResponseTO response = getResponse(urlParams, ReceiptStatusResponseTO.class); BasicUtils.validateTxResponse(response); return (response.getResult() == null || BasicUtils.isEmpty(response.getResult().getStatus())) diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionTimeoutException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionTimeoutException.java new file mode 100644 index 0000000..1b2ff22 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanConnectionTimeoutException.java @@ -0,0 +1,12 @@ +package io.goodforgod.api.etherscan.error; + +/** + * @author GoodforGod + * @since 12.11.2018 + */ +public class EtherScanConnectionTimeoutException extends EtherScanConnectionException { + + public EtherScanConnectionTimeoutException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java b/src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java deleted file mode 100644 index 7734139..0000000 --- a/src/main/java/io/goodforgod/api/etherscan/error/EtherScanTimeoutException.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.goodforgod.api.etherscan.error; - -/** - * @author GoodforGod - * @since 12.11.2018 - */ -public class EtherScanTimeoutException extends EtherScanConnectionException { - - public EtherScanTimeoutException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java index bd01f83..28142b8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/EthHttpClient.java @@ -17,7 +17,7 @@ public interface EthHttpClient { * @param uri as string * @return result as string */ - byte[] get(@NotNull URI uri); + EthResponse get(@NotNull URI uri); /** * Performs a Http POST request @@ -26,5 +26,5 @@ public interface EthHttpClient { * @param body to post * @return result as string */ - byte[] post(@NotNull URI uri, byte[] body); + EthResponse post(@NotNull URI uri, byte[] body); } diff --git a/src/main/java/io/goodforgod/api/etherscan/http/EthResponse.java b/src/main/java/io/goodforgod/api/etherscan/http/EthResponse.java new file mode 100644 index 0000000..20b7d81 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/http/EthResponse.java @@ -0,0 +1,30 @@ +package io.goodforgod.api.etherscan.http; + +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Anton Kurako (GoodforGod) + * + * @since 07.09.2025 + */ +public interface EthResponse { + + int statusCode(); + + @Nullable + byte[] body(); + + @NotNull + Map<String, List<String>> headers(); + + static EthResponse of(int statusCode, @Nullable byte[] body) { + return new SimpleEthResponse(statusCode, body, null); + } + + static EthResponse of(int statusCode, @Nullable byte[] body, @Nullable Map<String, List<String>> headers) { + return new SimpleEthResponse(statusCode, body, headers); + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/http/SimpleEthResponse.java b/src/main/java/io/goodforgod/api/etherscan/http/SimpleEthResponse.java new file mode 100644 index 0000000..2e9ebf4 --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/http/SimpleEthResponse.java @@ -0,0 +1,64 @@ +package io.goodforgod.api.etherscan.http; + +import java.util.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Anton Kurako (GoodforGod) + * + * @since 07.09.2025 + */ +class SimpleEthResponse implements EthResponse { + + private final int statusCode; + @Nullable + private final byte[] body; + private final Map<String, List<String>> headers; + + SimpleEthResponse(int statusCode, @Nullable byte[] body, @Nullable Map<String, List<String>> headers) { + this.statusCode = statusCode; + this.body = body; + this.headers = (headers == null) + ? Collections.emptyMap() + : headers; + } + + @Override + public int statusCode() { + return statusCode; + } + + @Nullable + @Override + public byte[] body() { + return body; + } + + @Override + public @NotNull Map<String, List<String>> headers() { + return headers; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + SimpleEthResponse that = (SimpleEthResponse) o; + return statusCode == that.statusCode && Arrays.equals(body, that.body) && Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + int result = Objects.hash(statusCode, headers); + result = 31 * result + Arrays.hashCode(body); + return result; + } + + @Override + public String toString() { + return "{\"statusCode\": " + statusCode + ", \"headers\": " + headers + ", \"body\": \"" + Arrays.toString(body) + "\"}"; + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java new file mode 100644 index 0000000..df7a28b --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java @@ -0,0 +1,148 @@ +package io.goodforgod.api.etherscan.http.impl; + +import io.goodforgod.api.etherscan.error.EtherScanConnectionException; +import io.goodforgod.api.etherscan.error.EtherScanConnectionTimeoutException; +import io.goodforgod.api.etherscan.http.EthHttpClient; +import io.goodforgod.api.etherscan.http.EthResponse; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpConnectTimeoutException; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Anton Kurako (GoodforGod) + * + * @since 07.09.2025 + */ +@Internal +public class JdkEthHttpClient implements EthHttpClient { + + private static final Map<String, String> DEFAULT_HEADERS = new HashMap<>(); + + private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(8); + private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(2); + + static { + DEFAULT_HEADERS.put("Accept-Language", "en"); + DEFAULT_HEADERS.put("Accept-Encoding", "deflate, gzip"); + DEFAULT_HEADERS.put("User-Agent", "Chrome/68.0.3440.106"); + DEFAULT_HEADERS.put("Accept-Charset", "UTF-8"); + } + + private final HttpClient httpClient; + private final Duration requestTimeout; + private final Map<String, String> headers; + + public JdkEthHttpClient() { + this(HttpClient.newBuilder() + .connectTimeout(DEFAULT_CONNECT_TIMEOUT) + .followRedirects(HttpClient.Redirect.NORMAL) + .version(HttpClient.Version.HTTP_2) + .build(), + DEFAULT_READ_TIMEOUT, + DEFAULT_HEADERS); + } + + public JdkEthHttpClient(HttpClient httpClient) { + this(httpClient, DEFAULT_READ_TIMEOUT, DEFAULT_HEADERS); + } + + public JdkEthHttpClient(HttpClient httpClient, Duration requestTimeout) { + this(httpClient, requestTimeout, DEFAULT_HEADERS); + } + + public JdkEthHttpClient(HttpClient httpClient, Duration requestTimeout, Map<String, String> headers) { + this.httpClient = httpClient; + this.requestTimeout = requestTimeout; + this.headers = headers; + } + + @Override + public EthResponse get(@NotNull URI uri) { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(uri) + .timeout(requestTimeout); + + headers.forEach(requestBuilder::header); + + try { + HttpResponse<byte[]> response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + return new EthResponse() { + + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public byte[] body() { + return response.body(); + } + + @Override + public @NotNull Map<String, List<String>> headers() { + return response.headers().map(); + } + }; + } catch (HttpConnectTimeoutException e) { + throw new EtherScanConnectionTimeoutException( + "Connection Timeout: Could not establish connection to Etherscan server for " + + httpClient.connectTimeout().orElse(DEFAULT_CONNECT_TIMEOUT) + " millis", + e); + } catch (IOException e) { + throw new EtherScanConnectionException("Etherscan HTTP server network error occurred: " + e.getMessage(), e); + } catch (InterruptedException e) { + throw new EtherScanConnectionException("Etherscan HTTP server interrupt exception occurred: " + e.getMessage(), e); + } + } + + @Override + public EthResponse post(@NotNull URI uri, byte[] body) { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofByteArray(body)) + .uri(uri) + .timeout(requestTimeout); + + headers.forEach(requestBuilder::header); + + try { + HttpResponse<byte[]> response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + return new EthResponse() { + + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public byte[] body() { + return response.body(); + } + + @Override + public @NotNull Map<String, List<String>> headers() { + return response.headers().map(); + } + }; + } catch (HttpConnectTimeoutException e) { + throw new EtherScanConnectionTimeoutException( + "Connection Timeout: Could not establish connection to Etherscan server for " + + httpClient.connectTimeout().orElse(DEFAULT_CONNECT_TIMEOUT) + " millis", + e); + } catch (IOException e) { + throw new EtherScanConnectionException("Etherscan HTTP server network error occurred: " + e.getMessage(), e); + } catch (InterruptedException e) { + throw new EtherScanConnectionException("Etherscan HTTP server interrupt exception occurred: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java index b298743..58f4658 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/UrlEthHttpClient.java @@ -3,20 +3,23 @@ import static java.net.HttpURLConnection.*; import io.goodforgod.api.etherscan.error.EtherScanConnectionException; -import io.goodforgod.api.etherscan.error.EtherScanTimeoutException; +import io.goodforgod.api.etherscan.error.EtherScanConnectionTimeoutException; import io.goodforgod.api.etherscan.http.EthHttpClient; -import java.io.*; -import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; -import java.net.URI; -import java.net.URL; +import io.goodforgod.api.etherscan.http.EthResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.*; import java.time.Duration; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; +import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Http client implementation @@ -25,12 +28,13 @@ * @see EthHttpClient * @since 28.10.2018 */ -public final class UrlEthHttpClient implements EthHttpClient { +@Internal +public class UrlEthHttpClient implements EthHttpClient { private static final Map<String, String> DEFAULT_HEADERS = new HashMap<>(); private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(8); - private static final Duration DEFAULT_READ_TIMEOUT = Duration.ZERO; + private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(2); static { DEFAULT_HEADERS.put("Accept-Language", "en"); @@ -39,6 +43,8 @@ public final class UrlEthHttpClient implements EthHttpClient { DEFAULT_HEADERS.put("Accept-Charset", "UTF-8"); } + @Nullable + private final Proxy proxy; private final Map<String, String> headers; private final int connectTimeout; private final int readTimeout; @@ -48,11 +54,19 @@ public UrlEthHttpClient() { } public UrlEthHttpClient(Duration connectTimeout) { - this(connectTimeout, DEFAULT_READ_TIMEOUT); + this(connectTimeout, DEFAULT_READ_TIMEOUT, DEFAULT_HEADERS, null); } public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout) { - this(connectTimeout, readTimeout, DEFAULT_HEADERS); + this(connectTimeout, readTimeout, DEFAULT_HEADERS, null); + } + + public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout, Proxy proxy) { + this(connectTimeout, readTimeout, DEFAULT_HEADERS, null); + } + + public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout, Map<String, String> headers) { + this(connectTimeout, readTimeout, headers, null); } /** @@ -62,15 +76,19 @@ public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout) { */ public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout, - Map<String, String> headers) { + Map<String, String> headers, + @Nullable Proxy proxy) { this.connectTimeout = Math.toIntExact(connectTimeout.toMillis()); this.readTimeout = Math.toIntExact(readTimeout.toMillis()); this.headers = Collections.unmodifiableMap(headers); + this.proxy = proxy; } private HttpURLConnection buildConnection(URI uri, String method) throws IOException { final URL url = uri.toURL(); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + final HttpURLConnection connection = (proxy == null) + ? (HttpURLConnection) url.openConnection() + : (HttpURLConnection) url.openConnection(proxy); connection.setRequestMethod(method); connection.setConnectTimeout(connectTimeout); connection.setReadTimeout(readTimeout); @@ -79,7 +97,7 @@ private HttpURLConnection buildConnection(URI uri, String method) throws IOExcep } @Override - public byte[] get(@NotNull URI uri) { + public EthResponse get(@NotNull URI uri) { try { final HttpURLConnection connection = buildConnection(uri, "GET"); final int status = connection.getResponseCode(); @@ -92,17 +110,19 @@ public byte[] get(@NotNull URI uri) { } final byte[] data = readData(connection); + EthResponse ethResponse = EthResponse.of(connection.getResponseCode(), data, connection.getHeaderFields()); connection.disconnect(); - return data; + return ethResponse; } catch (SocketTimeoutException e) { - throw new EtherScanTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); + throw new EtherScanConnectionTimeoutException( + "Timeout: Could not establish connection for " + connectTimeout + " millis", e); } catch (Exception e) { throw new EtherScanConnectionException(e.getMessage(), e); } } @Override - public byte[] post(@NotNull URI uri, byte[] body) { + public EthResponse post(@NotNull URI uri, byte[] body) { try { final HttpURLConnection connection = buildConnection(uri, "POST"); final int contentLength = body.length; @@ -126,10 +146,12 @@ public byte[] post(@NotNull URI uri, byte[] body) { } final byte[] data = readData(connection); + EthResponse ethResponse = EthResponse.of(connection.getResponseCode(), data, connection.getHeaderFields()); connection.disconnect(); - return data; + return ethResponse; } catch (SocketTimeoutException e) { - throw new EtherScanTimeoutException("Timeout: Could not establish connection for " + connectTimeout + " millis", e); + throw new EtherScanConnectionTimeoutException( + "Timeout: Could not establish connection for " + connectTimeout + " millis", e); } catch (Exception e) { throw new EtherScanConnectionException(e.getMessage(), e); } diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java index 626b4c1..ed81b94 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/FakeRequestQueueManager.java @@ -8,7 +8,7 @@ * @author GoodforGod * @since 03.11.2018 */ -public final class FakeRequestQueueManager implements RequestQueueManager { +public class FakeRequestQueueManager implements RequestQueueManager { @Override public void takeTurn() { diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java index 2a3483c..44c6bd5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java +++ b/src/main/java/io/goodforgod/api/etherscan/manager/impl/SemaphoreRequestQueueManager.java @@ -14,7 +14,7 @@ * @author GoodforGod * @since 30.10.2018 */ -public final class SemaphoreRequestQueueManager implements RequestQueueManager, AutoCloseable { +public class SemaphoreRequestQueueManager implements RequestQueueManager, AutoCloseable { private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); private final Semaphore semaphore; 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 fbf71be..21b6601 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java @@ -64,7 +64,7 @@ public static AbiBuilder builder() { return new AbiBuilder(); } - public static final class AbiBuilder { + public static class AbiBuilder { private String contractAbi; private boolean isVerified; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java index 8de679a..e0fc376 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BaseTx.java @@ -12,7 +12,7 @@ * @author GoodforGod * @since 28.10.2018 */ -abstract class BaseTx implements Comparable<BaseTx> { +public abstract class BaseTx implements Comparable<BaseTx> { long blockNumber; String timeStamp; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java b/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java index f3c4d67..f77e5d4 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockTx.java @@ -8,7 +8,7 @@ * @author Anton Kurako (GoodforGod) * @since 15.05.2023 */ -abstract class BlockTx extends BaseTx { +public abstract class BlockTx extends BaseTx { long nonce; String blockHash; 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 961db7e..058e13b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java @@ -64,7 +64,7 @@ public static UncleBuilder builder() { return new UncleBuilder(); } - public static final class UncleBuilder { + public static class UncleBuilder { private String miner; private BigInteger blockreward; @@ -138,7 +138,7 @@ public static BlockUncleBuilder builder() { return new BlockUncleBuilder(); } - public static final class BlockUncleBuilder extends Block.BlockBuilder { + public static class BlockUncleBuilder extends Block.BlockBuilder { private long blockNumber; private BigInteger blockReward; 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 2082883..41e62c5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java @@ -52,7 +52,7 @@ public static ContractCreationBuilder builder() { return new ContractCreationBuilder(); } - public static final class ContractCreationBuilder { + public static class ContractCreationBuilder { private String contractAddress; private String contractCreator; 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 c626069..4dbbce7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/EthSupply.java @@ -67,7 +67,7 @@ public static EthSupplyBuilder builder() { return new EthSupplyBuilder(); } - public static final class EthSupplyBuilder { + public static class EthSupplyBuilder { private Wei ethSupply; private Wei eth2Staking; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index 6fe1231..7ee8b50 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -83,7 +83,7 @@ public static GasOracleBuilder builder() { return new GasOracleBuilder(); } - public static final class GasOracleBuilder { + public static class GasOracleBuilder { private Long lastBlock; private Wei safeGasPrice; 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 d54766c..d29db31 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Log.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Log.java @@ -155,7 +155,7 @@ public static LogBuilder builder() { return new LogBuilder(); } - public static final class LogBuilder { + public static class LogBuilder { private Long blockNumber; private String address; 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 403b705..0baa38a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Price.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Price.java @@ -76,7 +76,7 @@ public static PriceBuilder builder() { return new PriceBuilder(); } - public static final class PriceBuilder { + public static class PriceBuilder { private BigDecimal ethusd; private BigDecimal ethbtc; 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 41b598a..f651b1f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Status.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Status.java @@ -53,7 +53,7 @@ public static StatusBuilder builder() { return new StatusBuilder(); } - public static final class StatusBuilder { + public static class StatusBuilder { private int isError; private String errDescription; 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 0a836d1..8843bd6 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/Tx.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/Tx.java @@ -59,7 +59,7 @@ public static TxBuilder builder() { return new TxBuilder(); } - public static final class TxBuilder { + public static class TxBuilder { private long blockNumber; private LocalDateTime timeStamp; 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 f0b1ce4..ed8754d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc1155.java @@ -82,7 +82,7 @@ public static TxErc1155Builder builder() { return new TxErc1155Builder(); } - public static final class TxErc1155Builder { + public static class TxErc1155Builder { private long blockNumber; private LocalDateTime timeStamp; 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 1d6080e..57d70cb 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc20.java @@ -83,7 +83,7 @@ public static TxERC20Builder builder() { return new TxERC20Builder(); } - public static final class TxERC20Builder { + public static class TxERC20Builder { private long blockNumber; private LocalDateTime timeStamp; 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 1ac49a0..64df779 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxErc721.java @@ -82,7 +82,7 @@ public static TxERC721Builder builder() { return new TxERC721Builder(); } - public static final class TxERC721Builder { + public static class TxERC721Builder { private long blockNumber; private LocalDateTime timeStamp; 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 389f456..91c2b27 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/TxInternal.java @@ -89,7 +89,7 @@ public static TxInternalBuilder builder() { return new TxInternalBuilder(); } - public static final class TxInternalBuilder { + public static class TxInternalBuilder { private long blockNumber; private LocalDateTime timeStamp; 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 bee4d64..a16fb21 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 @@ -194,7 +194,7 @@ public static BlockProxyBuilder builder() { return new BlockProxyBuilder(); } - public static final class BlockProxyBuilder { + public static class BlockProxyBuilder { private Long number; private String hash; 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 d88fd6d..717b2e1 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 @@ -134,7 +134,7 @@ public static ReceiptProxyBuilder builder() { return new ReceiptProxyBuilder(); } - public static final class ReceiptProxyBuilder { + public static class ReceiptProxyBuilder { private String root; private String from; 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 0a89921..1325cc4 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 @@ -156,7 +156,7 @@ public static TxProxyBuilder builder() { return new TxProxyBuilder(); } - public static final class TxProxyBuilder { + public static class TxProxyBuilder { private String to; private String hash; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java index ef57193..2960bfc 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java @@ -4,7 +4,7 @@ * @author GoodforGod * @since 31.10.2018 */ -abstract class BaseProxyTO { +public abstract class BaseProxyTO { private String id; private String jsonrpc; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java index 549bd47..f46a4e0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryBuilderImpl.java @@ -12,7 +12,7 @@ * @author GoodforGod * @since 31.10.2018 */ -final class LogQueryBuilderImpl implements LogQuery.Builder { +public class LogQueryBuilderImpl implements LogQuery.Builder { static final long MIN_BLOCK = 0; static final long MAX_BLOCK = 99999999999999999L; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java index ef66e72..6de9631 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java @@ -12,7 +12,7 @@ * @author GoodforGod * @since 31.10.2018 */ -final class LogQueryImpl implements LogQuery { +public class LogQueryImpl implements LogQuery { /** * Final request parameter for api call diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java index ac77ae8..12fb6d0 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryParams.java @@ -10,7 +10,7 @@ * @author GoodforGod * @since 31.10.2018 */ -final class LogQueryParams { +public class LogQueryParams { private LogQueryParams() {} diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java index 7fdd9db..f113a4f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java @@ -14,7 +14,7 @@ * @author GoodforGod * @since 31.10.2018 */ -public final class LogTopicQuadro implements LogTopicBuilder { +public class LogTopicQuadro implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java index a736ffa..b35c836 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java @@ -14,7 +14,7 @@ * @author GoodforGod * @since 31.10.2018 */ -public final class LogTopicSingle implements LogTopicBuilder { +public class LogTopicSingle implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java index ac9efb8..d888a49 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java @@ -14,7 +14,7 @@ * @author GoodforGod * @since 31.10.2018 */ -public final class LogTopicTriple implements LogTopicBuilder { +public class LogTopicTriple implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java index 2ef2bba..4a0218e 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java @@ -14,7 +14,7 @@ * @author GoodforGod * @since 31.10.2018 */ -public final class LogTopicTuple implements LogTopicBuilder { +public class LogTopicTuple implements LogTopicBuilder { private final String address; private final long startBlock, endBlock; diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java index 19fa0a1..8b01b3d 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/response/StringResponseTO.java @@ -16,7 +16,7 @@ public static StringResponseBuilder builder() { return new StringResponseBuilder(); } - public static final class StringResponseBuilder { + public static class StringResponseBuilder { private String status; private String message; 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 916d4ab..db98bd4 100644 --- a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java +++ b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java @@ -17,7 +17,7 @@ * @author GoodforGod * @since 28.10.2018 */ -public final class BasicUtils { +public class BasicUtils { private BasicUtils() {} From 8dbcc82ccba7d82b711275e8df892916796f98ad Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Sun, 7 Sep 2025 15:11:35 +0300 Subject: [PATCH 63/67] [3.0.0-SNAPSHOT] Cleanup --- .../io/goodforgod/api/etherscan/BasicProvider.java | 3 +-- .../api/etherscan/http/impl/JdkEthHttpClient.java | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 2abb2e0..0ab4f20 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -9,12 +9,11 @@ import io.goodforgod.api.etherscan.manager.RequestQueueManager; import io.goodforgod.api.etherscan.model.response.BaseListResponseTO; import io.goodforgod.api.etherscan.model.response.StringResponseTO; +import io.goodforgod.api.etherscan.util.BasicUtils; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; - -import io.goodforgod.api.etherscan.util.BasicUtils; import org.jetbrains.annotations.ApiStatus.Internal; /** diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java index df7a28b..eca88d8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java @@ -4,9 +4,6 @@ import io.goodforgod.api.etherscan.error.EtherScanConnectionTimeoutException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.EthResponse; -import org.jetbrains.annotations.ApiStatus.Internal; -import org.jetbrains.annotations.NotNull; - import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; @@ -17,6 +14,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; /** * Anton Kurako (GoodforGod) @@ -44,10 +43,10 @@ public class JdkEthHttpClient implements EthHttpClient { public JdkEthHttpClient() { this(HttpClient.newBuilder() - .connectTimeout(DEFAULT_CONNECT_TIMEOUT) - .followRedirects(HttpClient.Redirect.NORMAL) - .version(HttpClient.Version.HTTP_2) - .build(), + .connectTimeout(DEFAULT_CONNECT_TIMEOUT) + .followRedirects(HttpClient.Redirect.NORMAL) + .version(HttpClient.Version.HTTP_2) + .build(), DEFAULT_READ_TIMEOUT, DEFAULT_HEADERS); } From 260899ba17f32b8fab05d5a477d518e450ee3422 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 8 Sep 2025 00:23:30 +0300 Subject: [PATCH 64/67] [3.0.0-SNAPSHOT] Added JdkEthHttpClient gzip/deflate protocol support Added EthScanAPIBuilder key required --- .../api/etherscan/BasicProvider.java | 2 + .../api/etherscan/EthScanAPIBuilder.java | 16 +++---- .../api/etherscan/EtherScanAPI.java | 4 +- .../etherscan/http/impl/JdkEthHttpClient.java | 44 ++++++++++++++++--- .../goodforgod/api/etherscan/ApiRunner.java | 7 ++- .../api/etherscan/EtherScanAPITests.java | 11 ++--- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 0ab4f20..4f809b5 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -129,7 +129,9 @@ protected <T> T getResponse(String urlParameters, Class<T> tClass) { protected <T> T getResponse(String urlParameters, Class<T> tClass, int retryCount) { try { + System.out.println("URL - " + URI.create(baseUrl + module + urlParameters)); EthResponse response = getResponse(urlParameters); + System.out.println("Response - " + new String(response.body(), StandardCharsets.UTF_8)); return convert(response.body(), tClass); } catch (Exception e) { if (retryCount < retryCountLimit) { diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java index 12bb9d3..fa8af66 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java +++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.Objects; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; @@ -22,12 +23,11 @@ public class EthScanAPIBuilder implements EtherScanAPI.Builder { private static final Supplier<EthHttpClient> DEFAULT_SUPPLIER = JdkEthHttpClient::new; - private static final String DEFAULT_KEY = "YourApiKeyToken"; private final Gson gson = new GsonConfiguration().builder().create(); private int retryCountOnLimitReach = 0; - private String apiKey = DEFAULT_KEY; + private String apiKey; private RequestQueueManager queueManager; private EthNetwork ethNetwork = EthNetworks.MAINNET; private Supplier<EthHttpClient> ethHttpClientSupplier = DEFAULT_SUPPLIER; @@ -43,6 +43,10 @@ public class EthScanAPIBuilder implements EtherScanAPI.Builder { } }; + public EthScanAPIBuilder(String apiKey) { + this.apiKey = apiKey; + } + @NotNull @Override public EtherScanAPI.Builder withApiKey(@NotNull String apiKey) { @@ -101,13 +105,7 @@ public EtherScanAPI.Builder withRetryOnRateLimit(int maxRetryCount) { @Override public @NotNull EtherScanAPI build() { RequestQueueManager requestQueueManager; - if (queueManager != null) { - requestQueueManager = queueManager; - } else if (DEFAULT_KEY.equals(apiKey)) { - requestQueueManager = RequestQueueManager.anonymous(); - } else { - requestQueueManager = RequestQueueManager.planFree(); - } + requestQueueManager = Objects.requireNonNullElseGet(queueManager, RequestQueueManager::planFree); return new EtherScanAPIProvider(apiKey, ethNetwork, requestQueueManager, ethHttpClientSupplier.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 bae1902..4c703bb 100644 --- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java +++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java @@ -40,8 +40,8 @@ public interface EtherScanAPI extends AutoCloseable { GasTrackerAPI gasTracker(); @NotNull - static Builder builder() { - return new EthScanAPIBuilder(); + static Builder builder(@NotNull String key) { + return new EthScanAPIBuilder(key); } interface Builder { diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java index eca88d8..fea2680 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java @@ -5,15 +5,17 @@ import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.EthResponse; import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpConnectTimeoutException; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -75,7 +77,9 @@ public EthResponse get(@NotNull URI uri) { headers.forEach(requestBuilder::header); try { - HttpResponse<byte[]> response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + HttpResponse<InputStream> response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + byte[] bodyAsBytes = getDeflatedBytes(response); return new EthResponse() { @Override @@ -85,7 +89,7 @@ public int statusCode() { @Override public byte[] body() { - return response.body(); + return bodyAsBytes; } @Override @@ -115,7 +119,9 @@ public EthResponse post(@NotNull URI uri, byte[] body) { headers.forEach(requestBuilder::header); try { - HttpResponse<byte[]> response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + HttpResponse<InputStream> response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + byte[] bodyAsBytes = getDeflatedBytes(response); return new EthResponse() { @Override @@ -125,7 +131,7 @@ public int statusCode() { @Override public byte[] body() { - return response.body(); + return bodyAsBytes; } @Override @@ -144,4 +150,28 @@ public byte[] body() { throw new EtherScanConnectionException("Etherscan HTTP server interrupt exception occurred: " + e.getMessage(), e); } } + + private static byte[] getDeflatedBytes(HttpResponse<InputStream> response) { + try { + Optional<String> encoding = response.headers().firstValue("content-encoding"); + if (encoding.isEmpty()) { + try (var is = response.body()) { + return is.readAllBytes(); + } + } + + switch (encoding.get().strip().toLowerCase(Locale.ROOT)) { + case "gzip": + return new GZIPInputStream(response.body()).readAllBytes(); + case "deflate": + return new InflaterInputStream(response.body()).readAllBytes(); + default: + try (var is = response.body()) { + return is.readAllBytes(); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java index bc4f334..23df478 100644 --- a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java +++ b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java @@ -25,8 +25,7 @@ public class ApiRunner extends Assertions { ? RequestQueueManager.anonymous() : RequestQueueManager.planFree(); - API = EtherScanAPI.builder() - .withApiKey(ApiRunner.API_KEY) + API = EtherScanAPI.builder(ApiRunner.API_KEY) .withNetwork(EthNetworks.MAINNET) .withQueue(queueManager) .withRetryOnRateLimit(5) @@ -37,6 +36,10 @@ public static EtherScanAPI getApi() { return API; } + public static String getKey() { + return API_KEY; + } + @AfterAll public static void cleanup() throws Exception { API.close(); diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java index 36e23ec..d9e84f0 100644 --- a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -22,25 +22,26 @@ class EtherScanAPITests extends ApiRunner { @Test void validKey() { String validKey = "YourKey"; - EtherScanAPI api = EtherScanAPI.builder().withApiKey(validKey).withNetwork(network).build(); + EtherScanAPI api = EtherScanAPI.builder(validKey).withApiKey(validKey).withNetwork(network).build(); assertNotNull(api); } @Test void emptyKey() { - assertThrows(EtherScanKeyException.class, () -> EtherScanAPI.builder().withApiKey("").build()); + assertThrows(EtherScanKeyException.class, () -> EtherScanAPI.builder("someKey").withApiKey("").build()); } @Test void blankKey() { assertThrows(EtherScanKeyException.class, - () -> EtherScanAPI.builder().withApiKey(" ").withNetwork(network).build()); + () -> EtherScanAPI.builder("someKey").withApiKey(" ").withNetwork(network).build()); } @Test void noTimeoutOnRead() { Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300)); - EtherScanAPI api = EtherScanAPI.builder().withNetwork(EthNetworks.MAINNET).withHttpClient(supplier).build(); + EtherScanAPI api = EtherScanAPI.builder(ApiRunner.getKey()).withNetwork(EthNetworks.MAINNET).withHttpClient(supplier) + .build(); Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); assertNotNull(balance); } @@ -67,7 +68,7 @@ void noTimeoutUnlimitedAwait() { void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300), Duration.ofMillis(300)); - EtherScanAPI api = EtherScanAPI.builder() + EtherScanAPI api = EtherScanAPI.builder(ApiRunner.getKey()) .withNetwork(() -> URI.create("https://api-unknown.etherscan.io/api")) .withHttpClient(supplier) .build(); From 7ea05262d05a803099e79e4f783b6bd926d9c9c6 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 8 Sep 2025 01:08:49 +0300 Subject: [PATCH 65/67] [3.0.0-SNAPSHOT] Added JdkEthHttpClient gzip/deflate protocol support Added EthScanAPIBuilder key required Fixed JdkEthHttpClient fallback to UrlEthHttpClient cause content-length works incorrectly Updated test and fixed --- .github/workflows/master.yml | 37 +++++++++++++++++++ .github/workflows/publish-release.yml | 4 +- .github/workflows/publish-snapshot.yml | 4 +- .github/workflows/pull-request.yml | 18 +-------- build.gradle | 10 ++--- .../etherscan/http/impl/JdkEthHttpClient.java | 9 +++++ .../api/etherscan/model/GasOracle.java | 13 +++---- .../api/etherscan/EtherScanAPITests.java | 28 -------------- .../etherscan/account/AccountTxsTests.java | 4 +- .../etherscan/proxy/ProxyBlockApiTests.java | 1 - 10 files changed, 62 insertions(+), 66 deletions(-) create mode 100644 .github/workflows/master.yml diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 0000000..7e82142 --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,37 @@ +name: CI Master + +on: + push: + branches: + - master + schedule: + - cron: 0 0 * * 0 + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '11', '17' ] + name: Master Java ${{ matrix.java }} action + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: 'adopt' + + - name: Build + run: './gradlew classes' + + - name: Test + run: './gradlew test jacocoTestReport' + + - name: SonarQube + if: matrix.java == '17' + run: './gradlew sonar --info' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 3aa7884..6555593 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,4 +1,4 @@ -name: CI Master +name: Release on: release: @@ -22,8 +22,6 @@ jobs: - name: Test run: './gradlew test jacocoTestReport' - env: - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - name: SonarQube run: './gradlew sonar --info' diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index d181a6c..cc95412 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -1,4 +1,4 @@ -name: CI Dev +name: Snapshot on: push: @@ -32,8 +32,6 @@ jobs: - name: Test run: './gradlew test jacocoTestReport' - env: - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - name: Publish Snapshot run: './gradlew publish' diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 0b49f50..9da9b35 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: java: [ '11', '17' ] - name: Java ${{ matrix.java }} Pull Request setup + name: Pull Request Java ${{ matrix.java }} action steps: - uses: actions/checkout@v3 @@ -29,23 +29,7 @@ jobs: run: './gradlew classes' - name: Test - if: matrix.java == '11' run: './gradlew test jacocoTestReport' - env: - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_1 }} - - - name: Test - if: matrix.java == '17' - run: './gradlew test jacocoTestReport' - env: - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_2 }} - - - 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' diff --git a/build.gradle b/build.gradle index f946f1d..6fcbb8c 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ test { testLogging { events("passed", "skipped", "failed") exceptionFormat("full") - showStandardStreams(false) + showStandardStreams(true) } reports { @@ -71,8 +71,8 @@ nexusPublishing { 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/")) + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) } } } @@ -110,8 +110,8 @@ publishing { } repositories { maven { - def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2" - def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" + def releasesRepoUrl = "https://ossrh-staging-api.central.sonatype.com/service/local/" + def snapshotsRepoUrl = "https://central.sonatype.com/repository/maven-snapshots/" url = version.endsWith("SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl credentials { username System.getenv("OSS_USERNAME") diff --git a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java index fea2680..01580ce 100644 --- a/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java @@ -42,6 +42,7 @@ public class JdkEthHttpClient implements EthHttpClient { private final HttpClient httpClient; private final Duration requestTimeout; private final Map<String, String> headers; + private final UrlEthHttpClient urlEthHttpClient; public JdkEthHttpClient() { this(HttpClient.newBuilder() @@ -65,6 +66,7 @@ public JdkEthHttpClient(HttpClient httpClient, Duration requestTimeout, Map<Stri this.httpClient = httpClient; this.requestTimeout = requestTimeout; this.headers = headers; + this.urlEthHttpClient = new UrlEthHttpClient(DEFAULT_CONNECT_TIMEOUT, requestTimeout, headers); } @Override @@ -111,12 +113,19 @@ public byte[] body() { @Override public EthResponse post(@NotNull URI uri, byte[] body) { + return urlEthHttpClient.post(uri, body); + } + + // content-length somehow is not working properly and can't force user to override + // "jdk.httpclient.allowRestrictedHeaders" prop, so will force use UrlEthHttpClient + private EthResponse postJdk(@NotNull URI uri, byte[] body) { HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .POST(HttpRequest.BodyPublishers.ofByteArray(body)) .uri(uri) .timeout(requestTimeout); headers.forEach(requestBuilder::header); + requestBuilder.header("Content-Type", "application/json; charset=UTF-8"); try { HttpResponse<InputStream> response = httpClient.send(requestBuilder.build(), diff --git a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java index 7ee8b50..e7726f9 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/GasOracle.java @@ -1,7 +1,6 @@ package io.goodforgod.api.etherscan.model; import java.math.BigDecimal; -import java.math.BigInteger; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -14,9 +13,9 @@ public class GasOracle { private Long LastBlock; - private BigInteger SafeGasPrice; - private BigInteger ProposeGasPrice; - private BigInteger FastGasPrice; + private BigDecimal SafeGasPrice; + private BigDecimal ProposeGasPrice; + private BigDecimal FastGasPrice; private BigDecimal suggestBaseFee; private String gasUsedRatio; @@ -129,13 +128,13 @@ public GasOracle build() { gasOracle.LastBlock = this.lastBlock; gasOracle.suggestBaseFee = this.suggestBaseFee; if (this.proposeGasPrice != null) { - gasOracle.ProposeGasPrice = this.proposeGasPrice.asGwei().toBigInteger(); + gasOracle.ProposeGasPrice = this.proposeGasPrice.asGwei(); } if (this.safeGasPrice != null) { - gasOracle.SafeGasPrice = this.safeGasPrice.asGwei().toBigInteger(); + gasOracle.SafeGasPrice = this.safeGasPrice.asGwei(); } if (this.fastGasPrice != null) { - gasOracle.FastGasPrice = this.fastGasPrice.asGwei().toBigInteger(); + gasOracle.FastGasPrice = this.fastGasPrice.asGwei(); } if (this.gasUsedRatio != null) { gasOracle.gasUsedRatio = this.gasUsedRatio.stream() diff --git a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java index d9e84f0..636e752 100644 --- a/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java +++ b/src/test/java/io/goodforgod/api/etherscan/EtherScanAPITests.java @@ -4,7 +4,6 @@ import io.goodforgod.api.etherscan.error.EtherScanKeyException; import io.goodforgod.api.etherscan.http.EthHttpClient; import io.goodforgod.api.etherscan.http.impl.UrlEthHttpClient; -import io.goodforgod.api.etherscan.model.Balance; import java.net.URI; import java.time.Duration; import java.util.concurrent.TimeUnit; @@ -37,33 +36,6 @@ void blankKey() { () -> EtherScanAPI.builder("someKey").withApiKey(" ").withNetwork(network).build()); } - @Test - void noTimeoutOnRead() { - Supplier<EthHttpClient> supplier = () -> new UrlEthHttpClient(Duration.ofMillis(300)); - EtherScanAPI api = EtherScanAPI.builder(ApiRunner.getKey()).withNetwork(EthNetworks.MAINNET).withHttpClient(supplier) - .build(); - Balance balance = api.account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void noTimeoutOnReadGroli() { - Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void noTimeoutOnReadTobalala() { - Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - - @Test - void noTimeoutUnlimitedAwait() { - Balance balance = getApi().account().balance("0xF318ABc9A5a92357c4Fea8d082dade4D43e780B7"); - assertNotNull(balance); - } - @Test void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); diff --git a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java index 653f62a..42e89c4 100644 --- a/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/account/AccountTxsTests.java @@ -16,7 +16,7 @@ class AccountTxsTests extends ApiRunner { void correct() { List<Tx> txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9"); assertNotNull(txs); - assertEquals(5, txs.size()); + assertEquals(6, txs.size()); assertTxs(txs); assertNotNull(txs.get(0).getTimeStamp()); assertNotNull(txs.get(0).getHash()); @@ -39,7 +39,7 @@ void correct() { void correctStartBlock() { List<Tx> txs = getApi().account().txs("0x9327cb34984c3992ec1EA0eAE98Ccf80A74f95B9", 3892842); assertNotNull(txs); - assertEquals(4, txs.size()); + assertEquals(5, txs.size()); assertTxs(txs); } diff --git a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java index 363d5a2..53ed4cd 100644 --- a/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java +++ b/src/test/java/io/goodforgod/api/etherscan/proxy/ProxyBlockApiTests.java @@ -22,7 +22,6 @@ void correct() { assertNotNull(proxy.getStateRoot()); assertNotNull(proxy.getSize()); assertNotNull(proxy.getDifficulty()); - assertNotNull(proxy.getTotalDifficulty()); assertNotNull(proxy.getTimeStamp()); assertNotNull(proxy.getMiner()); assertNotNull(proxy.getNonce()); From 7790bb7484bd36657c8e6a67de87bff941120599 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 8 Sep 2025 01:26:05 +0300 Subject: [PATCH 66/67] [3.0.0-SNAPSHOT] Cleanup --- .github/workflows/master.yml | 2 +- .github/workflows/pull-request.yml | 2 +- src/main/java/io/goodforgod/api/etherscan/BasicProvider.java | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 7e82142..9892f9c 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '11', '17' ] + java: [ '11' ] name: Master Java ${{ matrix.java }} action steps: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 9da9b35..99940ee 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '11', '17' ] + java: [ '11' ] name: Pull Request Java ${{ matrix.java }} action steps: diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java index 4f809b5..0ab4f20 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -129,9 +129,7 @@ protected <T> T getResponse(String urlParameters, Class<T> tClass) { protected <T> T getResponse(String urlParameters, Class<T> tClass, int retryCount) { try { - System.out.println("URL - " + URI.create(baseUrl + module + urlParameters)); EthResponse response = getResponse(urlParameters); - System.out.println("Response - " + new String(response.body(), StandardCharsets.UTF_8)); return convert(response.body(), tClass); } catch (Exception e) { if (retryCount < retryCountLimit) { From 5c69eb92e32d337c2cdeb0d61598b185ee8eefd7 Mon Sep 17 00:00:00 2001 From: Anton Kurako <goodforgod.dev@gmail.com> Date: Mon, 8 Sep 2025 13:56:50 +0300 Subject: [PATCH 67/67] [3.0.0-SNAPSHOT] Updated CI with key Added more toString/equalsHashcode contracts --- .github/workflows/master.yml | 4 +- .github/workflows/publish-release.yml | 2 + .github/workflows/publish-snapshot.yml | 2 + .github/workflows/pull-request.yml | 4 +- README.md | 2 +- build.gradle | 2 +- .../api/etherscan/model/proxy/BlockProxy.java | 101 ++++++++++-------- .../etherscan/model/proxy/ReceiptProxy.java | 81 ++++++++------ .../api/etherscan/model/proxy/TxProxy.java | 86 ++++++++------- .../model/proxy/utility/BaseProxyTO.java | 26 +++++ .../model/proxy/utility/BlockProxyTO.java | 25 +++++ .../model/proxy/utility/ErrorProxyTO.java | 25 +++++ .../model/proxy/utility/StringProxyTO.java | 26 +++++ .../model/proxy/utility/TxInfoProxyTO.java | 25 +++++ .../model/proxy/utility/TxProxyTO.java | 25 +++++ .../etherscan/model/query/LogQueryImpl.java | 23 ++++ .../etherscan/model/query/LogTopicQuadro.java | 40 +++++++ .../etherscan/model/query/LogTopicSingle.java | 27 +++++ .../etherscan/model/query/LogTopicTriple.java | 34 ++++++ .../etherscan/model/query/LogTopicTuple.java | 30 ++++++ 20 files changed, 472 insertions(+), 118 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 9892f9c..32d0d9f 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -28,9 +28,11 @@ jobs: - name: Test run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} - name: SonarQube - if: matrix.java == '17' + if: matrix.java == '11' run: './gradlew sonar --info' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 6555593..ed10cc4 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -22,6 +22,8 @@ jobs: - name: Test run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_PUBLISH }} - name: SonarQube run: './gradlew sonar --info' diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index cc95412..4a4d958 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -32,6 +32,8 @@ jobs: - name: Test run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY_PUBLISH }} - name: Publish Snapshot run: './gradlew publish' diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 99940ee..c9c6a99 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -30,9 +30,11 @@ jobs: - name: Test run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} - name: SonarQube - if: matrix.java == '17' + if: matrix.java == '11' run: './gradlew sonar --info' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 160f8e4..c09c885 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Java EtherScan API -[](https://openjdk.org/projects/jdk8/) +[](https://openjdk.org/projects/jdk/11/) [](https://maven-badges.herokuapp.com/maven-central/com.github.goodforgod/java-etherscan-api) [](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3ACI+Master) [](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api) diff --git a/build.gradle b/build.gradle index 6fcbb8c..b9be8cd 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ test { testLogging { events("passed", "skipped", "failed") exceptionFormat("full") - showStandardStreams(true) + showStandardStreams(false) } reports { 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 a16fb21..b138c14 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 @@ -143,48 +143,6 @@ public List<TxProxy> getTransactions() { } // </editor-fold> - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof BlockProxy)) - return false; - BlockProxy that = (BlockProxy) o; - return Objects.equals(number, that.number) && Objects.equals(hash, that.hash) - && Objects.equals(parentHash, that.parentHash) && Objects.equals(nonce, that.nonce); - } - - @Override - public int hashCode() { - return Objects.hash(number, hash, parentHash, nonce); - } - - @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 + - ", uncles=" + uncles + - ", receiptsRoot=" + receiptsRoot + - ", transactionsRoot=" + transactionsRoot + - ", transactions=" + transactions + - '}'; - } - @Override public int compareTo(@NotNull BlockProxy o) { return Long.compare(getNumber(), o.getNumber()); @@ -353,4 +311,63 @@ public BlockProxy build() { return blockProxy; } } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BlockProxy that = (BlockProxy) o; + return Objects.equals(number, that.number) && Objects.equals(_number, that._number) && Objects.equals(hash, that.hash) + && Objects.equals(parentHash, that.parentHash) && Objects.equals(stateRoot, that.stateRoot) + && Objects.equals(size, that.size) && Objects.equals(_size, that._size) + && Objects.equals(difficulty, that.difficulty) && Objects.equals(totalDifficulty, that.totalDifficulty) + && Objects.equals(timestamp, that.timestamp) && Objects.equals(_timestamp, that._timestamp) + && Objects.equals(miner, that.miner) && Objects.equals(nonce, that.nonce) + && Objects.equals(extraData, that.extraData) && Objects.equals(logsBloom, that.logsBloom) + && Objects.equals(mixHash, that.mixHash) && Objects.equals(gasUsed, that.gasUsed) + && Objects.equals(_gasUsed, that._gasUsed) && Objects.equals(gasLimit, that.gasLimit) + && Objects.equals(_gasLimit, that._gasLimit) && Objects.equals(sha3Uncles, that.sha3Uncles) + && Objects.equals(uncles, that.uncles) && Objects.equals(receiptsRoot, that.receiptsRoot) + && Objects.equals(transactionsRoot, that.transactionsRoot) && Objects.equals(transactions, that.transactions); + } + + @Override + public int hashCode() { + return Objects.hash(number, number, hash, parentHash, stateRoot, size, size, difficulty, totalDifficulty, timestamp, + timestamp, miner, nonce, extraData, logsBloom, mixHash, gasUsed, gasUsed, gasLimit, gasLimit, sha3Uncles, uncles, + receiptsRoot, transactionsRoot, transactions); + } + + @Override + public String toString() { + return "BlockProxy{" + + "number=" + number + + ", number=" + _number + + ", hash=" + hash + + ", parentHash=" + parentHash + + ", stateRoot=" + stateRoot + + ", size=" + size + + ", size=" + _size + + ", difficulty=" + difficulty + + ", totalDifficulty=" + totalDifficulty + + ", timestamp=" + timestamp + + ", timestamp=" + _timestamp + + ", miner=" + miner + + ", nonce=" + nonce + + ", extraData=" + extraData + + ", logsBloom=" + logsBloom + + ", mixHash=" + mixHash + + ", gasUsed=" + gasUsed + + ", gasUsed=" + _gasUsed + + ", gasLimit=" + gasLimit + + ", gasLimit=" + _gasLimit + + ", sha3Uncles=" + sha3Uncles + + ", uncles=" + uncles + + ", 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 717b2e1..6c933c5 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 @@ -95,41 +95,6 @@ public String getLogsBloom() { } // </editor-fold> - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ReceiptProxy)) - return false; - ReceiptProxy that = (ReceiptProxy) o; - return Objects.equals(blockNumber, that.blockNumber) && Objects.equals(blockHash, that.blockHash) - && Objects.equals(transactionHash, that.transactionHash) - && Objects.equals(transactionIndex, that.transactionIndex); - } - - @Override - public int hashCode() { - return Objects.hash(blockNumber, blockHash, transactionHash, transactionIndex); - } - - @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 + - ", logs=" + logs + - ", logsBloom=" + logsBloom + - '}'; - } - public static ReceiptProxyBuilder builder() { return new ReceiptProxyBuilder(); } @@ -234,4 +199,50 @@ public ReceiptProxy build() { return receiptProxy; } } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ReceiptProxy that = (ReceiptProxy) o; + return Objects.equals(root, that.root) && Objects.equals(from, that.from) && Objects.equals(to, that.to) + && Objects.equals(blockNumber, that.blockNumber) && Objects.equals(_blockNumber, that._blockNumber) + && Objects.equals(blockHash, that.blockHash) && Objects.equals(transactionHash, that.transactionHash) + && Objects.equals(transactionIndex, that.transactionIndex) + && Objects.equals(_transactionIndex, that._transactionIndex) && Objects.equals(gasUsed, that.gasUsed) + && Objects.equals(_gasUsed, that._gasUsed) && Objects.equals(cumulativeGasUsed, that.cumulativeGasUsed) + && Objects.equals(_cumulativeGasUsed, that._cumulativeGasUsed) + && Objects.equals(contractAddress, that.contractAddress) && Objects.equals(logs, that.logs) + && Objects.equals(logsBloom, that.logsBloom); + } + + @Override + public int hashCode() { + return Objects.hash(root, from, to, blockNumber, blockNumber, blockHash, transactionHash, transactionIndex, + transactionIndex, gasUsed, gasUsed, cumulativeGasUsed, cumulativeGasUsed, contractAddress, logs, logsBloom); + } + + @Override + public String toString() { + return "ReceiptProxy{" + + "root=" + root + '\'' + + ", from=" + from + '\'' + + ", to=" + to + '\'' + + ", blockNumber=" + blockNumber + '\'' + + ", blockNumber=" + _blockNumber + + ", blockHash=" + blockHash + '\'' + + ", transactionHash=" + transactionHash + '\'' + + ", transactionIndex=" + transactionIndex + '\'' + + ", transactionIndex=" + _transactionIndex + + ", gasUsed=" + gasUsed + '\'' + + ", gasUsed=" + _gasUsed + + ", cumulativeGasUsed=" + cumulativeGasUsed + '\'' + + ", cumulativeGasUsed=" + _cumulativeGasUsed + + ", contractAddress=" + contractAddress + '\'' + + ", logs=" + logs + + ", 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 1325cc4..4372dde 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 @@ -107,43 +107,6 @@ public Long getBlockNumber() { } // </editor-fold> - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof TxProxy)) - return false; - TxProxy txProxy = (TxProxy) o; - return Objects.equals(hash, txProxy.hash) && Objects.equals(transactionIndex, txProxy.transactionIndex) - && Objects.equals(nonce, txProxy.nonce) && Objects.equals(blockHash, txProxy.blockHash) - && Objects.equals(blockNumber, txProxy.blockNumber); - } - - @Override - public int hashCode() { - return Objects.hash(hash, transactionIndex, nonce, blockHash, blockNumber); - } - - @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 + - '}'; - } - @Override public int compareTo(@NotNull TxProxy o) { final int firstCompare = Long.compare(getBlockNumber(), o.getBlockNumber()); @@ -271,4 +234,53 @@ public TxProxy build() { return txProxy; } } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + TxProxy txProxy = (TxProxy) o; + return Objects.equals(to, txProxy.to) && Objects.equals(hash, txProxy.hash) + && Objects.equals(transactionIndex, txProxy.transactionIndex) + && Objects.equals(_transactionIndex, txProxy._transactionIndex) && Objects.equals(from, txProxy.from) + && Objects.equals(v, txProxy.v) && Objects.equals(input, txProxy.input) && Objects.equals(s, txProxy.s) + && Objects.equals(r, txProxy.r) && Objects.equals(nonce, txProxy.nonce) && Objects.equals(_nonce, txProxy._nonce) + && Objects.equals(value, txProxy.value) && Objects.equals(gas, txProxy.gas) && Objects.equals(_gas, txProxy._gas) + && Objects.equals(gasPrice, txProxy.gasPrice) && Objects.equals(_gasPrice, txProxy._gasPrice) + && Objects.equals(blockHash, txProxy.blockHash) && Objects.equals(blockNumber, txProxy.blockNumber) + && Objects.equals(_blockNumber, txProxy._blockNumber); + } + + @Override + public int hashCode() { + return Objects.hash(to, hash, transactionIndex, transactionIndex, from, v, input, s, r, nonce, nonce, value, gas, gas, + gasPrice, gasPrice, blockHash, blockNumber, blockNumber); + } + + @Override + public String toString() { + return "TxProxy{" + + "to=" + to + '\'' + + ", hash=" + hash + '\'' + + ", transactionIndex=" + transactionIndex + '\'' + + ", transactionIndex=" + _transactionIndex + + ", from=" + from + '\'' + + ", v=" + v + '\'' + + ", input=" + input + '\'' + + ", s=" + s + '\'' + + ", r=" + r + '\'' + + ", nonce=" + nonce + '\'' + + ", nonce=" + _nonce + + ", value=" + value + '\'' + + ", gas=" + gas + '\'' + + ", gas=" + _gas + + ", gasPrice=" + gasPrice + '\'' + + ", gasPrice=" + _gasPrice + + ", blockHash=" + blockHash + '\'' + + ", blockNumber=" + blockNumber + '\'' + + ", blockNumber=" + _blockNumber + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java index 2960bfc..24588c7 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BaseProxyTO.java @@ -1,5 +1,7 @@ package io.goodforgod.api.etherscan.model.proxy.utility; +import java.util.Objects; + /** * @author GoodforGod * @since 31.10.2018 @@ -21,4 +23,28 @@ public String getJsonrpc() { public ErrorProxyTO getError() { return error; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BaseProxyTO that = (BaseProxyTO) o; + return Objects.equals(id, that.id) && Objects.equals(jsonrpc, that.jsonrpc) && Objects.equals(error, that.error); + } + + @Override + public int hashCode() { + return Objects.hash(id, jsonrpc, error); + } + + @Override + public String toString() { + return "BaseProxyTO{" + + "id=" + id + + ", jsonrpc=" + jsonrpc + + ", error=" + error + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java index cf6c16b..b6bf1c1 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/BlockProxyTO.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan.model.proxy.utility; import io.goodforgod.api.etherscan.model.proxy.BlockProxy; +import java.util.Objects; /** * @author GoodforGod @@ -13,4 +14,28 @@ public class BlockProxyTO extends BaseProxyTO { public BlockProxy getResult() { return result; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + BlockProxyTO that = (BlockProxyTO) o; + return Objects.equals(result, that.result); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), result); + } + + @Override + public String toString() { + return "BlockProxyTO{" + + "result=" + result + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java index 9b14cec..5aa1f0a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/ErrorProxyTO.java @@ -1,5 +1,7 @@ package io.goodforgod.api.etherscan.model.proxy.utility; +import java.util.Objects; + /** * @author GoodforGod * @since 03.11.2018 @@ -16,4 +18,27 @@ public String getMessage() { public String getCode() { return code; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ErrorProxyTO that = (ErrorProxyTO) o; + return Objects.equals(message, that.message) && Objects.equals(code, that.code); + } + + @Override + public int hashCode() { + return Objects.hash(message, code); + } + + @Override + public String toString() { + return "ErrorProxyTO{" + + "message=" + message + + ", code=" + code + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java index 489d87b..7afbf9c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/StringProxyTO.java @@ -1,5 +1,7 @@ package io.goodforgod.api.etherscan.model.proxy.utility; +import java.util.Objects; + /** * @author GoodforGod * @since 31.10.2018 @@ -11,4 +13,28 @@ public class StringProxyTO extends BaseProxyTO { public String getResult() { return result; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + StringProxyTO that = (StringProxyTO) o; + return Objects.equals(result, that.result); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), result); + } + + @Override + public String toString() { + return "StringProxyTO{" + + "result=" + result + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java index 208cdbe..672585b 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxInfoProxyTO.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan.model.proxy.utility; import io.goodforgod.api.etherscan.model.proxy.ReceiptProxy; +import java.util.Objects; /** * @author GoodforGod @@ -13,4 +14,28 @@ public class TxInfoProxyTO extends BaseProxyTO { public ReceiptProxy getResult() { return result; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + TxInfoProxyTO that = (TxInfoProxyTO) o; + return Objects.equals(result, that.result); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), result); + } + + @Override + public String toString() { + return "TxInfoProxyTO{" + + "result=" + result + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java index 0c084e7..45b885f 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/proxy/utility/TxProxyTO.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan.model.proxy.utility; import io.goodforgod.api.etherscan.model.proxy.TxProxy; +import java.util.Objects; /** * @author GoodforGod @@ -13,4 +14,28 @@ public class TxProxyTO extends BaseProxyTO { public TxProxy getResult() { return result; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + TxProxyTO txProxyTO = (TxProxyTO) o; + return Objects.equals(result, txProxyTO.result); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), result); + } + + @Override + public String toString() { + return "TxProxyTO{" + + "result=" + result + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java index 6de9631..6229c76 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogQueryImpl.java @@ -1,6 +1,7 @@ package io.goodforgod.api.etherscan.model.query; import io.goodforgod.api.etherscan.LogsAPI; +import java.util.Objects; import org.jetbrains.annotations.NotNull; /** @@ -27,4 +28,26 @@ public class LogQueryImpl implements LogQuery { public @NotNull String params() { return params; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LogQueryImpl logQuery = (LogQueryImpl) o; + return Objects.equals(params, logQuery.params); + } + + @Override + public int hashCode() { + return Objects.hash(params); + } + + @Override + public String toString() { + return "LogQueryImpl{" + + "params=" + params + '\'' + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java index f113a4f..ff7bc8a 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicQuadro.java @@ -4,6 +4,7 @@ import io.goodforgod.api.etherscan.LogsAPI; import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; +import java.util.Objects; import org.jetbrains.annotations.NotNull; /** @@ -96,4 +97,43 @@ public LogTopicQuadro setOpTopic1_3(LogOp topic1_3_opr) { + TOPIC_1_3_OPR_PARAM + topic1_2_opr.getOperation() + TOPIC_2_3_OPR_PARAM + topic0_2_opr.getOperation()); } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LogTopicQuadro that = (LogTopicQuadro) o; + return startBlock == that.startBlock && endBlock == that.endBlock && Objects.equals(address, that.address) + && Objects.equals(topic0, that.topic0) && Objects.equals(topic1, that.topic1) + && Objects.equals(topic2, that.topic2) && Objects.equals(topic3, that.topic3) && topic0_1_opr == that.topic0_1_opr + && topic1_2_opr == that.topic1_2_opr && topic2_3_opr == that.topic2_3_opr && topic0_2_opr == that.topic0_2_opr + && topic0_3_opr == that.topic0_3_opr && topic1_3_opr == that.topic1_3_opr; + } + + @Override + public int hashCode() { + return Objects.hash(address, startBlock, endBlock, topic0, topic1, topic2, topic3, topic0_1_opr, topic1_2_opr, + topic2_3_opr, topic0_2_opr, topic0_3_opr, topic1_3_opr); + } + + @Override + public String toString() { + return "LogTopicQuadro{" + + "address=" + address + '\'' + + ", startBlock=" + startBlock + + ", endBlock=" + endBlock + + ", topic0=" + topic0 + '\'' + + ", topic1=" + topic1 + '\'' + + ", topic2=" + topic2 + '\'' + + ", topic3=" + topic3 + '\'' + + ", topic0_1_opr=" + topic0_1_opr + + ", topic1_2_opr=" + topic1_2_opr + + ", topic2_3_opr=" + topic2_3_opr + + ", topic0_2_opr=" + topic0_2_opr + + ", topic0_3_opr=" + topic0_3_opr + + ", topic1_3_opr=" + topic1_3_opr + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java index b35c836..58d7bda 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicSingle.java @@ -4,6 +4,7 @@ import io.goodforgod.api.etherscan.LogsAPI; import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; +import java.util.Objects; import org.jetbrains.annotations.NotNull; /** @@ -34,4 +35,30 @@ public class LogTopicSingle implements LogTopicBuilder { + FROM_BLOCK_PARAM + startBlock + TO_BLOCK_PARAM + endBlock + TOPIC_0_PARAM + topic0); } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LogTopicSingle that = (LogTopicSingle) o; + return startBlock == that.startBlock && endBlock == that.endBlock && Objects.equals(address, that.address) + && Objects.equals(topic0, that.topic0); + } + + @Override + public int hashCode() { + return Objects.hash(address, startBlock, endBlock, topic0); + } + + @Override + public String toString() { + return "LogTopicSingle{" + + "address=" + address + '\'' + + ", startBlock=" + startBlock + + ", endBlock=" + endBlock + + ", topic0=" + topic0 + '\'' + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java index d888a49..b5a25fe 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTriple.java @@ -4,6 +4,7 @@ import io.goodforgod.api.etherscan.LogsAPI; import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; +import java.util.Objects; import org.jetbrains.annotations.NotNull; /** @@ -69,4 +70,37 @@ public LogTopicTriple setOpTopic1_2(LogOp topic1_2_opr) { + TOPIC_1_2_OPR_PARAM + topic1_2_opr.getOperation() + TOPIC_0_2_OPR_PARAM + topic0_2_opr.getOperation()); } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LogTopicTriple that = (LogTopicTriple) o; + return startBlock == that.startBlock && endBlock == that.endBlock && Objects.equals(address, that.address) + && Objects.equals(topic0, that.topic0) && Objects.equals(topic1, that.topic1) + && Objects.equals(topic2, that.topic2) && topic0_1_opr == that.topic0_1_opr && topic1_2_opr == that.topic1_2_opr + && topic0_2_opr == that.topic0_2_opr; + } + + @Override + public int hashCode() { + return Objects.hash(address, startBlock, endBlock, topic0, topic1, topic2, topic0_1_opr, topic1_2_opr, topic0_2_opr); + } + + @Override + public String toString() { + return "LogTopicTriple{" + + "address=" + address + '\'' + + ", startBlock=" + startBlock + + ", endBlock=" + endBlock + + ", topic0=" + topic0 + '\'' + + ", topic1=" + topic1 + '\'' + + ", topic2=" + topic2 + '\'' + + ", topic0_1_opr=" + topic0_1_opr + + ", topic1_2_opr=" + topic1_2_opr + + ", topic0_2_opr=" + topic0_2_opr + + '}'; + } } diff --git a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java index 4a0218e..e396ace 100644 --- a/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java +++ b/src/main/java/io/goodforgod/api/etherscan/model/query/LogTopicTuple.java @@ -4,6 +4,7 @@ import io.goodforgod.api.etherscan.LogsAPI; import io.goodforgod.api.etherscan.error.EtherScanLogQueryException; +import java.util.Objects; import org.jetbrains.annotations.NotNull; /** @@ -50,4 +51,33 @@ public LogTopicTuple setOpTopic0_1(LogOp topic0_1_opr) { + TOPIC_1_PARAM + topic1 + TOPIC_0_1_OPR_PARAM + topic0_1_opr.getOperation()); } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LogTopicTuple that = (LogTopicTuple) o; + return startBlock == that.startBlock && endBlock == that.endBlock && Objects.equals(address, that.address) + && Objects.equals(topic0, that.topic0) && Objects.equals(topic1, that.topic1) + && topic0_1_opr == that.topic0_1_opr; + } + + @Override + public int hashCode() { + return Objects.hash(address, startBlock, endBlock, topic0, topic1, topic0_1_opr); + } + + @Override + public String toString() { + return "LogTopicTuple{" + + "address=" + address + '\'' + + ", startBlock=" + startBlock + + ", endBlock=" + endBlock + + ", topic0=" + topic0 + '\'' + + ", topic1=" + topic1 + '\'' + + ", topic0_1_opr=" + topic0_1_opr + + '}'; + } }