Skip to content

Commit

Permalink
Merge pull request #27 from Blackmorse/contract_creation
Browse files Browse the repository at this point in the history
Contract creation API
  • Loading branch information
GoodforGod authored Oct 5, 2023
2 parents c64a301 + 06464f8 commit 3b1af9d
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ public List<Balance> balances(@NotNull List<String> addresses) throws EtherScanE
final List<List<String>> addressesAsBatches = BasicUtils.partition(addresses, 20);

for (final List<String> batch : addressesAsBatches) {
final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM + toAddressParam(batch);
final String urlParams = ACT_BALANCE_MULTI_ACTION + TAG_LATEST_PARAM + ADDRESS_PARAM
+ BasicUtils.toAddressParam(batch);
final BalanceResponseTO response = getRequest(urlParams, BalanceResponseTO.class);
if (response.getStatus() != 1) {
throw new EtherScanResponseException(response);
Expand All @@ -111,10 +112,6 @@ public List<Balance> balances(@NotNull List<String> addresses) throws EtherScanE
return balances;
}

private String toAddressParam(List<String> addresses) {
return String.join(",", addresses);
}

@NotNull
@Override
public List<Tx> txs(@NotNull String address) throws EtherScanException {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/io/goodforgod/api/etherscan/ContractAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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> contractCreation(@NotNull List<String> contractAddresses) throws EtherScanException;
}
30 changes: 30 additions & 0 deletions src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -22,6 +26,12 @@ final class ContractAPIProvider extends BasicProvider implements ContractAPI {

private static final String ADDRESS_PARAM = "&address=";

private static final String ACT_CONTRACT_CREATION_PARAM = "getcontractcreation";

private static final String ACT_CONTRACT_CREATION = ACT_PREFIX + ACT_CONTRACT_CREATION_PARAM;

private static final String ACT_CONTRACT_ADDRESSES_PARAM = "&contractaddresses=";

ContractAPIProvider(RequestQueueManager requestQueueManager,
String baseUrl,
EthHttpClient executor,
Expand All @@ -44,4 +54,24 @@ public Abi contractAbi(@NotNull String address) throws EtherScanException {
? Abi.nonVerified()
: Abi.verified(response.getResult());
}

@NotNull
@Override
public List<ContractCreation> contractCreation(@NotNull List<String> contractAddresses) throws EtherScanException {
BasicUtils.validateAddresses(contractAddresses);
final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM
+ BasicUtils.toAddressParam(contractAddresses);
final ContractCreationResponseTO response = getRequest(urlParam, ContractCreationResponseTO.class);
if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) {
throw new EtherScanResponseException(response);
}

return response.getResult().stream()
.map(to -> ContractCreation.builder()
.withContractCreator(to.getContractCreator())
.withContractAddress(to.getContractAddress())
.withTxHash(to.getTxHash())
.build())
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface StatisticAPI {
* ERC20 token total Supply
* <a href=
* "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan<a>
* Returns the current amount of an ERC-20 token in circulation.
*
* @param contract contract address
* @return token supply for specified contract
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.goodforgod.api.etherscan.model;

import java.util.Objects;

public class ContractCreation {

private final String contractAddress;
private final String contractCreator;
private final String txHash;

private ContractCreation(String contractAddress, String contractCreator, String txHash) {
this.contractAddress = contractAddress;
this.contractCreator = contractCreator;
this.txHash = txHash;
}

public String getContractAddress() {
return contractAddress;
}

public String getContractCreator() {
return contractCreator;
}

public String getTxHash() {
return txHash;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ContractCreation that = (ContractCreation) o;
return Objects.equals(contractAddress, that.contractAddress) && Objects.equals(contractCreator, that.contractCreator)
&& Objects.equals(txHash, that.txHash);
}

@Override
public int hashCode() {
return Objects.hash(contractAddress, contractCreator, txHash);
}

@Override
public String toString() {
return "ContractCreation{" +
"contractAddress='" + contractAddress + '\'' +
", contractCreator='" + contractCreator + '\'' +
", txHash='" + txHash + '\'' +
'}';
}

public static ContractCreationBuilder builder() {
return new ContractCreationBuilder();
}

public static final class ContractCreationBuilder {

private String contractAddress;
private String contractCreator;
private String txHash;

private ContractCreationBuilder() {}

public ContractCreationBuilder withContractAddress(String contractAddress) {
this.contractAddress = contractAddress;
return this;
}

public ContractCreationBuilder withContractCreator(String contractCreator) {
this.contractCreator = contractCreator;
return this;
}

public ContractCreationBuilder withTxHash(String txHash) {
this.txHash = txHash;
return this;
}

public ContractCreation build() {
return new ContractCreation(contractAddress, contractCreator, txHash);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.goodforgod.api.etherscan.model.response;

public class ContractCreationResponseTO extends BaseListResponseTO<ContractCreationTO> {}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,8 @@ public static List<List<String>> partition(List<String> list, int pairSize) {

return partitioned;
}

public static String toAddressParam(List<String> addresses) {
return String.join(",", addresses);
}
}
2 changes: 2 additions & 0 deletions src/test/java/io/goodforgod/api/etherscan/ApiRunner.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -37,4 +41,46 @@ void correctParamWithEmptyExpectedResult() {
assertNotNull(abi);
assertTrue(abi.isVerified());
}

@Test
void correctContractCreation() {
List<ContractCreation> contractCreations = getApi().contract()
.contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"));

assertEquals(1, contractCreations.size());
ContractCreation contractCreation = contractCreations.get(0);

assertEquals("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", contractCreation.getContractAddress());
assertEquals("0x793ea9692ada1900fbd0b80fffec6e431fe8b391", contractCreation.getContractCreator());
assertEquals("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9", contractCreation.getTxHash());
}

@Test
void correctMultipleContractCreation() {
List<ContractCreation> contractCreations = getApi().contract().contractCreation(
Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123"));
assertEquals(2, contractCreations.size());

ContractCreation contractCreation1 = ContractCreation.builder()
.withContractAddress("0xbb9bc244d798123fde783fcc1c72d3bb8c189413")
.withContractCreator("0x793ea9692ada1900fbd0b80fffec6e431fe8b391")
.withTxHash("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9")
.build();

ContractCreation contractCreation2 = ContractCreation.builder()
.withContractAddress("0x5eac95ad5b287cf44e058dcf694419333b796123")
.withContractCreator("0x7c675b7450e878e5af8550b41df42d134674e61f")
.withTxHash("0x79cdfec19e5a86d9022680a4d1c86d3d8cd76c21c01903a2f02c127a0a7dbfb3")
.build();

assertTrue(contractCreations.contains(contractCreation1));
assertTrue(contractCreations.contains(contractCreation2));
}

@Test
void contractCreationInvalidParamWithError() {
assertThrows(EtherScanInvalidAddressException.class,
() -> getApi().contract()
.contractCreation(Collections.singletonList("0xBBbc244D798123fDe783fCc1C72d3Bb8C189414")));
}
}

0 comments on commit 3b1af9d

Please sign in to comment.