Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract creation API #27

Merged
merged 6 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")));
}
}
Loading