Skip to content

Commit 3b1af9d

Browse files
authored
Merge pull request #27 from Blackmorse/contract_creation
Contract creation API
2 parents c64a301 + 06464f8 commit 3b1af9d

File tree

10 files changed

+204
-5
lines changed

10 files changed

+204
-5
lines changed

src/main/java/io/goodforgod/api/etherscan/AccountAPIProvider.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public List<Balance> balances(@NotNull List<String> addresses) throws EtherScanE
9595
final List<List<String>> addressesAsBatches = BasicUtils.partition(addresses, 20);
9696

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

114-
private String toAddressParam(List<String> addresses) {
115-
return String.join(",", addresses);
116-
}
117-
118115
@NotNull
119116
@Override
120117
public List<Tx> txs(@NotNull String address) throws EtherScanException {

src/main/java/io/goodforgod/api/etherscan/ContractAPI.java

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import io.goodforgod.api.etherscan.error.EtherScanException;
44
import io.goodforgod.api.etherscan.model.Abi;
5+
import io.goodforgod.api.etherscan.model.ContractCreation;
6+
import java.util.List;
57
import org.jetbrains.annotations.NotNull;
68

79
/**
@@ -21,4 +23,13 @@ public interface ContractAPI {
2123
*/
2224
@NotNull
2325
Abi contractAbi(@NotNull String address) throws EtherScanException;
26+
27+
/**
28+
* Returns a contract's deployer address and transaction hash it was created, up to 5 at a time.
29+
*
30+
* @param contractAddresses - list of addresses to fetch
31+
* @throws EtherScanException parent exception class
32+
*/
33+
@NotNull
34+
List<ContractCreation> contractCreation(@NotNull List<String> contractAddresses) throws EtherScanException;
2435
}

src/main/java/io/goodforgod/api/etherscan/ContractAPIProvider.java

+30
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
import io.goodforgod.api.etherscan.http.EthHttpClient;
66
import io.goodforgod.api.etherscan.manager.RequestQueueManager;
77
import io.goodforgod.api.etherscan.model.Abi;
8+
import io.goodforgod.api.etherscan.model.ContractCreation;
9+
import io.goodforgod.api.etherscan.model.response.ContractCreationResponseTO;
810
import io.goodforgod.api.etherscan.model.response.StringResponseTO;
911
import io.goodforgod.api.etherscan.util.BasicUtils;
12+
import java.util.List;
13+
import java.util.stream.Collectors;
1014
import org.jetbrains.annotations.NotNull;
1115

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

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

29+
private static final String ACT_CONTRACT_CREATION_PARAM = "getcontractcreation";
30+
31+
private static final String ACT_CONTRACT_CREATION = ACT_PREFIX + ACT_CONTRACT_CREATION_PARAM;
32+
33+
private static final String ACT_CONTRACT_ADDRESSES_PARAM = "&contractaddresses=";
34+
2535
ContractAPIProvider(RequestQueueManager requestQueueManager,
2636
String baseUrl,
2737
EthHttpClient executor,
@@ -44,4 +54,24 @@ public Abi contractAbi(@NotNull String address) throws EtherScanException {
4454
? Abi.nonVerified()
4555
: Abi.verified(response.getResult());
4656
}
57+
58+
@NotNull
59+
@Override
60+
public List<ContractCreation> contractCreation(@NotNull List<String> contractAddresses) throws EtherScanException {
61+
BasicUtils.validateAddresses(contractAddresses);
62+
final String urlParam = ACT_CONTRACT_CREATION + ACT_CONTRACT_ADDRESSES_PARAM
63+
+ BasicUtils.toAddressParam(contractAddresses);
64+
final ContractCreationResponseTO response = getRequest(urlParam, ContractCreationResponseTO.class);
65+
if (response.getStatus() != 1 && response.getMessage().startsWith("NOTOK")) {
66+
throw new EtherScanResponseException(response);
67+
}
68+
69+
return response.getResult().stream()
70+
.map(to -> ContractCreation.builder()
71+
.withContractCreator(to.getContractCreator())
72+
.withContractAddress(to.getContractAddress())
73+
.withTxHash(to.getTxHash())
74+
.build())
75+
.collect(Collectors.toList());
76+
}
4777
}

