diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 31c42f0..0000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Java CI - -on: - push: - branches: - - master - pull_request: - branches: - - master - - dev - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - java: [ '11', '17' ] - name: Java ${{ matrix.java }} setup - - steps: - - uses: actions/checkout@v1 - - name: Set up JDK - uses: actions/setup-java@v1 - - with: - java-version: ${{ matrix.java }} - - - name: Build - run: ./gradlew classes - - - name: Codestyle - run: ./gradlew spotlessCheck - - - 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: SonarQube - if: matrix.java == '17' - run: ./gradlew sonarqube - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 0000000..32d0d9f --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,39 @@ +name: CI Master + +on: + push: + branches: + - master + schedule: + - cron: 0 0 * * 0 + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '11' ] + 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' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + + - name: SonarQube + if: matrix.java == '11' + 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 new file mode 100644 index 0000000..ed10cc4 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,49 @@ +name: Release + +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_PUBLISH }} + + - 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..4a4d958 --- /dev/null +++ b/.github/workflows/publish-snapshot.yml @@ -0,0 +1,44 @@ +name: Snapshot + +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_PUBLISH }} + + - 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/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..c9c6a99 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,41 @@ +name: CI Pull Request + +on: + pull_request: + branches: + - master + - dev + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '11' ] + name: Pull Request 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: Code Style + run: './gradlew spotlessCheck' + + - name: Build + run: './gradlew classes' + + - name: Test + run: './gradlew test jacocoTestReport' + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + + - name: SonarQube + if: matrix.java == '11' + 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..c09c885 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) +[![Minimum required Java version](https://img.shields.io/badge/Java-11%2B-blue?logo=openjdk)](https://openjdk.org/projects/jdk/11/) +[![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:3.0.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 + 3.0.0 ``` @@ -48,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(); ``` @@ -75,6 +76,14 @@ EtherScanAPI api = EtherScanAPI.builder() .build(); ``` +Also you can use Java 11+ HttpClient: +```java +Supplier 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) @@ -96,7 +105,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"); ``` @@ -104,14 +113,14 @@ Balance balance = api.account().balance("0x8d4426f94e42f721C7116E81d6688cd935cB3 **Get uncles block for block height** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional uncles = api.block().uncles(200000); ``` ### Contract API **Request contract ABI from [verified codes](https://etherscan.io/contractsVerified)** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Abi abi = api.contract().contractAbi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); ``` @@ -119,7 +128,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(); @@ -128,7 +137,7 @@ List logs = api.logs().logs(query); **Get event logs for 3 topics with respectful operations** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); LogQuery query = LogQuery.builder("0x33990122638b9132ca29c723bdf037f1a891a70c") .withBlockFrom(379224) .withBlockTo(400000) @@ -147,13 +156,13 @@ List logs = api.logs().logs(query); **Get tx details with proxy endpoint** ```java -EtherScanAPI api = EtherScanAPI.build(); -Optional tx = api.proxy().tx("0x1e2910a262b1008d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); +EtherScanAPI api = EtherScanAPI.builder().build(); +Optional tx = api.proxy().tx("0x1e2910a263.0.08d0616a0beb24c1a491d78771baa54a33e66065e03b1f46bc1"); ``` **Get block info with proxy endpoint** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional block = api.proxy().block(15215); ``` @@ -161,7 +170,7 @@ Optional block = api.proxy().block(15215); **Statistic about last price** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Price price = api.stats().priceLast(); ``` @@ -169,7 +178,7 @@ Price price = api.stats().priceLast(); **Request receipt status for tx** ```java -EtherScanAPI api = EtherScanAPI.build(); +EtherScanAPI api = EtherScanAPI.builder().build(); Optional status = api.txs().receiptStatus("0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76"); ``` 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..b9be8cd 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 "6.3.1.5724" + id "com.diffplug.spotless" version "6.19.0" + id "io.github.gradle-nexus.publish-plugin" version "2.0.0" } repositories { @@ -13,21 +14,23 @@ 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 +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 { + 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://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) + } + } +} + publishing { publications { mavenJava(MavenPublication) { @@ -91,14 +110,24 @@ 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") 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..7b97567 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ groupId=com.github.goodforgod artifactId=java-etherscan-api -artifactVersion=2.0.0 +artifactVersion=3.0.0-SNAPSHOT ##### GRADLE ##### diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..1b33c55 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..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ 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.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..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/master/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/. @@ -80,13 +82,11 @@ 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##*/} - -# 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,22 +133,29 @@ 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. 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=SC2039,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=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,18 +200,28 @@ 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. +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..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @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 +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,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,13 +43,13 @@ 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. -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 @@ -56,32 +59,34 @@ 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 -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..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,12 @@ 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) { - super(requestQueueManager, "account", baseUrl, executor, converter); + public AccountAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { + super(requestQueueManager, "account", baseUrl, executor, converter, retryCount); } @NotNull @@ -60,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); @@ -74,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); @@ -95,8 +96,9 @@ 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 BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class); + final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + + BasicUtils.toAddressParam(batch); + final BalanceResponseTO response = getResponse(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 { @@ -141,34 +139,6 @@ public List 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 responseTO list T type - * @param responseListTO type - * @return List of T values - */ - private > List getRequestUsingOffset(final String urlParams, Class tClass) - throws EtherScanException { - final List 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 txsInternal(@NotNull String address) throws EtherScanException { @@ -202,7 +172,7 @@ public List 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 5c61aad..0ab4f20 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BasicProvider.java @@ -1,13 +1,20 @@ 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 io.goodforgod.api.etherscan.util.BasicUtils; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import org.jetbrains.annotations.ApiStatus.Internal; /** * Base provider for API Implementations @@ -16,10 +23,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; @@ -30,20 +40,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) { + public BasicProvider(RequestQueueManager requestQueueManager, + String module, + String baseUrl, + EthHttpClient ethHttpClient, + 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) { + protected 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 +79,93 @@ T convert(byte[] json, Class tClass) { } } - 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 responseTO list T type + * @param responseListTO type + * @return List of T values + */ + protected > List getRequestUsingOffset(final String urlParams, Class tClass) + throws EtherScanException { + final List 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); } - 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 getRequest(String urlParameters, Class tClass) { - return convert(getRequest(urlParameters), tClass); + protected T getResponse(String urlParameters, Class tClass) { + return getResponse(urlParameters, tClass, 0); } - T postRequest(String urlParameters, String dataToPost, Class tClass) { - return convert(postRequest(urlParameters, dataToPost), tClass); + protected T getResponse(String urlParameters, Class tClass, int retryCount) { + try { + EthResponse response = getResponse(urlParameters); + return convert(response.body(), tClass); + } catch (Exception e) { + if (retryCount < retryCountLimit) { + try { + Thread.sleep(1150); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + + return getResponse(urlParameters, tClass, retryCount + 1); + } else { + throw e; + } + } + } + + protected T postRequest(String urlParameters, String dataToPost, Class tClass) { + return postRequest(urlParameters, dataToPost, tClass, 0); + } + + protected T postRequest(String urlParameters, String dataToPost, Class tClass, int retryCount) { + try { + EthResponse response = postRequest(urlParameters, dataToPost); + return convert(response.body(), 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..7228943 100644 --- a/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/BlockAPIProvider.java @@ -17,30 +17,26 @@ * @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) { - super(requestQueueManager, "block", baseUrl, executor, converter); + public BlockAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor, + 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 = getResponse(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..0719569 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; /** @@ -16,17 +20,24 @@ * @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"; private static final String ADDRESS_PARAM = "&address="; - ContractAPIProvider(RequestQueueManager requestQueueManager, - String baseUrl, - EthHttpClient executor, - Converter converter) { - super(requestQueueManager, "contract", baseUrl, executor, converter); + 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="; + + public ContractAPIProvider(RequestQueueManager requestQueueManager, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { + super(requestQueueManager, "contract", baseUrl, executor, converter, retryCount); } @NotNull @@ -35,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); } @@ -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 = getResponse(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..fa8af66 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; @@ -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; @@ -19,14 +20,14 @@ * @author Anton Kurako (GoodforGod) * @since 11.05.2023 */ -final class EthScanAPIBuilder implements EtherScanAPI.Builder { +public class EthScanAPIBuilder implements EtherScanAPI.Builder { - private static final Supplier DEFAULT_SUPPLIER = UrlEthHttpClient::new; - private static final String DEFAULT_KEY = "YourApiKeyToken"; + private static final Supplier DEFAULT_SUPPLIER = JdkEthHttpClient::new; private final Gson gson = new GsonConfiguration().builder().create(); - private String apiKey = DEFAULT_KEY; + private int retryCountOnLimitReach = 0; + private String apiKey; private RequestQueueManager queueManager; private EthNetwork ethNetwork = EthNetworks.MAINNET; private Supplier ethHttpClientSupplier = DEFAULT_SUPPLIER; @@ -42,6 +43,10 @@ final class EthScanAPIBuilder implements EtherScanAPI.Builder { } }; + public EthScanAPIBuilder(String apiKey) { + this.apiKey = apiKey; + } + @NotNull @Override public EtherScanAPI.Builder withApiKey(@NotNull String apiKey) { @@ -87,18 +92,22 @@ 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; - 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()); + 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..4c703bb 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 ... @@ -38,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 { @@ -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..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,23 +22,24 @@ final class EtherScanAPIProvider implements EtherScanAPI { private final TransactionAPI txs; private final GasTrackerAPI gasTracker; - EtherScanAPIProvider(String apiKey, - EthNetwork network, - RequestQueueManager queue, - EthHttpClient ethHttpClient, - Converter converter) { + 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; 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..e7ecb7c 100644 --- a/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/GasTrackerAPIProvider.java @@ -18,24 +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) { - super(queue, "gastracker", baseUrl, ethHttpClient, converter); + 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); @@ -45,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 d294fb5..1002dc8 100644 --- a/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java +++ b/src/main/java/io/goodforgod/api/etherscan/LogsAPIProvider.java @@ -18,22 +18,23 @@ * @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) { - super(queue, "logs", baseUrl, executor, converter); + public LogsAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { + super(queue, "logs", baseUrl, executor, converter, retryCount); } @NotNull @Override public List 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 4dff589..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,16 +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) { - super(queue, "proxy", baseUrl, executor, converter); + 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(); @@ -78,7 +79,7 @@ public Optional 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()); } @@ -90,7 +91,7 @@ public Optional 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()); } @@ -100,7 +101,7 @@ public Optional 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()); } @@ -114,7 +115,7 @@ public Optional 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()); } @@ -122,7 +123,7 @@ public Optional 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(); } @@ -131,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(); } @@ -164,7 +165,7 @@ public Optional 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()); } @@ -176,7 +177,7 @@ public Optional 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()); } @@ -186,7 +187,7 @@ public Optional 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()); } @@ -197,7 +198,7 @@ public Optional 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()); @@ -206,7 +207,7 @@ public Optional 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())); @@ -225,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/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..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,17 +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) { - super(queue, "stats", baseUrl, executor, converter); + 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); @@ -49,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); @@ -62,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); @@ -72,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 da26b51..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,19 @@ * @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) { - super(queue, "transaction", baseUrl, executor, converter); + public TransactionAPIProvider(RequestQueueManager queue, + String baseUrl, + EthHttpClient executor, + Converter converter, + int retryCount) { + super(queue, "transaction", baseUrl, executor, converter, retryCount); } @NotNull @@ -37,7 +38,7 @@ public Optional 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()); @@ -49,7 +50,7 @@ public Optional 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> 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> 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> headers; + + SimpleEthResponse(int statusCode, @Nullable byte[] body, @Nullable Map> 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> 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..01580ce --- /dev/null +++ b/src/main/java/io/goodforgod/api/etherscan/http/impl/JdkEthHttpClient.java @@ -0,0 +1,186 @@ +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 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.*; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; + +/** + * Anton Kurako (GoodforGod) + * + * @since 07.09.2025 + */ +@Internal +public class JdkEthHttpClient implements EthHttpClient { + + private static final Map 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 headers; + private final UrlEthHttpClient urlEthHttpClient; + + 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 headers) { + this.httpClient = httpClient; + this.requestTimeout = requestTimeout; + this.headers = headers; + this.urlEthHttpClient = new UrlEthHttpClient(DEFAULT_CONNECT_TIMEOUT, requestTimeout, headers); + } + + @Override + public EthResponse get(@NotNull URI uri) { + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(uri) + .timeout(requestTimeout); + + headers.forEach(requestBuilder::header); + + try { + HttpResponse response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + byte[] bodyAsBytes = getDeflatedBytes(response); + return new EthResponse() { + + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public byte[] body() { + return bodyAsBytes; + } + + @Override + public @NotNull Map> 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) { + 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 response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + byte[] bodyAsBytes = getDeflatedBytes(response); + return new EthResponse() { + + @Override + public int statusCode() { + return response.statusCode(); + } + + @Override + public byte[] body() { + return bodyAsBytes; + } + + @Override + public @NotNull Map> 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); + } + } + + private static byte[] getDeflatedBytes(HttpResponse response) { + try { + Optional 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/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 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 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 headers) { + this(connectTimeout, readTimeout, headers, null); } /** @@ -62,15 +76,19 @@ public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout) { */ public UrlEthHttpClient(Duration connectTimeout, Duration readTimeout, - Map headers) { + Map 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/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/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 3536bf9..21b6601 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 + '}'; } @@ -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/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/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 { +public abstract class BaseTx implements Comparable { long blockNumber; String timeStamp; 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/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 9b110d9..058e13b 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 + '}'; @@ -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; @@ -128,9 +128,9 @@ public String getUncleInclusionReward() { @Override public String toString() { return "UncleBlock{" + - "blockMiner='" + blockMiner + '\'' + + "blockMiner=" + blockMiner + ", uncles=" + uncles + - ", uncleInclusionReward='" + uncleInclusionReward + '\'' + + ", uncleInclusionReward=" + uncleInclusionReward + '}'; } @@ -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 new file mode 100644 index 0000000..41e62c5 --- /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 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..4dbbce7 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 + '}'; } @@ -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..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; @@ -83,7 +82,7 @@ public static GasOracleBuilder builder() { return new GasOracleBuilder(); } - public static final class GasOracleBuilder { + public static class GasOracleBuilder { private Long lastBlock; private Wei safeGasPrice; @@ -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/main/java/io/goodforgod/api/etherscan/model/Log.java b/src/main/java/io/goodforgod/api/etherscan/model/Log.java index da6c295..d29db31 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 + '}'; } @@ -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 565dbed..0baa38a 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 + '}'; } @@ -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 052c187..f651b1f 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 + '}'; } @@ -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/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..8843bd6 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 + '}'; @@ -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 16d4457..ed8754d 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 + '}'; @@ -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 3dc22fd..57d70cb 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 + '}'; @@ -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 2180019..64df779 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 + '}'; @@ -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 a61cf83..91c2b27 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 + '}'; @@ -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 4a2b624..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 getTransactions() { } // - @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()); @@ -194,7 +152,7 @@ public static BlockProxyBuilder builder() { return new BlockProxyBuilder(); } - public static final class BlockProxyBuilder { + public static class BlockProxyBuilder { private Long number; private String hash; @@ -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 e6df01c..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,46 +95,11 @@ public String getLogsBloom() { } // - @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(); } - public static final class ReceiptProxyBuilder { + public static class ReceiptProxyBuilder { private String root; private String from; @@ -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 70b4fd7..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() { } // - @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()); @@ -156,7 +119,7 @@ public static TxProxyBuilder builder() { return new TxProxyBuilder(); } - public static final class TxProxyBuilder { + public static class TxProxyBuilder { private String to; private String hash; @@ -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 ef57193..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,10 +1,12 @@ package io.goodforgod.api.etherscan.model.proxy.utility; +import java.util.Objects; + /** * @author GoodforGod * @since 31.10.2018 */ -abstract class BaseProxyTO { +public abstract class BaseProxyTO { private String id; private String jsonrpc; @@ -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/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..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; /** @@ -12,7 +13,7 @@ * @author GoodforGod * @since 31.10.2018 */ -final class LogQueryImpl implements LogQuery { +public class LogQueryImpl implements LogQuery { /** * Final request parameter for api call @@ -27,4 +28,26 @@ final 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/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..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; /** @@ -14,7 +15,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; @@ -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 a736ffa..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; /** @@ -14,7 +15,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; @@ -34,4 +35,30 @@ public final 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 ac9efb8..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; /** @@ -14,7 +15,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; @@ -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 2ef2bba..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; /** @@ -14,7 +15,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; @@ -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 + + '}'; + } } 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/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 216ab62..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() {} @@ -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..23df478 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); @@ -23,10 +25,10 @@ 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) .build(); } @@ -34,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..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; @@ -22,52 +21,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()); - } - - @Test - void noTimeoutOnRead() { - Supplier 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() { - 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); + () -> EtherScanAPI.builder("someKey").withApiKey(" ").withNetwork(network).build()); } @Test void timeout() throws InterruptedException { TimeUnit.SECONDS.sleep(5); Supplier 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(); 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 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 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/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"))); + } } 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());