Skip to content

Commit bd44596

Browse files
committed
feat(samples): add CLI sample using picocli (issue #22)
1 parent f559b93 commit bd44596

8 files changed

Lines changed: 315 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Hiero Enterprise CLI Sample
2+
3+
A command-line interface for interacting with the Hiero network using [picocli](https://picocli.info) and `hiero-enterprise-base`.
4+
5+
## Prerequisites
6+
7+
- Java 21+
8+
- Maven 3.8+
9+
- A Hedera testnet account ([get one free](https://portal.hedera.com))
10+
11+
## Build
12+
13+
```bash
14+
mvn clean package -pl hiero-enterprise-cli-sample -am -DskipTests
15+
```
16+
17+
## Usage
18+
19+
```bash
20+
java -jar hiero-enterprise-cli-sample/target/hiero-enterprise-cli-sample-*.jar [command]
21+
```
22+
23+
### Create Account
24+
```bash
25+
java -jar target/*.jar create-account \
26+
--account-id 0.0.123 \
27+
--private-key <YOUR_PRIVATE_KEY>
28+
```
29+
30+
### Create Topic
31+
```bash
32+
java -jar target/*.jar create-topic \
33+
--account-id 0.0.123 \
34+
--private-key <YOUR_PRIVATE_KEY> \
35+
--memo "My first topic"
36+
```
37+
38+
### Send Message to Topic
39+
```bash
40+
java -jar target/*.jar send-message \
41+
--account-id 0.0.123 \
42+
--private-key <YOUR_PRIVATE_KEY> \
43+
--topic-id 0.0.456 \
44+
--message "Hello Hiero!"
45+
```
46+
47+
## Configuration
48+
49+
Set the following environment variables or pass them as CLI options:
50+
51+
| Variable | Description |
52+
|----------|-------------|
53+
| `--account-id` | Your Hedera operator account ID (e.g. `0.0.123`) |
54+
| `--private-key` | Your Hedera operator private key |
55+
| `--network` | Network name (default: `hedera-testnet`) |
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.hiero</groupId>
9+
<artifactId>hiero-enterprise</artifactId>
10+
<version>0.20.0-SNAPSHOT</version>
11+
<relativePath>../pom.xml</relativePath>
12+
</parent>
13+
14+
<artifactId>hiero-enterprise-cli-sample</artifactId>
15+
<name>Hiero Enterprise CLI Sample</name>
16+
<description>Sample for Hiero via CLI using picocli</description>
17+
<url>https://github.com/hiero-ledger/hiero-enterprise-java</url>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>${project.groupId}</groupId>
22+
<artifactId>hiero-enterprise-base</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>info.picocli</groupId>
26+
<artifactId>picocli</artifactId>
27+
<version>4.7.6</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>io.grpc</groupId>
31+
<artifactId>grpc-okhttp</artifactId>
32+
</dependency>
33+
<dependency>
34+
<groupId>io.grpc</groupId>
35+
<artifactId>grpc-inprocess</artifactId>
36+
</dependency>
37+
</dependencies>
38+
39+
<build>
40+
<plugins>
41+
<plugin>
42+
<groupId>org.apache.maven.plugins</groupId>
43+
<artifactId>maven-jar-plugin</artifactId>
44+
<configuration>
45+
<archive>
46+
<manifest>
47+
<mainClass>org.hiero.base.sample.HieroCli</mainClass>
48+
</manifest>
49+
</archive>
50+
</configuration>
51+
</plugin>
52+
<plugin>
53+
<groupId>org.apache.maven.plugins</groupId>
54+
<artifactId>maven-deploy-plugin</artifactId>
55+
<configuration>
56+
<skip>true</skip>
57+
</configuration>
58+
</plugin>
59+
</plugins>
60+
</build>
61+
</project>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.hiero.base.sample;
2+
3+
import com.hedera.hashgraph.sdk.AccountId;
4+
import com.hedera.hashgraph.sdk.Client;
5+
import com.hedera.hashgraph.sdk.PrivateKey;
6+
import com.hedera.hashgraph.sdk.PublicKey;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import org.hiero.base.HieroContext;
10+
import org.hiero.base.config.NetworkSettings;
11+
import org.hiero.base.data.Account;
12+
import org.jspecify.annotations.NonNull;
13+
14+
public class CliHieroContext implements HieroContext {
15+
16+
private final Account operationalAccount;
17+
private final Client client;
18+
19+
public CliHieroContext(final String accountIdStr, final String privateKeyStr, final String network) {
20+
final AccountId accountId = AccountId.fromString(accountIdStr);
21+
final PrivateKey privateKey = PrivateKey.fromString(privateKeyStr);
22+
final PublicKey publicKey = privateKey.getPublicKey();
23+
operationalAccount = new Account(accountId, publicKey, privateKey);
24+
25+
final NetworkSettings networkSettings =
26+
NetworkSettings.forIdentifier(network)
27+
.orElseThrow(
28+
() -> new IllegalStateException("Unknown network: " + network));
29+
30+
final Map<String, AccountId> nodes = new HashMap<>();
31+
networkSettings
32+
.getConsensusNodes()
33+
.forEach(n -> nodes.put(n.getAddress(), n.getAccountId()));
34+
35+
client = Client.forNetwork(nodes);
36+
if (!networkSettings.getMirrorNodeAddresses().isEmpty()) {
37+
try {
38+
client.setMirrorNetwork(networkSettings.getMirrorNodeAddresses().stream().toList());
39+
} catch (InterruptedException e) {
40+
throw new RuntimeException("Error configuring Mirror Node", e);
41+
}
42+
}
43+
client.setOperator(accountId, privateKey);
44+
}
45+
46+
@Override
47+
public @NonNull Account getOperatorAccount() {
48+
return operationalAccount;
49+
}
50+
51+
@Override
52+
public @NonNull Client getClient() {
53+
return client;
54+
}
55+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.hiero.base.sample;
2+
3+
import org.hiero.base.AccountClient;
4+
import org.hiero.base.implementation.AccountClientImpl;
5+
import org.hiero.base.implementation.ProtocolLayerClientImpl;
6+
import org.hiero.base.protocol.ProtocolLayerClient;
7+
import org.hiero.base.data.Account;
8+
import picocli.CommandLine.Command;
9+
import picocli.CommandLine.Option;
10+
11+
@Command(name = "create-account", description = "Create a new Hiero account", mixinStandardHelpOptions = true)
12+
public class CreateAccountCommand implements Runnable {
13+
14+
@Option(names = {"-n", "--network"}, description = "Hiero network", defaultValue = "hedera-testnet")
15+
private String network;
16+
17+
@Option(names = {"-a", "--account-id"}, description = "Operator account ID", required = true)
18+
private String accountId;
19+
20+
@Option(names = {"-k", "--private-key"}, description = "Operator private key", required = true)
21+
private String privateKey;
22+
23+
@Override
24+
public void run() {
25+
try {
26+
final CliHieroContext context = new CliHieroContext(accountId, privateKey, network);
27+
final ProtocolLayerClient protocolClient = new ProtocolLayerClientImpl(context);
28+
final AccountClient accountClient = new AccountClientImpl(protocolClient);
29+
System.out.println("Creating account on " + network + "...");
30+
final Account account = accountClient.createAccount();
31+
System.out.println("Account created: " + account.accountId());
32+
} catch (final Exception e) {
33+
System.err.println("Error: " + e.getMessage());
34+
}
35+
}
36+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.hiero.base.sample;
2+
3+
import com.hedera.hashgraph.sdk.TopicId;
4+
import org.hiero.base.TopicClient;
5+
import org.hiero.base.implementation.ProtocolLayerClientImpl;
6+
import org.hiero.base.implementation.TopicClientImpl;
7+
import org.hiero.base.protocol.ProtocolLayerClient;
8+
import picocli.CommandLine.Command;
9+
import picocli.CommandLine.Option;
10+
11+
@Command(name = "create-topic", description = "Create a new Hiero topic", mixinStandardHelpOptions = true)
12+
public class CreateTopicCommand implements Runnable {
13+
14+
@Option(names = {"-n", "--network"}, description = "Hiero network", defaultValue = "hedera-testnet")
15+
private String network;
16+
17+
@Option(names = {"-a", "--account-id"}, description = "Operator account ID", required = true)
18+
private String accountId;
19+
20+
@Option(names = {"-k", "--private-key"}, description = "Operator private key", required = true)
21+
private String privateKey;
22+
23+
@Option(names = {"-m", "--memo"}, description = "Topic memo", defaultValue = "Created via Hiero CLI")
24+
private String memo;
25+
26+
@Override
27+
public void run() {
28+
try {
29+
final CliHieroContext context = new CliHieroContext(accountId, privateKey, network);
30+
final ProtocolLayerClient protocolClient = new ProtocolLayerClientImpl(context);
31+
final TopicClient topicClient = new TopicClientImpl(protocolClient, context.getOperatorAccount());
32+
System.out.println("Creating topic on " + network + "...");
33+
final TopicId topicId = topicClient.createTopic(memo);
34+
System.out.println("Topic created: " + topicId);
35+
} catch (final Exception e) {
36+
System.err.println("Error: " + e.getMessage());
37+
}
38+
}
39+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.hiero.base.sample;
2+
3+
import picocli.CommandLine;
4+
import picocli.CommandLine.Command;
5+
6+
@Command(
7+
name = "hiero",
8+
mixinStandardHelpOptions = true,
9+
version = "1.0",
10+
description = "Hiero Enterprise CLI - interact with the Hiero network",
11+
subcommands = {
12+
CreateAccountCommand.class,
13+
CreateTopicCommand.class,
14+
SendMessageCommand.class
15+
})
16+
public class HieroCli implements Runnable {
17+
18+
public static void main(final String[] args) {
19+
final int exitCode = new CommandLine(new HieroCli()).execute(args);
20+
System.exit(exitCode);
21+
}
22+
23+
@Override
24+
public void run() {
25+
CommandLine.usage(this, System.out);
26+
}
27+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.hiero.base.sample;
2+
3+
import org.hiero.base.TopicClient;
4+
import org.hiero.base.implementation.ProtocolLayerClientImpl;
5+
import org.hiero.base.implementation.TopicClientImpl;
6+
import org.hiero.base.protocol.ProtocolLayerClient;
7+
import picocli.CommandLine.Command;
8+
import picocli.CommandLine.Option;
9+
10+
@Command(name = "send-message", description = "Send a message to a Hiero topic", mixinStandardHelpOptions = true)
11+
public class SendMessageCommand implements Runnable {
12+
13+
@Option(names = {"-n", "--network"}, description = "Hiero network", defaultValue = "hedera-testnet")
14+
private String network;
15+
16+
@Option(names = {"-a", "--account-id"}, description = "Operator account ID", required = true)
17+
private String accountId;
18+
19+
@Option(names = {"-k", "--private-key"}, description = "Operator private key", required = true)
20+
private String privateKey;
21+
22+
@Option(names = {"-t", "--topic-id"}, description = "Topic ID", required = true)
23+
private String topicId;
24+
25+
@Option(names = {"-msg", "--message"}, description = "Message content", required = true)
26+
private String message;
27+
28+
@Override
29+
public void run() {
30+
try {
31+
final CliHieroContext context = new CliHieroContext(accountId, privateKey, network);
32+
final ProtocolLayerClient protocolClient = new ProtocolLayerClientImpl(context);
33+
final TopicClient topicClient = new TopicClientImpl(protocolClient, context.getOperatorAccount());
34+
System.out.println("Sending message to topic " + topicId + "...");
35+
topicClient.submitMessage(topicId, message);
36+
System.out.println("Message sent successfully!");
37+
} catch (final Exception e) {
38+
System.err.println("Error: " + e.getMessage());
39+
}
40+
}
41+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@
215215
<module>hiero-enterprise-spring</module>
216216
<module>hiero-enterprise-microprofile</module>
217217
<module>hiero-enterprise-spring-sample</module>
218+
<module>hiero-enterprise-cli-sample</module>
218219
<module>hiero-enterprise-microprofile-sample</module>
219220
</modules>
220221

0 commit comments

Comments
 (0)