src/main/java/io/goodforgod/api/etherscan/StatisticAPI.java

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public interface StatisticAPI {
1818
* ERC20 token total Supply
1919
* <a href=
2020
* "https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress">EtherScan<a>
21+
* Returns the current amount of an ERC-20 token in circulation.
2122
*
2223
* @param contract contract address
2324
* @return token supply for specified contract
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package io.goodforgod.api.etherscan.model;
2+
3+
import java.util.Objects;
4+
5+
public class ContractCreation {
6+
7+
private final String contractAddress;
8+
private final String contractCreator;
9+
private final String txHash;
10+
11+
private ContractCreation(String contractAddress, String contractCreator, String txHash) {
12+
this.contractAddress = contractAddress;
13+
this.contractCreator = contractCreator;
14+
this.txHash = txHash;
15+
}
16+
17+
public String getContractAddress() {
18+
return contractAddress;
19+
}
20+
21+
public String getContractCreator() {
22+
return contractCreator;
23+
}
24+
25+
public String getTxHash() {
26+
return txHash;
27+
}
28+
29+
@Override
30+
public boolean equals(Object o) {
31+
if (this == o)
32+
return true;
33+
if (o == null || getClass() != o.getClass())
34+
return false;
35+
ContractCreation that = (ContractCreation) o;
36+
return Objects.equals(contractAddress, that.contractAddress) && Objects.equals(contractCreator, that.contractCreator)
37+
&& Objects.equals(txHash, that.txHash);
38+
}
39+
40+
@Override
41+
public int hashCode() {
42+
return Objects.hash(contractAddress, contractCreator, txHash);
43+
}
44+
45+
@Override
46+
public String toString() {
47+
return "ContractCreation{" +
48+
"contractAddress='" + contractAddress + '\'' +
49+
", contractCreator='" + contractCreator + '\'' +
50+
", txHash='" + txHash + '\'' +
51+
'}';
52+
}
53+
54+
public static ContractCreationBuilder builder() {
55+
return new ContractCreationBuilder();
56+
}
57+
58+
public static final class ContractCreationBuilder {
59+
60+
private String contractAddress;
61+
private String contractCreator;
62+
private String txHash;
63+
64+
private ContractCreationBuilder() {}
65+
66+
public ContractCreationBuilder withContractAddress(String contractAddress) {
67+
this.contractAddress = contractAddress;
68+
return this;
69+
}
70+
71+
public ContractCreationBuilder withContractCreator(String contractCreator) {
72+
this.contractCreator = contractCreator;
73+
return this;
74+
}
75+
76+
public ContractCreationBuilder withTxHash(String txHash) {
77+
this.txHash = txHash;
78+
return this;
79+
}
80+
81+
public ContractCreation build() {
82+
return new ContractCreation(contractAddress, contractCreator, txHash);
83+
}
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.goodforgod.api.etherscan.model.response;
2+
3+
public class ContractCreationResponseTO extends BaseListResponseTO<ContractCreationTO> {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.goodforgod.api.etherscan.model.response;
2+
3+
public class ContractCreationTO {
4+
5+
private String contractAddress;
6+
private String contractCreator;
7+
private String txHash;
8+
9+
public String getContractAddress() {
10+
return contractAddress;
11+
}
12+
13+
public String getContractCreator() {
14+
return contractCreator;
15+
}
16+
17+
public String getTxHash() {
18+
return txHash;
19+
}
20+
}

src/main/java/io/goodforgod/api/etherscan/util/BasicUtils.java

+4
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,8 @@ public static List<List<String>> partition(List<String> list, int pairSize) {
149149

150150
return partitioned;
151151
}
152+
153+
public static String toAddressParam(List<String> addresses) {
154+
return String.join(",", addresses);
155+
}
152156
}

src/test/java/io/goodforgod/api/etherscan/ApiRunner.java

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.goodforgod.api.etherscan;
22

33
import io.goodforgod.api.etherscan.manager.RequestQueueManager;
4+
import io.goodforgod.api.etherscan.util.BasicUtils;
45
import java.util.Map;
56
import org.junit.jupiter.api.AfterAll;
67
import org.junit.jupiter.api.Assertions;
@@ -15,6 +16,7 @@ public class ApiRunner extends Assertions {
1516
static {
1617
API_KEY = System.getenv().entrySet().stream()
1718
.filter(e -> e.getKey().startsWith("ETHERSCAN_API_KEY"))
19+
.filter(e -> !BasicUtils.isBlank(e.getValue()))
1820
.map(Map.Entry::getValue)
1921
.findFirst()
2022
.orElse(DEFAULT_KEY);

src/test/java/io/goodforgod/api/etherscan/contract/ContractApiTests.java

+46
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import io.goodforgod.api.etherscan.ApiRunner;
44
import io.goodforgod.api.etherscan.error.EtherScanInvalidAddressException;
55
import io.goodforgod.api.etherscan.model.Abi;
6+
import io.goodforgod.api.etherscan.model.ContractCreation;
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
import java.util.List;
610
import org.junit.jupiter.api.Test;
711

812
/**
@@ -37,4 +41,46 @@ void correctParamWithEmptyExpectedResult() {
3741
assertNotNull(abi);
3842
assertTrue(abi.isVerified());
3943
}
44+
45+
@Test
46+
void correctContractCreation() {
47+
List<ContractCreation> contractCreations = getApi().contract()
48+
.contractCreation(Collections.singletonList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"));
49+
50+
assertEquals(1, contractCreations.size());
51+
ContractCreation contractCreation = contractCreations.get(0);
52+
53+
assertEquals("0xbb9bc244d798123fde783fcc1c72d3bb8c189413", contractCreation.getContractAddress());
54+
assertEquals("0x793ea9692ada1900fbd0b80fffec6e431fe8b391", contractCreation.getContractCreator());
55+
assertEquals("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9", contractCreation.getTxHash());
56+
}
57+
58+
@Test
59+
void correctMultipleContractCreation() {
60+
List<ContractCreation> contractCreations = getApi().contract().contractCreation(
61+
Arrays.asList("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413", "0x5EaC95ad5b287cF44E058dCf694419333b796123"));
62+
assertEquals(2, contractCreations.size());
63+
64+
ContractCreation contractCreation1 = ContractCreation.builder()
65+
.withContractAddress("0xbb9bc244d798123fde783fcc1c72d3bb8c189413")
66+
.withContractCreator("0x793ea9692ada1900fbd0b80fffec6e431fe8b391")
67+
.withTxHash("0xe9ebfecc2fa10100db51a4408d18193b3ac504584b51a4e55bdef1318f0a30f9")
68+
.build();
69+
70+
ContractCreation contractCreation2 = ContractCreation.builder()
71+
.withContractAddress("0x5eac95ad5b287cf44e058dcf694419333b796123")
72+
.withContractCreator("0x7c675b7450e878e5af8550b41df42d134674e61f")
73+
.withTxHash("0x79cdfec19e5a86d9022680a4d1c86d3d8cd76c21c01903a2f02c127a0a7dbfb3")
74+
.build();
75+
76+
assertTrue(contractCreations.contains(contractCreation1));
77+
assertTrue(contractCreations.contains(contractCreation2));
78+
}
79+
80+
@Test
81+
void contractCreationInvalidParamWithError() {
82+
assertThrows(EtherScanInvalidAddressException.class,
83+
() -> getApi().contract()
84+
.contractCreation(Collections.singletonList("0xBBbc244D798123fDe783fCc1C72d3Bb8C189414")));
85+
}
4086
}

0 commit comments

Comments
 (0)