Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ The following services are available in spring and microprofile:

- `org.hiero.base.HieroContext`: component that provides the information about the Hiero network and the operator account
- `org.hiero.base.AccountClient`: to interact with accounts
- `org.hiero.base.HookClient`: to interact with hook storage updates
- `org.hiero.base.FileClient`: to interact with files
- `org.hiero.base.FungibleTokenClient`: to interact with fungible tokens
- `org.hiero.base.NftClient`: to interact with NFTs
Expand Down
30 changes: 28 additions & 2 deletions docs/managed-services.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Base / Managed Services

<!-- TODO: Add documentation for hiero-enterprise-base: HieroContext, AccountClient, FileClient, FungibleTokenClient, NftClient, SmartContractClient, ContractVerificationClient, mirror node repositories, ProtocolLayerClient, MirrorNodeClient. -->
`hiero-enterprise-base` provides the core managed clients used by Spring and MicroProfile modules.

Placeholder: technical documentation for the base module and managed services will be added here.
## Hook lifecycle support

Hook lifecycle operations are exposed through `AccountClient.updateAccountHooks(...)`.
This API supports both:

- creating hooks (`hooksToCreate`)
- deleting hooks (`hooksToDelete`)

Core types:

- `org.hiero.base.data.HookDetails`
- `com.hedera.hashgraph.sdk.HookExtensionPoint`
- `org.hiero.base.protocol.data.AccountHookUpdateRequest`
- `org.hiero.base.protocol.data.AccountHookUpdateResult`

Example:

```java
HookDetails hookToCreate =
new HookDetails(
HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK,
1001L,
new EvmHook(ContractId.fromString("0.0.5001")),
null);

accountClient.updateAccountHooks(account, List.of(hookToCreate), List.of(77L));
```
19 changes: 17 additions & 2 deletions docs/microprofile.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# MicroProfile Integration

<!-- TODO: Add MicroProfile/Quarkus setup, configuration, available clients and usage. -->
MicroProfile integration exposes `AccountClient` via CDI.

Placeholder: technical documentation for the MicroProfile integration (hiero-enterprise-microprofile) will be added here.
## Hook lifecycle usage

Use `AccountClient.updateAccountHooks(...)` to create and/or delete hooks in one account update transaction.

```java
@Inject AccountClient accountClient;

HookDetails hookToCreate =
new HookDetails(
HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK,
1001L,
new EvmHook(ContractId.fromString("0.0.5001")),
null);

accountClient.updateAccountHooks(account, List.of(hookToCreate), List.of(77L));
```
19 changes: 17 additions & 2 deletions docs/spring-boot.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Spring Boot Integration

<!-- TODO: Add Spring Boot setup, @EnableHiero, configuration properties, available clients and usage. -->
Spring integration exposes `AccountClient` as a managed bean.

Placeholder: technical documentation for the Spring Boot integration (hiero-enterprise-spring) will be added here.
## Hook lifecycle usage

Use `AccountClient.updateAccountHooks(...)` to create and/or delete hooks in one account update transaction.

```java
@Autowired AccountClient accountClient;

HookDetails hookToCreate =
new HookDetails(
HookExtensionPoint.ACCOUNT_ALLOWANCE_HOOK,
1001L,
new EvmHook(ContractId.fromString("0.0.5001")),
null);

accountClient.updateAccountHooks(account, List.of(hookToCreate), List.of(77L));
```
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ default Account createAccount(long initialBalanceInHbar) throws HieroException {
*/
void updateAccountMemo(@NonNull Account account, @NonNull String memo) throws HieroException;

/**
* Updates account hooks by creating and/or deleting hooks in one update transaction.
*
* @param account the account to update
* @param hooksToCreate hook definitions to create
* @param hooksToDelete hook ids to delete
* @throws HieroException if the account could not be updated
*/
void updateAccountHooks(
@NonNull Account account,
@NonNull List<HookDetails> hooksToCreate,
@NonNull List<Long> hooksToDelete)
throws HieroException;

/**
* Updates both key and memo of the given account.
*
Expand Down Expand Up @@ -142,7 +156,7 @@ default void addHook(@NonNull Account account, @NonNull HookDetails hookDetails)
throws HieroException {
Objects.requireNonNull(account, "account must not be null");
Objects.requireNonNull(hookDetails, "hookDetails must not be null");
updateHooks(account, List.of(hookDetails), List.of());
updateAccountHooks(account, List.of(hookDetails), List.of());
}

/** Deletes a hook from an account. */
Expand All @@ -151,7 +165,7 @@ default void deleteHook(@NonNull Account account, long hookId) throws HieroExcep
if (hookId < 0) {
throw new IllegalArgumentException("hookId must be non-negative");
}
updateHooks(account, List.of(), List.of(hookId));
updateAccountHooks(account, List.of(), List.of(hookId));
}

