com.openelements.hedera.spring.implementation.ContractVerificationClientImplementation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hedera-spring Show documentation
Show all versions of hedera-spring Show documentation
Library to work with Hedera in Spring
The newest version!
package com.openelements.hedera.spring.implementation;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hedera.hashgraph.sdk.ContractId;
import com.openelements.hedera.base.ContractVerificationClient;
import com.openelements.hedera.base.ContractVerificationState;
import com.openelements.hedera.base.HederaException;
import com.openelements.hedera.base.implementation.HederaNetwork;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.StreamSupport;
import org.jspecify.annotations.NonNull;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestClient;
public class ContractVerificationClientImplementation implements ContractVerificationClient {
private static final String CONTRACT_VERIFICATION_URL = "https://server-verify.hashscan.io";
private record VerifyRequest(String address, String chain, String creatorTxHash, String chosenContract,
Map files) {
}
private final HederaNetwork hederaNetwork;
private final ObjectMapper objectMapper;
private final RestClient restClient;
public ContractVerificationClientImplementation(@NonNull final HederaNetwork hederaNetwork) {
this.hederaNetwork = Objects.requireNonNull(hederaNetwork, "hederaNetwork must not be null");
objectMapper = new ObjectMapper();
restClient = RestClient.create();
}
@NonNull
private String getChainId() throws HederaException {
if (hederaNetwork == HederaNetwork.CUSTOM) {
throw new HederaException(
"A custom Hedera network is not supported for smart contract verification. Please use MainNet, TestNet or PreviewNet.");
}
return hederaNetwork.getChainId() + "";
}
private void handleError(@NonNull final HttpRequest request, @NonNull final ClientHttpResponse response)
throws IOException {
Objects.requireNonNull(response, "response must not be null");
final String error;
try {
final String body = new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8);
try {
final JsonNode rootNode = objectMapper.readTree(body);
final JsonNode errorNode = rootNode.get("error");
if (errorNode != null) {
error = errorNode.asText();
} else {
final JsonNode messageNode = rootNode.get("message");
if (messageNode != null) {
error = messageNode.asText();
} else {
error = body;
}
}
} catch (final Exception e) {
throw new IOException("Error parsing body as JSON: " + body, e);
}
} catch (final Exception e) {
throw new IOException("Error (" + response.getStatusCode() + "): " + response.getStatusText());
}
throw new IOException("Error (" + response.getStatusCode() + "): " + error);
}
@Override
public ContractVerificationState verify(@NonNull final ContractId contractId, @NonNull final String contractName,
@NonNull final Map files) throws HederaException {
Objects.requireNonNull(contractId, "contractId must not be null");
Objects.requireNonNull(contractName, "contractName must not be null");
Objects.requireNonNull(files, "files must not be null");
final ContractVerificationState state = checkVerification(contractId);
if (state != ContractVerificationState.NONE) {
throw new IllegalStateException("Contract is already verified");
}
final VerifyRequest verifyRequest = new VerifyRequest(
contractId.toSolidityAddress(),
getChainId(),
"",
"",
files
);
try {
final String resultBody = restClient.post()
.uri(CONTRACT_VERIFICATION_URL + "/verify")
.contentType(APPLICATION_JSON)
.accept(APPLICATION_JSON)
.body(verifyRequest)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
handleError(request, response);
}).body(String.class);
final JsonNode rootNode = objectMapper.readTree(resultBody);
final JsonNode resultNode = rootNode.get("result");
if (resultNode != null) {
if (resultNode.isArray()) {
final List results = StreamSupport
.stream(resultNode.spliterator(), false)
.toList();
if (results.size() != 1) {
throw new RuntimeException("Expected exactly one result, got " + results.size());
}
final JsonNode result = results.get(0);
final JsonNode statusNode = result.get("status");
if (statusNode != null) {
if (statusNode.asText().equals("perfect")) {
return ContractVerificationState.FULL;
} else if (statusNode.asText().equals("false")) {
return ContractVerificationState.NONE;
} else {
throw new RuntimeException("Status is not success: " + statusNode.asText());
}
} else {
throw new RuntimeException("No status in response");
}
} else {
throw new RuntimeException("Result is not an array");
}
} else {
throw new RuntimeException("No result in response");
}
} catch (Exception e) {
throw new HederaException("Error verification step", e);
}
}
@Override
public ContractVerificationState checkVerification(@NonNull final ContractId contractId) throws HederaException {
Objects.requireNonNull(contractId, "contractId must not be null");
final String uri =
CONTRACT_VERIFICATION_URL + "/check-by-addresses" + "?addresses=" + contractId.toSolidityAddress()
+ "&chainIds=" + getChainId();
try {
final String resultBody = restClient.get()
.uri(uri)
.accept(APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
handleError(request, response);
}).body(String.class);
final JsonNode rootNode = objectMapper.readTree(resultBody);
if (rootNode.isArray()) {
final List results = StreamSupport
.stream(rootNode.spliterator(), false)
.toList();
if (results.size() != 1) {
throw new RuntimeException("Expected exactly one result, got " + results.size());
}
final JsonNode result = results.get(0);
final JsonNode statusNode = result.get("status");
if (statusNode != null) {
if (statusNode.asText().equals("perfect")) {
return ContractVerificationState.FULL;
} else if (statusNode.asText().equals("false")) {
return ContractVerificationState.NONE;
} else {
throw new RuntimeException("Status is not success: " + statusNode.asText());
}
} else {
throw new RuntimeException("No status in response");
}
} else {
throw new RuntimeException("Result is not an array");
}
} catch (Exception e) {
throw new HederaException("Error verification step", e);
}
}
@Override
public boolean checkVerification(@NonNull final ContractId contractId, @NonNull final String fileName,
@NonNull final String fileContent) throws HederaException {
Objects.requireNonNull(contractId, "contractId must not be null");
Objects.requireNonNull(fileName, "fileName must not be null");
Objects.requireNonNull(fileContent, "fileContent must not be null");
final ContractVerificationState state = checkVerification(contractId);
if (state != ContractVerificationState.FULL) {
throw new IllegalStateException("Contract is not verified");
}
final String uri = CONTRACT_VERIFICATION_URL + "/files/" + getChainId() + "/" + contractId.toSolidityAddress();
try {
final String resultBody = restClient.get()
.uri(uri)
.header("accept", "application/json")
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
handleError(request, response);
}).body(String.class);
final JsonNode rootNode = objectMapper.readTree(resultBody);
if (rootNode.isArray()) {
final List results = StreamSupport
.stream(rootNode.spliterator(), false)
.filter(node -> node.get("name").asText().equals(fileName))
.toList();
if (results.size() != 1) {
throw new RuntimeException("Expected exactly one result, got " + results.size());
}
final JsonNode result = results.get(0);
final JsonNode contentNode = result.get("content");
if (contentNode != null) {
return contentNode.asText().equals(fileContent);
} else {
throw new RuntimeException("No content in response");
}
} else {
throw new RuntimeException("Result is not an array");
}
} catch (Exception e) {
throw new HederaException("Error verification step", e);
}
}
}