From c8a93cbc00469dadb159109f2e49788c3a3b8e73 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Tue, 5 May 2026 15:56:21 +0300 Subject: [PATCH 1/9] feat: reorganize account and block endpoints into domain-specific controllers - Created AccountController and BlockController - Added DTOs for account lifecycle operations - Removed legacy HieroEndpoint.java - Ensured Spring Boot 3 compatibility with explicit @PathVariable names Signed-off-by: Twiineenock --- .../hiero/spring/sample/HieroEndpoint.java | 42 ----- .../sample/controller/AccountController.java | 157 ++++++++++++++++++ .../sample/controller/BlockController.java | 56 +++++++ .../sample/dto/AccountCreateRequest.java | 8 + .../sample/dto/AccountDeleteRequest.java | 14 ++ .../spring/sample/dto/AccountResponse.java | 14 ++ .../sample/dto/AccountUpdateRequest.java | 16 ++ 7 files changed, 265 insertions(+), 42 deletions(-) delete mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/HieroEndpoint.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/HieroEndpoint.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/HieroEndpoint.java deleted file mode 100644 index de2e2b44..00000000 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/HieroEndpoint.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.hiero.spring.sample; - -import java.util.Objects; -import org.hiero.base.AccountClient; -import org.hiero.base.data.Account; -import org.hiero.base.data.Block; -import org.hiero.base.data.Page; -import org.hiero.base.mirrornode.BlockRepository; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class HieroEndpoint { - - private final AccountClient client; - private final BlockRepository blockRepository; - - public HieroEndpoint(final AccountClient client, final BlockRepository blockRepository) { - this.client = Objects.requireNonNull(client, "client must not be null"); - this.blockRepository = - Objects.requireNonNull(blockRepository, "blockRepository must not be null"); - } - - @GetMapping("/") - public String createAccount() { - try { - final Account account = client.createAccount(); - return "Account " + account.accountId() + " created!"; - } catch (final Exception e) { - throw new RuntimeException("Error in Hedera call", e); - } - } - - @GetMapping("/blocks") - public Page getBlocks() { - try { - return blockRepository.findAll(); - } catch (final Exception e) { - throw new RuntimeException("Error querying blocks", e); - } - } -} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java new file mode 100644 index 00000000..02eafcce --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java @@ -0,0 +1,157 @@ +package org.hiero.spring.sample.controller; + +import com.hedera.hashgraph.sdk.AccountId; +import com.hedera.hashgraph.sdk.Hbar; +import com.hedera.hashgraph.sdk.PrivateKey; +import java.util.Objects; +import org.hiero.base.AccountClient; +import org.hiero.base.data.Account; +import org.hiero.base.data.AccountInfo; +import org.hiero.base.mirrornode.AccountRepository; +import org.hiero.spring.sample.dto.AccountCreateRequest; +import org.hiero.spring.sample.dto.AccountDeleteRequest; +import org.hiero.spring.sample.dto.AccountResponse; +import org.hiero.spring.sample.dto.AccountUpdateRequest; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for Hiero account operations. + * This controller provides endpoints for account lifecycle management and queries. + */ +@RestController +@RequestMapping("/api/v1/hiero/accounts") +public class AccountController { + + private final AccountClient accountClient; + private final AccountRepository accountRepository; + + public AccountController( + final AccountClient accountClient, + final AccountRepository accountRepository) { + this.accountClient = Objects.requireNonNull(accountClient, "accountClient must not be null"); + this.accountRepository = + Objects.requireNonNull(accountRepository, "accountRepository must not be null"); + } + + /** + * Creates a new Hiero account. + * + * @param request The account creation request containing optional initial balance. + * @return Success message with the new account ID. + */ + @PostMapping + public AccountResponse createAccount(@RequestBody(required = false) final AccountCreateRequest request) { + try { + final Hbar initialBalance = (request != null && request.initialBalance() != null) + ? Hbar.from(request.initialBalance()) + : Hbar.ZERO; + + final Account account = accountClient.createAccount(initialBalance); + return new AccountResponse( + account.accountId().toString(), + account.publicKey().toString(), + account.privateKey().toString() + ); + } catch (final Exception e) { + throw new RuntimeException("Failed to create account", e); + } + } + + /** + * Updates an existing Hiero account. + * + * @param request The account update request containing new key or memo. + * @return Success message. + */ + @PutMapping + public String updateAccount(@RequestBody final AccountUpdateRequest request) { + try { + final AccountId accountId = AccountId.fromString(request.accountId()); + final PrivateKey currentKey = PrivateKey.fromString(request.privateKey()); + final Account account = Account.of(accountId, currentKey); + + if (request.newPrivateKey() != null && request.memo() != null) { + accountClient.updateAccount(account, PrivateKey.fromString(request.newPrivateKey()), request.memo()); + } else if (request.newPrivateKey() != null) { + accountClient.updateAccountKey(account, PrivateKey.fromString(request.newPrivateKey())); + } else if (request.memo() != null) { + accountClient.updateAccountMemo(account, request.memo()); + } + + return "Account " + request.accountId() + " updated successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to update account", e); + } + } + + /** + * Deletes a Hiero account. + * + * @param request The account deletion request. + */ + @DeleteMapping + public String deleteAccount(@RequestBody final AccountDeleteRequest request) { + try { + final AccountId accountId = AccountId.fromString(request.accountId()); + final PrivateKey privateKey = PrivateKey.fromString(request.privateKey()); + final Account account = Account.of(accountId, privateKey); + + if (request.transferToAccountId() != null) { + final AccountId transferTo = AccountId.fromString(request.transferToAccountId()); + // Since we don't have a full Account object for the transferTo target, + // we use a minimal one if the client allows it, or we might need more logic. + // For now, let's assume we can pass a dummy account if we only need the ID. + // Wait, AccountClient.deleteAccount(Account account, Account toAccount) + // actually takes Account objects. + // Let's check if we can create a shell Account for the transfer target. + final Account transferTarget = Account.of(transferTo, PrivateKey.generateED25519()); // Key won't be used for receiving + accountClient.deleteAccount(account, transferTarget); + } else { + accountClient.deleteAccount(account); + } + + return "Account " + request.accountId() + " deleted successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to delete account", e); + } + } + + /** + * Retrieves the balance of a Hiero account. + * + * @param accountId The ID of the account to query. + * @return The balance in Hbar. + */ + @GetMapping("/balance/{accountId}") + public String getBalance(@PathVariable("accountId") final String accountId) { + try { + final Hbar balance = accountClient.getAccountBalance(accountId); + return balance.toString(); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve balance for account " + accountId, e); + } + } + + /** + * Retrieves detailed information about a Hiero account from the mirror node. + * + * @param accountId The ID of the account to query. + * @return The AccountInfo object. + */ + @GetMapping("/info/{accountId}") + public AccountInfo getInfo(@PathVariable("accountId") final String accountId) { + try { + return accountRepository.findById(accountId) + .orElseThrow(() -> new RuntimeException("Account not found: " + accountId)); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve info for account " + accountId, e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java new file mode 100644 index 00000000..a1512510 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java @@ -0,0 +1,56 @@ +package org.hiero.spring.sample.controller; + +import java.util.Objects; +import org.hiero.base.data.Block; +import org.hiero.base.data.Page; +import org.hiero.base.mirrornode.BlockRepository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for Hiero block operations. + * This controller provides endpoints for querying block information from the mirror node. + */ +@RestController +@RequestMapping("/api/v1/hiero/blocks") +public class BlockController { + + private final BlockRepository blockRepository; + + public BlockController(final BlockRepository blockRepository) { + this.blockRepository = + Objects.requireNonNull(blockRepository, "blockRepository must not be null"); + } + + /** + * Retrieves a paginated list of all blocks. + * + * @return A page of blocks. + */ + @GetMapping + public Page getBlocks() { + try { + return blockRepository.findAll(); + } catch (final Exception e) { + throw new RuntimeException("Failed to query blocks", e); + } + } + + /** + * Retrieves a specific block by its number. + * + * @param number The block number. + * @return The block details. + */ + @GetMapping("/{number}") + public Block getBlockByNumber(@PathVariable("number") final long number) { + try { + return blockRepository.findByNumber(number) + .orElseThrow(() -> new RuntimeException("Block not found: " + number)); + } catch (final Exception e) { + throw new RuntimeException("Failed to query block by number: " + number, e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java new file mode 100644 index 00000000..f870dc99 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java @@ -0,0 +1,8 @@ +package org.hiero.spring.sample.dto; + +/** + * Request to create a new Hiero account. + * + * @param initialBalance The initial balance in Hbar (optional, defaults to 0). + */ +public record AccountCreateRequest(Long initialBalance) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java new file mode 100644 index 00000000..bf3a3f6f --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java @@ -0,0 +1,14 @@ +package org.hiero.spring.sample.dto; + +/** + * Request to delete a Hiero account. + * + * @param accountId The ID of the account to delete. + * @param privateKey The private key of the account to delete. + * @param transferToAccountId The ID of the account to transfer remaining funds to (optional, defaults to operator). + */ +public record AccountDeleteRequest( + String accountId, + String privateKey, + String transferToAccountId +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java new file mode 100644 index 00000000..53ce73b8 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java @@ -0,0 +1,14 @@ +package org.hiero.spring.sample.dto; + +/** + * Response containing Hiero account details. + * + * @param accountId The ID of the account. + * @param publicKey The public key of the account. + * @param privateKey The private key of the account. + */ +public record AccountResponse( + String accountId, + String publicKey, + String privateKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java new file mode 100644 index 00000000..36d6c368 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java @@ -0,0 +1,16 @@ +package org.hiero.spring.sample.dto; + +/** + * Request to update an existing Hiero account. + * + * @param accountId The ID of the account to update. + * @param privateKey The current private key of the account. + * @param newPrivateKey The new private key to set (optional). + * @param memo The new memo to set (optional). + */ +public record AccountUpdateRequest( + String accountId, + String privateKey, + String newPrivateKey, + String memo +) {} From 699831c133716cc7a80ebb56a8786d6653de6b95 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Tue, 5 May 2026 18:11:47 +0300 Subject: [PATCH 2/9] feat: finalize fungible token endpoints and enable CORS for sample app Signed-off-by: Twiineenock --- .../sample/controller/AccountController.java | 8 +- .../sample/controller/TokenController.java | 104 ++++++++++++++++++ .../{ => account}/AccountCreateRequest.java | 2 +- .../{ => account}/AccountDeleteRequest.java | 2 +- .../dto/{ => account}/AccountResponse.java | 2 +- .../{ => account}/AccountUpdateRequest.java | 2 +- .../sample/dto/token/TokenCreateRequest.java | 16 +++ .../sample/dto/token/TokenMintRequest.java | 12 ++ .../dto/token/TokenTransferRequest.java | 16 +++ 9 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java rename hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/{ => account}/AccountCreateRequest.java (81%) rename hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/{ => account}/AccountDeleteRequest.java (89%) rename hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/{ => account}/AccountResponse.java (87%) rename hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/{ => account}/AccountUpdateRequest.java (90%) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java index 02eafcce..39c48363 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java @@ -8,10 +8,10 @@ import org.hiero.base.data.Account; import org.hiero.base.data.AccountInfo; import org.hiero.base.mirrornode.AccountRepository; -import org.hiero.spring.sample.dto.AccountCreateRequest; -import org.hiero.spring.sample.dto.AccountDeleteRequest; -import org.hiero.spring.sample.dto.AccountResponse; -import org.hiero.spring.sample.dto.AccountUpdateRequest; +import org.hiero.spring.sample.dto.account.AccountCreateRequest; +import org.hiero.spring.sample.dto.account.AccountDeleteRequest; +import org.hiero.spring.sample.dto.account.AccountResponse; +import org.hiero.spring.sample.dto.account.AccountUpdateRequest; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java new file mode 100644 index 00000000..3edf65c4 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java @@ -0,0 +1,104 @@ +package org.hiero.spring.sample.controller; + +import com.hedera.hashgraph.sdk.AccountId; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.TokenId; +import java.util.Objects; +import org.hiero.base.FungibleTokenClient; +import org.hiero.base.data.Account; +import org.hiero.base.data.TokenInfo; +import org.hiero.base.mirrornode.TokenRepository; +import org.hiero.spring.sample.dto.token.TokenCreateRequest; +import org.hiero.spring.sample.dto.token.TokenMintRequest; +import org.hiero.spring.sample.dto.token.TokenTransferRequest; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@CrossOrigin +@RequestMapping("/api/v1/hiero/fungible-token") +public class TokenController { + + private final FungibleTokenClient tokenClient; + private final TokenRepository tokenRepository; + + public TokenController( + final FungibleTokenClient tokenClient, + final TokenRepository tokenRepository) { + this.tokenClient = Objects.requireNonNull(tokenClient, "tokenClient must not be null"); + this.tokenRepository = Objects.requireNonNull(tokenRepository, "tokenRepository must not be null"); + } + + /** + * Creates a new Hiero fungible token. + */ + @PostMapping + public String createToken(@RequestBody final TokenCreateRequest request) { + try { + final TokenId tokenId = tokenClient.createToken(request.name(), request.symbol()); + return "Token " + tokenId + " created successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to create token", e); + } + } + + /** + * Retrieves detailed information about a Hiero fungible token. + */ + @GetMapping("/{tokenId}") + public TokenInfo getToken(@PathVariable("tokenId") final String tokenId) { + try { + return tokenRepository.findById(tokenId) + .orElseThrow(() -> new RuntimeException("Token not found: " + tokenId)); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve token info for " + tokenId, e); + } + } + + /** + * Mints more of a Hiero fungible token. + */ + @PostMapping("/{tokenId}/mint") + public String mintToken( + @PathVariable("tokenId") final String tokenId, + @RequestBody final TokenMintRequest request) { + try { + final long newTotalSupply; + if (request.supplyKey() != null) { + newTotalSupply = tokenClient.mintToken(tokenId, request.supplyKey(), request.amount()); + } else { + newTotalSupply = tokenClient.mintToken(tokenId, request.amount()); + } + return "Minted " + request.amount() + " units. New total supply: " + newTotalSupply; + } catch (final Exception e) { + throw new RuntimeException("Failed to mint token " + tokenId, e); + } + } + + /** + * Transfers Hiero fungible tokens between accounts. + */ + @PostMapping("/{tokenId}/transfer") + public String transferToken( + @PathVariable("tokenId") final String tokenId, + @RequestBody final TokenTransferRequest request) { + try { + final TokenId id = TokenId.fromString(tokenId); + final Account fromAccount = Account.of( + AccountId.fromString(request.fromAccountId()), + PrivateKey.fromString(request.fromPrivateKey()) + ); + final AccountId toAccount = AccountId.fromString(request.toAccountId()); + + tokenClient.transferToken(id, fromAccount, toAccount, request.amount()); + return "Transferred " + request.amount() + " tokens from " + request.fromAccountId() + " to " + request.toAccountId(); + } catch (final Exception e) { + throw new RuntimeException("Failed to transfer token " + tokenId, e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java similarity index 81% rename from hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java rename to hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java index f870dc99..05d45366 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java @@ -1,4 +1,4 @@ -package org.hiero.spring.sample.dto; +package org.hiero.spring.sample.dto.account; /** * Request to create a new Hiero account. diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java similarity index 89% rename from hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java rename to hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java index bf3a3f6f..3892a56a 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountDeleteRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java @@ -1,4 +1,4 @@ -package org.hiero.spring.sample.dto; +package org.hiero.spring.sample.dto.account; /** * Request to delete a Hiero account. diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java similarity index 87% rename from hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java rename to hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java index 53ce73b8..9eb18020 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java @@ -1,4 +1,4 @@ -package org.hiero.spring.sample.dto; +package org.hiero.spring.sample.dto.account; /** * Response containing Hiero account details. diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java similarity index 90% rename from hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java rename to hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java index 36d6c368..6e119248 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/AccountUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java @@ -1,4 +1,4 @@ -package org.hiero.spring.sample.dto; +package org.hiero.spring.sample.dto.account; /** * Request to update an existing Hiero account. diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java new file mode 100644 index 00000000..909b8b4d --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java @@ -0,0 +1,16 @@ +package org.hiero.spring.sample.dto.token; + +/** + * Request to create a new Hiero fungible token. + * + * @param name The name of the token. + * @param symbol The symbol of the token. + * @param decimals The number of decimals for the token (optional, defaults to 0). + * @param initialSupply The initial supply of the token (optional, defaults to 0). + */ +public record TokenCreateRequest( + String name, + String symbol, + Integer decimals, + Long initialSupply +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java new file mode 100644 index 00000000..0cee6c63 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java @@ -0,0 +1,12 @@ +package org.hiero.spring.sample.dto.token; + +/** + * Request to mint more of a Hiero fungible token. + * + * @param amount The amount to mint. + * @param supplyKey The supply key of the token (required if the token has a supply key). + */ +public record TokenMintRequest( + long amount, + String supplyKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java new file mode 100644 index 00000000..972a5a84 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java @@ -0,0 +1,16 @@ +package org.hiero.spring.sample.dto.token; + +/** + * Request to transfer Hiero fungible tokens between accounts. + * + * @param fromAccountId The ID of the account to transfer tokens from. + * @param fromPrivateKey The private key of the sender account (required). + * @param toAccountId The ID of the account to transfer tokens to. + * @param amount The amount of tokens to transfer. + */ +public record TokenTransferRequest( + String fromAccountId, + String fromPrivateKey, + String toAccountId, + long amount +) {} From 390114323c171b22f8c6651cf6b4251c95dd865d Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Tue, 5 May 2026 18:38:54 +0300 Subject: [PATCH 3/9] feat: implement supported NFT endpoints and DTOs Signed-off-by: Twiineenock --- .../sample/controller/NftController.java | 126 ++++++++++++++++++ .../sample/dto/nft/NftCreateRequest.java | 7 + .../spring/sample/dto/nft/NftMintRequest.java | 7 + .../sample/dto/nft/NftTransferRequest.java | 12 ++ 4 files changed, 152 insertions(+) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java new file mode 100644 index 00000000..7b599946 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java @@ -0,0 +1,126 @@ +package org.hiero.spring.sample.controller; + +import com.hedera.hashgraph.sdk.AccountId; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.TokenId; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import org.hiero.base.NftClient; +import org.hiero.base.data.Account; +import org.hiero.base.data.Nft; +import org.hiero.base.data.NftMetadata; +import org.hiero.base.data.Page; +import org.hiero.base.data.TokenInfo; +import org.hiero.base.mirrornode.NftRepository; +import org.hiero.base.mirrornode.TokenRepository; +import org.hiero.spring.sample.dto.nft.NftCreateRequest; +import org.hiero.spring.sample.dto.nft.NftMintRequest; +import org.hiero.spring.sample.dto.nft.NftTransferRequest; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for Hiero NFT operations. + */ +@RestController +@CrossOrigin +@RequestMapping("/api/v1/hiero/nfts") +public class NftController { + + private final NftClient nftClient; + private final NftRepository nftRepository; + private final TokenRepository tokenRepository; + + public NftController( + final NftClient nftClient, + final NftRepository nftRepository, + final TokenRepository tokenRepository) { + this.nftClient = Objects.requireNonNull(nftClient, "nftClient must not be null"); + this.nftRepository = Objects.requireNonNull(nftRepository, "nftRepository must not be null"); + this.tokenRepository = Objects.requireNonNull(tokenRepository, "tokenRepository must not be null"); + } + + /** + * Creates a new NFT type. + */ + @PostMapping + public String createNft(@RequestBody final NftCreateRequest request) { + try { + final TokenId tokenId = nftClient.createNftType(request.name(), request.symbol()); + return "NFT Type " + tokenId + " created successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to create NFT type", e); + } + } + + /** + * Retrieves detailed information about an NFT type. + */ + @GetMapping("/{nftId}") + public TokenInfo getNftById(@PathVariable("nftId") final String nftId) { + try { + return tokenRepository.findById(nftId) + .orElseThrow(() -> new RuntimeException("NFT Type not found: " + nftId)); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve NFT info for " + nftId, e); + } + } + + /** + * Mints a new NFT instance. + */ + @PostMapping("/{nftId}/mint") + public String mintNft( + @PathVariable("nftId") final String nftId, + @RequestBody final NftMintRequest request) { + try { + final byte[] metadata = request.metadata().getBytes(StandardCharsets.UTF_8); + final long serialNumber = nftClient.mintNft(nftId, metadata); + return "Minted NFT instance of " + nftId + " with serial number " + serialNumber; + } catch (final Exception e) { + throw new RuntimeException("Failed to mint NFT for " + nftId, e); + } + } + + /** + * Retrieves a specific NFT instance by ID and serial number. + */ + @GetMapping("/{nftId}/serial/{serial}") + public Nft getNftBySerial( + @PathVariable("nftId") final String nftId, + @PathVariable("serial") final long serial) { + try { + return nftRepository.findByTypeAndSerial(nftId, serial) + .orElseThrow(() -> new RuntimeException("NFT not found: " + nftId + " Serial: " + serial)); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve NFT instance", e); + } + } + + /** + * Transfers an NFT instance between accounts. + */ + @PostMapping("/{nftId}/transfer") + public String transferNft( + @PathVariable("nftId") final String nftId, + @RequestBody final NftTransferRequest request) { + try { + final TokenId tokenId = TokenId.fromString(nftId); + final Account fromAccount = Account.of( + AccountId.fromString(request.fromAccountId()), + PrivateKey.fromString(request.fromPrivateKey()) + ); + final AccountId toAccountId = AccountId.fromString(request.toAccountId()); + + nftClient.transferNft(tokenId, request.serialNumber(), fromAccount, toAccountId); + return "Transferred NFT " + nftId + " (Serial: " + request.serialNumber() + ") to " + request.toAccountId(); + } catch (final Exception e) { + throw new RuntimeException("Failed to transfer NFT", e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java new file mode 100644 index 00000000..3e58ee31 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java @@ -0,0 +1,7 @@ +package org.hiero.spring.sample.dto.nft; + +/** + * Request DTO for creating a new NFT type. + */ +public record NftCreateRequest(String name, String symbol) { +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java new file mode 100644 index 00000000..94fdd1f6 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java @@ -0,0 +1,7 @@ +package org.hiero.spring.sample.dto.nft; + +/** + * Request DTO for minting a new NFT instance. + */ +public record NftMintRequest(String metadata) { +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java new file mode 100644 index 00000000..08a8cc2f --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java @@ -0,0 +1,12 @@ +package org.hiero.spring.sample.dto.nft; + +/** + * Request DTO for transferring an NFT instance. + */ +public record NftTransferRequest( + String fromAccountId, + String fromPrivateKey, + String toAccountId, + long serialNumber +) { +} From 3abdc0acba9b99cff1010a26ef997f1d244ee123 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Tue, 5 May 2026 22:25:29 +0300 Subject: [PATCH 4/9] feat: implement consensus (topic) resource endpoints Signed-off-by: Twiineenock --- .../sample/controller/TopicController.java | 166 ++++++++++++++++++ .../sample/dto/topic/TopicCreateRequest.java | 10 ++ .../sample/dto/topic/TopicMessageRequest.java | 10 ++ .../sample/dto/topic/TopicUpdateRequest.java | 12 ++ .../src/main/resources/application.properties | 2 +- 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java new file mode 100644 index 00000000..7bf001bd --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java @@ -0,0 +1,166 @@ +package org.hiero.spring.sample.controller; + +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.TopicId; +import java.util.Objects; +import java.util.Optional; +import org.hiero.base.TopicClient; +import org.hiero.base.data.Page; +import org.hiero.base.data.Topic; +import org.hiero.base.data.TopicMessage; +import org.hiero.base.mirrornode.TopicRepository; +import org.hiero.spring.sample.dto.topic.TopicCreateRequest; +import org.hiero.spring.sample.dto.topic.TopicMessageRequest; +import org.hiero.spring.sample.dto.topic.TopicUpdateRequest; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for Hiero Consensus Service (Topic) operations. + */ +@RestController +@RequestMapping("/api/v1/hiero/topics") +@CrossOrigin(origins = "*") +public class TopicController { + + private final TopicClient topicClient; + private final TopicRepository topicRepository; + + public TopicController(final TopicClient topicClient, final TopicRepository topicRepository) { + this.topicClient = Objects.requireNonNull(topicClient, "topicClient must not be null"); + this.topicRepository = Objects.requireNonNull(topicRepository, "topicRepository must not be null"); + } + + /** + * Creates a new topic. + */ + @PostMapping + public String createTopic(@RequestBody final TopicCreateRequest request) { + try { + final TopicId topicId; + final PrivateKey adminKey = request.adminKey() != null ? PrivateKey.fromString(request.adminKey()) : null; + final PrivateKey submitKey = request.submitKey() != null ? PrivateKey.fromString(request.submitKey()) : null; + final String memo = request.memo() != null ? request.memo() : ""; + + if (submitKey != null) { + if (adminKey != null) { + topicId = topicClient.createPrivateTopic(adminKey, submitKey, memo); + } else { + topicId = topicClient.createPrivateTopic(submitKey, memo); + } + } else { + if (adminKey != null) { + topicId = topicClient.createTopic(adminKey, memo); + } else { + topicId = topicClient.createTopic(memo); + } + } + return "Topic " + topicId.toString() + " created successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to create topic", e); + } + } + + /** + * Updates an existing topic. + */ + @PutMapping + public String updateTopic(@RequestBody final TopicUpdateRequest request) { + try { + final TopicId topicId = TopicId.fromString(request.topicId()); + final String memo = request.memo() != null ? request.memo() : ""; + + if (request.adminKey() != null && request.updatedAdminKey() != null && request.submitKey() != null) { + topicClient.updateTopic(topicId, + PrivateKey.fromString(request.adminKey()), + PrivateKey.fromString(request.updatedAdminKey()), + PrivateKey.fromString(request.submitKey()), + memo); + } else if (request.adminKey() != null) { + topicClient.updateTopic(topicId, PrivateKey.fromString(request.adminKey()), memo); + } else { + topicClient.updateTopic(topicId, memo); + } + return "Topic " + topicId.toString() + " updated successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to update topic", e); + } + } + + /** + * Deletes a topic. + */ + @DeleteMapping("/{topicId}") + public String deleteTopic(@PathVariable("topicId") final String topicId) { + try { + topicClient.deleteTopic(topicId); + return "Topic " + topicId + " deleted successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to delete topic", e); + } + } + + /** + * Submits a message to a topic. + */ + @PostMapping("/message") + public String submitMessage(@RequestBody final TopicMessageRequest request) { + try { + if (request.submitKey() != null) { + topicClient.submitMessage(request.topicId(), request.submitKey(), request.message()); + } else { + topicClient.submitMessage(request.topicId(), request.message()); + } + return "Message submitted successfully to topic " + request.topicId(); + } catch (final Exception e) { + throw new RuntimeException("Failed to submit message", e); + } + } + + /** + * Retrieves info for a specific topic. + */ + @GetMapping("/{topicId}/info") + public Topic getTopicInfo(@PathVariable("topicId") final String topicId) { + try { + return topicRepository.findTopicById(topicId) + .orElseThrow(() -> new RuntimeException("Topic not found: " + topicId)); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve topic info", e); + } + } + + /** + * Retrieves messages for a specific topic. + */ + @GetMapping("/{topicId}/message") + public Page getTopicMessages(@PathVariable("topicId") final String topicId) { + try { + return topicRepository.getMessages(topicId); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve topic messages", e); + } + } + + /** + * Retrieves a specific message by its sequence number. + */ + @GetMapping("/{topicId}/message/{sequenceNumber}") + public TopicMessage getTopicMessageBySequenceNumber( + @PathVariable("topicId") final String topicId, + @PathVariable("sequenceNumber") final long sequenceNumber) { + try { + return topicRepository.getMessageBySequenceNumber(topicId, sequenceNumber) + .orElseThrow(() -> new RuntimeException("Message not found for Topic: " + topicId + " Sequence: " + sequenceNumber)); + } catch (final Exception e) { + throw new RuntimeException("Failed to retrieve topic message", e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java new file mode 100644 index 00000000..56ec65b9 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java @@ -0,0 +1,10 @@ +package org.hiero.spring.sample.dto.topic; + +/** + * Request DTO for creating a new Consensus Topic. + */ +public record TopicCreateRequest( + String memo, + String adminKey, + String submitKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java new file mode 100644 index 00000000..b37b7c1b --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java @@ -0,0 +1,10 @@ +package org.hiero.spring.sample.dto.topic; + +/** + * Request DTO for submitting a message to a Consensus Topic. + */ +public record TopicMessageRequest( + String topicId, + String message, + String submitKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java new file mode 100644 index 00000000..e9cba1cb --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java @@ -0,0 +1,12 @@ +package org.hiero.spring.sample.dto.topic; + +/** + * Request DTO for updating an existing Consensus Topic. + */ +public record TopicUpdateRequest( + String topicId, + String memo, + String adminKey, + String updatedAdminKey, + String submitKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/resources/application.properties b/hiero-enterprise-spring-sample/src/main/resources/application.properties index 8da268ef..9060ae81 100644 --- a/hiero-enterprise-spring-sample/src/main/resources/application.properties +++ b/hiero-enterprise-spring-sample/src/main/resources/application.properties @@ -4,6 +4,6 @@ spring.hiero.privateKey=${HEDERA_PRIVATE_KEY:302e020100300506032b657004220420d39 spring.hiero.network.name=${HEDERA_NETWORK:hedera-testnet} logging.level.org.hiero=DEBUG logging.pattern.console=%d{HH:mm:ss.SSS} %logger{36} - %msg%n -server.port=${SERVER_PORT:8080} +server.port=${SERVER_PORT:8081} # Needs to be done to activate the actuator endpoints (for metrics) management.endpoints.web.exposure.include=* \ No newline at end of file From 9377daf4b9122ba4d36f26f411dfd127b09a346b Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Tue, 5 May 2026 22:53:05 +0300 Subject: [PATCH 5/9] feat: implement network and file resource endpoints Signed-off-by: Twiineenock --- .../sample/controller/FileController.java | 107 ++++++++++++++++++ .../sample/controller/NetworkController.java | 79 +++++++++++++ .../sample/dto/file/FileCreateRequest.java | 21 ++++ .../sample/dto/file/FileUpdateRequest.java | 21 ++++ 4 files changed, 228 insertions(+) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java new file mode 100644 index 00000000..f3b7ea6f --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java @@ -0,0 +1,107 @@ +package org.hiero.spring.sample.controller; + +import com.hedera.hashgraph.sdk.FileId; +import java.util.Base64; +import java.util.Objects; +import org.hiero.base.FileClient; +import org.hiero.spring.sample.dto.file.FileCreateRequest; +import org.hiero.spring.sample.dto.file.FileUpdateRequest; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for Hiero file operations. + * Provides endpoints for creating, deleting, updating and querying files on the network. + */ +@RestController +@RequestMapping("/api/v1/hiero/files") +public class FileController { + + private final FileClient fileClient; + + public FileController(final FileClient fileClient) { + this.fileClient = Objects.requireNonNull(fileClient, "fileClient must not be null"); + } + + /** + * Creates a new file. + */ + @PostMapping + public String createFile(@RequestBody final FileCreateRequest request) { + try { + final FileId fileId; + if (request.expirationTime() != null) { + fileId = fileClient.createFile(request.getDecodedContent(), request.getExpirationInstant()); + } else { + fileId = fileClient.createFile(request.getDecodedContent()); + } + return "File " + fileId + " created successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to create file", e); + } + } + + /** + * Deletes a file. + */ + @DeleteMapping("/{fileId}") + public String deleteFile(@PathVariable("fileId") final String fileId) { + try { + fileClient.deleteFile(fileId); + return "File " + fileId + " deleted successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to delete file: " + fileId, e); + } + } + + /** + * Updates an existing file (Content and/or Expiration Time). + */ + @PostMapping("/{fileId}") + public String updateFile( + @PathVariable("fileId") final String fileId, + @RequestBody final FileUpdateRequest request) { + try { + final FileId fid = FileId.fromString(fileId); + if (request.content() != null) { + fileClient.updateFile(fid, request.getDecodedContent()); + } + if (request.expirationTime() != null) { + fileClient.updateExpirationTime(fid, request.getExpirationInstant()); + } + return "File " + fileId + " updated successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to update file: " + fileId, e); + } + } + + /** + * Retrieves the content of a file (returned as Base64 string). + */ + @GetMapping("/{fileId}/content") + public String getFileContent(@PathVariable("fileId") final String fileId) { + try { + final byte[] content = fileClient.readFile(fileId); + return Base64.getEncoder().encodeToString(content); + } catch (final Exception e) { + throw new RuntimeException("Failed to read file content: " + fileId, e); + } + } + + /** + * Retrieves the size of a file in bytes. + */ + @GetMapping("/{fileId}/size") + public Integer getFileSize(@PathVariable("fileId") final String fileId) { + try { + return fileClient.getSize(FileId.fromString(fileId)); + } catch (final Exception e) { + throw new RuntimeException("Failed to get file size: " + fileId, e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java new file mode 100644 index 00000000..51c7e798 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java @@ -0,0 +1,79 @@ +package org.hiero.spring.sample.controller; + +import java.util.List; +import java.util.Objects; +import org.hiero.base.data.ExchangeRates; +import org.hiero.base.data.NetworkFee; +import org.hiero.base.data.NetworkStake; +import org.hiero.base.data.NetworkSupplies; +import org.hiero.base.mirrornode.NetworkRepository; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST controller for Hiero network operations. + * Provides endpoints for querying network-wide information like exchange rates, fees, and staking. + */ +@RestController +@RequestMapping("/api/v1/hiero/network") +public class NetworkController { + + private final NetworkRepository networkRepository; + + public NetworkController(final NetworkRepository networkRepository) { + this.networkRepository = + Objects.requireNonNull(networkRepository, "networkRepository must not be null"); + } + + /** + * Retrieves the current and next exchange rates. + */ + @GetMapping("/exchange-rate") + public ExchangeRates getExchangeRates() { + try { + return networkRepository.exchangeRates() + .orElseThrow(() -> new RuntimeException("Exchange rates not available")); + } catch (final Exception e) { + throw new RuntimeException("Failed to query exchange rates", e); + } + } + + /** + * Retrieves the network fees. + */ + @GetMapping("/fee") + public List getFees() { + try { + return networkRepository.fees(); + } catch (final Exception e) { + throw new RuntimeException("Failed to query network fees", e); + } + } + + /** + * Retrieves network staking information. + */ + @GetMapping("/stake") + public NetworkStake getStake() { + try { + return networkRepository.stake() + .orElseThrow(() -> new RuntimeException("Network stake info not available")); + } catch (final Exception e) { + throw new RuntimeException("Failed to query network stake", e); + } + } + + /** + * Retrieves network supply information. + */ + @GetMapping("/supplies") + public NetworkSupplies getSupplies() { + try { + return networkRepository.supplies() + .orElseThrow(() -> new RuntimeException("Network supply info not available")); + } catch (final Exception e) { + throw new RuntimeException("Failed to query network supplies", e); + } + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java new file mode 100644 index 00000000..89ee9cf9 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java @@ -0,0 +1,21 @@ +package org.hiero.spring.sample.dto.file; + +import java.time.Instant; + +/** + * Request DTO for creating a Hiero file. + * @param content Base64 encoded content of the file. + * @param expirationTime Optional expiration time in seconds since epoch. + */ +public record FileCreateRequest( + String content, + Long expirationTime +) { + public byte[] getDecodedContent() { + return java.util.Base64.getDecoder().decode(content); + } + + public Instant getExpirationInstant() { + return expirationTime != null ? Instant.ofEpochSecond(expirationTime) : null; + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java new file mode 100644 index 00000000..fd303b78 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java @@ -0,0 +1,21 @@ +package org.hiero.spring.sample.dto.file; + +import java.time.Instant; + +/** + * Request DTO for updating a Hiero file. + * @param content Optional Base64 encoded content to update. + * @param expirationTime Optional new expiration time in seconds since epoch. + */ +public record FileUpdateRequest( + String content, + Long expirationTime +) { + public byte[] getDecodedContent() { + return content != null ? java.util.Base64.getDecoder().decode(content) : null; + } + + public Instant getExpirationInstant() { + return expirationTime != null ? Instant.ofEpochSecond(expirationTime) : null; + } +} From 64d14d391d9b456acf4647f65499156e9822a9d4 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Tue, 5 May 2026 23:02:50 +0300 Subject: [PATCH 6/9] feat: add network resource DTOs and update controller Signed-off-by: Twiineenock --- .../sample/controller/NetworkController.java | 23 ++++++----- .../dto/network/ExchangeRatesResponse.java | 25 ++++++++++++ .../dto/network/NetworkFeeResponse.java | 15 +++++++ .../dto/network/NetworkStakeResponse.java | 40 +++++++++++++++++++ .../dto/network/NetworkSuppliesResponse.java | 15 +++++++ 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java index 51c7e798..8eb6819b 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java @@ -2,11 +2,11 @@ import java.util.List; import java.util.Objects; -import org.hiero.base.data.ExchangeRates; -import org.hiero.base.data.NetworkFee; -import org.hiero.base.data.NetworkStake; -import org.hiero.base.data.NetworkSupplies; import org.hiero.base.mirrornode.NetworkRepository; +import org.hiero.spring.sample.dto.network.ExchangeRatesResponse; +import org.hiero.spring.sample.dto.network.NetworkFeeResponse; +import org.hiero.spring.sample.dto.network.NetworkStakeResponse; +import org.hiero.spring.sample.dto.network.NetworkSuppliesResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -30,9 +30,10 @@ public NetworkController(final NetworkRepository networkRepository) { * Retrieves the current and next exchange rates. */ @GetMapping("/exchange-rate") - public ExchangeRates getExchangeRates() { + public ExchangeRatesResponse getExchangeRates() { try { return networkRepository.exchangeRates() + .map(ExchangeRatesResponse::fromDomain) .orElseThrow(() -> new RuntimeException("Exchange rates not available")); } catch (final Exception e) { throw new RuntimeException("Failed to query exchange rates", e); @@ -43,9 +44,11 @@ public ExchangeRates getExchangeRates() { * Retrieves the network fees. */ @GetMapping("/fee") - public List getFees() { + public List getFees() { try { - return networkRepository.fees(); + return networkRepository.fees().stream() + .map(NetworkFeeResponse::fromDomain) + .toList(); } catch (final Exception e) { throw new RuntimeException("Failed to query network fees", e); } @@ -55,9 +58,10 @@ public List getFees() { * Retrieves network staking information. */ @GetMapping("/stake") - public NetworkStake getStake() { + public NetworkStakeResponse getStake() { try { return networkRepository.stake() + .map(NetworkStakeResponse::fromDomain) .orElseThrow(() -> new RuntimeException("Network stake info not available")); } catch (final Exception e) { throw new RuntimeException("Failed to query network stake", e); @@ -68,9 +72,10 @@ public NetworkStake getStake() { * Retrieves network supply information. */ @GetMapping("/supplies") - public NetworkSupplies getSupplies() { + public NetworkSuppliesResponse getSupplies() { try { return networkRepository.supplies() + .map(NetworkSuppliesResponse::fromDomain) .orElseThrow(() -> new RuntimeException("Network supply info not available")); } catch (final Exception e) { throw new RuntimeException("Failed to query network supplies", e); diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java new file mode 100644 index 00000000..efafb267 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java @@ -0,0 +1,25 @@ +package org.hiero.spring.sample.dto.network; + +import java.time.Instant; +import org.hiero.base.data.ExchangeRates; + +/** + * Response DTO for Network Exchange Rates. + */ +public record ExchangeRatesResponse( + RateInfo currentRate, + RateInfo nextRate +) { + public record RateInfo( + int centEquivalent, + int hbarEquivalent, + Instant expirationTime + ) {} + + public static ExchangeRatesResponse fromDomain(ExchangeRates rates) { + return new ExchangeRatesResponse( + new RateInfo(rates.currentRate().centEquivalent(), rates.currentRate().hbarEquivalent(), rates.currentRate().expirationTime()), + new RateInfo(rates.nextRate().centEquivalent(), rates.nextRate().hbarEquivalent(), rates.nextRate().expirationTime()) + ); + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java new file mode 100644 index 00000000..0fa98d1b --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java @@ -0,0 +1,15 @@ +package org.hiero.spring.sample.dto.network; + +import org.hiero.base.data.NetworkFee; + +/** + * Response DTO for Network Fees. + */ +public record NetworkFeeResponse( + long gas, + String transactionType +) { + public static NetworkFeeResponse fromDomain(NetworkFee fee) { + return new NetworkFeeResponse(fee.gas(), fee.transactionType()); + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java new file mode 100644 index 00000000..82f55ffb --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java @@ -0,0 +1,40 @@ +package org.hiero.spring.sample.dto.network; + +import org.hiero.base.data.NetworkStake; + +/** + * Response DTO for Network Stake Information. + */ +public record NetworkStakeResponse( + long maxStakeReward, + long maxStakeRewardPerHbar, + long maxTotalReward, + double nodeRewardFeeFraction, + long reservedStakingRewards, + long rewardBalanceThreshold, + long stakeTotal, + long stakingPeriodDuration, + long stakingPeriodsStored, + double stakingRewardFeeFraction, + long stakingRewardRate, + long stakingStartThreshold, + long unreservedStakingRewardBalance +) { + public static NetworkStakeResponse fromDomain(NetworkStake stake) { + return new NetworkStakeResponse( + stake.maxStakeReward(), + stake.maxStakeRewardPerHbar(), + stake.maxTotalReward(), + stake.nodeRewardFeeFraction(), + stake.reservedStakingRewards(), + stake.rewardBalanceThreshold(), + stake.stakeTotal(), + stake.stakingPeriodDuration(), + stake.stakingPeriodsStored(), + stake.stakingRewardFeeFraction(), + stake.stakingRewardRate(), + stake.stakingStartThreshold(), + stake.unreservedStakingRewardBalance() + ); + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java new file mode 100644 index 00000000..f2f2ac4c --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java @@ -0,0 +1,15 @@ +package org.hiero.spring.sample.dto.network; + +import org.hiero.base.data.NetworkSupplies; + +/** + * Response DTO for Network Supplies. + */ +public record NetworkSuppliesResponse( + String releasedSupply, + String totalSupply +) { + public static NetworkSuppliesResponse fromDomain(NetworkSupplies supplies) { + return new NetworkSuppliesResponse(supplies.releasedSupply(), supplies.totalSupply()); + } +} From a8feaf01ca5eb513b2390a972a7e805d5960cd71 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Wed, 6 May 2026 00:36:53 +0300 Subject: [PATCH 7/9] refactor: revert server port to 8080 in application.properties Signed-off-by: Twiineenock --- .../src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hiero-enterprise-spring-sample/src/main/resources/application.properties b/hiero-enterprise-spring-sample/src/main/resources/application.properties index 9060ae81..8da268ef 100644 --- a/hiero-enterprise-spring-sample/src/main/resources/application.properties +++ b/hiero-enterprise-spring-sample/src/main/resources/application.properties @@ -4,6 +4,6 @@ spring.hiero.privateKey=${HEDERA_PRIVATE_KEY:302e020100300506032b657004220420d39 spring.hiero.network.name=${HEDERA_NETWORK:hedera-testnet} logging.level.org.hiero=DEBUG logging.pattern.console=%d{HH:mm:ss.SSS} %logger{36} - %msg%n -server.port=${SERVER_PORT:8081} +server.port=${SERVER_PORT:8080} # Needs to be done to activate the actuator endpoints (for metrics) management.endpoints.web.exposure.include=* \ No newline at end of file From 464b802d6302745d012fd302d188e8dc1096b564 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Wed, 6 May 2026 21:29:48 +0300 Subject: [PATCH 8/9] feat(spring-sample): improve API robustness and organize Swagger UI - Implement input trimming across all controllers to prevent ID parsing failures. - Add NFT association endpoint and NftAssociateRequest DTO. - Organize Swagger schemas into logical groups using @Schema(name). - Enforce logical resource ordering in Swagger UI via custom OpenApiCustomizer. - Resolve file creation range errors and topic message parsing issues. Signed-off-by: Twiineenock --- hiero-enterprise-spring-sample/pom.xml | 5 ++ .../spring/sample/config/OpenApiConfig.java | 59 +++++++++++++++++ .../sample/controller/AccountController.java | 43 ++++++++----- .../sample/controller/BlockController.java | 5 ++ .../sample/controller/FileController.java | 16 +++-- .../sample/controller/NetworkController.java | 7 ++ .../sample/controller/NftController.java | 64 ++++++++++++++++--- .../sample/controller/TokenController.java | 57 ++++++++++++++--- .../sample/controller/TopicController.java | 45 +++++++++---- .../dto/account/AccountCreateRequest.java | 10 ++- .../dto/account/AccountDeleteRequest.java | 10 +-- .../sample/dto/account/AccountResponse.java | 5 ++ .../dto/account/AccountUpdateRequest.java | 12 ++-- .../sample/dto/file/FileCreateRequest.java | 6 +- .../sample/dto/file/FileUpdateRequest.java | 6 +- .../dto/network/ExchangeRatesResponse.java | 2 + .../dto/network/NetworkFeeResponse.java | 4 ++ .../dto/network/NetworkStakeResponse.java | 2 + .../dto/network/NetworkSuppliesResponse.java | 4 ++ .../sample/dto/nft/NftAssociateRequest.java | 14 ++++ .../sample/dto/nft/NftCreateRequest.java | 11 +++- .../spring/sample/dto/nft/NftMintRequest.java | 9 ++- .../sample/dto/nft/NftTransferRequest.java | 10 ++- .../dto/token/TokenAssociateRequest.java | 15 +++++ .../sample/dto/token/TokenCreateRequest.java | 12 ++-- .../sample/dto/token/TokenMintRequest.java | 8 ++- .../dto/token/TokenTransferRequest.java | 12 ++-- .../sample/dto/topic/TopicCreateRequest.java | 6 ++ .../sample/dto/topic/TopicMessageRequest.java | 6 ++ .../sample/dto/topic/TopicUpdateRequest.java | 8 +++ 30 files changed, 387 insertions(+), 86 deletions(-) create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java create mode 100644 hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java diff --git a/hiero-enterprise-spring-sample/pom.xml b/hiero-enterprise-spring-sample/pom.xml index 224bd11b..42e36a56 100644 --- a/hiero-enterprise-spring-sample/pom.xml +++ b/hiero-enterprise-spring-sample/pom.xml @@ -42,6 +42,11 @@ io.grpc grpc-inprocess + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java new file mode 100644 index 00000000..f607f5d9 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java @@ -0,0 +1,59 @@ +package org.hiero.spring.sample.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.tags.Tag; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI hieroEnterpriseOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Hiero Enterprise Spring Sample API") + .description("Interactive REST API for Hiero Enterprise Java integration. " + + "Provides endpoints for Accounts, Tokens, NFTs, Consensus Topics, and Files.") + .version("v0.20.0") + .contact(new Contact() + .name("Hiero Enterprise Team") + .url("https://github.com/hiero-ledger/hiero-enterprise-java")) + .license(new License() + .name("Apache 2.0") + .url("http://www.apache.org/licenses/LICENSE-2.0.html"))); + } + + @Bean + public OpenApiCustomizer sortTagsCustomizer() { + return openApi -> { + final List order = List.of( + "Accounts", + "Fungible Tokens", + "Non-Fungible Tokens", + "Consensus Topics", + "Blocks", + "Network", + "Files" + ); + + List tags = openApi.getTags(); + if (tags != null) { + // Create a copy to avoid modification issues during sorting if it's a fixed-size list + List sortedTags = new ArrayList<>(tags); + sortedTags.sort(Comparator.comparingInt(tag -> { + int index = order.indexOf(tag.getName()); + return index == -1 ? order.size() : index; + })); + openApi.setTags(sortedTags); + } + }; + } +} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java index 39c48363..c6eb1c5e 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java @@ -4,6 +4,8 @@ import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.PrivateKey; import java.util.Objects; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.hiero.base.AccountClient; import org.hiero.base.data.Account; import org.hiero.base.data.AccountInfo; @@ -25,6 +27,7 @@ * REST controller for Hiero account operations. * This controller provides endpoints for account lifecycle management and queries. */ +@Tag(name = "Accounts", description = "Operations related to Hiero account lifecycle and balance queries") @RestController @RequestMapping("/api/v1/hiero/accounts") public class AccountController { @@ -46,6 +49,7 @@ public AccountController( * @param request The account creation request containing optional initial balance. * @return Success message with the new account ID. */ + @Operation(summary = "Create a new Hiero account", description = "Creates a new Hiero account with an optional initial balance.") @PostMapping public AccountResponse createAccount(@RequestBody(required = false) final AccountCreateRequest request) { try { @@ -70,17 +74,21 @@ public AccountResponse createAccount(@RequestBody(required = false) final Accoun * @param request The account update request containing new key or memo. * @return Success message. */ + @Operation(summary = "Update an existing Hiero account", description = "Updates an account's metadata such as keys or memo.") @PutMapping public String updateAccount(@RequestBody final AccountUpdateRequest request) { try { - final AccountId accountId = AccountId.fromString(request.accountId()); - final PrivateKey currentKey = PrivateKey.fromString(request.privateKey()); + if (request.accountId() == null || request.privateKey() == null) { + throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + } + final AccountId accountId = AccountId.fromString(request.accountId().trim()); + final PrivateKey currentKey = PrivateKey.fromString(request.privateKey().trim()); final Account account = Account.of(accountId, currentKey); if (request.newPrivateKey() != null && request.memo() != null) { - accountClient.updateAccount(account, PrivateKey.fromString(request.newPrivateKey()), request.memo()); + accountClient.updateAccount(account, PrivateKey.fromString(request.newPrivateKey().trim()), request.memo()); } else if (request.newPrivateKey() != null) { - accountClient.updateAccountKey(account, PrivateKey.fromString(request.newPrivateKey())); + accountClient.updateAccountKey(account, PrivateKey.fromString(request.newPrivateKey().trim())); } else if (request.memo() != null) { accountClient.updateAccountMemo(account, request.memo()); } @@ -96,22 +104,20 @@ public String updateAccount(@RequestBody final AccountUpdateRequest request) { * * @param request The account deletion request. */ + @Operation(summary = "Delete a Hiero account", description = "Deletes an account and optionally transfers remaining balance to another account.") @DeleteMapping public String deleteAccount(@RequestBody final AccountDeleteRequest request) { try { - final AccountId accountId = AccountId.fromString(request.accountId()); - final PrivateKey privateKey = PrivateKey.fromString(request.privateKey()); + if (request.accountId() == null || request.privateKey() == null) { + throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + } + final AccountId accountId = AccountId.fromString(request.accountId().trim()); + final PrivateKey privateKey = PrivateKey.fromString(request.privateKey().trim()); final Account account = Account.of(accountId, privateKey); if (request.transferToAccountId() != null) { - final AccountId transferTo = AccountId.fromString(request.transferToAccountId()); - // Since we don't have a full Account object for the transferTo target, - // we use a minimal one if the client allows it, or we might need more logic. - // For now, let's assume we can pass a dummy account if we only need the ID. - // Wait, AccountClient.deleteAccount(Account account, Account toAccount) - // actually takes Account objects. - // Let's check if we can create a shell Account for the transfer target. - final Account transferTarget = Account.of(transferTo, PrivateKey.generateED25519()); // Key won't be used for receiving + final AccountId transferTo = AccountId.fromString(request.transferToAccountId().trim()); + final Account transferTarget = Account.of(transferTo, PrivateKey.generateED25519()); accountClient.deleteAccount(account, transferTarget); } else { accountClient.deleteAccount(account); @@ -129,10 +135,11 @@ public String deleteAccount(@RequestBody final AccountDeleteRequest request) { * @param accountId The ID of the account to query. * @return The balance in Hbar. */ + @Operation(summary = "Get account balance", description = "Retrieves the current balance of a Hiero account in Hbar.") @GetMapping("/balance/{accountId}") public String getBalance(@PathVariable("accountId") final String accountId) { try { - final Hbar balance = accountClient.getAccountBalance(accountId); + final Hbar balance = accountClient.getAccountBalance(accountId.trim()); return balance.toString(); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve balance for account " + accountId, e); @@ -145,11 +152,13 @@ public String getBalance(@PathVariable("accountId") final String accountId) { * @param accountId The ID of the account to query. * @return The AccountInfo object. */ + @Operation(summary = "Get account information", description = "Retrieves detailed account information from the Hiero mirror node.") @GetMapping("/info/{accountId}") public AccountInfo getInfo(@PathVariable("accountId") final String accountId) { try { - return accountRepository.findById(accountId) - .orElseThrow(() -> new RuntimeException("Account not found: " + accountId)); + final String trimmedAccountId = accountId.trim(); + return accountRepository.findById(trimmedAccountId) + .orElseThrow(() -> new RuntimeException("Account not found: " + trimmedAccountId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve info for account " + accountId, e); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java index a1512510..80c4792e 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java @@ -1,5 +1,7 @@ package org.hiero.spring.sample.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Objects; import org.hiero.base.data.Block; import org.hiero.base.data.Page; @@ -13,6 +15,7 @@ * REST controller for Hiero block operations. * This controller provides endpoints for querying block information from the mirror node. */ +@Tag(name = "Blocks", description = "Operations related to Hiero network blocks (Mirror Node)") @RestController @RequestMapping("/api/v1/hiero/blocks") public class BlockController { @@ -29,6 +32,7 @@ public BlockController(final BlockRepository blockRepository) { * * @return A page of blocks. */ + @Operation(summary = "Get all blocks", description = "Retrieves a paginated list of blocks from the Hiero mirror node.") @GetMapping public Page getBlocks() { try { @@ -44,6 +48,7 @@ public Page getBlocks() { * @param number The block number. * @return The block details. */ + @Operation(summary = "Get block by number", description = "Retrieves details of a specific block by its block number.") @GetMapping("/{number}") public Block getBlockByNumber(@PathVariable("number") final long number) { try { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java index f3b7ea6f..84b23074 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java @@ -1,6 +1,8 @@ package org.hiero.spring.sample.controller; import com.hedera.hashgraph.sdk.FileId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Base64; import java.util.Objects; import org.hiero.base.FileClient; @@ -18,6 +20,7 @@ * REST controller for Hiero file operations. * Provides endpoints for creating, deleting, updating and querying files on the network. */ +@Tag(name = "Files", description = "Operations related to Hiero File Service (HFS)") @RestController @RequestMapping("/api/v1/hiero/files") public class FileController { @@ -31,6 +34,7 @@ public FileController(final FileClient fileClient) { /** * Creates a new file. */ + @Operation(summary = "Create a new file", description = "Creates a new file on the Hiero network with the provided content.") @PostMapping public String createFile(@RequestBody final FileCreateRequest request) { try { @@ -49,10 +53,11 @@ public String createFile(@RequestBody final FileCreateRequest request) { /** * Deletes a file. */ + @Operation(summary = "Delete a file", description = "Deletes an existing file from the Hiero network.") @DeleteMapping("/{fileId}") public String deleteFile(@PathVariable("fileId") final String fileId) { try { - fileClient.deleteFile(fileId); + fileClient.deleteFile(fileId.trim()); return "File " + fileId + " deleted successfully!"; } catch (final Exception e) { throw new RuntimeException("Failed to delete file: " + fileId, e); @@ -62,12 +67,13 @@ public String deleteFile(@PathVariable("fileId") final String fileId) { /** * Updates an existing file (Content and/or Expiration Time). */ + @Operation(summary = "Update a file", description = "Updates the content or expiration time of an existing file.") @PostMapping("/{fileId}") public String updateFile( @PathVariable("fileId") final String fileId, @RequestBody final FileUpdateRequest request) { try { - final FileId fid = FileId.fromString(fileId); + final FileId fid = FileId.fromString(fileId.trim()); if (request.content() != null) { fileClient.updateFile(fid, request.getDecodedContent()); } @@ -83,10 +89,11 @@ public String updateFile( /** * Retrieves the content of a file (returned as Base64 string). */ + @Operation(summary = "Get file content", description = "Retrieves the byte content of a file, encoded as a Base64 string.") @GetMapping("/{fileId}/content") public String getFileContent(@PathVariable("fileId") final String fileId) { try { - final byte[] content = fileClient.readFile(fileId); + final byte[] content = fileClient.readFile(fileId.trim()); return Base64.getEncoder().encodeToString(content); } catch (final Exception e) { throw new RuntimeException("Failed to read file content: " + fileId, e); @@ -96,10 +103,11 @@ public String getFileContent(@PathVariable("fileId") final String fileId) { /** * Retrieves the size of a file in bytes. */ + @Operation(summary = "Get file size", description = "Retrieves the size of a file in bytes.") @GetMapping("/{fileId}/size") public Integer getFileSize(@PathVariable("fileId") final String fileId) { try { - return fileClient.getSize(FileId.fromString(fileId)); + return fileClient.getSize(FileId.fromString(fileId.trim())); } catch (final Exception e) { throw new RuntimeException("Failed to get file size: " + fileId, e); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java index 8eb6819b..c998ec1a 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java @@ -1,5 +1,7 @@ package org.hiero.spring.sample.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.Objects; import org.hiero.base.mirrornode.NetworkRepository; @@ -15,6 +17,7 @@ * REST controller for Hiero network operations. * Provides endpoints for querying network-wide information like exchange rates, fees, and staking. */ +@Tag(name = "Network", description = "Operations related to Hiero network status (rates, fees, staking)") @RestController @RequestMapping("/api/v1/hiero/network") public class NetworkController { @@ -29,6 +32,7 @@ public NetworkController(final NetworkRepository networkRepository) { /** * Retrieves the current and next exchange rates. */ + @Operation(summary = "Get exchange rates", description = "Retrieves the current and next Hbar-to-USD exchange rates.") @GetMapping("/exchange-rate") public ExchangeRatesResponse getExchangeRates() { try { @@ -43,6 +47,7 @@ public ExchangeRatesResponse getExchangeRates() { /** * Retrieves the network fees. */ + @Operation(summary = "Get network fees", description = "Retrieves a list of Hiero network transaction fees.") @GetMapping("/fee") public List getFees() { try { @@ -57,6 +62,7 @@ public List getFees() { /** * Retrieves network staking information. */ + @Operation(summary = "Get network staking info", description = "Retrieves current staking parameters and status for the Hiero network.") @GetMapping("/stake") public NetworkStakeResponse getStake() { try { @@ -71,6 +77,7 @@ public NetworkStakeResponse getStake() { /** * Retrieves network supply information. */ + @Operation(summary = "Get network supplies", description = "Retrieves the total and circulating supply of Hbar.") @GetMapping("/supplies") public NetworkSuppliesResponse getSupplies() { try { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java index 7b599946..b86f16fb 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java @@ -3,6 +3,8 @@ import com.hedera.hashgraph.sdk.AccountId; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.TokenId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.nio.charset.StandardCharsets; import java.util.Objects; import org.hiero.base.NftClient; @@ -13,6 +15,7 @@ import org.hiero.base.data.TokenInfo; import org.hiero.base.mirrornode.NftRepository; import org.hiero.base.mirrornode.TokenRepository; +import org.hiero.spring.sample.dto.nft.NftAssociateRequest; import org.hiero.spring.sample.dto.nft.NftCreateRequest; import org.hiero.spring.sample.dto.nft.NftMintRequest; import org.hiero.spring.sample.dto.nft.NftTransferRequest; @@ -27,6 +30,7 @@ /** * REST controller for Hiero NFT operations. */ +@Tag(name = "Non-Fungible Tokens", description = "Operations related to Hiero Non-Fungible Token Service (NFTs)") @RestController @CrossOrigin @RequestMapping("/api/v1/hiero/nfts") @@ -48,9 +52,13 @@ public NftController( /** * Creates a new NFT type. */ + @Operation(summary = "Create an NFT type", description = "Creates a new HTS NFT collection.") @PostMapping public String createNft(@RequestBody final NftCreateRequest request) { try { + if (request.name() == null || request.symbol() == null) { + throw new IllegalArgumentException("Missing required fields: name and symbol are mandatory."); + } final TokenId tokenId = nftClient.createNftType(request.name(), request.symbol()); return "NFT Type " + tokenId + " created successfully!"; } catch (final Exception e) { @@ -61,11 +69,13 @@ public String createNft(@RequestBody final NftCreateRequest request) { /** * Retrieves detailed information about an NFT type. */ + @Operation(summary = "Get NFT type information", description = "Retrieves detailed information about an NFT collection from the mirror node.") @GetMapping("/{nftId}") public TokenInfo getNftById(@PathVariable("nftId") final String nftId) { try { - return tokenRepository.findById(nftId) - .orElseThrow(() -> new RuntimeException("NFT Type not found: " + nftId)); + final String trimmedNftId = nftId.trim(); + return tokenRepository.findById(trimmedNftId) + .orElseThrow(() -> new RuntimeException("NFT Type not found: " + trimmedNftId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve NFT info for " + nftId, e); } @@ -74,13 +84,18 @@ public TokenInfo getNftById(@PathVariable("nftId") final String nftId) { /** * Mints a new NFT instance. */ + @Operation(summary = "Mint an NFT", description = "Mints a new serial number for an existing NFT collection.") @PostMapping("/{nftId}/mint") public String mintNft( @PathVariable("nftId") final String nftId, @RequestBody final NftMintRequest request) { try { + if (request.metadata() == null) { + throw new IllegalArgumentException("Missing required field: metadata is mandatory."); + } + final String trimmedNftId = nftId.trim(); final byte[] metadata = request.metadata().getBytes(StandardCharsets.UTF_8); - final long serialNumber = nftClient.mintNft(nftId, metadata); + final long serialNumber = nftClient.mintNft(trimmedNftId, metadata); return "Minted NFT instance of " + nftId + " with serial number " + serialNumber; } catch (final Exception e) { throw new RuntimeException("Failed to mint NFT for " + nftId, e); @@ -90,13 +105,15 @@ public String mintNft( /** * Retrieves a specific NFT instance by ID and serial number. */ + @Operation(summary = "Get NFT instance", description = "Retrieves information about a specific NFT instance (serial number) from the mirror node.") @GetMapping("/{nftId}/serial/{serial}") public Nft getNftBySerial( @PathVariable("nftId") final String nftId, @PathVariable("serial") final long serial) { try { - return nftRepository.findByTypeAndSerial(nftId, serial) - .orElseThrow(() -> new RuntimeException("NFT not found: " + nftId + " Serial: " + serial)); + final String trimmedNftId = nftId.trim(); + return nftRepository.findByTypeAndSerial(trimmedNftId, serial) + .orElseThrow(() -> new RuntimeException("NFT not found: " + trimmedNftId + " Serial: " + serial)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve NFT instance", e); } @@ -105,17 +122,21 @@ public Nft getNftBySerial( /** * Transfers an NFT instance between accounts. */ + @Operation(summary = "Transfer an NFT", description = "Transfers a specific NFT serial number between two Hiero accounts.") @PostMapping("/{nftId}/transfer") public String transferNft( @PathVariable("nftId") final String nftId, @RequestBody final NftTransferRequest request) { try { - final TokenId tokenId = TokenId.fromString(nftId); + if (request.fromAccountId() == null || request.fromPrivateKey() == null || request.toAccountId() == null) { + throw new IllegalArgumentException("Missing required fields: fromAccountId, fromPrivateKey, and toAccountId are mandatory."); + } + final TokenId tokenId = TokenId.fromString(nftId.trim()); final Account fromAccount = Account.of( - AccountId.fromString(request.fromAccountId()), - PrivateKey.fromString(request.fromPrivateKey()) + AccountId.fromString(request.fromAccountId().trim()), + PrivateKey.fromString(request.fromPrivateKey().trim()) ); - final AccountId toAccountId = AccountId.fromString(request.toAccountId()); + final AccountId toAccountId = AccountId.fromString(request.toAccountId().trim()); nftClient.transferNft(tokenId, request.serialNumber(), fromAccount, toAccountId); return "Transferred NFT " + nftId + " (Serial: " + request.serialNumber() + ") to " + request.toAccountId(); @@ -123,4 +144,29 @@ public String transferNft( throw new RuntimeException("Failed to transfer NFT", e); } } + + /** + * Associates an account with a Hiero NFT collection. + */ + @Operation(summary = "Associate account with NFT", description = "Explicitly associates a Hiero account with an NFT collection. Required before receiving NFTs.") + @PostMapping("/{nftId}/associate") + public String associateNft( + @PathVariable("nftId") final String nftId, + @RequestBody final NftAssociateRequest request) { + try { + if (request.accountId() == null || request.privateKey() == null) { + throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + } + final TokenId id = TokenId.fromString(nftId.trim()); + final Account account = Account.of( + AccountId.fromString(request.accountId().trim()), + PrivateKey.fromString(request.privateKey().trim()) + ); + + nftClient.associateNft(id, account); + return "Account " + request.accountId() + " associated with NFT collection " + nftId + " successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to associate account " + request.accountId() + " with NFT " + nftId, e); + } + } } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java index 3edf65c4..6390e031 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java @@ -3,11 +3,14 @@ import com.hedera.hashgraph.sdk.AccountId; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.TokenId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Objects; import org.hiero.base.FungibleTokenClient; import org.hiero.base.data.Account; import org.hiero.base.data.TokenInfo; import org.hiero.base.mirrornode.TokenRepository; +import org.hiero.spring.sample.dto.token.TokenAssociateRequest; import org.hiero.spring.sample.dto.token.TokenCreateRequest; import org.hiero.spring.sample.dto.token.TokenMintRequest; import org.hiero.spring.sample.dto.token.TokenTransferRequest; @@ -19,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "Fungible Tokens", description = "Operations related to Hiero fungible tokens (HTS)") @RestController @CrossOrigin @RequestMapping("/api/v1/hiero/fungible-token") @@ -37,9 +41,13 @@ public TokenController( /** * Creates a new Hiero fungible token. */ + @Operation(summary = "Create a new fungible token", description = "Creates a new Hiero fungible token (HTS).") @PostMapping public String createToken(@RequestBody final TokenCreateRequest request) { try { + if (request.name() == null || request.symbol() == null) { + throw new IllegalArgumentException("Missing required fields: name and symbol are mandatory."); + } final TokenId tokenId = tokenClient.createToken(request.name(), request.symbol()); return "Token " + tokenId + " created successfully!"; } catch (final Exception e) { @@ -50,11 +58,13 @@ public String createToken(@RequestBody final TokenCreateRequest request) { /** * Retrieves detailed information about a Hiero fungible token. */ + @Operation(summary = "Get token information", description = "Retrieves detailed information about a fungible token from the mirror node.") @GetMapping("/{tokenId}") public TokenInfo getToken(@PathVariable("tokenId") final String tokenId) { try { - return tokenRepository.findById(tokenId) - .orElseThrow(() -> new RuntimeException("Token not found: " + tokenId)); + final String trimmedTokenId = tokenId.trim(); + return tokenRepository.findById(trimmedTokenId) + .orElseThrow(() -> new RuntimeException("Token not found: " + trimmedTokenId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve token info for " + tokenId, e); } @@ -63,16 +73,18 @@ public TokenInfo getToken(@PathVariable("tokenId") final String tokenId) { /** * Mints more of a Hiero fungible token. */ + @Operation(summary = "Mint tokens", description = "Mints additional units of a fungible token to the treasury account.") @PostMapping("/{tokenId}/mint") public String mintToken( @PathVariable("tokenId") final String tokenId, @RequestBody final TokenMintRequest request) { try { + final String trimmedTokenId = tokenId.trim(); final long newTotalSupply; if (request.supplyKey() != null) { - newTotalSupply = tokenClient.mintToken(tokenId, request.supplyKey(), request.amount()); + newTotalSupply = tokenClient.mintToken(trimmedTokenId, request.supplyKey(), request.amount()); } else { - newTotalSupply = tokenClient.mintToken(tokenId, request.amount()); + newTotalSupply = tokenClient.mintToken(trimmedTokenId, request.amount()); } return "Minted " + request.amount() + " units. New total supply: " + newTotalSupply; } catch (final Exception e) { @@ -83,17 +95,21 @@ public String mintToken( /** * Transfers Hiero fungible tokens between accounts. */ + @Operation(summary = "Transfer tokens", description = "Transfers fungible tokens between two Hiero accounts.") @PostMapping("/{tokenId}/transfer") public String transferToken( @PathVariable("tokenId") final String tokenId, @RequestBody final TokenTransferRequest request) { try { - final TokenId id = TokenId.fromString(tokenId); + if (request.fromAccountId() == null || request.fromPrivateKey() == null || request.toAccountId() == null) { + throw new IllegalArgumentException("Missing required fields: fromAccountId, fromPrivateKey, and toAccountId are mandatory."); + } + final TokenId id = TokenId.fromString(tokenId.trim()); final Account fromAccount = Account.of( - AccountId.fromString(request.fromAccountId()), - PrivateKey.fromString(request.fromPrivateKey()) + AccountId.fromString(request.fromAccountId().trim()), + PrivateKey.fromString(request.fromPrivateKey().trim()) ); - final AccountId toAccount = AccountId.fromString(request.toAccountId()); + final AccountId toAccount = AccountId.fromString(request.toAccountId().trim()); tokenClient.transferToken(id, fromAccount, toAccount, request.amount()); return "Transferred " + request.amount() + " tokens from " + request.fromAccountId() + " to " + request.toAccountId(); @@ -101,4 +117,29 @@ public String transferToken( throw new RuntimeException("Failed to transfer token " + tokenId, e); } } + + /** + * Associates an account with a Hiero fungible token. + */ + @Operation(summary = "Associate account with token", description = "Explicitly associates a Hiero account with a fungible token. Required before receiving tokens.") + @PostMapping("/{tokenId}/associate") + public String associateToken( + @PathVariable("tokenId") final String tokenId, + @RequestBody final TokenAssociateRequest request) { + try { + if (request.accountId() == null || request.privateKey() == null) { + throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + } + final TokenId id = TokenId.fromString(tokenId.trim()); + final Account account = Account.of( + AccountId.fromString(request.accountId().trim()), + PrivateKey.fromString(request.privateKey().trim()) + ); + + tokenClient.associateToken(id, account); + return "Account " + request.accountId() + " associated with token " + tokenId + " successfully!"; + } catch (final Exception e) { + throw new RuntimeException("Failed to associate account " + request.accountId() + " with token " + tokenId, e); + } + } } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java index 7bf001bd..180eea97 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java @@ -2,6 +2,8 @@ import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.TopicId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Objects; import java.util.Optional; import org.hiero.base.TopicClient; @@ -25,6 +27,7 @@ /** * REST controller for Hiero Consensus Service (Topic) operations. */ +@Tag(name = "Consensus Topics", description = "Operations related to Hiero Consensus Service (HCS)") @RestController @RequestMapping("/api/v1/hiero/topics") @CrossOrigin(origins = "*") @@ -41,6 +44,7 @@ public TopicController(final TopicClient topicClient, final TopicRepository topi /** * Creates a new topic. */ + @Operation(summary = "Create a new topic", description = "Creates a new public or private HCS topic.") @PostMapping public String createTopic(@RequestBody final TopicCreateRequest request) { try { @@ -71,20 +75,24 @@ public String createTopic(@RequestBody final TopicCreateRequest request) { /** * Updates an existing topic. */ + @Operation(summary = "Update a topic", description = "Updates an existing HCS topic's memo or keys.") @PutMapping public String updateTopic(@RequestBody final TopicUpdateRequest request) { try { - final TopicId topicId = TopicId.fromString(request.topicId()); + if (request.topicId() == null) { + throw new IllegalArgumentException("Missing required field: topicId is mandatory."); + } + final TopicId topicId = TopicId.fromString(request.topicId().trim()); final String memo = request.memo() != null ? request.memo() : ""; if (request.adminKey() != null && request.updatedAdminKey() != null && request.submitKey() != null) { topicClient.updateTopic(topicId, - PrivateKey.fromString(request.adminKey()), - PrivateKey.fromString(request.updatedAdminKey()), - PrivateKey.fromString(request.submitKey()), + PrivateKey.fromString(request.adminKey().trim()), + PrivateKey.fromString(request.updatedAdminKey().trim()), + PrivateKey.fromString(request.submitKey().trim()), memo); } else if (request.adminKey() != null) { - topicClient.updateTopic(topicId, PrivateKey.fromString(request.adminKey()), memo); + topicClient.updateTopic(topicId, PrivateKey.fromString(request.adminKey().trim()), memo); } else { topicClient.updateTopic(topicId, memo); } @@ -97,10 +105,11 @@ public String updateTopic(@RequestBody final TopicUpdateRequest request) { /** * Deletes a topic. */ + @Operation(summary = "Delete a topic", description = "Deletes an existing HCS topic.") @DeleteMapping("/{topicId}") public String deleteTopic(@PathVariable("topicId") final String topicId) { try { - topicClient.deleteTopic(topicId); + topicClient.deleteTopic(topicId.trim()); return "Topic " + topicId + " deleted successfully!"; } catch (final Exception e) { throw new RuntimeException("Failed to delete topic", e); @@ -110,13 +119,18 @@ public String deleteTopic(@PathVariable("topicId") final String topicId) { /** * Submits a message to a topic. */ + @Operation(summary = "Submit a message", description = "Submits a message to a specific HCS topic.") @PostMapping("/message") public String submitMessage(@RequestBody final TopicMessageRequest request) { try { + if (request.topicId() == null || request.message() == null) { + throw new IllegalArgumentException("Missing required fields: topicId and message are mandatory."); + } + final String trimmedTopicId = request.topicId().trim(); if (request.submitKey() != null) { - topicClient.submitMessage(request.topicId(), request.submitKey(), request.message()); + topicClient.submitMessage(trimmedTopicId, request.submitKey().trim(), request.message()); } else { - topicClient.submitMessage(request.topicId(), request.message()); + topicClient.submitMessage(trimmedTopicId, request.message()); } return "Message submitted successfully to topic " + request.topicId(); } catch (final Exception e) { @@ -127,11 +141,13 @@ public String submitMessage(@RequestBody final TopicMessageRequest request) { /** * Retrieves info for a specific topic. */ + @Operation(summary = "Get topic information", description = "Retrieves detailed information about an HCS topic from the mirror node.") @GetMapping("/{topicId}/info") public Topic getTopicInfo(@PathVariable("topicId") final String topicId) { try { - return topicRepository.findTopicById(topicId) - .orElseThrow(() -> new RuntimeException("Topic not found: " + topicId)); + final String trimmedTopicId = topicId.trim(); + return topicRepository.findTopicById(trimmedTopicId) + .orElseThrow(() -> new RuntimeException("Topic not found: " + trimmedTopicId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve topic info", e); } @@ -140,10 +156,11 @@ public Topic getTopicInfo(@PathVariable("topicId") final String topicId) { /** * Retrieves messages for a specific topic. */ + @Operation(summary = "Get topic messages", description = "Retrieves a list of messages submitted to a specific HCS topic.") @GetMapping("/{topicId}/message") public Page getTopicMessages(@PathVariable("topicId") final String topicId) { try { - return topicRepository.getMessages(topicId); + return topicRepository.getMessages(topicId.trim()); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve topic messages", e); } @@ -152,13 +169,15 @@ public Page getTopicMessages(@PathVariable("topicId") final String /** * Retrieves a specific message by its sequence number. */ + @Operation(summary = "Get topic message by sequence", description = "Retrieves a specific message from an HCS topic by its sequence number.") @GetMapping("/{topicId}/message/{sequenceNumber}") public TopicMessage getTopicMessageBySequenceNumber( @PathVariable("topicId") final String topicId, @PathVariable("sequenceNumber") final long sequenceNumber) { try { - return topicRepository.getMessageBySequenceNumber(topicId, sequenceNumber) - .orElseThrow(() -> new RuntimeException("Message not found for Topic: " + topicId + " Sequence: " + sequenceNumber)); + final String trimmedTopicId = topicId.trim(); + return topicRepository.getMessageBySequenceNumber(trimmedTopicId, sequenceNumber) + .orElseThrow(() -> new RuntimeException("Message not found for Topic: " + trimmedTopicId + " Sequence: " + sequenceNumber)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve topic message", e); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java index 05d45366..4a3a8c90 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java @@ -1,8 +1,12 @@ package org.hiero.spring.sample.dto.account; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request to create a new Hiero account. - * - * @param initialBalance The initial balance in Hbar (optional, defaults to 0). */ -public record AccountCreateRequest(Long initialBalance) {} +@Schema(name = "Account: Create Request", description = "Request DTO for creating a new Hiero account.") +public record AccountCreateRequest( + @Schema(description = "The initial balance in Hbar (optional, defaults to 0).", example = "100") + Long initialBalance +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java index 3892a56a..6ab5c931 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java @@ -1,14 +1,16 @@ package org.hiero.spring.sample.dto.account; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request to delete a Hiero account. - * - * @param accountId The ID of the account to delete. - * @param privateKey The private key of the account to delete. - * @param transferToAccountId The ID of the account to transfer remaining funds to (optional, defaults to operator). */ +@Schema(name = "Account: Delete Request", description = "Request DTO for deleting a Hiero account.") public record AccountDeleteRequest( + @Schema(description = "The ID of the account to delete.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) String accountId, + @Schema(description = "The private key of the account to delete.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) String privateKey, + @Schema(description = "The ID of the account to transfer remaining funds to (optional, defaults to operator).", example = "0.0.5678", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String transferToAccountId ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java index 9eb18020..2a890474 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java @@ -1,4 +1,5 @@ package org.hiero.spring.sample.dto.account; +import io.swagger.v3.oas.annotations.media.Schema; /** * Response containing Hiero account details. @@ -7,8 +8,12 @@ * @param publicKey The public key of the account. * @param privateKey The private key of the account. */ +@Schema(name = "Account: Response", description = "Response containing Hiero account details.") public record AccountResponse( + @Schema(description = "The ID of the account.") String accountId, + @Schema(description = "The public key of the account.") String publicKey, + @Schema(description = "The private key of the account.") String privateKey ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java index 6e119248..5cf8f360 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java @@ -1,16 +1,18 @@ package org.hiero.spring.sample.dto.account; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request to update an existing Hiero account. - * - * @param accountId The ID of the account to update. - * @param privateKey The current private key of the account. - * @param newPrivateKey The new private key to set (optional). - * @param memo The new memo to set (optional). */ +@Schema(name = "Account: Update Request", description = "Request DTO for updating an existing Hiero account.") public record AccountUpdateRequest( + @Schema(description = "The ID of the account to update.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) String accountId, + @Schema(description = "The current private key of the account.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) String privateKey, + @Schema(description = "The new private key to set (optional).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String newPrivateKey, + @Schema(description = "The new memo to set (optional).", example = "Updated account memo", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String memo ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java index 89ee9cf9..419d8444 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java @@ -1,14 +1,16 @@ package org.hiero.spring.sample.dto.file; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; /** * Request DTO for creating a Hiero file. - * @param content Base64 encoded content of the file. - * @param expirationTime Optional expiration time in seconds since epoch. */ +@Schema(name = "File: Create Request", description = "Request DTO for creating a new file on the Hiero File Service (HFS).") public record FileCreateRequest( + @Schema(description = "Base64 encoded content of the file.", example = "SGVsbG8gSGllcm8h") String content, + @Schema(description = "Optional expiration time in seconds since epoch.", example = "1735689600") Long expirationTime ) { public byte[] getDecodedContent() { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java index fd303b78..37243f1b 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java @@ -1,14 +1,16 @@ package org.hiero.spring.sample.dto.file; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; /** * Request DTO for updating a Hiero file. - * @param content Optional Base64 encoded content to update. - * @param expirationTime Optional new expiration time in seconds since epoch. */ +@Schema(name = "File: Update Request", description = "Request DTO for updating the content or expiration of a Hiero file.") public record FileUpdateRequest( + @Schema(description = "Optional Base64 encoded content to update.", example = "VXBkYXRlZCBjb250ZW50") String content, + @Schema(description = "Optional new expiration time in seconds since epoch.", example = "1767225600") Long expirationTime ) { public byte[] getDecodedContent() { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java index efafb267..f20651cb 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java @@ -1,11 +1,13 @@ package org.hiero.spring.sample.dto.network; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; import org.hiero.base.data.ExchangeRates; /** * Response DTO for Network Exchange Rates. */ +@Schema(name = "Network: Exchange Rates", description = "Response DTO containing current and next network exchange rates.") public record ExchangeRatesResponse( RateInfo currentRate, RateInfo nextRate diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java index 0fa98d1b..4e40f3c8 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java @@ -1,12 +1,16 @@ package org.hiero.spring.sample.dto.network; +import io.swagger.v3.oas.annotations.media.Schema; import org.hiero.base.data.NetworkFee; /** * Response DTO for Network Fees. */ +@Schema(name = "Network: Fee", description = "Response DTO containing network transaction fee information.") public record NetworkFeeResponse( + @Schema(description = "The gas cost associated with the transaction.") long gas, + @Schema(description = "The type of transaction.") String transactionType ) { public static NetworkFeeResponse fromDomain(NetworkFee fee) { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java index 82f55ffb..eb885111 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java @@ -1,10 +1,12 @@ package org.hiero.spring.sample.dto.network; +import io.swagger.v3.oas.annotations.media.Schema; import org.hiero.base.data.NetworkStake; /** * Response DTO for Network Stake Information. */ +@Schema(name = "Network: Stake", description = "Response DTO containing network staking status and parameters.") public record NetworkStakeResponse( long maxStakeReward, long maxStakeRewardPerHbar, diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java index f2f2ac4c..26628cf6 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java @@ -1,12 +1,16 @@ package org.hiero.spring.sample.dto.network; +import io.swagger.v3.oas.annotations.media.Schema; import org.hiero.base.data.NetworkSupplies; /** * Response DTO for Network Supplies. */ +@Schema(name = "Network: Supplies", description = "Response DTO containing Hbar supply information.") public record NetworkSuppliesResponse( + @Schema(description = "The released supply of Hbar.") String releasedSupply, + @Schema(description = "The total supply of Hbar.") String totalSupply ) { public static NetworkSuppliesResponse fromDomain(NetworkSupplies supplies) { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java new file mode 100644 index 00000000..8f68e683 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java @@ -0,0 +1,14 @@ +package org.hiero.spring.sample.dto.nft; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Request DTO for associating an account with an NFT collection. + */ +@Schema(name = "Non-Fungible Token: Associate Request", description = "Request DTO for explicitly associating a Hiero account with an NFT collection.") +public record NftAssociateRequest( + @Schema(description = "The ID of the account to associate.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) + String accountId, + @Schema(description = "The private key of the account to associate.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) + String privateKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java index 3e58ee31..3232373c 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java @@ -1,7 +1,14 @@ package org.hiero.spring.sample.dto.nft; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request DTO for creating a new NFT type. */ -public record NftCreateRequest(String name, String symbol) { -} +@Schema(name = "Non-Fungible Token: Create Request", description = "Request DTO for creating a new Hiero NFT collection.") +public record NftCreateRequest( + @Schema(description = "The name of the NFT collection.", example = "Hiero Heroes") + String name, + @Schema(description = "The symbol of the NFT collection.", example = "HERO") + String symbol +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java index 94fdd1f6..8113bdf8 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java @@ -1,7 +1,12 @@ package org.hiero.spring.sample.dto.nft; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request DTO for minting a new NFT instance. */ -public record NftMintRequest(String metadata) { -} +@Schema(name = "Non-Fungible Token: Mint Request", description = "Request DTO for minting a new NFT instance into a collection.") +public record NftMintRequest( + @Schema(description = "The metadata for the NFT instance (e.g., IPFS CID).", example = "ipfs://Qm...") + String metadata +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java index 08a8cc2f..ebf358db 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java @@ -1,12 +1,18 @@ package org.hiero.spring.sample.dto.nft; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request DTO for transferring an NFT instance. */ +@Schema(name = "Non-Fungible Token: Transfer Request", description = "Request DTO for transferring a specific NFT serial number between accounts.") public record NftTransferRequest( + @Schema(description = "The ID of the account transferring the NFT.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) String fromAccountId, + @Schema(description = "The private key of the account transferring the NFT.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) String fromPrivateKey, + @Schema(description = "The ID of the account receiving the NFT.", example = "0.0.5678", requiredMode = Schema.RequiredMode.REQUIRED) String toAccountId, + @Schema(description = "The serial number of the NFT instance to transfer.", example = "1", requiredMode = Schema.RequiredMode.REQUIRED) long serialNumber -) { -} +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java new file mode 100644 index 00000000..5f3400c6 --- /dev/null +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java @@ -0,0 +1,15 @@ +package org.hiero.spring.sample.dto.token; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Data Transfer Object for token association requests. + */ +@Schema(name = "Fungible Token: Associate Request", description = "Request DTO for explicitly associating a Hiero account with a fungible token.") +public record TokenAssociateRequest( + @Schema(description = "The ID of the account to associate with the token", example = "0.0.12345", requiredMode = Schema.RequiredMode.REQUIRED) + String accountId, + + @Schema(description = "The private key of the account to sign the association", example = "302e...", requiredMode = Schema.RequiredMode.REQUIRED) + String privateKey +) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java index 909b8b4d..6a9502bf 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java @@ -1,16 +1,18 @@ package org.hiero.spring.sample.dto.token; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request to create a new Hiero fungible token. - * - * @param name The name of the token. - * @param symbol The symbol of the token. - * @param decimals The number of decimals for the token (optional, defaults to 0). - * @param initialSupply The initial supply of the token (optional, defaults to 0). */ +@Schema(name = "Fungible Token: Create Request", description = "Request DTO for creating a new Hiero fungible token.") public record TokenCreateRequest( + @Schema(description = "The name of the token.", example = "Hiero Gold") String name, + @Schema(description = "The symbol of the token.", example = "GOLD") String symbol, + @Schema(description = "The number of decimals for the token (optional, defaults to 0).", example = "8") Integer decimals, + @Schema(description = "The initial supply of the token (optional, defaults to 0).", example = "1000000") Long initialSupply ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java index 0cee6c63..35d8c7fa 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java @@ -1,12 +1,14 @@ package org.hiero.spring.sample.dto.token; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request to mint more of a Hiero fungible token. - * - * @param amount The amount to mint. - * @param supplyKey The supply key of the token (required if the token has a supply key). */ +@Schema(name = "Fungible Token: Mint Request", description = "Request DTO for minting additional units of a fungible token.") public record TokenMintRequest( + @Schema(description = "The amount of tokens to mint.", example = "500000") long amount, + @Schema(description = "The supply key of the token (required if the token has a supply key).", example = "302e020100300506032b657004220420...") String supplyKey ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java index 972a5a84..ba6bf05e 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java @@ -1,16 +1,18 @@ package org.hiero.spring.sample.dto.token; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request to transfer Hiero fungible tokens between accounts. - * - * @param fromAccountId The ID of the account to transfer tokens from. - * @param fromPrivateKey The private key of the sender account (required). - * @param toAccountId The ID of the account to transfer tokens to. - * @param amount The amount of tokens to transfer. */ +@Schema(name = "Fungible Token: Transfer Request", description = "Request DTO for transferring units of a fungible token between accounts.") public record TokenTransferRequest( + @Schema(description = "The ID of the account to transfer tokens from.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) String fromAccountId, + @Schema(description = "The private key of the sender account.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) String fromPrivateKey, + @Schema(description = "The ID of the account to transfer tokens to.", example = "0.0.5678", requiredMode = Schema.RequiredMode.REQUIRED) String toAccountId, + @Schema(description = "The amount of tokens to transfer.", example = "1000", requiredMode = Schema.RequiredMode.REQUIRED) long amount ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java index 56ec65b9..3a727a68 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java @@ -1,10 +1,16 @@ package org.hiero.spring.sample.dto.topic; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request DTO for creating a new Consensus Topic. */ +@Schema(name = "Consensus Topic: Create Request", description = "Request DTO for creating a new Hiero Consensus Service (HCS) topic.") public record TopicCreateRequest( + @Schema(description = "Optional memo for the topic.", example = "Project alerts topic") String memo, + @Schema(description = "Optional admin key for the topic.", example = "302e020100300506032b657004220420...") String adminKey, + @Schema(description = "Optional submit key for the topic.", example = "302e020100300506032b657004220420...") String submitKey ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java index b37b7c1b..fb24d181 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java @@ -1,10 +1,16 @@ package org.hiero.spring.sample.dto.topic; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request DTO for submitting a message to a Consensus Topic. */ +@Schema(name = "Consensus Topic: Message Request", description = "Request DTO for submitting a message to a Hiero Consensus Service (HCS) topic.") public record TopicMessageRequest( + @Schema(description = "The ID of the topic to submit the message to.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) String topicId, + @Schema(description = "The message content to submit.", example = "Hello Hiero Consensus Service!", requiredMode = Schema.RequiredMode.REQUIRED) String message, + @Schema(description = "The submit key (required if the topic is private).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String submitKey ) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java index e9cba1cb..0d1ffaf8 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java @@ -1,12 +1,20 @@ package org.hiero.spring.sample.dto.topic; +import io.swagger.v3.oas.annotations.media.Schema; + /** * Request DTO for updating an existing Consensus Topic. */ +@Schema(name = "Consensus Topic: Update Request", description = "Request DTO for updating an existing Hiero Consensus Service (HCS) topic.") public record TopicUpdateRequest( + @Schema(description = "The ID of the topic to update.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) String topicId, + @Schema(description = "The new memo for the topic.", example = "Updated topic memo", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String memo, + @Schema(description = "The current admin key (required for most updates).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String adminKey, + @Schema(description = "The new admin key to set (optional).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String updatedAdminKey, + @Schema(description = "The new submit key to set (optional).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String submitKey ) {} From 7e387e8c1c41fd09b2803335d6525463ebd7f825 Mon Sep 17 00:00:00 2001 From: Twiineenock Date: Thu, 7 May 2026 09:20:50 +0300 Subject: [PATCH 9/9] fix(spring-sample): resolve spotless violations and codeql xss vulnerabilities Signed-off-by: Twiineenock --- .../spring/sample/config/OpenApiConfig.java | 60 ++++---- .../sample/controller/AccountController.java | 64 ++++++--- .../sample/controller/BlockController.java | 15 +- .../sample/controller/FileController.java | 48 +++---- .../sample/controller/NetworkController.java | 53 +++---- .../sample/controller/NftController.java | 131 ++++++++++-------- .../sample/controller/TokenController.java | 102 ++++++++------ .../sample/controller/TopicController.java | 109 ++++++++------- .../dto/account/AccountCreateRequest.java | 11 +- .../dto/account/AccountDeleteRequest.java | 27 ++-- .../sample/dto/account/AccountResponse.java | 11 +- .../dto/account/AccountUpdateRequest.java | 37 +++-- .../sample/dto/file/FileCreateRequest.java | 17 +-- .../sample/dto/file/FileUpdateRequest.java | 21 +-- .../dto/network/ExchangeRatesResponse.java | 30 ++-- .../dto/network/NetworkFeeResponse.java | 15 +- .../dto/network/NetworkStakeResponse.java | 14 +- .../dto/network/NetworkSuppliesResponse.java | 15 +- .../sample/dto/nft/NftAssociateRequest.java | 23 +-- .../sample/dto/nft/NftCreateRequest.java | 15 +- .../spring/sample/dto/nft/NftMintRequest.java | 15 +- .../sample/dto/nft/NftTransferRequest.java | 37 +++-- .../dto/token/TokenAssociateRequest.java | 24 ++-- .../sample/dto/token/TokenCreateRequest.java | 27 ++-- .../sample/dto/token/TokenMintRequest.java | 18 +-- .../dto/token/TokenTransferRequest.java | 37 +++-- .../sample/dto/topic/TopicCreateRequest.java | 23 +-- .../sample/dto/topic/TopicMessageRequest.java | 30 ++-- .../sample/dto/topic/TopicUpdateRequest.java | 44 ++++-- 29 files changed, 606 insertions(+), 467 deletions(-) diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java index f607f5d9..8747b8da 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java @@ -5,12 +5,12 @@ import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.tags.Tag; -import org.springdoc.core.customizers.OpenApiCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; @Configuration public class OpenApiConfig { @@ -18,40 +18,46 @@ public class OpenApiConfig { @Bean public OpenAPI hieroEnterpriseOpenAPI() { return new OpenAPI() - .info(new Info() - .title("Hiero Enterprise Spring Sample API") - .description("Interactive REST API for Hiero Enterprise Java integration. " + - "Provides endpoints for Accounts, Tokens, NFTs, Consensus Topics, and Files.") - .version("v0.20.0") - .contact(new Contact() - .name("Hiero Enterprise Team") - .url("https://github.com/hiero-ledger/hiero-enterprise-java")) - .license(new License() - .name("Apache 2.0") - .url("http://www.apache.org/licenses/LICENSE-2.0.html"))); + .info( + new Info() + .title("Hiero Enterprise Spring Sample API") + .description( + "Interactive REST API for Hiero Enterprise Java integration. " + + "Provides endpoints for Accounts, Tokens, NFTs, Consensus Topics, and Files.") + .version("v0.20.0") + .contact( + new Contact() + .name("Hiero Enterprise Team") + .url("https://github.com/hiero-ledger/hiero-enterprise-java")) + .license( + new License() + .name("Apache 2.0") + .url("http://www.apache.org/licenses/LICENSE-2.0.html"))); } @Bean public OpenApiCustomizer sortTagsCustomizer() { return openApi -> { - final List order = List.of( - "Accounts", - "Fungible Tokens", - "Non-Fungible Tokens", - "Consensus Topics", - "Blocks", - "Network", - "Files" - ); + final List order = + List.of( + "Accounts", + "Fungible Tokens", + "Non-Fungible Tokens", + "Consensus Topics", + "Blocks", + "Network", + "Files"); List tags = openApi.getTags(); if (tags != null) { // Create a copy to avoid modification issues during sorting if it's a fixed-size list List sortedTags = new ArrayList<>(tags); - sortedTags.sort(Comparator.comparingInt(tag -> { - int index = order.indexOf(tag.getName()); - return index == -1 ? order.size() : index; - })); + sortedTags.sort( + Comparator.comparingInt( + tag -> { + int index = order.indexOf(tag.getName()); + return index == -1 ? order.size() : index; + })); openApi.setTags(sortedTags); } }; diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java index c6eb1c5e..dec0506d 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java @@ -3,9 +3,9 @@ import com.hedera.hashgraph.sdk.AccountId; import com.hedera.hashgraph.sdk.Hbar; import com.hedera.hashgraph.sdk.PrivateKey; -import java.util.Objects; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Objects; import org.hiero.base.AccountClient; import org.hiero.base.data.Account; import org.hiero.base.data.AccountInfo; @@ -24,10 +24,12 @@ import org.springframework.web.bind.annotation.RestController; /** - * REST controller for Hiero account operations. - * This controller provides endpoints for account lifecycle management and queries. + * REST controller for Hiero account operations. This controller provides endpoints for account + * lifecycle management and queries. */ -@Tag(name = "Accounts", description = "Operations related to Hiero account lifecycle and balance queries") +@Tag( + name = "Accounts", + description = "Operations related to Hiero account lifecycle and balance queries") @RestController @RequestMapping("/api/v1/hiero/accounts") public class AccountController { @@ -36,8 +38,7 @@ public class AccountController { private final AccountRepository accountRepository; public AccountController( - final AccountClient accountClient, - final AccountRepository accountRepository) { + final AccountClient accountClient, final AccountRepository accountRepository) { this.accountClient = Objects.requireNonNull(accountClient, "accountClient must not be null"); this.accountRepository = Objects.requireNonNull(accountRepository, "accountRepository must not be null"); @@ -49,20 +50,23 @@ public AccountController( * @param request The account creation request containing optional initial balance. * @return Success message with the new account ID. */ - @Operation(summary = "Create a new Hiero account", description = "Creates a new Hiero account with an optional initial balance.") + @Operation( + summary = "Create a new Hiero account", + description = "Creates a new Hiero account with an optional initial balance.") @PostMapping - public AccountResponse createAccount(@RequestBody(required = false) final AccountCreateRequest request) { + public AccountResponse createAccount( + @RequestBody(required = false) final AccountCreateRequest request) { try { - final Hbar initialBalance = (request != null && request.initialBalance() != null) - ? Hbar.from(request.initialBalance()) - : Hbar.ZERO; - + final Hbar initialBalance = + (request != null && request.initialBalance() != null) + ? Hbar.from(request.initialBalance()) + : Hbar.ZERO; + final Account account = accountClient.createAccount(initialBalance); return new AccountResponse( account.accountId().toString(), account.publicKey().toString(), - account.privateKey().toString() - ); + account.privateKey().toString()); } catch (final Exception e) { throw new RuntimeException("Failed to create account", e); } @@ -74,21 +78,26 @@ public AccountResponse createAccount(@RequestBody(required = false) final Accoun * @param request The account update request containing new key or memo. * @return Success message. */ - @Operation(summary = "Update an existing Hiero account", description = "Updates an account's metadata such as keys or memo.") + @Operation( + summary = "Update an existing Hiero account", + description = "Updates an account's metadata such as keys or memo.") @PutMapping public String updateAccount(@RequestBody final AccountUpdateRequest request) { try { if (request.accountId() == null || request.privateKey() == null) { - throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: accountId and privateKey are mandatory."); } final AccountId accountId = AccountId.fromString(request.accountId().trim()); final PrivateKey currentKey = PrivateKey.fromString(request.privateKey().trim()); final Account account = Account.of(accountId, currentKey); if (request.newPrivateKey() != null && request.memo() != null) { - accountClient.updateAccount(account, PrivateKey.fromString(request.newPrivateKey().trim()), request.memo()); + accountClient.updateAccount( + account, PrivateKey.fromString(request.newPrivateKey().trim()), request.memo()); } else if (request.newPrivateKey() != null) { - accountClient.updateAccountKey(account, PrivateKey.fromString(request.newPrivateKey().trim())); + accountClient.updateAccountKey( + account, PrivateKey.fromString(request.newPrivateKey().trim())); } else if (request.memo() != null) { accountClient.updateAccountMemo(account, request.memo()); } @@ -104,12 +113,16 @@ public String updateAccount(@RequestBody final AccountUpdateRequest request) { * * @param request The account deletion request. */ - @Operation(summary = "Delete a Hiero account", description = "Deletes an account and optionally transfers remaining balance to another account.") + @Operation( + summary = "Delete a Hiero account", + description = + "Deletes an account and optionally transfers remaining balance to another account.") @DeleteMapping public String deleteAccount(@RequestBody final AccountDeleteRequest request) { try { if (request.accountId() == null || request.privateKey() == null) { - throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: accountId and privateKey are mandatory."); } final AccountId accountId = AccountId.fromString(request.accountId().trim()); final PrivateKey privateKey = PrivateKey.fromString(request.privateKey().trim()); @@ -135,7 +148,9 @@ public String deleteAccount(@RequestBody final AccountDeleteRequest request) { * @param accountId The ID of the account to query. * @return The balance in Hbar. */ - @Operation(summary = "Get account balance", description = "Retrieves the current balance of a Hiero account in Hbar.") + @Operation( + summary = "Get account balance", + description = "Retrieves the current balance of a Hiero account in Hbar.") @GetMapping("/balance/{accountId}") public String getBalance(@PathVariable("accountId") final String accountId) { try { @@ -152,12 +167,15 @@ public String getBalance(@PathVariable("accountId") final String accountId) { * @param accountId The ID of the account to query. * @return The AccountInfo object. */ - @Operation(summary = "Get account information", description = "Retrieves detailed account information from the Hiero mirror node.") + @Operation( + summary = "Get account information", + description = "Retrieves detailed account information from the Hiero mirror node.") @GetMapping("/info/{accountId}") public AccountInfo getInfo(@PathVariable("accountId") final String accountId) { try { final String trimmedAccountId = accountId.trim(); - return accountRepository.findById(trimmedAccountId) + return accountRepository + .findById(trimmedAccountId) .orElseThrow(() -> new RuntimeException("Account not found: " + trimmedAccountId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve info for account " + accountId, e); diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java index 80c4792e..5bbced33 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java @@ -12,8 +12,8 @@ import org.springframework.web.bind.annotation.RestController; /** - * REST controller for Hiero block operations. - * This controller provides endpoints for querying block information from the mirror node. + * REST controller for Hiero block operations. This controller provides endpoints for querying block + * information from the mirror node. */ @Tag(name = "Blocks", description = "Operations related to Hiero network blocks (Mirror Node)") @RestController @@ -32,7 +32,9 @@ public BlockController(final BlockRepository blockRepository) { * * @return A page of blocks. */ - @Operation(summary = "Get all blocks", description = "Retrieves a paginated list of blocks from the Hiero mirror node.") + @Operation( + summary = "Get all blocks", + description = "Retrieves a paginated list of blocks from the Hiero mirror node.") @GetMapping public Page getBlocks() { try { @@ -48,11 +50,14 @@ public Page getBlocks() { * @param number The block number. * @return The block details. */ - @Operation(summary = "Get block by number", description = "Retrieves details of a specific block by its block number.") + @Operation( + summary = "Get block by number", + description = "Retrieves details of a specific block by its block number.") @GetMapping("/{number}") public Block getBlockByNumber(@PathVariable("number") final long number) { try { - return blockRepository.findByNumber(number) + return blockRepository + .findByNumber(number) .orElseThrow(() -> new RuntimeException("Block not found: " + number)); } catch (final Exception e) { throw new RuntimeException("Failed to query block by number: " + number, e); diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java index 84b23074..4e6f8ad0 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java @@ -15,10 +15,11 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.HtmlUtils; /** - * REST controller for Hiero file operations. - * Provides endpoints for creating, deleting, updating and querying files on the network. + * REST controller for Hiero file operations. Provides endpoints for creating, deleting, updating + * and querying files on the network. */ @Tag(name = "Files", description = "Operations related to Hiero File Service (HFS)") @RestController @@ -31,10 +32,10 @@ public FileController(final FileClient fileClient) { this.fileClient = Objects.requireNonNull(fileClient, "fileClient must not be null"); } - /** - * Creates a new file. - */ - @Operation(summary = "Create a new file", description = "Creates a new file on the Hiero network with the provided content.") + /** Creates a new file. */ + @Operation( + summary = "Create a new file", + description = "Creates a new file on the Hiero network with the provided content.") @PostMapping public String createFile(@RequestBody final FileCreateRequest request) { try { @@ -50,28 +51,27 @@ public String createFile(@RequestBody final FileCreateRequest request) { } } - /** - * Deletes a file. - */ - @Operation(summary = "Delete a file", description = "Deletes an existing file from the Hiero network.") + /** Deletes a file. */ + @Operation( + summary = "Delete a file", + description = "Deletes an existing file from the Hiero network.") @DeleteMapping("/{fileId}") public String deleteFile(@PathVariable("fileId") final String fileId) { try { fileClient.deleteFile(fileId.trim()); - return "File " + fileId + " deleted successfully!"; + return "File " + HtmlUtils.htmlEscape(fileId) + " deleted successfully!"; } catch (final Exception e) { throw new RuntimeException("Failed to delete file: " + fileId, e); } } - /** - * Updates an existing file (Content and/or Expiration Time). - */ - @Operation(summary = "Update a file", description = "Updates the content or expiration time of an existing file.") + /** Updates an existing file (Content and/or Expiration Time). */ + @Operation( + summary = "Update a file", + description = "Updates the content or expiration time of an existing file.") @PostMapping("/{fileId}") public String updateFile( - @PathVariable("fileId") final String fileId, - @RequestBody final FileUpdateRequest request) { + @PathVariable("fileId") final String fileId, @RequestBody final FileUpdateRequest request) { try { final FileId fid = FileId.fromString(fileId.trim()); if (request.content() != null) { @@ -80,16 +80,16 @@ public String updateFile( if (request.expirationTime() != null) { fileClient.updateExpirationTime(fid, request.getExpirationInstant()); } - return "File " + fileId + " updated successfully!"; + return "File " + HtmlUtils.htmlEscape(fileId) + " updated successfully!"; } catch (final Exception e) { throw new RuntimeException("Failed to update file: " + fileId, e); } } - /** - * Retrieves the content of a file (returned as Base64 string). - */ - @Operation(summary = "Get file content", description = "Retrieves the byte content of a file, encoded as a Base64 string.") + /** Retrieves the content of a file (returned as Base64 string). */ + @Operation( + summary = "Get file content", + description = "Retrieves the byte content of a file, encoded as a Base64 string.") @GetMapping("/{fileId}/content") public String getFileContent(@PathVariable("fileId") final String fileId) { try { @@ -100,9 +100,7 @@ public String getFileContent(@PathVariable("fileId") final String fileId) { } } - /** - * Retrieves the size of a file in bytes. - */ + /** Retrieves the size of a file in bytes. */ @Operation(summary = "Get file size", description = "Retrieves the size of a file in bytes.") @GetMapping("/{fileId}/size") public Integer getFileSize(@PathVariable("fileId") final String fileId) { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java index c998ec1a..324a350e 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java @@ -14,10 +14,12 @@ import org.springframework.web.bind.annotation.RestController; /** - * REST controller for Hiero network operations. - * Provides endpoints for querying network-wide information like exchange rates, fees, and staking. + * REST controller for Hiero network operations. Provides endpoints for querying network-wide + * information like exchange rates, fees, and staking. */ -@Tag(name = "Network", description = "Operations related to Hiero network status (rates, fees, staking)") +@Tag( + name = "Network", + description = "Operations related to Hiero network status (rates, fees, staking)") @RestController @RequestMapping("/api/v1/hiero/network") public class NetworkController { @@ -29,14 +31,15 @@ public NetworkController(final NetworkRepository networkRepository) { Objects.requireNonNull(networkRepository, "networkRepository must not be null"); } - /** - * Retrieves the current and next exchange rates. - */ - @Operation(summary = "Get exchange rates", description = "Retrieves the current and next Hbar-to-USD exchange rates.") + /** Retrieves the current and next exchange rates. */ + @Operation( + summary = "Get exchange rates", + description = "Retrieves the current and next Hbar-to-USD exchange rates.") @GetMapping("/exchange-rate") public ExchangeRatesResponse getExchangeRates() { try { - return networkRepository.exchangeRates() + return networkRepository + .exchangeRates() .map(ExchangeRatesResponse::fromDomain) .orElseThrow(() -> new RuntimeException("Exchange rates not available")); } catch (final Exception e) { @@ -44,29 +47,28 @@ public ExchangeRatesResponse getExchangeRates() { } } - /** - * Retrieves the network fees. - */ - @Operation(summary = "Get network fees", description = "Retrieves a list of Hiero network transaction fees.") + /** Retrieves the network fees. */ + @Operation( + summary = "Get network fees", + description = "Retrieves a list of Hiero network transaction fees.") @GetMapping("/fee") public List getFees() { try { - return networkRepository.fees().stream() - .map(NetworkFeeResponse::fromDomain) - .toList(); + return networkRepository.fees().stream().map(NetworkFeeResponse::fromDomain).toList(); } catch (final Exception e) { throw new RuntimeException("Failed to query network fees", e); } } - /** - * Retrieves network staking information. - */ - @Operation(summary = "Get network staking info", description = "Retrieves current staking parameters and status for the Hiero network.") + /** Retrieves network staking information. */ + @Operation( + summary = "Get network staking info", + description = "Retrieves current staking parameters and status for the Hiero network.") @GetMapping("/stake") public NetworkStakeResponse getStake() { try { - return networkRepository.stake() + return networkRepository + .stake() .map(NetworkStakeResponse::fromDomain) .orElseThrow(() -> new RuntimeException("Network stake info not available")); } catch (final Exception e) { @@ -74,14 +76,15 @@ public NetworkStakeResponse getStake() { } } - /** - * Retrieves network supply information. - */ - @Operation(summary = "Get network supplies", description = "Retrieves the total and circulating supply of Hbar.") + /** Retrieves network supply information. */ + @Operation( + summary = "Get network supplies", + description = "Retrieves the total and circulating supply of Hbar.") @GetMapping("/supplies") public NetworkSuppliesResponse getSupplies() { try { - return networkRepository.supplies() + return networkRepository + .supplies() .map(NetworkSuppliesResponse::fromDomain) .orElseThrow(() -> new RuntimeException("Network supply info not available")); } catch (final Exception e) { diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java index b86f16fb..b4947c9e 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java @@ -10,8 +10,6 @@ import org.hiero.base.NftClient; import org.hiero.base.data.Account; import org.hiero.base.data.Nft; -import org.hiero.base.data.NftMetadata; -import org.hiero.base.data.Page; import org.hiero.base.data.TokenInfo; import org.hiero.base.mirrornode.NftRepository; import org.hiero.base.mirrornode.TokenRepository; @@ -26,11 +24,12 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.HtmlUtils; -/** - * REST controller for Hiero NFT operations. - */ -@Tag(name = "Non-Fungible Tokens", description = "Operations related to Hiero Non-Fungible Token Service (NFTs)") +/** REST controller for Hiero NFT operations. */ +@Tag( + name = "Non-Fungible Tokens", + description = "Operations related to Hiero Non-Fungible Token Service (NFTs)") @RestController @CrossOrigin @RequestMapping("/api/v1/hiero/nfts") @@ -46,18 +45,18 @@ public NftController( final TokenRepository tokenRepository) { this.nftClient = Objects.requireNonNull(nftClient, "nftClient must not be null"); this.nftRepository = Objects.requireNonNull(nftRepository, "nftRepository must not be null"); - this.tokenRepository = Objects.requireNonNull(tokenRepository, "tokenRepository must not be null"); + this.tokenRepository = + Objects.requireNonNull(tokenRepository, "tokenRepository must not be null"); } - /** - * Creates a new NFT type. - */ + /** Creates a new NFT type. */ @Operation(summary = "Create an NFT type", description = "Creates a new HTS NFT collection.") @PostMapping public String createNft(@RequestBody final NftCreateRequest request) { try { if (request.name() == null || request.symbol() == null) { - throw new IllegalArgumentException("Missing required fields: name and symbol are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: name and symbol are mandatory."); } final TokenId tokenId = nftClient.createNftType(request.name(), request.symbol()); return "NFT Type " + tokenId + " created successfully!"; @@ -66,29 +65,29 @@ public String createNft(@RequestBody final NftCreateRequest request) { } } - /** - * Retrieves detailed information about an NFT type. - */ - @Operation(summary = "Get NFT type information", description = "Retrieves detailed information about an NFT collection from the mirror node.") + /** Retrieves detailed information about an NFT type. */ + @Operation( + summary = "Get NFT type information", + description = "Retrieves detailed information about an NFT collection from the mirror node.") @GetMapping("/{nftId}") public TokenInfo getNftById(@PathVariable("nftId") final String nftId) { try { final String trimmedNftId = nftId.trim(); - return tokenRepository.findById(trimmedNftId) + return tokenRepository + .findById(trimmedNftId) .orElseThrow(() -> new RuntimeException("NFT Type not found: " + trimmedNftId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve NFT info for " + nftId, e); } } - /** - * Mints a new NFT instance. - */ - @Operation(summary = "Mint an NFT", description = "Mints a new serial number for an existing NFT collection.") + /** Mints a new NFT instance. */ + @Operation( + summary = "Mint an NFT", + description = "Mints a new serial number for an existing NFT collection.") @PostMapping("/{nftId}/mint") public String mintNft( - @PathVariable("nftId") final String nftId, - @RequestBody final NftMintRequest request) { + @PathVariable("nftId") final String nftId, @RequestBody final NftMintRequest request) { try { if (request.metadata() == null) { throw new IllegalArgumentException("Missing required field: metadata is mandatory."); @@ -96,77 +95,95 @@ public String mintNft( final String trimmedNftId = nftId.trim(); final byte[] metadata = request.metadata().getBytes(StandardCharsets.UTF_8); final long serialNumber = nftClient.mintNft(trimmedNftId, metadata); - return "Minted NFT instance of " + nftId + " with serial number " + serialNumber; + return "Minted NFT instance of " + + HtmlUtils.htmlEscape(nftId) + + " with serial number " + + serialNumber; } catch (final Exception e) { throw new RuntimeException("Failed to mint NFT for " + nftId, e); } } - /** - * Retrieves a specific NFT instance by ID and serial number. - */ - @Operation(summary = "Get NFT instance", description = "Retrieves information about a specific NFT instance (serial number) from the mirror node.") + /** Retrieves a specific NFT instance by ID and serial number. */ + @Operation( + summary = "Get NFT instance", + description = + "Retrieves information about a specific NFT instance (serial number) from the mirror node.") @GetMapping("/{nftId}/serial/{serial}") public Nft getNftBySerial( - @PathVariable("nftId") final String nftId, - @PathVariable("serial") final long serial) { + @PathVariable("nftId") final String nftId, @PathVariable("serial") final long serial) { try { final String trimmedNftId = nftId.trim(); - return nftRepository.findByTypeAndSerial(trimmedNftId, serial) - .orElseThrow(() -> new RuntimeException("NFT not found: " + trimmedNftId + " Serial: " + serial)); + return nftRepository + .findByTypeAndSerial(trimmedNftId, serial) + .orElseThrow( + () -> new RuntimeException("NFT not found: " + trimmedNftId + " Serial: " + serial)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve NFT instance", e); } } - /** - * Transfers an NFT instance between accounts. - */ - @Operation(summary = "Transfer an NFT", description = "Transfers a specific NFT serial number between two Hiero accounts.") + /** Transfers an NFT instance between accounts. */ + @Operation( + summary = "Transfer an NFT", + description = "Transfers a specific NFT serial number between two Hiero accounts.") @PostMapping("/{nftId}/transfer") public String transferNft( - @PathVariable("nftId") final String nftId, - @RequestBody final NftTransferRequest request) { + @PathVariable("nftId") final String nftId, @RequestBody final NftTransferRequest request) { try { - if (request.fromAccountId() == null || request.fromPrivateKey() == null || request.toAccountId() == null) { - throw new IllegalArgumentException("Missing required fields: fromAccountId, fromPrivateKey, and toAccountId are mandatory."); + if (request.fromAccountId() == null + || request.fromPrivateKey() == null + || request.toAccountId() == null) { + throw new IllegalArgumentException( + "Missing required fields: fromAccountId, fromPrivateKey, and toAccountId are mandatory."); } final TokenId tokenId = TokenId.fromString(nftId.trim()); - final Account fromAccount = Account.of( - AccountId.fromString(request.fromAccountId().trim()), - PrivateKey.fromString(request.fromPrivateKey().trim()) - ); + final Account fromAccount = + Account.of( + AccountId.fromString(request.fromAccountId().trim()), + PrivateKey.fromString(request.fromPrivateKey().trim())); final AccountId toAccountId = AccountId.fromString(request.toAccountId().trim()); nftClient.transferNft(tokenId, request.serialNumber(), fromAccount, toAccountId); - return "Transferred NFT " + nftId + " (Serial: " + request.serialNumber() + ") to " + request.toAccountId(); + return "Transferred NFT " + + HtmlUtils.htmlEscape(nftId) + + " (Serial: " + + request.serialNumber() + + ") to " + + HtmlUtils.htmlEscape(request.toAccountId()); } catch (final Exception e) { throw new RuntimeException("Failed to transfer NFT", e); } } - /** - * Associates an account with a Hiero NFT collection. - */ - @Operation(summary = "Associate account with NFT", description = "Explicitly associates a Hiero account with an NFT collection. Required before receiving NFTs.") + /** Associates an account with a Hiero NFT collection. */ + @Operation( + summary = "Associate account with NFT", + description = + "Explicitly associates a Hiero account with an NFT collection. Required before receiving NFTs.") @PostMapping("/{nftId}/associate") public String associateNft( - @PathVariable("nftId") final String nftId, - @RequestBody final NftAssociateRequest request) { + @PathVariable("nftId") final String nftId, @RequestBody final NftAssociateRequest request) { try { if (request.accountId() == null || request.privateKey() == null) { - throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: accountId and privateKey are mandatory."); } final TokenId id = TokenId.fromString(nftId.trim()); - final Account account = Account.of( - AccountId.fromString(request.accountId().trim()), - PrivateKey.fromString(request.privateKey().trim()) - ); + final Account account = + Account.of( + AccountId.fromString(request.accountId().trim()), + PrivateKey.fromString(request.privateKey().trim())); nftClient.associateNft(id, account); - return "Account " + request.accountId() + " associated with NFT collection " + nftId + " successfully!"; + return "Account " + + HtmlUtils.htmlEscape(request.accountId()) + + " associated with NFT collection " + + HtmlUtils.htmlEscape(nftId) + + " successfully!"; } catch (final Exception e) { - throw new RuntimeException("Failed to associate account " + request.accountId() + " with NFT " + nftId, e); + throw new RuntimeException( + "Failed to associate account " + request.accountId() + " with NFT " + nftId, e); } } } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java index 6390e031..907add59 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java @@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.HtmlUtils; @Tag(name = "Fungible Tokens", description = "Operations related to Hiero fungible tokens (HTS)") @RestController @@ -32,21 +33,22 @@ public class TokenController { private final TokenRepository tokenRepository; public TokenController( - final FungibleTokenClient tokenClient, - final TokenRepository tokenRepository) { + final FungibleTokenClient tokenClient, final TokenRepository tokenRepository) { this.tokenClient = Objects.requireNonNull(tokenClient, "tokenClient must not be null"); - this.tokenRepository = Objects.requireNonNull(tokenRepository, "tokenRepository must not be null"); + this.tokenRepository = + Objects.requireNonNull(tokenRepository, "tokenRepository must not be null"); } - /** - * Creates a new Hiero fungible token. - */ - @Operation(summary = "Create a new fungible token", description = "Creates a new Hiero fungible token (HTS).") + /** Creates a new Hiero fungible token. */ + @Operation( + summary = "Create a new fungible token", + description = "Creates a new Hiero fungible token (HTS).") @PostMapping public String createToken(@RequestBody final TokenCreateRequest request) { try { if (request.name() == null || request.symbol() == null) { - throw new IllegalArgumentException("Missing required fields: name and symbol are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: name and symbol are mandatory."); } final TokenId tokenId = tokenClient.createToken(request.name(), request.symbol()); return "Token " + tokenId + " created successfully!"; @@ -55,34 +57,35 @@ public String createToken(@RequestBody final TokenCreateRequest request) { } } - /** - * Retrieves detailed information about a Hiero fungible token. - */ - @Operation(summary = "Get token information", description = "Retrieves detailed information about a fungible token from the mirror node.") + /** Retrieves detailed information about a Hiero fungible token. */ + @Operation( + summary = "Get token information", + description = "Retrieves detailed information about a fungible token from the mirror node.") @GetMapping("/{tokenId}") public TokenInfo getToken(@PathVariable("tokenId") final String tokenId) { try { final String trimmedTokenId = tokenId.trim(); - return tokenRepository.findById(trimmedTokenId) + return tokenRepository + .findById(trimmedTokenId) .orElseThrow(() -> new RuntimeException("Token not found: " + trimmedTokenId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve token info for " + tokenId, e); } } - /** - * Mints more of a Hiero fungible token. - */ - @Operation(summary = "Mint tokens", description = "Mints additional units of a fungible token to the treasury account.") + /** Mints more of a Hiero fungible token. */ + @Operation( + summary = "Mint tokens", + description = "Mints additional units of a fungible token to the treasury account.") @PostMapping("/{tokenId}/mint") public String mintToken( - @PathVariable("tokenId") final String tokenId, - @RequestBody final TokenMintRequest request) { + @PathVariable("tokenId") final String tokenId, @RequestBody final TokenMintRequest request) { try { final String trimmedTokenId = tokenId.trim(); final long newTotalSupply; if (request.supplyKey() != null) { - newTotalSupply = tokenClient.mintToken(trimmedTokenId, request.supplyKey(), request.amount()); + newTotalSupply = + tokenClient.mintToken(trimmedTokenId, request.supplyKey(), request.amount()); } else { newTotalSupply = tokenClient.mintToken(trimmedTokenId, request.amount()); } @@ -92,54 +95,69 @@ public String mintToken( } } - /** - * Transfers Hiero fungible tokens between accounts. - */ - @Operation(summary = "Transfer tokens", description = "Transfers fungible tokens between two Hiero accounts.") + /** Transfers Hiero fungible tokens between accounts. */ + @Operation( + summary = "Transfer tokens", + description = "Transfers fungible tokens between two Hiero accounts.") @PostMapping("/{tokenId}/transfer") public String transferToken( @PathVariable("tokenId") final String tokenId, @RequestBody final TokenTransferRequest request) { try { - if (request.fromAccountId() == null || request.fromPrivateKey() == null || request.toAccountId() == null) { - throw new IllegalArgumentException("Missing required fields: fromAccountId, fromPrivateKey, and toAccountId are mandatory."); + if (request.fromAccountId() == null + || request.fromPrivateKey() == null + || request.toAccountId() == null) { + throw new IllegalArgumentException( + "Missing required fields: fromAccountId, fromPrivateKey, and toAccountId are mandatory."); } final TokenId id = TokenId.fromString(tokenId.trim()); - final Account fromAccount = Account.of( - AccountId.fromString(request.fromAccountId().trim()), - PrivateKey.fromString(request.fromPrivateKey().trim()) - ); + final Account fromAccount = + Account.of( + AccountId.fromString(request.fromAccountId().trim()), + PrivateKey.fromString(request.fromPrivateKey().trim())); final AccountId toAccount = AccountId.fromString(request.toAccountId().trim()); tokenClient.transferToken(id, fromAccount, toAccount, request.amount()); - return "Transferred " + request.amount() + " tokens from " + request.fromAccountId() + " to " + request.toAccountId(); + return "Transferred " + + request.amount() + + " tokens from " + + HtmlUtils.htmlEscape(request.fromAccountId()) + + " to " + + HtmlUtils.htmlEscape(request.toAccountId()); } catch (final Exception e) { throw new RuntimeException("Failed to transfer token " + tokenId, e); } } - /** - * Associates an account with a Hiero fungible token. - */ - @Operation(summary = "Associate account with token", description = "Explicitly associates a Hiero account with a fungible token. Required before receiving tokens.") + /** Associates an account with a Hiero fungible token. */ + @Operation( + summary = "Associate account with token", + description = + "Explicitly associates a Hiero account with a fungible token. Required before receiving tokens.") @PostMapping("/{tokenId}/associate") public String associateToken( @PathVariable("tokenId") final String tokenId, @RequestBody final TokenAssociateRequest request) { try { if (request.accountId() == null || request.privateKey() == null) { - throw new IllegalArgumentException("Missing required fields: accountId and privateKey are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: accountId and privateKey are mandatory."); } final TokenId id = TokenId.fromString(tokenId.trim()); - final Account account = Account.of( - AccountId.fromString(request.accountId().trim()), - PrivateKey.fromString(request.privateKey().trim()) - ); + final Account account = + Account.of( + AccountId.fromString(request.accountId().trim()), + PrivateKey.fromString(request.privateKey().trim())); tokenClient.associateToken(id, account); - return "Account " + request.accountId() + " associated with token " + tokenId + " successfully!"; + return "Account " + + HtmlUtils.htmlEscape(request.accountId()) + + " associated with token " + + HtmlUtils.htmlEscape(tokenId) + + " successfully!"; } catch (final Exception e) { - throw new RuntimeException("Failed to associate account " + request.accountId() + " with token " + tokenId, e); + throw new RuntimeException( + "Failed to associate account " + request.accountId() + " with token " + tokenId, e); } } } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java index 180eea97..e067a7e8 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java @@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Objects; -import java.util.Optional; import org.hiero.base.TopicClient; import org.hiero.base.data.Page; import org.hiero.base.data.Topic; @@ -23,10 +22,9 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.HtmlUtils; -/** - * REST controller for Hiero Consensus Service (Topic) operations. - */ +/** REST controller for Hiero Consensus Service (Topic) operations. */ @Tag(name = "Consensus Topics", description = "Operations related to Hiero Consensus Service (HCS)") @RestController @RequestMapping("/api/v1/hiero/topics") @@ -38,19 +36,22 @@ public class TopicController { public TopicController(final TopicClient topicClient, final TopicRepository topicRepository) { this.topicClient = Objects.requireNonNull(topicClient, "topicClient must not be null"); - this.topicRepository = Objects.requireNonNull(topicRepository, "topicRepository must not be null"); + this.topicRepository = + Objects.requireNonNull(topicRepository, "topicRepository must not be null"); } - /** - * Creates a new topic. - */ - @Operation(summary = "Create a new topic", description = "Creates a new public or private HCS topic.") + /** Creates a new topic. */ + @Operation( + summary = "Create a new topic", + description = "Creates a new public or private HCS topic.") @PostMapping public String createTopic(@RequestBody final TopicCreateRequest request) { try { final TopicId topicId; - final PrivateKey adminKey = request.adminKey() != null ? PrivateKey.fromString(request.adminKey()) : null; - final PrivateKey submitKey = request.submitKey() != null ? PrivateKey.fromString(request.submitKey()) : null; + final PrivateKey adminKey = + request.adminKey() != null ? PrivateKey.fromString(request.adminKey()) : null; + final PrivateKey submitKey = + request.submitKey() != null ? PrivateKey.fromString(request.submitKey()) : null; final String memo = request.memo() != null ? request.memo() : ""; if (submitKey != null) { @@ -72,10 +73,10 @@ public String createTopic(@RequestBody final TopicCreateRequest request) { } } - /** - * Updates an existing topic. - */ - @Operation(summary = "Update a topic", description = "Updates an existing HCS topic's memo or keys.") + /** Updates an existing topic. */ + @Operation( + summary = "Update a topic", + description = "Updates an existing HCS topic's memo or keys.") @PutMapping public String updateTopic(@RequestBody final TopicUpdateRequest request) { try { @@ -84,17 +85,20 @@ public String updateTopic(@RequestBody final TopicUpdateRequest request) { } final TopicId topicId = TopicId.fromString(request.topicId().trim()); final String memo = request.memo() != null ? request.memo() : ""; - - if (request.adminKey() != null && request.updatedAdminKey() != null && request.submitKey() != null) { - topicClient.updateTopic(topicId, - PrivateKey.fromString(request.adminKey().trim()), - PrivateKey.fromString(request.updatedAdminKey().trim()), - PrivateKey.fromString(request.submitKey().trim()), - memo); + + if (request.adminKey() != null + && request.updatedAdminKey() != null + && request.submitKey() != null) { + topicClient.updateTopic( + topicId, + PrivateKey.fromString(request.adminKey().trim()), + PrivateKey.fromString(request.updatedAdminKey().trim()), + PrivateKey.fromString(request.submitKey().trim()), + memo); } else if (request.adminKey() != null) { - topicClient.updateTopic(topicId, PrivateKey.fromString(request.adminKey().trim()), memo); + topicClient.updateTopic(topicId, PrivateKey.fromString(request.adminKey().trim()), memo); } else { - topicClient.updateTopic(topicId, memo); + topicClient.updateTopic(topicId, memo); } return "Topic " + topicId.toString() + " updated successfully!"; } catch (final Exception e) { @@ -102,29 +106,28 @@ public String updateTopic(@RequestBody final TopicUpdateRequest request) { } } - /** - * Deletes a topic. - */ + /** Deletes a topic. */ @Operation(summary = "Delete a topic", description = "Deletes an existing HCS topic.") @DeleteMapping("/{topicId}") public String deleteTopic(@PathVariable("topicId") final String topicId) { try { topicClient.deleteTopic(topicId.trim()); - return "Topic " + topicId + " deleted successfully!"; + return "Topic " + HtmlUtils.htmlEscape(topicId) + " deleted successfully!"; } catch (final Exception e) { throw new RuntimeException("Failed to delete topic", e); } } - /** - * Submits a message to a topic. - */ - @Operation(summary = "Submit a message", description = "Submits a message to a specific HCS topic.") + /** Submits a message to a topic. */ + @Operation( + summary = "Submit a message", + description = "Submits a message to a specific HCS topic.") @PostMapping("/message") public String submitMessage(@RequestBody final TopicMessageRequest request) { try { if (request.topicId() == null || request.message() == null) { - throw new IllegalArgumentException("Missing required fields: topicId and message are mandatory."); + throw new IllegalArgumentException( + "Missing required fields: topicId and message are mandatory."); } final String trimmedTopicId = request.topicId().trim(); if (request.submitKey() != null) { @@ -132,31 +135,32 @@ public String submitMessage(@RequestBody final TopicMessageRequest request) { } else { topicClient.submitMessage(trimmedTopicId, request.message()); } - return "Message submitted successfully to topic " + request.topicId(); + return "Message submitted successfully to topic " + HtmlUtils.htmlEscape(request.topicId()); } catch (final Exception e) { throw new RuntimeException("Failed to submit message", e); } } - /** - * Retrieves info for a specific topic. - */ - @Operation(summary = "Get topic information", description = "Retrieves detailed information about an HCS topic from the mirror node.") + /** Retrieves info for a specific topic. */ + @Operation( + summary = "Get topic information", + description = "Retrieves detailed information about an HCS topic from the mirror node.") @GetMapping("/{topicId}/info") public Topic getTopicInfo(@PathVariable("topicId") final String topicId) { try { final String trimmedTopicId = topicId.trim(); - return topicRepository.findTopicById(trimmedTopicId) + return topicRepository + .findTopicById(trimmedTopicId) .orElseThrow(() -> new RuntimeException("Topic not found: " + trimmedTopicId)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve topic info", e); } } - /** - * Retrieves messages for a specific topic. - */ - @Operation(summary = "Get topic messages", description = "Retrieves a list of messages submitted to a specific HCS topic.") + /** Retrieves messages for a specific topic. */ + @Operation( + summary = "Get topic messages", + description = "Retrieves a list of messages submitted to a specific HCS topic.") @GetMapping("/{topicId}/message") public Page getTopicMessages(@PathVariable("topicId") final String topicId) { try { @@ -166,18 +170,25 @@ public Page getTopicMessages(@PathVariable("topicId") final String } } - /** - * Retrieves a specific message by its sequence number. - */ - @Operation(summary = "Get topic message by sequence", description = "Retrieves a specific message from an HCS topic by its sequence number.") + /** Retrieves a specific message by its sequence number. */ + @Operation( + summary = "Get topic message by sequence", + description = "Retrieves a specific message from an HCS topic by its sequence number.") @GetMapping("/{topicId}/message/{sequenceNumber}") public TopicMessage getTopicMessageBySequenceNumber( @PathVariable("topicId") final String topicId, @PathVariable("sequenceNumber") final long sequenceNumber) { try { final String trimmedTopicId = topicId.trim(); - return topicRepository.getMessageBySequenceNumber(trimmedTopicId, sequenceNumber) - .orElseThrow(() -> new RuntimeException("Message not found for Topic: " + trimmedTopicId + " Sequence: " + sequenceNumber)); + return topicRepository + .getMessageBySequenceNumber(trimmedTopicId, sequenceNumber) + .orElseThrow( + () -> + new RuntimeException( + "Message not found for Topic: " + + trimmedTopicId + + " Sequence: " + + sequenceNumber)); } catch (final Exception e) { throw new RuntimeException("Failed to retrieve topic message", e); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java index 4a3a8c90..a3910002 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java @@ -2,11 +2,10 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request to create a new Hiero account. - */ -@Schema(name = "Account: Create Request", description = "Request DTO for creating a new Hiero account.") +/** Request to create a new Hiero account. */ +@Schema( + name = "Account: Create Request", + description = "Request DTO for creating a new Hiero account.") public record AccountCreateRequest( @Schema(description = "The initial balance in Hbar (optional, defaults to 0).", example = "100") - Long initialBalance -) {} + Long initialBalance) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java index 6ab5c931..788f81bb 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java @@ -2,15 +2,22 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request to delete a Hiero account. - */ +/** Request to delete a Hiero account. */ @Schema(name = "Account: Delete Request", description = "Request DTO for deleting a Hiero account.") public record AccountDeleteRequest( - @Schema(description = "The ID of the account to delete.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String accountId, - @Schema(description = "The private key of the account to delete.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) - String privateKey, - @Schema(description = "The ID of the account to transfer remaining funds to (optional, defaults to operator).", example = "0.0.5678", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String transferToAccountId -) {} + @Schema( + description = "The ID of the account to delete.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String accountId, + @Schema( + description = "The private key of the account to delete.", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.REQUIRED) + String privateKey, + @Schema( + description = + "The ID of the account to transfer remaining funds to (optional, defaults to operator).", + example = "0.0.5678", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String transferToAccountId) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java index 2a890474..eb2c50a3 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java @@ -1,4 +1,5 @@ package org.hiero.spring.sample.dto.account; + import io.swagger.v3.oas.annotations.media.Schema; /** @@ -10,10 +11,6 @@ */ @Schema(name = "Account: Response", description = "Response containing Hiero account details.") public record AccountResponse( - @Schema(description = "The ID of the account.") - String accountId, - @Schema(description = "The public key of the account.") - String publicKey, - @Schema(description = "The private key of the account.") - String privateKey -) {} + @Schema(description = "The ID of the account.") String accountId, + @Schema(description = "The public key of the account.") String publicKey, + @Schema(description = "The private key of the account.") String privateKey) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java index 5cf8f360..f51e7569 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java @@ -2,17 +2,28 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request to update an existing Hiero account. - */ -@Schema(name = "Account: Update Request", description = "Request DTO for updating an existing Hiero account.") +/** Request to update an existing Hiero account. */ +@Schema( + name = "Account: Update Request", + description = "Request DTO for updating an existing Hiero account.") public record AccountUpdateRequest( - @Schema(description = "The ID of the account to update.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String accountId, - @Schema(description = "The current private key of the account.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) - String privateKey, - @Schema(description = "The new private key to set (optional).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String newPrivateKey, - @Schema(description = "The new memo to set (optional).", example = "Updated account memo", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String memo -) {} + @Schema( + description = "The ID of the account to update.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String accountId, + @Schema( + description = "The current private key of the account.", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.REQUIRED) + String privateKey, + @Schema( + description = "The new private key to set (optional).", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String newPrivateKey, + @Schema( + description = "The new memo to set (optional).", + example = "Updated account memo", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String memo) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java index 419d8444..f9a34506 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java @@ -3,16 +3,17 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; -/** - * Request DTO for creating a Hiero file. - */ -@Schema(name = "File: Create Request", description = "Request DTO for creating a new file on the Hiero File Service (HFS).") +/** Request DTO for creating a Hiero file. */ +@Schema( + name = "File: Create Request", + description = "Request DTO for creating a new file on the Hiero File Service (HFS).") public record FileCreateRequest( @Schema(description = "Base64 encoded content of the file.", example = "SGVsbG8gSGllcm8h") - String content, - @Schema(description = "Optional expiration time in seconds since epoch.", example = "1735689600") - Long expirationTime -) { + String content, + @Schema( + description = "Optional expiration time in seconds since epoch.", + example = "1735689600") + Long expirationTime) { public byte[] getDecodedContent() { return java.util.Base64.getDecoder().decode(content); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java index 37243f1b..7c990c18 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java @@ -3,16 +3,19 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; -/** - * Request DTO for updating a Hiero file. - */ -@Schema(name = "File: Update Request", description = "Request DTO for updating the content or expiration of a Hiero file.") +/** Request DTO for updating a Hiero file. */ +@Schema( + name = "File: Update Request", + description = "Request DTO for updating the content or expiration of a Hiero file.") public record FileUpdateRequest( - @Schema(description = "Optional Base64 encoded content to update.", example = "VXBkYXRlZCBjb250ZW50") - String content, - @Schema(description = "Optional new expiration time in seconds since epoch.", example = "1767225600") - Long expirationTime -) { + @Schema( + description = "Optional Base64 encoded content to update.", + example = "VXBkYXRlZCBjb250ZW50") + String content, + @Schema( + description = "Optional new expiration time in seconds since epoch.", + example = "1767225600") + Long expirationTime) { public byte[] getDecodedContent() { return content != null ? java.util.Base64.getDecoder().decode(content) : null; } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java index f20651cb..f3723d8c 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/ExchangeRatesResponse.java @@ -4,24 +4,22 @@ import java.time.Instant; import org.hiero.base.data.ExchangeRates; -/** - * Response DTO for Network Exchange Rates. - */ -@Schema(name = "Network: Exchange Rates", description = "Response DTO containing current and next network exchange rates.") -public record ExchangeRatesResponse( - RateInfo currentRate, - RateInfo nextRate -) { - public record RateInfo( - int centEquivalent, - int hbarEquivalent, - Instant expirationTime - ) {} +/** Response DTO for Network Exchange Rates. */ +@Schema( + name = "Network: Exchange Rates", + description = "Response DTO containing current and next network exchange rates.") +public record ExchangeRatesResponse(RateInfo currentRate, RateInfo nextRate) { + public record RateInfo(int centEquivalent, int hbarEquivalent, Instant expirationTime) {} public static ExchangeRatesResponse fromDomain(ExchangeRates rates) { return new ExchangeRatesResponse( - new RateInfo(rates.currentRate().centEquivalent(), rates.currentRate().hbarEquivalent(), rates.currentRate().expirationTime()), - new RateInfo(rates.nextRate().centEquivalent(), rates.nextRate().hbarEquivalent(), rates.nextRate().expirationTime()) - ); + new RateInfo( + rates.currentRate().centEquivalent(), + rates.currentRate().hbarEquivalent(), + rates.currentRate().expirationTime()), + new RateInfo( + rates.nextRate().centEquivalent(), + rates.nextRate().hbarEquivalent(), + rates.nextRate().expirationTime())); } } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java index 4e40f3c8..488bbbe5 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java @@ -3,16 +3,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import org.hiero.base.data.NetworkFee; -/** - * Response DTO for Network Fees. - */ -@Schema(name = "Network: Fee", description = "Response DTO containing network transaction fee information.") +/** Response DTO for Network Fees. */ +@Schema( + name = "Network: Fee", + description = "Response DTO containing network transaction fee information.") public record NetworkFeeResponse( - @Schema(description = "The gas cost associated with the transaction.") - long gas, - @Schema(description = "The type of transaction.") - String transactionType -) { + @Schema(description = "The gas cost associated with the transaction.") long gas, + @Schema(description = "The type of transaction.") String transactionType) { public static NetworkFeeResponse fromDomain(NetworkFee fee) { return new NetworkFeeResponse(fee.gas(), fee.transactionType()); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java index eb885111..f97dab54 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkStakeResponse.java @@ -3,10 +3,10 @@ import io.swagger.v3.oas.annotations.media.Schema; import org.hiero.base.data.NetworkStake; -/** - * Response DTO for Network Stake Information. - */ -@Schema(name = "Network: Stake", description = "Response DTO containing network staking status and parameters.") +/** Response DTO for Network Stake Information. */ +@Schema( + name = "Network: Stake", + description = "Response DTO containing network staking status and parameters.") public record NetworkStakeResponse( long maxStakeReward, long maxStakeRewardPerHbar, @@ -20,8 +20,7 @@ public record NetworkStakeResponse( double stakingRewardFeeFraction, long stakingRewardRate, long stakingStartThreshold, - long unreservedStakingRewardBalance -) { + long unreservedStakingRewardBalance) { public static NetworkStakeResponse fromDomain(NetworkStake stake) { return new NetworkStakeResponse( stake.maxStakeReward(), @@ -36,7 +35,6 @@ public static NetworkStakeResponse fromDomain(NetworkStake stake) { stake.stakingRewardFeeFraction(), stake.stakingRewardRate(), stake.stakingStartThreshold(), - stake.unreservedStakingRewardBalance() - ); + stake.unreservedStakingRewardBalance()); } } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java index 26628cf6..467d3f0b 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java @@ -3,16 +3,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import org.hiero.base.data.NetworkSupplies; -/** - * Response DTO for Network Supplies. - */ -@Schema(name = "Network: Supplies", description = "Response DTO containing Hbar supply information.") +/** Response DTO for Network Supplies. */ +@Schema( + name = "Network: Supplies", + description = "Response DTO containing Hbar supply information.") public record NetworkSuppliesResponse( - @Schema(description = "The released supply of Hbar.") - String releasedSupply, - @Schema(description = "The total supply of Hbar.") - String totalSupply -) { + @Schema(description = "The released supply of Hbar.") String releasedSupply, + @Schema(description = "The total supply of Hbar.") String totalSupply) { public static NetworkSuppliesResponse fromDomain(NetworkSupplies supplies) { return new NetworkSuppliesResponse(supplies.releasedSupply(), supplies.totalSupply()); } diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java index 8f68e683..dd233b82 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java @@ -2,13 +2,18 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for associating an account with an NFT collection. - */ -@Schema(name = "Non-Fungible Token: Associate Request", description = "Request DTO for explicitly associating a Hiero account with an NFT collection.") +/** Request DTO for associating an account with an NFT collection. */ +@Schema( + name = "Non-Fungible Token: Associate Request", + description = "Request DTO for explicitly associating a Hiero account with an NFT collection.") public record NftAssociateRequest( - @Schema(description = "The ID of the account to associate.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String accountId, - @Schema(description = "The private key of the account to associate.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) - String privateKey -) {} + @Schema( + description = "The ID of the account to associate.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String accountId, + @Schema( + description = "The private key of the account to associate.", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.REQUIRED) + String privateKey) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java index 3232373c..c48824f6 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java @@ -2,13 +2,10 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for creating a new NFT type. - */ -@Schema(name = "Non-Fungible Token: Create Request", description = "Request DTO for creating a new Hiero NFT collection.") +/** Request DTO for creating a new NFT type. */ +@Schema( + name = "Non-Fungible Token: Create Request", + description = "Request DTO for creating a new Hiero NFT collection.") public record NftCreateRequest( - @Schema(description = "The name of the NFT collection.", example = "Hiero Heroes") - String name, - @Schema(description = "The symbol of the NFT collection.", example = "HERO") - String symbol -) {} + @Schema(description = "The name of the NFT collection.", example = "Hiero Heroes") String name, + @Schema(description = "The symbol of the NFT collection.", example = "HERO") String symbol) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java index 8113bdf8..10bc2709 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java @@ -2,11 +2,12 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for minting a new NFT instance. - */ -@Schema(name = "Non-Fungible Token: Mint Request", description = "Request DTO for minting a new NFT instance into a collection.") +/** Request DTO for minting a new NFT instance. */ +@Schema( + name = "Non-Fungible Token: Mint Request", + description = "Request DTO for minting a new NFT instance into a collection.") public record NftMintRequest( - @Schema(description = "The metadata for the NFT instance (e.g., IPFS CID).", example = "ipfs://Qm...") - String metadata -) {} + @Schema( + description = "The metadata for the NFT instance (e.g., IPFS CID).", + example = "ipfs://Qm...") + String metadata) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java index ebf358db..d37b7477 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java @@ -2,17 +2,28 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for transferring an NFT instance. - */ -@Schema(name = "Non-Fungible Token: Transfer Request", description = "Request DTO for transferring a specific NFT serial number between accounts.") +/** Request DTO for transferring an NFT instance. */ +@Schema( + name = "Non-Fungible Token: Transfer Request", + description = "Request DTO for transferring a specific NFT serial number between accounts.") public record NftTransferRequest( - @Schema(description = "The ID of the account transferring the NFT.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String fromAccountId, - @Schema(description = "The private key of the account transferring the NFT.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) - String fromPrivateKey, - @Schema(description = "The ID of the account receiving the NFT.", example = "0.0.5678", requiredMode = Schema.RequiredMode.REQUIRED) - String toAccountId, - @Schema(description = "The serial number of the NFT instance to transfer.", example = "1", requiredMode = Schema.RequiredMode.REQUIRED) - long serialNumber -) {} + @Schema( + description = "The ID of the account transferring the NFT.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String fromAccountId, + @Schema( + description = "The private key of the account transferring the NFT.", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.REQUIRED) + String fromPrivateKey, + @Schema( + description = "The ID of the account receiving the NFT.", + example = "0.0.5678", + requiredMode = Schema.RequiredMode.REQUIRED) + String toAccountId, + @Schema( + description = "The serial number of the NFT instance to transfer.", + example = "1", + requiredMode = Schema.RequiredMode.REQUIRED) + long serialNumber) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java index 5f3400c6..3fd92238 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java @@ -2,14 +2,18 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Data Transfer Object for token association requests. - */ -@Schema(name = "Fungible Token: Associate Request", description = "Request DTO for explicitly associating a Hiero account with a fungible token.") +/** Data Transfer Object for token association requests. */ +@Schema( + name = "Fungible Token: Associate Request", + description = "Request DTO for explicitly associating a Hiero account with a fungible token.") public record TokenAssociateRequest( - @Schema(description = "The ID of the account to associate with the token", example = "0.0.12345", requiredMode = Schema.RequiredMode.REQUIRED) - String accountId, - - @Schema(description = "The private key of the account to sign the association", example = "302e...", requiredMode = Schema.RequiredMode.REQUIRED) - String privateKey -) {} + @Schema( + description = "The ID of the account to associate with the token", + example = "0.0.12345", + requiredMode = Schema.RequiredMode.REQUIRED) + String accountId, + @Schema( + description = "The private key of the account to sign the association", + example = "302e...", + requiredMode = Schema.RequiredMode.REQUIRED) + String privateKey) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java index 6a9502bf..67c2482f 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java @@ -2,17 +2,18 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request to create a new Hiero fungible token. - */ -@Schema(name = "Fungible Token: Create Request", description = "Request DTO for creating a new Hiero fungible token.") +/** Request to create a new Hiero fungible token. */ +@Schema( + name = "Fungible Token: Create Request", + description = "Request DTO for creating a new Hiero fungible token.") public record TokenCreateRequest( - @Schema(description = "The name of the token.", example = "Hiero Gold") - String name, - @Schema(description = "The symbol of the token.", example = "GOLD") - String symbol, - @Schema(description = "The number of decimals for the token (optional, defaults to 0).", example = "8") - Integer decimals, - @Schema(description = "The initial supply of the token (optional, defaults to 0).", example = "1000000") - Long initialSupply -) {} + @Schema(description = "The name of the token.", example = "Hiero Gold") String name, + @Schema(description = "The symbol of the token.", example = "GOLD") String symbol, + @Schema( + description = "The number of decimals for the token (optional, defaults to 0).", + example = "8") + Integer decimals, + @Schema( + description = "The initial supply of the token (optional, defaults to 0).", + example = "1000000") + Long initialSupply) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java index 35d8c7fa..f0c86aed 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java @@ -2,13 +2,13 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request to mint more of a Hiero fungible token. - */ -@Schema(name = "Fungible Token: Mint Request", description = "Request DTO for minting additional units of a fungible token.") +/** Request to mint more of a Hiero fungible token. */ +@Schema( + name = "Fungible Token: Mint Request", + description = "Request DTO for minting additional units of a fungible token.") public record TokenMintRequest( - @Schema(description = "The amount of tokens to mint.", example = "500000") - long amount, - @Schema(description = "The supply key of the token (required if the token has a supply key).", example = "302e020100300506032b657004220420...") - String supplyKey -) {} + @Schema(description = "The amount of tokens to mint.", example = "500000") long amount, + @Schema( + description = "The supply key of the token (required if the token has a supply key).", + example = "302e020100300506032b657004220420...") + String supplyKey) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java index ba6bf05e..c4c0cc8f 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java @@ -2,17 +2,28 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request to transfer Hiero fungible tokens between accounts. - */ -@Schema(name = "Fungible Token: Transfer Request", description = "Request DTO for transferring units of a fungible token between accounts.") +/** Request to transfer Hiero fungible tokens between accounts. */ +@Schema( + name = "Fungible Token: Transfer Request", + description = "Request DTO for transferring units of a fungible token between accounts.") public record TokenTransferRequest( - @Schema(description = "The ID of the account to transfer tokens from.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String fromAccountId, - @Schema(description = "The private key of the sender account.", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.REQUIRED) - String fromPrivateKey, - @Schema(description = "The ID of the account to transfer tokens to.", example = "0.0.5678", requiredMode = Schema.RequiredMode.REQUIRED) - String toAccountId, - @Schema(description = "The amount of tokens to transfer.", example = "1000", requiredMode = Schema.RequiredMode.REQUIRED) - long amount -) {} + @Schema( + description = "The ID of the account to transfer tokens from.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String fromAccountId, + @Schema( + description = "The private key of the sender account.", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.REQUIRED) + String fromPrivateKey, + @Schema( + description = "The ID of the account to transfer tokens to.", + example = "0.0.5678", + requiredMode = Schema.RequiredMode.REQUIRED) + String toAccountId, + @Schema( + description = "The amount of tokens to transfer.", + example = "1000", + requiredMode = Schema.RequiredMode.REQUIRED) + long amount) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java index 3a727a68..523ffcb8 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java @@ -2,15 +2,18 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for creating a new Consensus Topic. - */ -@Schema(name = "Consensus Topic: Create Request", description = "Request DTO for creating a new Hiero Consensus Service (HCS) topic.") +/** Request DTO for creating a new Consensus Topic. */ +@Schema( + name = "Consensus Topic: Create Request", + description = "Request DTO for creating a new Hiero Consensus Service (HCS) topic.") public record TopicCreateRequest( @Schema(description = "Optional memo for the topic.", example = "Project alerts topic") - String memo, - @Schema(description = "Optional admin key for the topic.", example = "302e020100300506032b657004220420...") - String adminKey, - @Schema(description = "Optional submit key for the topic.", example = "302e020100300506032b657004220420...") - String submitKey -) {} + String memo, + @Schema( + description = "Optional admin key for the topic.", + example = "302e020100300506032b657004220420...") + String adminKey, + @Schema( + description = "Optional submit key for the topic.", + example = "302e020100300506032b657004220420...") + String submitKey) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java index fb24d181..483842bd 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java @@ -2,15 +2,23 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for submitting a message to a Consensus Topic. - */ -@Schema(name = "Consensus Topic: Message Request", description = "Request DTO for submitting a message to a Hiero Consensus Service (HCS) topic.") +/** Request DTO for submitting a message to a Consensus Topic. */ +@Schema( + name = "Consensus Topic: Message Request", + description = "Request DTO for submitting a message to a Hiero Consensus Service (HCS) topic.") public record TopicMessageRequest( - @Schema(description = "The ID of the topic to submit the message to.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String topicId, - @Schema(description = "The message content to submit.", example = "Hello Hiero Consensus Service!", requiredMode = Schema.RequiredMode.REQUIRED) - String message, - @Schema(description = "The submit key (required if the topic is private).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String submitKey -) {} + @Schema( + description = "The ID of the topic to submit the message to.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String topicId, + @Schema( + description = "The message content to submit.", + example = "Hello Hiero Consensus Service!", + requiredMode = Schema.RequiredMode.REQUIRED) + String message, + @Schema( + description = "The submit key (required if the topic is private).", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String submitKey) {} diff --git a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java index 0d1ffaf8..fa2a38a2 100644 --- a/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java +++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java @@ -2,19 +2,33 @@ import io.swagger.v3.oas.annotations.media.Schema; -/** - * Request DTO for updating an existing Consensus Topic. - */ -@Schema(name = "Consensus Topic: Update Request", description = "Request DTO for updating an existing Hiero Consensus Service (HCS) topic.") +/** Request DTO for updating an existing Consensus Topic. */ +@Schema( + name = "Consensus Topic: Update Request", + description = "Request DTO for updating an existing Hiero Consensus Service (HCS) topic.") public record TopicUpdateRequest( - @Schema(description = "The ID of the topic to update.", example = "0.0.1234", requiredMode = Schema.RequiredMode.REQUIRED) - String topicId, - @Schema(description = "The new memo for the topic.", example = "Updated topic memo", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String memo, - @Schema(description = "The current admin key (required for most updates).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String adminKey, - @Schema(description = "The new admin key to set (optional).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String updatedAdminKey, - @Schema(description = "The new submit key to set (optional).", example = "302e020100300506032b657004220420...", requiredMode = Schema.RequiredMode.NOT_REQUIRED) - String submitKey -) {} + @Schema( + description = "The ID of the topic to update.", + example = "0.0.1234", + requiredMode = Schema.RequiredMode.REQUIRED) + String topicId, + @Schema( + description = "The new memo for the topic.", + example = "Updated topic memo", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String memo, + @Schema( + description = "The current admin key (required for most updates).", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String adminKey, + @Schema( + description = "The new admin key to set (optional).", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String updatedAdminKey, + @Schema( + description = "The new submit key to set (optional).", + example = "302e020100300506032b657004220420...", + requiredMode = Schema.RequiredMode.NOT_REQUIRED) + String submitKey) {}