hiero-ledger / hiero-ledger/hiero-enterprise-java

Proposal: share ContractVerificationClient logic in base and use framework-specific transports

Open
#89 1 comment 0 reactions 0 assignees View on GitHub
pending-review
Dominant language
Java
Stars
6
Forks
21
Avg merge
10h 27m
Merged PRs (30d)
37

Description

### Summary

There is already an open issue for the immediate MicroProfile gap: [#88: microprofile: Implement ContractVerificationClient (Hashscan verify API)](https://github.com/hiero-ledger/hiero-enterprise-java/issues/88).

While looking at that issue, I noticed that simply copying the Spring implementation into MicroProfile would fix the immediate problem, but it would duplicate contract verification behavior across both framework modules. This proposal suggests using a shape similar to the existing mirror-node client: shared logic in `base`, framework-specific REST/JSON implementations in Spring and MicroProfile.

### Current situation

`ContractVerificationClient` is defined in `hiero-enterprise-base`, but implementation behavior currently lives in framework modules.

Today:

- Spring has a complete implementation: `hiero-enterprise-spring/.../ContractVerificationClientImplementation.java`
- MicroProfile has an incomplete implementation: `hiero-enterprise-microprofile/.../ContractVerificationClientImpl.java`
- In MicroProfile, `checkVerification(ContractId)` and `verify(...)` currently throw `UnsupportedOperationException`.
- The MicroProfile file-level check calls `checkVerification(ContractId)` first, so it cannot work end-to-end today.
- The Spring implementation currently owns both shared verification rules and framework-specific details such as HTTP calls and JSON parsing.

This creates a risk of behavior drift between Spring and MicroProfile.

### Existing repo pattern: mirror-node client

The repo already has a similar pattern in the mirror-node layer.

In `hiero-enterprise-base`:

- `MirrorNodeClient` is the public API.
- `AbstractMirrorNodeClient` implements the shared orchestration.
- `MirrorNodeRestClient` is the REST adapter contract.
- `MirrorNodeJsonConverter` is the JSON/domain conversion contract.

Then each framework module provides its own implementations:

Spring:

- `MirrorNodeClientImpl extends AbstractMirrorNodeClient`
- `MirrorNodeRestClientImpl implements MirrorNodeRestClient` using Spring `RestClient`
- `MirrorNodeJsonConverterImpl implements MirrorNodeJsonConverter` using Jackson

MicroProfile:

- `MirrorNodeClientImpl extends AbstractMirrorNodeClient`
- `MirrorNodeRestClientImpl implements MirrorNodeRestClient` using JAX-RS
- `MirrorNodeJsonConverterImpl implements MirrorNodeJsonConverter` using Jakarta JSON

That keeps shared behavior in `base`, while each framework keeps its native HTTP/JSON stack.

### Proposal

Apply the same idea to contract verification.

Suggested base types:

```java
public interface ContractVerificationRestClient {
@NonNull
ContractVerificationState checkVerification(
@NonNull String chainId, @NonNull String contractAddress)
throws HieroException;

@NonNull
ContractVerificationState verify(
@NonNull String chainId,
@NonNull String contractAddress,
@NonNull String contractName,
@NonNull Map files)
throws HieroException;

@NonNull
List fetchFiles(
@NonNull String chainId, @NonNull String contractAddress)
throws HieroException;
}

public record ContractVerificationFile(
@NonNull String name,
@NonNull String content) {}
```

Then add a shared implementation in `base`, for example:

```java
public final class DefaultContractVerificationClient implements ContractVerificationClient {
private final HieroConfig hieroConfig;
private final ContractVerificationRestClient restClient;

@Override
public ContractVerificationState checkVerification(@NonNull ContractId contractId)
throws HieroException {
return restClient.checkVerification(getChainId(), contractId.toSolidityAddress());
}

@Override
public boolean checkVerification(
@NonNull ContractId contractId,
@NonNull String fileName,
@NonNull String fileContent)
throws HieroException {
if (checkVerification(contractId) != ContractVerificationState.FULL) {
throw new IllegalStateException("Contract is not verified");
}

final List matches =
restClient.fetchFiles(getChainId(), contractId.toSolidityAddress()).stream()
.filter(file -> file.name().equals(fileName))
.toList();

if (matches.size() != 1) {
throw new RuntimeException("Expected exactly one result, got " + matches.size());
}

return Objects.equals(matches.get(0).content(), fileContent);
}
}
```

The framework modules would provide the concrete REST/JSON implementation:

- Spring: `ContractVerificationRestClientImpl` using Spring `RestClient` + Jackson
- MicroProfile: `ContractVerificationRestClientImpl` using JAX-RS `Client` + Jakarta JSON

### Why this direction

This would:

- Solve the MicroProfile gap described in #88.
- Avoid copying Spring’s full implementation into MicroProfile.
- Keep contract verification rules in one place.
- Keep `hiero-enterprise-base` free of concrete HTTP/JSON dependencies.
- Match the existing mirror-node layering style.
- Allow tests for shared verification behavior in `base` using a fake `ContractVerificationRestClient`.

### Scope / acceptance criteria

If maintainers agree with this direction:

- `ContractVerificationClient` behavior is implemented once in `hiero-enterprise-base`.
- Spring and MicroProfile delegate to the shared implementation.
- MicroProfile no longer throws `UnsupportedOperationException` for `checkVerification(ContractId)` or `verify(...)`.
- Existing Spring behavior for `FULL`, `NONE`, verification, and file-content checks is preserved.
- No Spring, Jackson, Jakarta JSON, or JAX-RS dependencies are added to `hiero-enterprise-base`.
- Base-level tests cover shared verification rules with a fake `ContractVerificationRestClient`.

### Open questions

- Should the shared implementation live in `org.hiero.base.implementation`, or somewhere closer to `org.hiero.base.verification`?
- Should the Hashscan base URL remain hardcoded in the framework-specific `ContractVerificationRestClientImpl`, or become configurable?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.