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/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/config/OpenApiConfig.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java
new file mode 100644
index 00000000..8747b8da
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/config/OpenApiConfig.java
@@ -0,0 +1,65 @@
+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 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 {
+
+ @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
new file mode 100644
index 00000000..dec0506d
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/AccountController.java
@@ -0,0 +1,184 @@
+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 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;
+import org.hiero.base.mirrornode.AccountRepository;
+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;
+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.
+ */
+@Tag(
+ name = "Accounts",
+ description = "Operations related to Hiero account lifecycle and balance 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.
+ */
+ @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 {
+ 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.
+ */
+ @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.");
+ }
+ 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());
+ } else if (request.newPrivateKey() != null) {
+ accountClient.updateAccountKey(
+ account, PrivateKey.fromString(request.newPrivateKey().trim()));
+ } 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.
+ */
+ @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.");
+ }
+ 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().trim());
+ final Account transferTarget = Account.of(transferTo, PrivateKey.generateED25519());
+ 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.
+ */
+ @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.trim());
+ 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.
+ */
+ @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)
+ .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
new file mode 100644
index 00000000..5bbced33
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/BlockController.java
@@ -0,0 +1,66 @@
+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;
+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.
+ */
+@Tag(name = "Blocks", description = "Operations related to Hiero network blocks (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.
+ */
+ @Operation(
+ summary = "Get all blocks",
+ description = "Retrieves a paginated list of blocks from the Hiero mirror node.")
+ @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.
+ */
+ @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)
+ .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
new file mode 100644
index 00000000..4e6f8ad0
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/FileController.java
@@ -0,0 +1,113 @@
+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;
+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;
+import org.springframework.web.util.HtmlUtils;
+
+/**
+ * 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 {
+
+ private final FileClient fileClient;
+
+ 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.")
+ @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. */
+ @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 " + 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.")
+ @PostMapping("/{fileId}")
+ public String updateFile(
+ @PathVariable("fileId") final String fileId, @RequestBody final FileUpdateRequest request) {
+ try {
+ final FileId fid = FileId.fromString(fileId.trim());
+ if (request.content() != null) {
+ fileClient.updateFile(fid, request.getDecodedContent());
+ }
+ if (request.expirationTime() != null) {
+ fileClient.updateExpirationTime(fid, request.getExpirationInstant());
+ }
+ 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.")
+ @GetMapping("/{fileId}/content")
+ public String getFileContent(@PathVariable("fileId") final String fileId) {
+ try {
+ 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);
+ }
+ }
+
+ /** 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.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
new file mode 100644
index 00000000..324a350e
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NetworkController.java
@@ -0,0 +1,94 @@
+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;
+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;
+
+/**
+ * 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 {
+
+ 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. */
+ @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()
+ .map(ExchangeRatesResponse::fromDomain)
+ .orElseThrow(() -> new RuntimeException("Exchange rates not available"));
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to query exchange rates", e);
+ }
+ }
+
+ /** 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();
+ } 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.")
+ @GetMapping("/stake")
+ 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);
+ }
+ }
+
+ /** 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()
+ .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/controller/NftController.java b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java
new file mode 100644
index 00000000..b4947c9e
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/NftController.java
@@ -0,0 +1,189 @@
+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 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;
+import org.hiero.base.data.Account;
+import org.hiero.base.data.Nft;
+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;
+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;
+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)")
+@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. */
+ @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) {
+ throw new RuntimeException("Failed to create NFT type", e);
+ }
+ }
+
+ /** 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)
+ .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.")
+ @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(trimmedNftId, metadata);
+ 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.")
+ @GetMapping("/{nftId}/serial/{serial}")
+ public Nft getNftBySerial(
+ @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));
+ } 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.")
+ @PostMapping("/{nftId}/transfer")
+ public String transferNft(
+ @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.");
+ }
+ final TokenId tokenId = TokenId.fromString(nftId.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 "
+ + 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.")
+ @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 "
+ + 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);
+ }
+ }
+}
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..907add59
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TokenController.java
@@ -0,0 +1,163 @@
+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 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;
+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;
+import org.springframework.web.util.HtmlUtils;
+
+@Tag(name = "Fungible Tokens", description = "Operations related to Hiero fungible tokens (HTS)")
+@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. */
+ @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) {
+ throw new RuntimeException("Failed to create token", e);
+ }
+ }
+
+ /** 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)
+ .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.")
+ @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(trimmedTokenId, request.supplyKey(), request.amount());
+ } else {
+ newTotalSupply = tokenClient.mintToken(trimmedTokenId, 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. */
+ @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.");
+ }
+ final TokenId id = TokenId.fromString(tokenId.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 "
+ + 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.")
+ @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 "
+ + 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);
+ }
+ }
+}
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..e067a7e8
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/controller/TopicController.java
@@ -0,0 +1,196 @@
+package org.hiero.spring.sample.controller;
+
+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 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;
+import org.springframework.web.util.HtmlUtils;
+
+/** 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 = "*")
+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. */
+ @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 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. */
+ @Operation(
+ summary = "Update a topic",
+ description = "Updates an existing HCS topic's memo or keys.")
+ @PutMapping
+ public String updateTopic(@RequestBody final TopicUpdateRequest request) {
+ try {
+ 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().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);
+ } 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. */
+ @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 " + 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.")
+ @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(trimmedTopicId, request.submitKey().trim(), request.message());
+ } else {
+ topicClient.submitMessage(trimmedTopicId, request.message());
+ }
+ 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.")
+ @GetMapping("/{topicId}/info")
+ public Topic getTopicInfo(@PathVariable("topicId") final String topicId) {
+ try {
+ 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);
+ }
+ }
+
+ /** 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.trim());
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to retrieve topic messages", e);
+ }
+ }
+
+ /** 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));
+ } 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
new file mode 100644
index 00000000..a3910002
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountCreateRequest.java
@@ -0,0 +1,11 @@
+package org.hiero.spring.sample.dto.account;
+
+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.")
+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
new file mode 100644
index 00000000..788f81bb
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountDeleteRequest.java
@@ -0,0 +1,23 @@
+package org.hiero.spring.sample.dto.account;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/** 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) {}
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
new file mode 100644
index 00000000..eb2c50a3
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountResponse.java
@@ -0,0 +1,16 @@
+package org.hiero.spring.sample.dto.account;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/**
+ * 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.
+ */
+@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
new file mode 100644
index 00000000..f51e7569
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/account/AccountUpdateRequest.java
@@ -0,0 +1,29 @@
+package org.hiero.spring.sample.dto.account;
+
+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.")
+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
new file mode 100644
index 00000000..f9a34506
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileCreateRequest.java
@@ -0,0 +1,24 @@
+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. */
+@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() {
+ 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..7c990c18
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/file/FileUpdateRequest.java
@@ -0,0 +1,26 @@
+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. */
+@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() {
+ return content != null ? java.util.Base64.getDecoder().decode(content) : null;
+ }
+
+ 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/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..f3723d8c
--- /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 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) {
+ 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..488bbbe5
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkFeeResponse.java
@@ -0,0 +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) {
+ 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..f97dab54
--- /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 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,
+ 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..467d3f0b
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/network/NetworkSuppliesResponse.java
@@ -0,0 +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) {
+ 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
new file mode 100644
index 00000000..dd233b82
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftAssociateRequest.java
@@ -0,0 +1,19 @@
+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
new file mode 100644
index 00000000..c48824f6
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftCreateRequest.java
@@ -0,0 +1,11 @@
+package org.hiero.spring.sample.dto.nft;
+
+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.")
+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
new file mode 100644
index 00000000..10bc2709
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftMintRequest.java
@@ -0,0 +1,13 @@
+package org.hiero.spring.sample.dto.nft;
+
+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.")
+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
new file mode 100644
index 00000000..d37b7477
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/nft/NftTransferRequest.java
@@ -0,0 +1,29 @@
+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..3fd92238
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenAssociateRequest.java
@@ -0,0 +1,19 @@
+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
new file mode 100644
index 00000000..67c2482f
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenCreateRequest.java
@@ -0,0 +1,19 @@
+package org.hiero.spring.sample.dto.token;
+
+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.")
+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
new file mode 100644
index 00000000..f0c86aed
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenMintRequest.java
@@ -0,0 +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. */
+@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
new file mode 100644
index 00000000..c4c0cc8f
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/token/TokenTransferRequest.java
@@ -0,0 +1,29 @@
+package org.hiero.spring.sample.dto.token;
+
+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.")
+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
new file mode 100644
index 00000000..523ffcb8
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicCreateRequest.java
@@ -0,0 +1,19 @@
+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
new file mode 100644
index 00000000..483842bd
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicMessageRequest.java
@@ -0,0 +1,24 @@
+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
new file mode 100644
index 00000000..fa2a38a2
--- /dev/null
+++ b/hiero-enterprise-spring-sample/src/main/java/org/hiero/spring/sample/dto/topic/TopicUpdateRequest.java
@@ -0,0 +1,34 @@
+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) {}