diff --git a/hiero-enterprise-base/pom.xml b/hiero-enterprise-base/pom.xml
index 05d62e78..e99b9f94 100644
--- a/hiero-enterprise-base/pom.xml
+++ b/hiero-enterprise-base/pom.xml
@@ -23,6 +23,14 @@
com.hedera.hashgraph
sdk
+
+ com.open-elements.solidity
+ abi-parser
+
+
+ org.web3j
+ abi
+
io.github.cdimascio
dotenv-java
diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractEventInstance.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractEventInstance.java
new file mode 100644
index 00000000..4ef98770
--- /dev/null
+++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractEventInstance.java
@@ -0,0 +1,26 @@
+package com.openelements.hiero.base.data;
+
+import com.hedera.hashgraph.sdk.ContractId;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameterType;
+import java.util.List;
+import java.util.Objects;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+public record ContractEventInstance(@NonNull ContractId contractId, @Nullable String eventName,
+ @NonNull List parameters) {
+
+ public ContractEventInstance {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ Objects.requireNonNull(parameters, "parameters must be provided");
+ }
+
+ public record ParameterInstance(@NonNull String name, @NonNull AbiParameterType type, @NonNull byte[] value) {
+
+ public ParameterInstance {
+ Objects.requireNonNull(name, "name must be provided");
+ Objects.requireNonNull(type, "type must be provided");
+ Objects.requireNonNull(value, "value must be provided");
+ }
+ }
+}
diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractLog.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractLog.java
new file mode 100644
index 00000000..3aae4747
--- /dev/null
+++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/ContractLog.java
@@ -0,0 +1,83 @@
+package com.openelements.hiero.base.data;
+
+import com.hedera.hashgraph.sdk.ContractId;
+import com.openelements.hiero.base.solidity.SolidityTools;
+import com.openelements.hiero.smartcontract.abi.model.AbiEvent;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Objects;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Represents a log entry for a contract.
+ *
+ * @param address The hex encoded EVM address of the contract. example:
+ * {@code 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef} - pattern:
+ * {@code ^0x[0-9A-Fa-f]{40}$}
+ * @param bloom The hex encoded bloom filter of the contract log. example:
+ * {@code 0x549358c4c2e573e02410ef7b5a5ffa5f36dd7398}
+ * @param contractId Network entity ID in the format of shard.realm.num example: {@code 0.0.1234}
+ * @param data The hex encoded data of the contract log - example:
+ * {@code 0x00000000000000000000000000000000000000000000000000000000000000fa}
+ * @param index The index of the contract log in the chain of logs for an execution
+ * @param topics A list of hex encoded topics associated with this log event. example:
+ * {@code List [ "0xf4757a49b326036464bec6fe419a4ae38c8a02ce3e68bf0809674f6aab8ad300" ]}
+ * @param block_hash The hex encoded block (record file chain) hash. example:
+ * {@code
+ * 0x553f9311833391c0a3b2f9ed64540a89f2190a511986cd94889f1c0cf7fa63e898b1c6730f14a61755d1fb4ca05fb073}
+ * @param blockNumber The block height calculated as the number of record files starting from zero since network
+ * start.
+ * @param rootContractId The executed contract that created this contract log. pattern:
+ * {@code ^\d{1,10}\.\d{1,10}\.\d{1,10}$}. example: {@code 0.0.1234}
+ * @param timestamp A Unix timestamp in seconds.nanoseconds format. pattern: {@code ^\d{1,10}(\.\d{1,9})?$}.
+ * example: {@code 1586567700.453054000}
+ * @param transactionHash A hex encoded transaction hash. example:
+ * {@code 0x397022d1e5baeb89d0ab66e6bf602640610e6fb7e55d78638db861e2c6339aa9}
+ * @param transactionIndex The position of the transaction in the block
+ */
+public record ContractLog(@NonNull String address, @Nullable String bloom, @Nullable ContractId contractId,
+ @Nullable String data, long index,
+ @NonNull List topics, @NonNull String block_hash, long blockNumber,
+ @Nullable ContractId rootContractId, @NonNull String timestamp,
+ @NonNull String transactionHash,
+ @Nullable Long transactionIndex) {
+
+ public ContractLog {
+ Objects.requireNonNull(address, "address must not be null");
+ Objects.requireNonNull(topics, "topics must not be null");
+ Objects.requireNonNull(block_hash, "block_hash must not be null");
+ Objects.requireNonNull(timestamp, "timestamp must not be null");
+ Objects.requireNonNull(transactionHash, "transactionHash must not be null");
+ }
+
+ public ZonedDateTime getTimestamp() {
+ String[] parts = timestamp.split("\\.");
+ long seconds = Long.parseLong(parts[0]);
+ int nanoseconds = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
+ Instant instant = Instant.ofEpochSecond(seconds, nanoseconds);
+ return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
+ }
+
+ public boolean isEventOfType(final @NonNull AbiEvent event) {
+ Objects.requireNonNull(event, "event");
+ if (event.anonymous()) {
+ throw new IllegalStateException("Cannot check anonymous event");
+ }
+ if (topics.isEmpty()) {
+ return false;
+ }
+ final String eventHashAsHex = event.createEventSignatureHashAsHex();
+ return topics.get(0).equals(eventHashAsHex);
+ }
+
+ public ContractEventInstance asEventInstance(final @NonNull AbiEvent event) {
+ Objects.requireNonNull(event, "event must not be null");
+ if (!isEventOfType(event)) {
+ throw new IllegalArgumentException("Event does not match log");
+ }
+ return SolidityTools.asEventInstance(this, event);
+ }
+}
diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/Order.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/Order.java
new file mode 100644
index 00000000..00afb41d
--- /dev/null
+++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/data/Order.java
@@ -0,0 +1,6 @@
+package com.openelements.hiero.base.data;
+
+public enum Order {
+ ASC,
+ DESC
+}
diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/mirrornode/ContractRepository.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/mirrornode/ContractRepository.java
new file mode 100644
index 00000000..3778f622
--- /dev/null
+++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/mirrornode/ContractRepository.java
@@ -0,0 +1,246 @@
+package com.openelements.hiero.base.mirrornode;
+
+import com.hedera.hashgraph.sdk.ContractId;
+import com.openelements.hiero.base.data.ContractLog;
+import com.openelements.hiero.base.data.Order;
+import com.openelements.hiero.base.data.Page;
+import com.openelements.hiero.smartcontract.abi.model.AbiEvent;
+import java.time.ZonedDateTime;
+import java.util.Objects;
+import org.jspecify.annotations.NonNull;
+
+public interface ContractRepository {
+
+ default Page findLogsByContract(@NonNull String contractId) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId));
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, @NonNull Order order) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), order);
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), abiEvent);
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), abiEvent, order);
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, int pageLimit) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), pageLimit);
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, @NonNull Order order, int pageLimit) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), order, pageLimit);
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ int pageLimit) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), abiEvent, pageLimit);
+ }
+
+ default Page findLogsByContract(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, int pageLimit) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContract(ContractId.fromString(contractId), abiEvent, order, pageLimit);
+ }
+
+ default Page findLogsByContract(@NonNull ContractId contractId) {
+ return findLogsByContract(contractId, Order.DESC);
+ }
+
+ default Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent) {
+ return findLogsByContract(contractId, abiEvent, Order.DESC);
+ }
+
+ default Page findLogsByContract(@NonNull ContractId contractId, int pageLimit) {
+ return findLogsByContract(contractId, Order.DESC, pageLimit);
+ }
+
+ default Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ int pageLimit) {
+ return findLogsByContract(contractId, abiEvent, Order.DESC, pageLimit);
+ }
+
+ Page findLogsByContract(@NonNull ContractId contractId, @NonNull Order order);
+
+ Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order);
+
+ Page findLogsByContract(@NonNull ContractId contractId, @NonNull Order order, int pageLimit);
+
+ Page findLogsByContract(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, int pageLimit);
+
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull Order order,
+ ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), order, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, order, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, int pageLimit,
+ ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull Order order,
+ int pageLimit, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), order, pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ int pageLimit, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, int pageLimit, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractBeforeTimestamp(ContractId.fromString(contractId), abiEvent, order, pageLimit,
+ timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId,
+ ZonedDateTime timestamp) {
+ return findLogsByContractBeforeTimestamp(contractId, Order.DESC, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId,
+ @NonNull AbiEvent abiEvent, ZonedDateTime timestamp) {
+ return findLogsByContractBeforeTimestamp(contractId, abiEvent, Order.DESC, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, int pageLimit,
+ ZonedDateTime timestamp) {
+ return findLogsByContractBeforeTimestamp(contractId, Order.DESC, pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId,
+ @NonNull AbiEvent abiEvent,
+ int pageLimit, ZonedDateTime timestamp) {
+ return findLogsByContractBeforeTimestamp(contractId, abiEvent, Order.DESC, pageLimit, timestamp);
+ }
+
+ Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull Order order,
+ ZonedDateTime timestamp);
+
+ Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, ZonedDateTime timestamp);
+
+ Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull Order order,
+ int pageLimit, ZonedDateTime timestamp);
+
+ Page findLogsByContractBeforeTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, int pageLimit, ZonedDateTime timestamp);
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull Order order,
+ ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), order, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, order, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, int pageLimit,
+ ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull Order order,
+ int pageLimit, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), order, pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ int pageLimit, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull String contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, int pageLimit, ZonedDateTime timestamp) {
+ Objects.requireNonNull(contractId, "contractId must be provided");
+ return findLogsByContractAfterTimestamp(ContractId.fromString(contractId), abiEvent, order, pageLimit,
+ timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId,
+ ZonedDateTime timestamp) {
+ return findLogsByContractAfterTimestamp(contractId, Order.DESC, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId,
+ @NonNull AbiEvent abiEvent, ZonedDateTime timestamp) {
+ return findLogsByContractAfterTimestamp(contractId, abiEvent, Order.DESC, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, int pageLimit,
+ ZonedDateTime timestamp) {
+ return findLogsByContractAfterTimestamp(contractId, Order.DESC, pageLimit, timestamp);
+ }
+
+ default Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId,
+ @NonNull AbiEvent abiEvent,
+ int pageLimit, ZonedDateTime timestamp) {
+ return findLogsByContractAfterTimestamp(contractId, abiEvent, Order.DESC, pageLimit, timestamp);
+ }
+
+ Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull Order order,
+ ZonedDateTime timestamp);
+
+ Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, ZonedDateTime timestamp);
+
+ Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull Order order,
+ int pageLimit, ZonedDateTime timestamp);
+
+ Page findLogsByContractAfterTimestamp(@NonNull ContractId contractId, @NonNull AbiEvent abiEvent,
+ @NonNull Order order, int pageLimit, ZonedDateTime timestamp);
+}
diff --git a/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/solidity/SolidityTools.java b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/solidity/SolidityTools.java
new file mode 100644
index 00000000..7ea356c0
--- /dev/null
+++ b/hiero-enterprise-base/src/main/java/com/openelements/hiero/base/solidity/SolidityTools.java
@@ -0,0 +1,95 @@
+package com.openelements.hiero.base.solidity;
+
+import com.openelements.hiero.base.data.ContractEventInstance;
+import com.openelements.hiero.base.data.ContractEventInstance.ParameterInstance;
+import com.openelements.hiero.base.data.ContractLog;
+import com.openelements.hiero.smartcontract.abi.model.AbiEvent;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameter;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameterType;
+import com.openelements.hiero.smartcontract.abi.util.HexConverter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.jspecify.annotations.NonNull;
+import org.web3j.abi.TypeDecoder;
+import org.web3j.abi.datatypes.Address;
+import org.web3j.abi.datatypes.Bool;
+import org.web3j.abi.datatypes.DynamicStruct;
+import org.web3j.abi.datatypes.Type;
+import org.web3j.abi.datatypes.Utf8String;
+import org.web3j.abi.datatypes.generated.Bytes32;
+import org.web3j.abi.datatypes.generated.Uint256;
+
+public class SolidityTools {
+
+ private static Class extends Type> getMatchingTypeClass(
+ AbiParameterType abiParameter) {
+ return switch (abiParameter) {
+ case AbiParameterType.ADDRESS -> Address.class;
+ case STRING -> Utf8String.class;
+ case BYTE32 -> Bytes32.class;
+ case AbiParameterType.BOOL -> Bool.class;
+ case UINT256 -> Uint256.class;
+ case UINT -> Uint256.class;
+ case TUPLE -> DynamicStruct.class;
+ default -> throw new IllegalArgumentException("Unknown type: " + abiParameter);
+ };
+ }
+
+ public static ContractEventInstance asEventInstance(final @NonNull ContractLog contractLog,
+ final @NonNull AbiEvent event) {
+ Objects.requireNonNull(contractLog, "contractLog must not be null");
+ Objects.requireNonNull(event, "event must not be null");
+
+ if (!event.anonymous()) {
+ if (!contractLog.isEventOfType(event)) {
+ throw new IllegalArgumentException("Event does not match log");
+ }
+ }
+ //see https://github.com/web3/web3.js/blob/bf1691765bd9d4b0f7a4479e915207707d69226d/packages/web3-eth-abi/src/api/logs_api.ts#L74
+ //see https://docs.soliditylang.org/en/latest/abi-spec.html#index-0
+ int topicIndex = 0;
+ if (!event.anonymous()) {
+ topicIndex = 1;
+ }
+
+ final List indexedParameters = event.getIndexedInputParameters();
+ final List nonIndexedParameters = event.getNonIndexedInputParameters();
+ final Map mapping = new HashMap<>();
+
+ for (AbiParameter parameter : indexedParameters) {
+ mapping.put(parameter, new ParameterInstance(parameter.name(), parameter.type(),
+ contractLog.topics().get(topicIndex++).getBytes(StandardCharsets.UTF_8)));
+ }
+
+ final String data = contractLog.data().substring(2);
+ int offset = 0;
+ for (AbiParameter parameter : nonIndexedParameters) {
+ final Class extends Type> web3jTypeClass = getMatchingTypeClass(parameter.type());
+ final Type web3jType = TypeDecoder.decode(contractLog.data(), offset, web3jTypeClass);
+ final int byteLength = web3jType.bytes32PaddedLength();
+ final String substring = data.substring(offset, byteLength * 2);
+ final byte[] value = HexConverter.hexToBytes(substring);
+ mapping.put(parameter, new ParameterInstance(parameter.name(), parameter.type(), value));
+ offset += byteLength * 2;
+ }
+
+ final List parameters = new ArrayList<>();
+ for (AbiParameter parameter : event.inputs()) {
+ parameters.add(mapping.get(parameter));
+ }
+ return new ContractEventInstance(contractLog.contractId(), event.name(),
+ Collections.unmodifiableList(parameters));
+ }
+
+ public static T getValue(ParameterInstance parameterInstance) {
+ final Class extends Type> web3jTypeClass = getMatchingTypeClass(parameterInstance.type());
+ final String dataAsHex = HexConverter.bytesToHex(parameterInstance.value());
+ final Type web3jType = TypeDecoder.decode(dataAsHex, web3jTypeClass);
+ return (T) web3jType.getValue();
+ }
+}
diff --git a/hiero-enterprise-base/src/main/java/module-info.java b/hiero-enterprise-base/src/main/java/module-info.java
index 546a85e2..4a617e44 100644
--- a/hiero-enterprise-base/src/main/java/module-info.java
+++ b/hiero-enterprise-base/src/main/java/module-info.java
@@ -3,6 +3,7 @@
exports com.openelements.hiero.base.protocol;
exports com.openelements.hiero.base.mirrornode;
exports com.openelements.hiero.base.verification;
+ exports com.openelements.hiero.base.solidity;
exports com.openelements.hiero.base.data;
exports com.openelements.hiero.base.config;
exports com.openelements.hiero.base.implementation to com.openelements.hiero.base.test;
@@ -15,7 +16,10 @@
requires transitive sdk; //Hedera SDK
requires org.slf4j;
- requires com.google.protobuf; //TODO: We should not have the need to use it
requires static org.jspecify;
+ requires com.google.protobuf; //TODO: We should not have the need to use it
requires com.google.auto.service;
+ requires com.openelements.hiero.smartcontract.abi;
+ requires abi;
+ requires org.bouncycastle.provider;
}
\ No newline at end of file
diff --git a/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/data/ContractLogTest.java b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/data/ContractLogTest.java
new file mode 100644
index 00000000..873544f5
--- /dev/null
+++ b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/data/ContractLogTest.java
@@ -0,0 +1,83 @@
+package com.openelements.hiero.base.test.data;
+
+import com.hedera.hashgraph.sdk.ContractId;
+import com.openelements.hiero.base.data.ContractEventInstance;
+import com.openelements.hiero.base.data.ContractLog;
+import com.openelements.hiero.smartcontract.abi.model.AbiEvent;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameter;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameterType;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ContractLogTest {
+
+ @Test
+ void test() {
+ //given
+ // from https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.5615061/results/logs?limit=10&order=asc
+ final String address = "0x000000000000000000000000000000000055add5";
+ final String bloom = "0x00000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000";
+ final ContractId contractId = ContractId.fromString("0.0.5615061");
+ final String data = "0x0000000000000000000000000000000000000000000000000000000000000001";
+ final long index = 1;
+ final List topics = List.of("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60");
+ final String block_hash = "0x3c33e36ebaa60192ff6c714ec15adb3829e8dd24e0f28b607fa88f6052600fb07ab29aeeb828e3faffd85b90960cc679";
+ final long blockNumber = 16076698;
+ final ContractId rootContractId = ContractId.fromString("0.0.5615061");
+ final String timestamp = "1740183080.776076000";
+ final String transactionHash = "0x254fa8781dc4babe1df5925bc2e3e8aa50d1b57d019189116f93fbdbb5497812";
+ final Long transactionIndex = 6L;
+ final ContractLog contractLog = new ContractLog(address, bloom, contractId, data, index, topics, block_hash,
+ blockNumber, rootContractId, timestamp, transactionHash, transactionIndex);
+ final List inputs = List.of(
+ new AbiParameter("count", AbiParameterType.UINT, List.of(), false)
+ );
+ final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false);
+
+ //then
+ Assertions.assertTrue(contractLog.isEventOfType(event));
+ }
+
+ @Test
+ void test2() {
+ //given
+ // from https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.5615061/results/logs?limit=10&order=asc
+ final String address = "0x000000000000000000000000000000000055add5";
+ final String bloom = "0x00000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000";
+ final ContractId contractId = ContractId.fromString("0.0.5615061");
+ final String data = "0x0000000000000000000000000000000000000000000000000000000000000001";
+ final long index = 1;
+ final List topics = List.of("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60");
+ final String block_hash = "0x3c33e36ebaa60192ff6c714ec15adb3829e8dd24e0f28b607fa88f6052600fb07ab29aeeb828e3faffd85b90960cc679";
+ final long blockNumber = 16076698;
+ final ContractId rootContractId = ContractId.fromString("0.0.5615061");
+ final String timestamp = "1740183080.776076000";
+ final String transactionHash = "0x254fa8781dc4babe1df5925bc2e3e8aa50d1b57d019189116f93fbdbb5497812";
+ final Long transactionIndex = 6L;
+ final ContractLog contractLog = new ContractLog(address, bloom, contractId, data, index, topics, block_hash,
+ blockNumber, rootContractId, timestamp, transactionHash, transactionIndex);
+ final List inputs = List.of(
+ new AbiParameter("count", AbiParameterType.UINT, List.of(), false)
+ );
+ final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false);
+
+ //when
+ final ContractEventInstance eventInstance = contractLog.asEventInstance(event);
+
+ //then
+ Assertions.assertNotNull(eventInstance);
+ Assertions.assertEquals(contractId, eventInstance.contractId());
+ Assertions.assertEquals("MissingVerificationCountUpdated", eventInstance.eventName());
+ Assertions.assertEquals(1, eventInstance.parameters().size());
+ Assertions.assertEquals("count", eventInstance.parameters().get(0).name());
+ Assertions.assertEquals(AbiParameterType.UINT, eventInstance.parameters().get(0).type());
+ Assertions.assertArrayEquals(
+ new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01},
+ eventInstance.parameters().get(0).value());
+ }
+
+}
diff --git a/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/solidty/SolidityToolsTests.java b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/solidty/SolidityToolsTests.java
new file mode 100644
index 00000000..64d91817
--- /dev/null
+++ b/hiero-enterprise-base/src/test/java/com/openelements/hiero/base/test/solidty/SolidityToolsTests.java
@@ -0,0 +1,48 @@
+package com.openelements.hiero.base.test.solidty;
+
+import com.hedera.hashgraph.sdk.ContractId;
+import com.openelements.hiero.base.data.ContractEventInstance;
+import com.openelements.hiero.base.data.ContractLog;
+import com.openelements.hiero.base.solidity.SolidityTools;
+import com.openelements.hiero.smartcontract.abi.model.AbiEvent;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameter;
+import com.openelements.hiero.smartcontract.abi.model.AbiParameterType;
+import java.math.BigInteger;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class SolidityToolsTests {
+
+ @Test
+ void test() {
+ //given
+ // from https://testnet.mirrornode.hedera.com/api/v1/contracts/0.0.5615061/results/logs?limit=10&order=asc
+ final String address = "0x000000000000000000000000000000000055add5";
+ final String bloom = "0x00000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000";
+ final ContractId contractId = ContractId.fromString("0.0.5615061");
+ final String data = "0x0000000000000000000000000000000000000000000000000000000000000001";
+ final long index = 1;
+ final List topics = List.of("0x271219bdbb9b91472a5df68ef7a9d3f8de02f3c27b93a35306f888acf081ea60");
+ final String block_hash = "0x3c33e36ebaa60192ff6c714ec15adb3829e8dd24e0f28b607fa88f6052600fb07ab29aeeb828e3faffd85b90960cc679";
+ final long blockNumber = 16076698;
+ final ContractId rootContractId = ContractId.fromString("0.0.5615061");
+ final String timestamp = "1740183080.776076000";
+ final String transactionHash = "0x254fa8781dc4babe1df5925bc2e3e8aa50d1b57d019189116f93fbdbb5497812";
+ final Long transactionIndex = 6L;
+ final ContractLog contractLog = new ContractLog(address, bloom, contractId, data, index, topics, block_hash,
+ blockNumber, rootContractId, timestamp, transactionHash, transactionIndex);
+ final List inputs = List.of(
+ new AbiParameter("count", AbiParameterType.UINT, List.of(), false)
+ );
+ final AbiEvent event = new AbiEvent("MissingVerificationCountUpdated", inputs, false);
+
+ //when
+ final ContractEventInstance eventInstance = SolidityTools.asEventInstance(contractLog, event);
+ final BigInteger value = SolidityTools.getValue(eventInstance.parameters().get(0));
+
+ //then
+ Assertions.assertNotNull(eventInstance);
+ Assertions.assertEquals(BigInteger.ONE, value);
+ }
+}
diff --git a/hiero-enterprise-base/src/test/java/module-info.java b/hiero-enterprise-base/src/test/java/module-info.java
index c5ac374a..7f9ace69 100644
--- a/hiero-enterprise-base/src/test/java/module-info.java
+++ b/hiero-enterprise-base/src/test/java/module-info.java
@@ -1,11 +1,12 @@
open module com.openelements.hiero.base.test {
requires com.openelements.hiero.base;
requires io.github.cdimascio.dotenv.java;
- requires static org.jspecify;
requires org.junit.jupiter.api;
requires org.junit.jupiter.params;
requires org.mockito;
requires org.slf4j;
+ requires static org.jspecify;
+ requires com.openelements.hiero.smartcontract.abi;
provides com.openelements.hiero.base.config.NetworkSettingsProvider with com.openelements.hiero.base.test.config.SoloActionNetworkSettingsProvider;
}
\ No newline at end of file
diff --git a/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java b/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java
index 66417c11..c834a4ed 100644
--- a/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java
+++ b/hiero-enterprise-spring/src/test/java/com/openelements/hiero/spring/test/TopicRepositoryTest.java
@@ -8,14 +8,13 @@
import com.openelements.hiero.base.data.TopicMessage;
import com.openelements.hiero.base.mirrornode.TopicRepository;
import com.openelements.hiero.test.HieroTestUtils;
+import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
-import java.util.Optional;
-
-@SpringBootTest(classes = TestConfig.class)
+@SpringBootTest(classes = HieroTestConfig.class)
public class TopicRepositoryTest {
@Autowired
private TopicRepository topicRepository;
diff --git a/pom.xml b/pom.xml
index 26199ff9..9b1405ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,9 @@
3.17.2
1.1.1
3.6.1.Final
+ 0.3.0
+ 4.12.3
+
3.3.1
3.13.0
3.3.1
@@ -120,6 +123,11 @@
+
+ com.open-elements.solidity
+ abi-parser
+ ${solidity-enterprise.version}
+
io.grpc
grpc-okhttp
@@ -200,6 +208,11 @@
auto-service-annotations
${google.auto.version}
+
+ org.web3j
+ abi
+ ${web3j.version}
+