/** Updates account hooks by creating and/or deleting hooks. */
Expand All @@ -160,6 +174,6 @@ default void updateHooks(
@NonNull List<HookDetails> hooksToCreate,
@NonNull List<Long> hookIdsToDelete)
throws HieroException {
throw new UnsupportedOperationException("Account hook management is not implemented yet.");
updateAccountHooks(account, hooksToCreate, hookIdsToDelete);
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
package org.hiero.base.data;

import com.hedera.hashgraph.sdk.ContractId;
import com.hedera.hashgraph.sdk.EvmHookStorageUpdate;
import com.hedera.hashgraph.sdk.EvmHook;
import com.hedera.hashgraph.sdk.HookCreationDetails;
import com.hedera.hashgraph.sdk.HookExtensionPoint;
import com.hedera.hashgraph.sdk.Key;
import java.util.List;
import java.util.Objects;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

/**
* High-level representation of a hook to attach to an account.
*
* @param extensionPoint the extension point where the hook should be attached
* @param hookId unique identifier of the hook on the owning entity
* @param evmHookContractId contract implementing the hook logic
* @param initialStorageUpdates initial EVM storage updates to apply on hook creation
* @param adminKey optional key used to authorize management operations for this hook
*/
/** Data model describing a hook to be created on an account update transaction. */
public record HookDetails(
@NonNull HookExtensionPoint extensionPoint,
long hookId,
@NonNull ContractId evmHookContractId,
@NonNull List<EvmHookStorageUpdate> initialStorageUpdates,
@NonNull EvmHook hook,
@Nullable Key adminKey) {

public HookDetails {
Objects.requireNonNull(extensionPoint, "extensionPoint must not be null");
Objects.requireNonNull(evmHookContractId, "evmHookContractId must not be null");
Objects.requireNonNull(initialStorageUpdates, "initialStorageUpdates must not be null");
initialStorageUpdates.forEach(
update -> Objects.requireNonNull(update, "initialStorageUpdates must not contain null"));
initialStorageUpdates = List.copyOf(initialStorageUpdates);
Objects.requireNonNull(extensionPoint, "extensionPoint is required");
Objects.requireNonNull(hook, "hook is required");
if (hookId < 0) {
throw new IllegalArgumentException("hookId must be non-negative");
}
}

@NonNull
public HookCreationDetails toHederaSdkType() {
if (adminKey != null) {
return new HookCreationDetails(extensionPoint, hookId, hook, adminKey);
}
return new HookCreationDetails(extensionPoint, hookId, hook);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
import com.hedera.hashgraph.sdk.AccountId;
import com.hedera.hashgraph.sdk.Hbar;
import com.hedera.hashgraph.sdk.PrivateKey;
import java.util.List;
import java.util.Objects;
import org.hiero.base.AccountClient;
import org.hiero.base.HieroException;
import org.hiero.base.data.Account;
import org.hiero.base.data.HookDetails;
import org.hiero.base.protocol.ProtocolLayerClient;
import org.hiero.base.protocol.data.AccountBalanceRequest;
import org.hiero.base.protocol.data.AccountBalanceResponse;
import org.hiero.base.protocol.data.AccountCreateRequest;
import org.hiero.base.protocol.data.AccountCreateResult;
import org.hiero.base.protocol.data.AccountDeleteRequest;
import org.hiero.base.protocol.data.AccountHookUpdateRequest;
import org.hiero.base.protocol.data.AccountUpdateRequest;
import org.jspecify.annotations.NonNull;

Expand Down Expand Up @@ -76,6 +79,20 @@ public void updateAccountMemo(@NonNull Account account, @NonNull String memo)
client.executeAccountUpdateTransaction(request);
}

@Override
public void updateAccountHooks(
@NonNull final Account account,
@NonNull final List<HookDetails> hooksToCreate,
@NonNull final List<Long> hooksToDelete)
throws HieroException {
Objects.requireNonNull(account, "account must not be null");
Objects.requireNonNull(hooksToCreate, "hooksToCreate must not be null");
Objects.requireNonNull(hooksToDelete, "hooksToDelete must not be null");
final AccountHookUpdateRequest request =
AccountHookUpdateRequest.of(account, hooksToCreate, hooksToDelete);
client.executeAccountHookUpdateTransaction(request);
}

@Override
public @NonNull Account updateAccount(
@NonNull Account account, @NonNull PrivateKey updatedPrivateKey, @NonNull String memo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
import org.hiero.base.protocol.data.AccountCreateResult;
import org.hiero.base.protocol.data.AccountDeleteRequest;
import org.hiero.base.protocol.data.AccountDeleteResult;
import org.hiero.base.protocol.data.AccountHookUpdateRequest;
import org.hiero.base.protocol.data.AccountHookUpdateResult;
import org.hiero.base.protocol.data.AccountUpdateRequest;
import org.hiero.base.protocol.data.AccountUpdateResult;
import org.hiero.base.protocol.data.ContractCallRequest;
Expand Down Expand Up @@ -407,6 +409,30 @@ public AccountUpdateResult executeAccountUpdateTransaction(
return new AccountUpdateResult(receipt.transactionId, receipt.status);
}

@Override
@NonNull
public AccountHookUpdateResult executeAccountHookUpdateTransaction(
@NonNull final AccountHookUpdateRequest request) throws HieroException {
Objects.requireNonNull(request, "request must not be null");
final AccountUpdateTransaction transaction =
new AccountUpdateTransaction()
.setMaxTransactionFee(request.maxTransactionFee())
.setTransactionValidDuration(request.transactionValidDuration())
.setAccountId(request.toUpdate().accountId());

for (final var hookToCreate : request.hooksToCreate()) {
transaction.addHookToCreate(hookToCreate.toHederaSdkType());
}
for (final Long hookIdToDelete : request.hooksToDelete()) {
transaction.addHookToDelete(hookIdToDelete);
}

sign(transaction, request.toUpdate().privateKey());
final TransactionReceipt receipt =
executeTransactionAndWaitOnReceipt(transaction, TransactionType.ACCOUNT_UPDATE);
return new AccountHookUpdateResult(receipt.transactionId, receipt.status);
}

public TopicCreateResult executeTopicCreateTransaction(@NonNull final TopicCreateRequest request)
throws HieroException {
Objects.requireNonNull(request, "request must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,6 @@ public interface ProtocolLayerClient {
@NonNull AccountDeleteResult executeAccountDeleteTransaction(
@NonNull AccountDeleteRequest request) throws HieroException;

/**
* Executes an account hook update transaction.
*
* @param request the request containing hooks to create and hooks to delete on an account
* @return the result of the account hook update transaction
* @throws HieroException if the transaction could not be executed
*/
@NonNull
default AccountHookUpdateResult executeAccountHookUpdateTransaction(
@NonNull AccountHookUpdateRequest request) throws HieroException {
throw new UnsupportedOperationException("Account hook update transaction is not implemented.");
}

/**
* Executes an account update transaction.
*
Expand All @@ -202,6 +189,16 @@ default AccountHookUpdateResult executeAccountHookUpdateTransaction(
@NonNull AccountUpdateResult executeAccountUpdateTransaction(
@NonNull AccountUpdateRequest request) throws HieroException;

/**
* Executes an account hook update transaction.
*
* @param request the request containing the details of the account hook update transaction
* @return the result of the account hook update transaction
* @throws HieroException if the transaction could not be executed
*/
@NonNull AccountHookUpdateResult executeAccountHookUpdateTransaction(
@NonNull AccountHookUpdateRequest request) throws HieroException;

/**
* Executes a token create transaction.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,70 +8,52 @@
import org.hiero.base.data.HookDetails;
import org.jspecify.annotations.NonNull;

/** Request model for updating account hooks. */
public record AccountHookUpdateRequest(
@NonNull Hbar maxTransactionFee,
@NonNull Duration transactionValidDuration,
@NonNull Account account,
@NonNull Account toUpdate,
@NonNull List<HookDetails> hooksToCreate,
@NonNull List<Long> hooksToDelete)
implements TransactionRequest {

public AccountHookUpdateRequest {
Objects.requireNonNull(maxTransactionFee, "maxTransactionFee is required");
Objects.requireNonNull(transactionValidDuration, "transactionValidDuration is required");
Objects.requireNonNull(account, "account is required");
Objects.requireNonNull(toUpdate, "toUpdate is required");
Objects.requireNonNull(hooksToCreate, "hooksToCreate is required");
Objects.requireNonNull(hooksToDelete, "hooksToDelete is required");
if (maxTransactionFee.toTinybars() < 0) {
throw new IllegalArgumentException("maxTransactionFee must be non-negative");
}
if (transactionValidDuration.isNegative() || transactionValidDuration.isZero()) {
throw new IllegalArgumentException("transactionValidDuration must be positive");
}
hooksToCreate.forEach(
hookDetails -> Objects.requireNonNull(hookDetails, "hooksToCreate must not contain null"));
hooksToDelete.forEach(
hookId -> {
Objects.requireNonNull(hookId, "hooksToDelete must not contain null values");
Objects.requireNonNull(hookId, "hooksToDelete must not contain null");
if (hookId < 0) {
throw new IllegalArgumentException("hook IDs in hooksToDelete must be non-negative");
throw new IllegalArgumentException("hooksToDelete must contain only non-negative ids");
}
});
}

@NonNull
public static AccountHookUpdateRequest addHook(
@NonNull Account account, @NonNull HookDetails hookToCreate) {
Objects.requireNonNull(hookToCreate, "hookToCreate is required");
return new AccountHookUpdateRequest(
DEFAULT_MAX_TRANSACTION_FEE,
DEFAULT_TRANSACTION_VALID_DURATION,
account,
List.of(hookToCreate),
List.of());
}

@NonNull
public static AccountHookUpdateRequest deleteHook(@NonNull Account account, long hookIdToDelete) {
if (hookIdToDelete < 0) {
throw new IllegalArgumentException("hookIdToDelete must be non-negative");
if (maxTransactionFee.toTinybars() < 0) {
throw new IllegalArgumentException("maxTransactionFee must be non-negative");
}
if (transactionValidDuration.isNegative() || transactionValidDuration.isZero()) {
throw new IllegalArgumentException("transactionValidDuration must be positive");
}
if (hooksToCreate.isEmpty() && hooksToDelete.isEmpty()) {
throw new IllegalArgumentException("at least one hook action must be set");
}
return new AccountHookUpdateRequest(
DEFAULT_MAX_TRANSACTION_FEE,
DEFAULT_TRANSACTION_VALID_DURATION,
account,
List.of(),
List.of(hookIdToDelete));
}

@NonNull
public static AccountHookUpdateRequest of(
@NonNull Account account,
@NonNull List<HookDetails> hooksToCreate,
@NonNull List<Long> hooksToDelete) {
@NonNull final Account toUpdate,
@NonNull final List<HookDetails> hooksToCreate,
@NonNull final List<Long> hooksToDelete) {
return new AccountHookUpdateRequest(
DEFAULT_MAX_TRANSACTION_FEE,
DEFAULT_TRANSACTION_VALID_DURATION,
account,
hooksToCreate,
hooksToDelete);
Objects.requireNonNull(toUpdate, "toUpdate is required"),
List.copyOf(Objects.requireNonNull(hooksToCreate, "hooksToCreate is required")),
List.copyOf(Objects.requireNonNull(hooksToDelete, "hooksToDelete is required")));
}
}
Loading
Loading