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..0b49f50 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: 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 sonarqube
+ run: './gradlew sonar --info'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..5abd8dc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# Contributing Code or Documentation Guide
+
+## Running Tests
+
+The new code should contain tests that check new behavior.
+
+Run tests `./gradlew test` to check that code works as behavior.
+
+## Code Style
+
+The code base should remain clean, following industry best practices for organization, javadoc and style, as much as possible.
+
+To run the Code Style check use `./gradlew spotlessCheck`.
+
+If check found any errors, you can apply Code Style by running `./gradlew spotlessApply`
+
+## Creating a pull request
+
+Once you are satisfied with your changes:
+
+- Commit changes to the local branch you created.
+- Push that branch with changes to the corresponding remote branch on GitHub
+- Submit a [pull request](https://help.github.com/articles/creating-a-pull-request) to `dev` branch.
\ No newline at end of file
diff --git a/README.md b/README.md
index dd244b5..c086a6b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
# Java EtherScan API
[![Minimum required Java version](https://img.shields.io/badge/Java-1.8%2B-blue?logo=openjdk)](https://openjdk.org/projects/jdk8/)
-[![GitHub Action](https://github.com/goodforgod/java-etherscan-api/workflows/Java%20CI/badge.svg)](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3A%22Java+CI%22)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.goodforgod/java-etherscan-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.goodforgod/java-etherscan-api)
+[![Java CI](https://github.com/GoodforGod/java-etherscan-api/workflows/CI%20Master/badge.svg)](https://github.com/GoodforGod/java-etherscan-api/actions?query=workflow%3ACI+Master)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=GoodforGod_java-etherscan-api&metric=coverage)](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=GoodforGod_java-etherscan-api&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=GoodforGod_java-etherscan-api&metric=ncloc)](https://sonarcloud.io/dashboard?id=GoodforGod_java-etherscan-api)
@@ -14,7 +15,7 @@ Library supports EtherScan *API* for all available *Ethereum Networks* for *ethe
**Gradle**
```groovy
-implementation "com.github.goodforgod:java-etherscan-api:2.0.0"
+implementation "com.github.goodforgod:java-etherscan-api:2.1.0"
```
**Maven**
@@ -22,7 +23,7 @@ implementation "com.github.goodforgod:java-etherscan-api:2.0.0"
com.github.goodforgod
java-etherscan-api
- 2.0.0
+ 2.1.0
```
diff --git a/_config.yml b/_config.yml
deleted file mode 100644
index 2f7efbe..0000000
--- a/_config.yml
+++ /dev/null
@@ -1 +0,0 @@
-theme: jekyll-theme-minimal
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 3d766c2..7e28207 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 = true
}
+
+ 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.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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180..943f0cb 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
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
diff --git a/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java
index 442edff..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
@@ -95,7 +96,8 @@ public List balances(@NotNull List addresses) throws EtherScanE
final List> addressesAsBatches = BasicUtils.partition(addresses, 20);
for (final List batch : addressesAsBatches) {
- final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + 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 +113,6 @@ public List balances(@NotNull List addresses) throws EtherScanE
return balances;
}
- private String toAddressParam(List addresses) {
- return String.join(",", addresses);
- }
-
@NotNull
@Override
public List txs(@NotNull String address) throws EtherScanException {
diff --git a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java
index 5c61aad..41abd16 100644
--- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java
+++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java
@@ -30,20 +30,23 @@ abstract class BasicProvider {
private final EthHttpClient executor;
private final RequestQueueManager queue;
private final Converter converter;
+ private final int retryCountLimit;
BasicProvider(RequestQueueManager requestQueueManager,
String module,
String baseUrl,
EthHttpClient ethHttpClient,
- Converter converter) {
+ Converter converter,
+ int retryCountLimit) {
this.queue = requestQueueManager;
this.module = "&module=" + module;
this.baseUrl = baseUrl;
this.executor = ethHttpClient;
this.converter = converter;
+ this.retryCountLimit = retryCountLimit;
}
- T convert(byte[] json, Class tClass) {
+ private T convert(byte[] json, Class tClass) {
try {
final T t = converter.fromJson(json, tClass);
if (t instanceof StringResponseTO && ((StringResponseTO) t).getResult().startsWith(MAX_RATE_LIMIT_REACHED)) {
@@ -66,23 +69,59 @@ T convert(byte[] json, Class tClass) {
}
}
- byte[] getRequest(String urlParameters) {
+ private byte[] getRequest(String urlParameters) {
queue.takeTurn();
final URI uri = URI.create(baseUrl + module + urlParameters);
return executor.get(uri);
}
- byte[] postRequest(String urlParameters, String dataToPost) {
+ private byte[] postRequest(String urlParameters, String dataToPost) {
queue.takeTurn();
final URI uri = URI.create(baseUrl + module + urlParameters);
return executor.post(uri, dataToPost.getBytes(StandardCharsets.UTF_8));
}
T getRequest(String urlParameters, Class tClass) {
- return convert(getRequest(urlParameters), tClass);
+ return getRequest(urlParameters, tClass, 0);
+ }
+
+ private T getRequest(String urlParameters, Class tClass, int retryCount) {
+ try {
+ return convert(getRequest(urlParameters), tClass);
+ } catch (Exception e) {
+ if (retryCount < retryCountLimit) {
+ try {
+ Thread.sleep(1150);
+ } catch (InterruptedException ex) {
+ throw new IllegalStateException(ex);
+ }
+
+ return getRequest(urlParameters, tClass, retryCount + 1);
+ } else {
+ throw e;
+ }
+ }
}
T postRequest(String urlParameters, String dataToPost, Class tClass) {
- return convert(postRequest(urlParameters, dataToPost), tClass);
+ return postRequest(urlParameters, dataToPost, tClass, 0);
+ }
+
+ private T postRequest(String urlParameters, String dataToPost, Class tClass, int retryCount) {
+ try {
+ return convert(postRequest(urlParameters, dataToPost), tClass);
+ } catch (EtherScanRateLimitException e) {
+ if (retryCount < retryCountLimit) {
+ try {
+ Thread.sleep(1150);
+ } catch (InterruptedException ex) {
+ throw new IllegalStateException(ex);
+ }
+
+ return postRequest(urlParameters, dataToPost, tClass, retryCount + 1);
+ } else {
+ throw e;
+ }
+ }
}
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java
index 406ac19..b3604a7 100644
--- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java
+++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java
@@ -26,21 +26,17 @@ final class BlockAPIProvider extends BasicProvider implements BlockAPI {
BlockAPIProvider(RequestQueueManager requestQueueManager,
String baseUrl,
EthHttpClient executor,
- Converter converter) {
- super(requestQueueManager, "block", baseUrl, executor, converter);
+ Converter converter,
+ int retryCount) {
+ super(requestQueueManager, "block", baseUrl, executor, converter, retryCount);
}
@NotNull
@Override
public Optional uncles(long blockNumber) throws EtherScanException {
final String urlParam = ACT_BLOCK_PARAM + BLOCKNO_PARAM + blockNumber;
- final byte[] response = getRequest(urlParam);
- if (response.length == 0) {
- return Optional.empty();
- }
-
try {
- final UncleBlockResponseTO responseTO = convert(response, UncleBlockResponseTO.class);
+ final UncleBlockResponseTO responseTO = getRequest(urlParam, UncleBlockResponseTO.class);
if (responseTO.getMessage().startsWith("NOTOK")) {
return Optional.empty();
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java
index af0852c..c076b74 100644
--- a/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java
+++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPI.java
@@ -2,6 +2,8 @@
import io.goodforgod.api.etherscan.error.EtherScanException;
import io.goodforgod.api.etherscan.model.Abi;
+import io.goodforgod.api.etherscan.model.ContractCreation;
+import java.util.List;
import org.jetbrains.annotations.NotNull;
/**
@@ -21,4 +23,13 @@ public interface ContractAPI {
*/
@NotNull
Abi contractAbi(@NotNull String address) throws EtherScanException;
+
+ /**
+ * Returns a contract's deployer address and transaction hash it was created, up to 5 at a time.
+ *
+ * @param contractAddresses - list of addresses to fetch
+ * @throws EtherScanException parent exception class
+ */
+ @NotNull
+ List contractCreation(@NotNull List contractAddresses) throws EtherScanException;
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java
index 6b4404a..898a7b7 100644
--- a/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java
+++ b/src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java
@@ -5,8 +5,12 @@
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 java.util.List;
+import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
/**
@@ -22,11 +26,18 @@ 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,
- Converter converter) {
- super(requestQueueManager, "contract", baseUrl, executor, converter);
+ Converter converter,
+ int retryCount) {
+ super(requestQueueManager, "contract", baseUrl, executor, converter, retryCount);
}
@NotNull
@@ -44,4 +55,24 @@ public Abi contractAbi(@NotNull String address) throws EtherScanException {
? Abi.nonVerified()
: Abi.verified(response.getResult());
}
+
+ @NotNull
+ @Override
+ public List contractCreation(@NotNull List contractAddresses) throws EtherScanException {
+ BasicUtils.validateAddresses(contractAddresses);
+ final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM
+ + BasicUtils.toAddressParam(contractAddresses);
+ final ContractCreationResponseTO response = getRequest(urlParam, ContractCreationResponseTO.class);
+ if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) {
+ throw new EtherScanResponseException(response);
+ }
+
+ return response.getResult().stream()
+ .map(to -> ContractCreation.builder()
+ .withContractCreator(to.getContractCreator())
+ .withContractAddress(to.getContractAddress())
+ .withTxHash(to.getTxHash())
+ .build())
+ .collect(Collectors.toList());
+ }
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java
index dad9c50..2b70711 100644
--- a/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java
+++ b/src/main/java/io/goodforgod/api/etherscan/EthScanAPIBuilder.java
@@ -26,6 +26,7 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder {
private final Gson gson = new GsonConfiguration().builder().create();
+ private int retryCountOnLimitReach = 0;
private String apiKey = DEFAULT_KEY;
private RequestQueueManager queueManager;
private EthNetwork ethNetwork = EthNetworks.MAINNET;
@@ -87,6 +88,16 @@ public EtherScanAPI.Builder withConverter(@NotNull Supplier converter
return this;
}
+ @NotNull
+ public EtherScanAPI.Builder withRetryOnRateLimit(int maxRetryCount) {
+ if (maxRetryCount < 0 || maxRetryCount > 20) {
+ throw new IllegalStateException("maxRetryCount value must be in range from 0 to 20, but was: " + maxRetryCount);
+ }
+
+ this.retryCountOnLimitReach = maxRetryCount;
+ return this;
+ }
+
@Override
public @NotNull EtherScanAPI build() {
RequestQueueManager requestQueueManager;
@@ -99,6 +110,6 @@ public EtherScanAPI.Builder withConverter(@NotNull Supplier converter
}
return new EtherScanAPIProvider(apiKey, ethNetwork, requestQueueManager, ethHttpClientSupplier.get(),
- converterSupplier.get());
+ converterSupplier.get(), retryCountOnLimitReach);
}
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java
index 6da3d8f..bae1902 100644
--- a/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java
+++ b/src/main/java/io/goodforgod/api/etherscan/EtherScanAPI.java
@@ -1,9 +1,11 @@
package io.goodforgod.api.etherscan;
+import io.goodforgod.api.etherscan.error.EtherScanRateLimitException;
import io.goodforgod.api.etherscan.http.EthHttpClient;
import io.goodforgod.api.etherscan.manager.RequestQueueManager;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Range;
/**
* EtherScan full API Description ...
@@ -62,6 +64,15 @@ interface Builder {
@NotNull
Builder withConverter(@NotNull Supplier converterSupplier);
+ /**
+ * By default is disabled
+ *
+ * @param maxRetryCount to retry if {@link EtherScanRateLimitException} thrown
+ * @return self
+ */
+ @NotNull
+ EtherScanAPI.Builder withRetryOnRateLimit(@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/StatisticAPI.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java
index d7b48b8..3f48127 100644
--- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java
+++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java
@@ -15,6 +15,9 @@
public interface StatisticAPI {
/**
+ * ERC20 token total Supply
+ * EtherScan
* Returns the current amount of an ERC-20 token in circulation.
*
* @param contract contract address
diff --git a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java
index 131df71..a2bba16 100644
--- a/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java
+++ b/src/main/java/io/goodforgod/api/etherscan/StatisticAPIProvider.java
@@ -33,8 +33,9 @@ final class StatisticAPIProvider extends BasicProvider implements StatisticAPI {
StatisticAPIProvider(RequestQueueManager queue,
String baseUrl,
EthHttpClient executor,
- Converter converter) {
- super(queue, "stats", baseUrl, executor, converter);
+ Converter converter,
+ int retry) {
+ super(queue, "stats", baseUrl, executor, converter, retry);
}
@NotNull
diff --git a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java
index da26b51..7374335 100644
--- a/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java
+++ b/src/main/java/io/goodforgod/api/etherscan/TransactionAPIProvider.java
@@ -27,8 +27,9 @@ final class TransactionAPIProvider extends BasicProvider implements TransactionA
TransactionAPIProvider(RequestQueueManager queue,
String baseUrl,
EthHttpClient executor,
- Converter converter) {
- super(queue, "transaction", baseUrl, executor, converter);
+ Converter converter,
+ int retryCount) {
+ super(queue, "transaction", baseUrl, executor, converter, retryCount);
}
@NotNull
diff --git a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java
index 0f36b23..92875d0 100644
--- a/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java
+++ b/src/main/java/io/goodforgod/api/etherscan/manager/RequestQueueManager.java
@@ -17,7 +17,7 @@ public interface RequestQueueManager extends AutoCloseable {
* Is used by default when no API KEY is provided
*/
static RequestQueueManager anonymous() {
- return new SemaphoreRequestQueueManager(1, Duration.ofMillis(5015L));
+ return new SemaphoreRequestQueueManager(1, Duration.ofMillis(5045L));
}
/**
@@ -25,19 +25,19 @@ static RequestQueueManager anonymous() {
* Free API KEY
*/
static RequestQueueManager planFree() {
- return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1015L));
+ return new SemaphoreRequestQueueManager(5, Duration.ofMillis(1045L));
}
static RequestQueueManager planStandard() {
- return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1015L));
+ return new SemaphoreRequestQueueManager(10, Duration.ofMillis(1045L));
}
static RequestQueueManager planAdvanced() {
- return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1015L));
+ return new SemaphoreRequestQueueManager(20, Duration.ofMillis(1045L));
}
static RequestQueueManager planProfessional() {
- return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1015L));
+ return new SemaphoreRequestQueueManager(30, Duration.ofMillis(1045L));
}
static RequestQueueManager unlimited() {
diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java
index 3536bf9..fbf71be 100644
--- a/src/main/java/io/goodforgod/api/etherscan/model/Abi.java
+++ b/src/main/java/io/goodforgod/api/etherscan/model/Abi.java
@@ -55,7 +55,7 @@ public int hashCode() {
@Override
public String toString() {
return "Abi{" +
- "contractAbi='" + contractAbi + '\'' +
+ "contractAbi=" + contractAbi +
", isVerified=" + isVerified +
'}';
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java
index 079d4b6..1d2f743 100644
--- a/src/main/java/io/goodforgod/api/etherscan/model/Balance.java
+++ b/src/main/java/io/goodforgod/api/etherscan/model/Balance.java
@@ -45,7 +45,7 @@ public int hashCode() {
@Override
public String toString() {
return "Balance{" +
- "address='" + address + '\'' +
+ "address=" + address +
", balance=" + balance +
'}';
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/model/Block.java b/src/main/java/io/goodforgod/api/etherscan/model/Block.java
index 0550000..da1184b 100644
--- a/src/main/java/io/goodforgod/api/etherscan/model/Block.java
+++ b/src/main/java/io/goodforgod/api/etherscan/model/Block.java
@@ -58,7 +58,7 @@ public String toString() {
return "Block{" +
"blockNumber=" + blockNumber +
", blockReward=" + blockReward +
- ", timeStamp='" + timeStamp + '\'' +
+ ", timeStamp=" + timeStamp +
'}';
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java
index 9b110d9..961db7e 100644
--- a/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java
+++ b/src/main/java/io/goodforgod/api/etherscan/model/BlockUncle.java
@@ -19,7 +19,7 @@ public static class Uncle {
private BigInteger blockreward;
private int unclePosition;
- private Uncle() {}
+ protected Uncle() {}
//
public String getMiner() {
@@ -54,7 +54,7 @@ public int hashCode() {
@Override
public String toString() {
return "Uncle{" +
- "miner='" + miner + '\'' +
+ "miner=" + miner +
", blockreward=" + blockreward +
", unclePosition=" + unclePosition +
'}';
@@ -128,9 +128,9 @@ public String getUncleInclusionReward() {
@Override
public String toString() {
return "UncleBlock{" +
- "blockMiner='" + blockMiner + '\'' +
+ "blockMiner=" + blockMiner +
", uncles=" + uncles +
- ", uncleInclusionReward='" + uncleInclusionReward + '\'' +
+ ", uncleInclusionReward=" + uncleInclusionReward +
'}';
}
diff --git a/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java
new file mode 100644
index 0000000..2082883
--- /dev/null
+++ b/src/main/java/io/goodforgod/api/etherscan/model/ContractCreation.java
@@ -0,0 +1,86 @@
+package io.goodforgod.api.etherscan.model;
+
+import java.util.Objects;
+
+public class ContractCreation {
+
+ private String contractAddress;
+ private String contractCreator;
+ private String txHash;
+
+ protected ContractCreation() {}
+
+ 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() {
+ 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/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..e3766c3
--- /dev/null
+++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationResponseTO.java
@@ -0,0 +1,3 @@
+package io.goodforgod.api.etherscan.model.response;
+
+public class ContractCreationResponseTO extends BaseListResponseTO {}
diff --git a/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java
new file mode 100644
index 0000000..9e1551e
--- /dev/null
+++ b/src/main/java/io/goodforgod/api/etherscan/model/response/ContractCreationTO.java
@@ -0,0 +1,20 @@
+package io.goodforgod.api.etherscan.model.response;
+
+public class ContractCreationTO {
+
+ private String contractAddress;
+ private String contractCreator;
+ private String txHash;
+
+ public String getContractAddress() {
+ return contractAddress;
+ }
+
+ public String getContractCreator() {
+ return contractCreator;
+ }
+
+ public String getTxHash() {
+ return txHash;
+ }
+}
diff --git a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java
index 216ab62..916d4ab 100644
--- a/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java
+++ b/src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java
@@ -149,4 +149,8 @@ public static List> partition(List list, int pairSize) {
return partitioned;
}
+
+ public static String toAddressParam(List addresses) {
+ return String.join(",", addresses);
+ }
}
diff --git a/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java b/src/test/java/io/goodforgod/api/etherscan/ApiRunner.java
index 4b52c00..bc4f334 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 io.goodforgod.api.etherscan.util.BasicUtils;
import java.util.Map;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
@@ -15,6 +16,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);
@@ -27,6 +29,7 @@ public class ApiRunner extends Assertions {
.withApiKey(ApiRunner.API_KEY)
.withNetwork(EthNetworks.MAINNET)
.withQueue(queueManager)
+ .withRetryOnRateLimit(5)
.build();
}
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..d1e4de4 100644
--- a/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java
+++ b/src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java
@@ -3,6 +3,10 @@
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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import org.junit.jupiter.api.Test;
/**
@@ -37,4 +41,46 @@ void correctParamWithEmptyExpectedResult() {
assertNotNull(abi);
assertTrue(abi.isVerified());
}
+
+ @Test
+ void correctContractCreation() {
+ List contractCreations = getApi().contract()
+ .contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"));
+
+ assertEquals(1, contractCreations.size());
+ ContractCreation contractCreation = contractCreations.get(0);
+
+ assertEquals("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", contractCreation.getContractAddress());
+ assertEquals("0x793ea9692ada1900fbd0b80fffec6e431fe8b391", contractCreation.getContractCreator());
+ assertEquals("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9", contractCreation.getTxHash());
+ }
+
+ @Test
+ void correctMultipleContractCreation() {
+ List contractCreations = getApi().contract().contractCreation(
+ Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123"));
+ assertEquals(2, contractCreations.size());
+
+ ContractCreation contractCreation1 = ContractCreation.builder()
+ .withContractAddress("0xbb9bc244d798123fde783fcc1c72d3bb8c189413")
+ .withContractCreator("0x793ea9692ada1900fbd0b80fffec6e431fe8b391")
+ .withTxHash("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9")
+ .build();
+
+ ContractCreation contractCreation2 = ContractCreation.builder()
+ .withContractAddress("0x5eac95ad5b287cf44e058dcf694419333b796123")
+ .withContractCreator("0x7c675b7450e878e5af8550b41df42d134674e61f")
+ .withTxHash("0x79cdfec19e5a86d9022680a4d1c86d3d8cd76c21c01903a2f02c127a0a7dbfb3")
+ .build();
+
+ assertTrue(contractCreations.contains(contractCreation1));
+ assertTrue(contractCreations.contains(contractCreation2));
+ }
+
+ @Test
+ void contractCreationInvalidParamWithError() {
+ assertThrows(EtherScanInvalidAddressException.class,
+ () -> getApi().contract()
+ .contractCreation(Collections.singletonList("0xBBbc244D798123fDe783fCc1C72d3Bb8C189414")));
+ }
}